diff --git a/src/Type/Generic/TemplateTypeArgumentStrategy.php b/src/Type/Generic/TemplateTypeArgumentStrategy.php index ec7541a6c76..025762fe423 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 6c6afcc9e7c..5a981128752 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -112,7 +112,16 @@ public function equals(Type $type): bool public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic { - return $this->isSubTypeOf($acceptingType); + 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/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 3f9b8ea5d35..b99b23ba491 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 00000000000..6eb08dda4e3 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-6589.php @@ -0,0 +1,33 @@ + [ + $templateType('T', new ObjectType(Iterable::class)), + new ObjectType(Traversable::class), + TrinaryLogic::createNo(), + TrinaryLogic::createNo(), + ], ]; } diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index c324442fd80..1a82c405f00 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' => [