From 9c0e454e5883e50d253023a842e472a0957cf4f6 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 7 Feb 2022 14:42:22 +0100 Subject: [PATCH] Split TemplateType::isSubTypeOf() and TemplateType::isAcceptedBy() --- .../Generic/TemplateTypeArgumentStrategy.php | 10 +++- src/Type/Generic/TemplateTypeTrait.php | 22 ++++++++- src/Type/UnionType.php | 2 +- .../Rules/Methods/ReturnTypeRuleTest.php | 19 +++++++- tests/PHPStan/Rules/Methods/data/bug-6589.php | 33 +++++++++++++ tests/PHPStan/Type/ObjectTypeTest.php | 2 +- tests/PHPStan/Type/TemplateTypeTest.php | 8 ++++ tests/PHPStan/Type/UnionTypeTest.php | 46 ++++++++++++++++++- 8 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-6589.php diff --git a/src/Type/Generic/TemplateTypeArgumentStrategy.php b/src/Type/Generic/TemplateTypeArgumentStrategy.php index ec7541a6c7..025762fe42 100644 --- a/src/Type/Generic/TemplateTypeArgumentStrategy.php +++ b/src/Type/Generic/TemplateTypeArgumentStrategy.php @@ -25,8 +25,14 @@ public function accepts(TemplateType $left, Type $right, bool $strictTypes): Tri return TrinaryLogic::createNo(); } - return $left->isSuperTypeOf($right) - ->or(TrinaryLogic::createFromBoolean($right->equals(new MixedType()))); + if ($right instanceof TemplateType) { + $accepts = $right->isAcceptedBy($left, $strictTypes); + } else { + $accepts = $left->getBound()->accepts($right, $strictTypes) + ->and(TrinaryLogic::createMaybe()); + } + + return $accepts->or(TrinaryLogic::createFromBoolean($right->equals(new MixedType()))); } public function isArgument(): bool diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 6c6afcc9e7..50c5e2b320 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -112,7 +112,27 @@ public function equals(Type $type): bool public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic { - return $this->isSubTypeOf($acceptingType); + /** @var Type $bound */ + $bound = $this->getBound(); + if ( + !$acceptingType instanceof $bound + && !$this instanceof $acceptingType + && !$acceptingType instanceof TemplateType + && ($acceptingType instanceof UnionType || $acceptingType instanceof IntersectionType) + ) { + return $acceptingType->accepts($this, $strictTypes); + } + + if (!$acceptingType instanceof TemplateType) { + return $acceptingType->accepts($this->getBound(), $strictTypes); + } + + if ($this->getScope()->equals($acceptingType->getScope()) && $this->getName() === $acceptingType->getName()) { + return $acceptingType->getBound()->accepts($this->getBound(), $strictTypes); + } + + return $acceptingType->getBound()->accepts($this->getBound(), $strictTypes) + ->and(TrinaryLogic::createMaybe()); } public function accepts(Type $type, bool $strictTypes): TrinaryLogic diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 9dc2f0aa6e..d7cd5380ae 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -92,7 +92,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic return TrinaryLogic::createYes(); } - if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateUnionType) { + if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateType) { return $type->isAcceptedBy($this, $strictTypes); } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 3f9b8ea5d3..b99b23ba49 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -16,9 +16,11 @@ class ReturnTypeRuleTest extends RuleTestCase private bool $checkExplicitMixed = false; + private bool $checkUnionTypes = true; + protected function getRule(): Rule { - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed))); + return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, $this->checkExplicitMixed))); } public function testReturnTypeRule(): void @@ -586,4 +588,19 @@ public function testBug6438(): void $this->analyse([__DIR__ . '/data/bug-6438.php'], []); } + public function testBug6589(): void + { + $this->checkUnionTypes = false; + $this->analyse([__DIR__ . '/data/bug-6589.php'], [ + [ + 'Method Bug6589\HelloWorldTemplated::getField() should return TField of Bug6589\Field2 but returns Bug6589\Field.', + 17, + ], + [ + 'Method Bug6589\HelloWorldSimple::getField() should return Bug6589\Field2 but returns Bug6589\Field.', + 31, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-6589.php b/tests/PHPStan/Rules/Methods/data/bug-6589.php new file mode 100644 index 0000000000..6eb08dda4e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-6589.php @@ -0,0 +1,33 @@ + [ + $templateType('T', new ObjectType(Iterator::class)), + new ObjectType(Traversable::class), + TrinaryLogic::createNo(), + TrinaryLogic::createNo(), + ], ]; } diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index c324442fd8..36636f4c05 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -919,7 +919,7 @@ public function dataAccepts(): array ), TrinaryLogic::createYes(), ], - 'maybe accepts template-of-union sub type of a union member' => [ + 'accepts template-of-union sub type of a union member' => [ new UnionType([ TemplateTypeFactory::create( TemplateTypeScope::createWithClass('Foo'), @@ -941,6 +941,30 @@ public function dataAccepts(): array ]), TemplateTypeVariance::createInvariant(), ), + TrinaryLogic::createYes(), + ], + 'maybe accepts template-of-union sub type of a union member (argument)' => [ + new UnionType([ + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'T', + new UnionType([ + new IntegerType(), + new FloatType(), + ]), + TemplateTypeVariance::createInvariant(), + )->toArgument(), + new NullType(), + ]), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Bar'), + 'T', + new UnionType([ + new IntegerType(), + new FloatType(), + ]), + TemplateTypeVariance::createInvariant(), + ), TrinaryLogic::createMaybe(), ], 'accepts template-of-string equal to a union member' => [ @@ -961,7 +985,7 @@ public function dataAccepts(): array ), TrinaryLogic::createYes(), ], - 'maybe accepts template-of-string sub type of a union member' => [ + 'accepts template-of-string sub type of a union member' => [ new UnionType([ TemplateTypeFactory::create( TemplateTypeScope::createWithClass('Foo'), @@ -979,6 +1003,24 @@ public function dataAccepts(): array ), TrinaryLogic::createMaybe(), ], + 'maybe accepts template-of-string sub type of a union member (argument)' => [ + new UnionType([ + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'T', + new StringType(), + TemplateTypeVariance::createInvariant(), + )->toArgument(), + new NullType(), + ]), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Bar'), + 'T', + new StringType(), + TemplateTypeVariance::createInvariant(), + ), + TrinaryLogic::createMaybe(), + ], 'accepts template-of-union containing a union member' => [ new UnionType([ new IntegerType(),