From 0d288353ca782ef79e362657c2581abf4246a2f8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 5 Feb 2022 13:37:13 +0100 Subject: [PATCH] Fixed intersecting and removal from TemplateUnionType --- src/Type/Generic/TemplateTypeTrait.php | 20 ++++++-- .../Analyser/NodeScopeResolverTest.php | 1 + .../PHPStan/Analyser/data/bug-6566-types.php | 49 +++++++++++++++++++ tests/PHPStan/Analyser/data/generics.php | 2 +- .../Properties/AccessPropertiesRuleTest.php | 7 +++ .../Rules/Properties/data/bug-6566.php | 34 +++++++++++++ tests/PHPStan/Type/TypeCombinatorTest.php | 4 +- 7 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-6566-types.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-6566.php diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index d16f7f51a5..6c6afcc9e7 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -5,8 +5,8 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; -use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -17,8 +17,6 @@ trait TemplateTypeTrait { - use NonRemoveableTypeTrait; - private string $name; private TemplateTypeScope $scope; @@ -198,6 +196,22 @@ protected function shouldGeneralizeInferredType(): bool return true; } + public function tryRemove(Type $typeToRemove): ?Type + { + $removedBound = TypeCombinator::remove($this->getBound(), $typeToRemove); + $type = TemplateTypeFactory::create( + $this->getScope(), + $this->getName(), + $removedBound, + $this->getVariance(), + ); + if ($this->isArgument()) { + return TemplateTypeHelper::toArgument($type); + } + + return $type; + } + /** * @param mixed[] $properties */ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index aaf6bc04b3..ca08d9654e 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -694,6 +694,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-6473.php'); } yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6500.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6566-types.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-6566-types.php b/tests/PHPStan/Analyser/data/bug-6566-types.php new file mode 100644 index 0000000000..cc4b76ee99 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6566-types.php @@ -0,0 +1,49 @@ +getObject(); + assertType('T of Bug6566Types\A|Bug6566Types\B|Bug6566Types\C (class Bug6566Types\HelloWorld, argument)', $object); + if ($object instanceof C) { + assertType('T of Bug6566Types\C (class Bug6566Types\HelloWorld, argument)', $object); + return; + } + assertType('T of Bug6566Types\A|Bug6566Types\B (class Bug6566Types\HelloWorld, argument)', $object); + if ($object instanceof B) { + assertType('T of Bug6566Types\B (class Bug6566Types\HelloWorld, argument)', $object); + return; + } + assertType('T of Bug6566Types\A (class Bug6566Types\HelloWorld, argument)', $object); + if ($object instanceof A) { + assertType('T of Bug6566Types\A (class Bug6566Types\HelloWorld, argument)', $object); + return; + } + assertType('*NEVER*', $object); + } + + /** + * @return T + */ + abstract protected function getObject(): A|B|C; +} diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 26570ffa5b..95a15b2598 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1089,7 +1089,7 @@ function testGenericObjectWithoutClassType2($a) return $a; } - assertType('T of object (function PHPStan\Generics\FunctionsAssertType\testGenericObjectWithoutClassType2(), argument)', $b); + assertType('T of object~stdClass (function PHPStan\Generics\FunctionsAssertType\testGenericObjectWithoutClassType2(), argument)', $b); return $a; } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 9831b58b6b..5143119525 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -543,4 +543,11 @@ public function testBug6385(): void ]); } + public function testBug6566(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-6566.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-6566.php b/tests/PHPStan/Rules/Properties/data/bug-6566.php new file mode 100644 index 0000000000..4574c6a15f --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-6566.php @@ -0,0 +1,34 @@ +getObject(); + if (!$object instanceof C) { + echo $object->name; + } + } + + /** + * @return T + */ + abstract protected function getObject(): A|B|C; +} diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 145c7d080b..0ab7381406 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -3914,8 +3914,8 @@ public function dataRemove(): array TemplateTypeVariance::createInvariant(), ), new ConstantBooleanType(false), - TemplateBooleanType::class, - 'T of bool (class Foo, parameter)', + TemplateMixedType::class, // should be TemplateConstantBooleanType + 'T (class Foo, parameter)', // should be T of true ], ]; }