Skip to content

Commit

Permalink
Improve intersection of generic class string
Browse files Browse the repository at this point in the history
  • Loading branch information
rvanvelzen authored and ondrejmirtes committed Sep 22, 2022
1 parent 22eeeae commit 4b22f3d
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 2 deletions.
9 changes: 9 additions & 0 deletions src/Type/TypeCombinator.php
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Type\Constant\ConstantFloatType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\TemplateBenevolentUnionType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeFactory;
Expand Down Expand Up @@ -975,6 +976,14 @@ public static function intersect(Type ...$types): Type
continue 2;
}

if ($types[$i] instanceof GenericClassStringType && $types[$j] instanceof GenericClassStringType) {
$genericType = self::intersect($types[$i]->getGenericType(), $types[$j]->getGenericType());
$types[$i] = new GenericClassStringType($genericType);
array_splice($types, $j--, 1);
$typesCount--;
continue;
}

continue;
}

Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -1038,6 +1038,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/cli-globals.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8033.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-union-unshift.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7987.php');
}

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/data/bug-4875.php
Expand Up @@ -30,8 +30,8 @@ function doFoo()
$mock = $this->mockIt(Blah::class);

assertType('Bug4875\Blah&Bug4875\Mock', $mock);
assertType('class-string<Bug4875\Blah>&class-string<Bug4875\Mock>&literal-string', $mock::class);
assertType('class-string<Bug4875\Blah>&class-string<Bug4875\Mock>', get_class($mock));
assertType('class-string<Bug4875\Blah&Bug4875\Mock>&literal-string', $mock::class);
assertType('class-string<Bug4875\Blah&Bug4875\Mock>', get_class($mock));
}

}
53 changes: 53 additions & 0 deletions tests/PHPStan/Analyser/data/bug-7987.php
@@ -0,0 +1,53 @@
<?php declare(strict_types=1);

namespace Bug7987;

use function PHPStan\Testing\assertType;

class AbstractA{}
class AbstractB{}

class Factory
{
/**
* @template TypeObject of AbstractA|AbstractB
* @param class-string<TypeObject> $objectClass
* @return TypeObject
*/
public function getObject(string $objectClass): AbstractA|AbstractB
{
if (is_subclass_of($objectClass, AbstractA::class)) {
assertType('class-string<TypeObject of Bug7987\AbstractA (method Bug7987\Factory::getObject(), argument)>', $objectClass);
$object = $this->getObjectA($objectClass);
assertType('TypeObject of Bug7987\AbstractA (method Bug7987\Factory::getObject(), argument)', $object);
} elseif (is_subclass_of($objectClass, AbstractB::class)) {
assertType('class-string<TypeObject of Bug7987\AbstractB (method Bug7987\Factory::getObject(), argument)>', $objectClass);
$object = $this->getObjectB($objectClass);
assertType('TypeObject of Bug7987\AbstractB (method Bug7987\Factory::getObject(), argument)', $object);
} else {
throw new \Exception("unable to instantiate $objectClass");
}
assertType('TypeObject of Bug7987\AbstractA (method Bug7987\Factory::getObject(), argument)|TypeObject of Bug7987\AbstractB (method Bug7987\Factory::getObject(), argument)', $object);
return $object;
}

/**
* @template TypeObject of AbstractA
* @param class-string<TypeObject> $objectClass
* @return TypeObject
*/
private function getObjectA(string $objectClass): AbstractA
{
return new $objectClass();
}

/**
* @template TypeObject of AbstractB
* @param class-string<TypeObject> $objectClass
* @return TypeObject
*/
private function getObjectB(string $objectClass): AbstractB
{
return new $objectClass();
}
}

0 comments on commit 4b22f3d

Please sign in to comment.