diff --git a/src/Type/Generic/TemplateTypeArgumentStrategy.php b/src/Type/Generic/TemplateTypeArgumentStrategy.php index 07d1cc35e5..ec7541a6c7 100644 --- a/src/Type/Generic/TemplateTypeArgumentStrategy.php +++ b/src/Type/Generic/TemplateTypeArgumentStrategy.php @@ -25,7 +25,7 @@ public function accepts(TemplateType $left, Type $right, bool $strictTypes): Tri return TrinaryLogic::createNo(); } - return TrinaryLogic::createFromBoolean($left->equals($right)) + return $left->isSuperTypeOf($right) ->or(TrinaryLogic::createFromBoolean($right->equals(new MixedType()))); } diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 911b2bc1ff..d16f7f51a5 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -124,7 +124,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function isSuperTypeOf(Type $type): TrinaryLogic { - if ($type instanceof self || $type instanceof IntersectionType) { + if ($type instanceof TemplateType || $type instanceof IntersectionType) { return $type->isSubTypeOf($this); } @@ -149,16 +149,12 @@ public function isSubTypeOf(Type $type): TrinaryLogic return $type->isSuperTypeOf($this->getBound()); } - if ($this->equals($type)) { - return TrinaryLogic::createYes(); + if ($this->getScope()->equals($type->getScope()) && $this->getName() === $type->getName()) { + return $type->getBound()->isSuperTypeOf($this->getBound()); } - if ($type->getBound()->isSuperTypeOf($this->getBound())->no() && - $this->getBound()->isSuperTypeOf($type->getBound())->no()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createMaybe(); + return $type->getBound()->isSuperTypeOf($this->getBound()) + ->and(TrinaryLogic::createMaybe()); } public function inferTemplateTypes(Type $receivedType): TemplateTypeMap diff --git a/tests/PHPStan/Generics/data/classes-3.json b/tests/PHPStan/Generics/data/classes-7.json similarity index 100% rename from tests/PHPStan/Generics/data/classes-3.json rename to tests/PHPStan/Generics/data/classes-7.json diff --git a/tests/PHPStan/Generics/data/invalidReturn-3.json b/tests/PHPStan/Generics/data/invalidReturn-7.json similarity index 100% rename from tests/PHPStan/Generics/data/invalidReturn-3.json rename to tests/PHPStan/Generics/data/invalidReturn-7.json diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 74ec061a1c..aa713d7b9a 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -106,4 +106,14 @@ public function testBug2723(): void ]); } + public function testBug5706(): void + { + $this->analyse([__DIR__ . '/data/bug-5706.php'], []); + } + + public function testBug5844(): void + { + $this->analyse([__DIR__ . '/data/bug-5844.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-5706.php b/tests/PHPStan/Rules/Functions/data/bug-5706.php new file mode 100644 index 0000000000..9255da5622 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-5706.php @@ -0,0 +1,18 @@ +toArgument(), new StringType(), - TrinaryLogic::createNo(), + TrinaryLogic::createMaybe(), ]; yield [ diff --git a/tests/PHPStan/Type/TemplateTypeTest.php b/tests/PHPStan/Type/TemplateTypeTest.php index f24e6897ad..ac4c1ee701 100644 --- a/tests/PHPStan/Type/TemplateTypeTest.php +++ b/tests/PHPStan/Type/TemplateTypeTest.php @@ -31,37 +31,37 @@ public function dataAccepts(): array ); return [ - [ + 0 => [ $templateType('T', new ObjectType('DateTime')), new ObjectType('DateTime'), TrinaryLogic::createYes(), - TrinaryLogic::createNo(), + TrinaryLogic::createMaybe(), ], - [ + 1 => [ $templateType('T', new ObjectType('DateTimeInterface')), new ObjectType('DateTime'), TrinaryLogic::createYes(), - TrinaryLogic::createNo(), + TrinaryLogic::createMaybe(), ], - [ + 2 => [ $templateType('T', new ObjectType('DateTime')), $templateType('T', new ObjectType('DateTime')), TrinaryLogic::createYes(), TrinaryLogic::createYes(), ], - [ + 3 => [ $templateType('T', new ObjectType('DateTime'), 'a'), $templateType('T', new ObjectType('DateTime'), 'b'), TrinaryLogic::createMaybe(), - TrinaryLogic::createNo(), + TrinaryLogic::createMaybe(), ], - [ + 4 => [ $templateType('T', null), new MixedType(), TrinaryLogic::createYes(), TrinaryLogic::createYes(), ], - [ + 5 => [ $templateType('T', null), new IntersectionType([ new ObjectWithoutClassType(), @@ -70,6 +70,21 @@ public function dataAccepts(): array TrinaryLogic::createYes(), TrinaryLogic::createYes(), ], + 'accepts itself with a sub-type union bound' => [ + $templateType('T', new UnionType([ + new IntegerType(), + new StringType(), + ])), + $templateType('T', new IntegerType()), + TrinaryLogic::createYes(), + TrinaryLogic::createYes(), + ], + 'accepts itself with a sub-type object bound' => [ + $templateType('T', new ObjectWithoutClassType()), + $templateType('T', new ObjectType('stdClass')), + TrinaryLogic::createYes(), + TrinaryLogic::createYes(), + ], ]; } @@ -146,7 +161,7 @@ public function dataIsSuperTypeOf(): array $templateType('T', new ObjectType('DateTime')), $templateType('T', new ObjectType('DateTimeInterface')), TrinaryLogic::createMaybe(), // (T of DateTime) isSuperTypeTo (T of DateTimeInterface) - TrinaryLogic::createMaybe(), // (T of DateTimeInterface) isSuperTypeTo (T of DateTime) + TrinaryLogic::createYes(), // (T of DateTimeInterface) isSuperTypeTo (T of DateTime) ], 6 => [ $templateType('T', new ObjectType('DateTime')), @@ -211,49 +226,49 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), ], - [ + 15 => [ $templateType('T', new MixedType(true)), $templateType('U', new UnionType([new IntegerType(), new StringType()])), TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), ], - [ + 16 => [ $templateType('T', new MixedType(true)), $templateType('U', new BenevolentUnionType([new IntegerType(), new StringType()])), TrinaryLogic::createMaybe(), TrinaryLogic::createMaybe(), ], - [ + 17 => [ $templateType('T', new ObjectType(stdClass::class)), $templateType('U', new BenevolentUnionType([new IntegerType(), new StringType()])), TrinaryLogic::createNo(), TrinaryLogic::createNo(), ], - [ + 18 => [ $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), TrinaryLogic::createYes(), TrinaryLogic::createYes(), ], - [ + 19 => [ $templateType('T', new UnionType([new IntegerType(), new StringType()])), $templateType('T', new UnionType([new IntegerType(), new StringType()])), TrinaryLogic::createYes(), TrinaryLogic::createYes(), ], - [ + 20 => [ $templateType('T', new UnionType([new IntegerType(), new StringType()])), $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), - TrinaryLogic::createMaybe(), - TrinaryLogic::createMaybe(), + TrinaryLogic::createYes(), + TrinaryLogic::createYes(), ], - [ + 21 => [ $templateType('T', new UnionType([new IntegerType(), new StringType()])), $templateType('T', new IntegerType()), - TrinaryLogic::createMaybe(), + TrinaryLogic::createYes(), TrinaryLogic::createMaybe(), ], - [ + 22 => [ $templateType('T', new BenevolentUnionType([new IntegerType(), new StringType()])), new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType(), new NullType()]), TrinaryLogic::createMaybe(),