Skip to content

Commit 4b22f3d

Browse files
rvanvelzenondrejmirtes
authored andcommitted
Improve intersection of generic class string
1 parent 22eeeae commit 4b22f3d

File tree

4 files changed

+65
-2
lines changed

4 files changed

+65
-2
lines changed

src/Type/TypeCombinator.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\Type\Constant\ConstantFloatType;
1414
use PHPStan\Type\Constant\ConstantIntegerType;
1515
use PHPStan\Type\Constant\ConstantStringType;
16+
use PHPStan\Type\Generic\GenericClassStringType;
1617
use PHPStan\Type\Generic\TemplateBenevolentUnionType;
1718
use PHPStan\Type\Generic\TemplateType;
1819
use PHPStan\Type\Generic\TemplateTypeFactory;
@@ -975,6 +976,14 @@ public static function intersect(Type ...$types): Type
975976
continue 2;
976977
}
977978

979+
if ($types[$i] instanceof GenericClassStringType && $types[$j] instanceof GenericClassStringType) {
980+
$genericType = self::intersect($types[$i]->getGenericType(), $types[$j]->getGenericType());
981+
$types[$i] = new GenericClassStringType($genericType);
982+
array_splice($types, $j--, 1);
983+
$typesCount--;
984+
continue;
985+
}
986+
978987
continue;
979988
}
980989

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,7 @@ public function dataFileAsserts(): iterable
10381038
yield from $this->gatherAssertTypes(__DIR__ . '/data/cli-globals.php');
10391039
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8033.php');
10401040
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-union-unshift.php');
1041+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7987.php');
10411042
}
10421043

10431044
/**

tests/PHPStan/Analyser/data/bug-4875.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ function doFoo()
3030
$mock = $this->mockIt(Blah::class);
3131

3232
assertType('Bug4875\Blah&Bug4875\Mock', $mock);
33-
assertType('class-string<Bug4875\Blah>&class-string<Bug4875\Mock>&literal-string', $mock::class);
34-
assertType('class-string<Bug4875\Blah>&class-string<Bug4875\Mock>', get_class($mock));
33+
assertType('class-string<Bug4875\Blah&Bug4875\Mock>&literal-string', $mock::class);
34+
assertType('class-string<Bug4875\Blah&Bug4875\Mock>', get_class($mock));
3535
}
3636

3737
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug7987;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class AbstractA{}
8+
class AbstractB{}
9+
10+
class Factory
11+
{
12+
/**
13+
* @template TypeObject of AbstractA|AbstractB
14+
* @param class-string<TypeObject> $objectClass
15+
* @return TypeObject
16+
*/
17+
public function getObject(string $objectClass): AbstractA|AbstractB
18+
{
19+
if (is_subclass_of($objectClass, AbstractA::class)) {
20+
assertType('class-string<TypeObject of Bug7987\AbstractA (method Bug7987\Factory::getObject(), argument)>', $objectClass);
21+
$object = $this->getObjectA($objectClass);
22+
assertType('TypeObject of Bug7987\AbstractA (method Bug7987\Factory::getObject(), argument)', $object);
23+
} elseif (is_subclass_of($objectClass, AbstractB::class)) {
24+
assertType('class-string<TypeObject of Bug7987\AbstractB (method Bug7987\Factory::getObject(), argument)>', $objectClass);
25+
$object = $this->getObjectB($objectClass);
26+
assertType('TypeObject of Bug7987\AbstractB (method Bug7987\Factory::getObject(), argument)', $object);
27+
} else {
28+
throw new \Exception("unable to instantiate $objectClass");
29+
}
30+
assertType('TypeObject of Bug7987\AbstractA (method Bug7987\Factory::getObject(), argument)|TypeObject of Bug7987\AbstractB (method Bug7987\Factory::getObject(), argument)', $object);
31+
return $object;
32+
}
33+
34+
/**
35+
* @template TypeObject of AbstractA
36+
* @param class-string<TypeObject> $objectClass
37+
* @return TypeObject
38+
*/
39+
private function getObjectA(string $objectClass): AbstractA
40+
{
41+
return new $objectClass();
42+
}
43+
44+
/**
45+
* @template TypeObject of AbstractB
46+
* @param class-string<TypeObject> $objectClass
47+
* @return TypeObject
48+
*/
49+
private function getObjectB(string $objectClass): AbstractB
50+
{
51+
return new $objectClass();
52+
}
53+
}

0 commit comments

Comments
 (0)