From 4a45db594789fe1b9cafd0d013359eba8790bd3a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 14 May 2021 14:43:59 +0200 Subject: [PATCH] Fix TemplateUnionType as part of intersection --- src/Type/TypeCombinator.php | 45 +++++++++++----- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5000.php | 53 +++++++++++++++++++ 3 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5000.php diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 475a1d9a5e..33464a69f6 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -13,6 +13,9 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateType; +use PHPStan\Type\Generic\TemplateTypeFactory; +use PHPStan\Type\Generic\TemplateTypeHelper; +use PHPStan\Type\Generic\TemplateUnionType; class TypeCombinator { @@ -603,23 +606,37 @@ public static function intersect(Type ...$types): Type }); // transform A & (B | C) to (A & B) | (A & C) foreach ($types as $i => $type) { - if ($type instanceof UnionType) { - $topLevelUnionSubTypes = []; - foreach ($type->getTypes() as $innerUnionSubType) { - $topLevelUnionSubTypes[] = self::intersect( - $innerUnionSubType, - ...array_slice($types, 0, $i), - ...array_slice($types, $i + 1) - ); - } + if (!$type instanceof UnionType) { + continue; + } - $union = self::union(...$topLevelUnionSubTypes); - if ($type instanceof BenevolentUnionType) { - return TypeUtils::toBenevolentUnion($union); - } + $topLevelUnionSubTypes = []; + foreach ($type->getTypes() as $innerUnionSubType) { + $topLevelUnionSubTypes[] = self::intersect( + $innerUnionSubType, + ...array_slice($types, 0, $i), + ...array_slice($types, $i + 1) + ); + } + + $union = self::union(...$topLevelUnionSubTypes); + if ($type instanceof BenevolentUnionType) { + $union = TypeUtils::toBenevolentUnion($union); + } - return $union; + if ($type instanceof TemplateUnionType) { + $union = TemplateTypeFactory::create( + $type->getScope(), + $type->getName(), + $union, + $type->getVariance() + ); + if ($type->isArgument()) { + return TemplateTypeHelper::toArgument($union); + } } + + return $union; } // transform A & (B & C) to A & B & C diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 528839845c..45318d4b29 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -405,6 +405,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/getopt.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/generics-default.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4985.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5000.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5000.php b/tests/PHPStan/Analyser/data/bug-5000.php new file mode 100644 index 0000000000..da5edf9613 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5000.php @@ -0,0 +1,53 @@ + $f + */ + function foo(F $f): void { + $values = $f->getValues(); + assertType('array', $values); + + $f->getDetail($values[0]); + assertType('array', $values); + foreach($values as $val) { + assertType('T of Bug5000\A|Bug5000\B (method Bug5000\Foo::foo(), argument)', $val); + $f->getDetail($val); + } + } + + +}