Skip to content

Commit 35606f9

Browse files
arnaud-lbondrejmirtes
authored andcommitted
Split TemplateType::isSubTypeOf() and TemplateType::isAcceptedBy()
1 parent bd4f179 commit 35606f9

File tree

8 files changed

+134
-8
lines changed

8 files changed

+134
-8
lines changed

src/Type/Generic/TemplateTypeArgumentStrategy.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@ public function accepts(TemplateType $left, Type $right, bool $strictTypes): Tri
2525
return TrinaryLogic::createNo();
2626
}
2727

28-
return $left->isSuperTypeOf($right)
29-
->or(TrinaryLogic::createFromBoolean($right->equals(new MixedType())));
28+
if ($right instanceof TemplateType) {
29+
$accepts = $right->isAcceptedBy($left, $strictTypes);
30+
} else {
31+
$accepts = $left->getBound()->accepts($right, $strictTypes)
32+
->and(TrinaryLogic::createMaybe());
33+
}
34+
35+
return $accepts->or(TrinaryLogic::createFromBoolean($right->equals(new MixedType())));
3036
}
3137

3238
public function isArgument(): bool

src/Type/Generic/TemplateTypeTrait.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,27 @@ public function equals(Type $type): bool
112112

113113
public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
114114
{
115-
return $this->isSubTypeOf($acceptingType);
115+
/** @var Type $bound */
116+
$bound = $this->getBound();
117+
if (
118+
!$acceptingType instanceof $bound
119+
&& !$this instanceof $acceptingType
120+
&& !$acceptingType instanceof TemplateType
121+
&& ($acceptingType instanceof UnionType || $acceptingType instanceof IntersectionType)
122+
) {
123+
return $acceptingType->accepts($this, $strictTypes);
124+
}
125+
126+
if (!$acceptingType instanceof TemplateType) {
127+
return $acceptingType->accepts($this->getBound(), $strictTypes);
128+
}
129+
130+
if ($this->getScope()->equals($acceptingType->getScope()) && $this->getName() === $acceptingType->getName()) {
131+
return $acceptingType->getBound()->accepts($this->getBound(), $strictTypes);
132+
}
133+
134+
return $acceptingType->getBound()->accepts($this->getBound(), $strictTypes)
135+
->and(TrinaryLogic::createMaybe());
116136
}
117137

118138
public function accepts(Type $type, bool $strictTypes): TrinaryLogic

src/Type/UnionType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic
9292
return TrinaryLogic::createYes();
9393
}
9494

95-
if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateUnionType) {
95+
if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateType) {
9696
return $type->isAcceptedBy($this, $strictTypes);
9797
}
9898

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ class ReturnTypeRuleTest extends RuleTestCase
1616

1717
private bool $checkExplicitMixed = false;
1818

19+
private bool $checkUnionTypes = true;
20+
1921
protected function getRule(): Rule
2022
{
21-
return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed)));
23+
return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, $this->checkExplicitMixed)));
2224
}
2325

2426
public function testReturnTypeRule(): void
@@ -586,4 +588,19 @@ public function testBug6438(): void
586588
$this->analyse([__DIR__ . '/data/bug-6438.php'], []);
587589
}
588590

591+
public function testBug6589(): void
592+
{
593+
$this->checkUnionTypes = false;
594+
$this->analyse([__DIR__ . '/data/bug-6589.php'], [
595+
[
596+
'Method Bug6589\HelloWorldTemplated::getField() should return TField of Bug6589\Field2 but returns Bug6589\Field.',
597+
17,
598+
],
599+
[
600+
'Method Bug6589\HelloWorldSimple::getField() should return Bug6589\Field2 but returns Bug6589\Field.',
601+
31,
602+
],
603+
]);
604+
}
605+
589606
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug6589;
4+
5+
class Field {}
6+
7+
class Field2 extends Field {}
8+
9+
/**
10+
* @template TField of Field2
11+
*/
12+
class HelloWorldTemplated
13+
{
14+
/** @return TField */
15+
public function getField()
16+
{
17+
return new Field();
18+
}
19+
20+
/** @return TField */
21+
public function getField2()
22+
{
23+
return new Field2();
24+
}
25+
}
26+
27+
class HelloWorldSimple
28+
{
29+
public function getField(string $name): Field2
30+
{
31+
return new Field();
32+
}
33+
}

tests/PHPStan/Type/ObjectTypeTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ public function dataAccepts(): array
412412
new ObjectType(DateTimeInterface::class),
413413
TemplateTypeVariance::createInvariant(),
414414
),
415-
TrinaryLogic::createMaybe(),
415+
TrinaryLogic::createNo(),
416416
],
417417
];
418418
}

tests/PHPStan/Type/TemplateTypeTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use DateTime;
66
use DateTimeInterface;
77
use Exception;
8+
use Iterator;
89
use PHPStan\Testing\PHPStanTestCase;
910
use PHPStan\TrinaryLogic;
1011
use PHPStan\Type\Generic\TemplateType;
@@ -14,6 +15,7 @@
1415
use PHPStan\Type\Generic\TemplateTypeVariance;
1516
use stdClass;
1617
use Throwable;
18+
use Traversable;
1719
use function array_map;
1820
use function assert;
1921
use function sprintf;
@@ -85,6 +87,12 @@ public function dataAccepts(): array
8587
TrinaryLogic::createYes(),
8688
TrinaryLogic::createYes(),
8789
],
90+
'does not accept ObjectType that is a super type of bound' => [
91+
$templateType('T', new ObjectType(Iterator::class)),
92+
new ObjectType(Traversable::class),
93+
TrinaryLogic::createNo(),
94+
TrinaryLogic::createNo(),
95+
],
8896
];
8997
}
9098

tests/PHPStan/Type/UnionTypeTest.php

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,7 @@ public function dataAccepts(): array
919919
),
920920
TrinaryLogic::createYes(),
921921
],
922-
'maybe accepts template-of-union sub type of a union member' => [
922+
'accepts template-of-union sub type of a union member' => [
923923
new UnionType([
924924
TemplateTypeFactory::create(
925925
TemplateTypeScope::createWithClass('Foo'),
@@ -941,6 +941,30 @@ public function dataAccepts(): array
941941
]),
942942
TemplateTypeVariance::createInvariant(),
943943
),
944+
TrinaryLogic::createYes(),
945+
],
946+
'maybe accepts template-of-union sub type of a union member (argument)' => [
947+
new UnionType([
948+
TemplateTypeFactory::create(
949+
TemplateTypeScope::createWithClass('Foo'),
950+
'T',
951+
new UnionType([
952+
new IntegerType(),
953+
new FloatType(),
954+
]),
955+
TemplateTypeVariance::createInvariant(),
956+
)->toArgument(),
957+
new NullType(),
958+
]),
959+
TemplateTypeFactory::create(
960+
TemplateTypeScope::createWithClass('Bar'),
961+
'T',
962+
new UnionType([
963+
new IntegerType(),
964+
new FloatType(),
965+
]),
966+
TemplateTypeVariance::createInvariant(),
967+
),
944968
TrinaryLogic::createMaybe(),
945969
],
946970
'accepts template-of-string equal to a union member' => [
@@ -961,7 +985,7 @@ public function dataAccepts(): array
961985
),
962986
TrinaryLogic::createYes(),
963987
],
964-
'maybe accepts template-of-string sub type of a union member' => [
988+
'accepts template-of-string sub type of a union member' => [
965989
new UnionType([
966990
TemplateTypeFactory::create(
967991
TemplateTypeScope::createWithClass('Foo'),
@@ -979,6 +1003,24 @@ public function dataAccepts(): array
9791003
),
9801004
TrinaryLogic::createMaybe(),
9811005
],
1006+
'maybe accepts template-of-string sub type of a union member (argument)' => [
1007+
new UnionType([
1008+
TemplateTypeFactory::create(
1009+
TemplateTypeScope::createWithClass('Foo'),
1010+
'T',
1011+
new StringType(),
1012+
TemplateTypeVariance::createInvariant(),
1013+
)->toArgument(),
1014+
new NullType(),
1015+
]),
1016+
TemplateTypeFactory::create(
1017+
TemplateTypeScope::createWithClass('Bar'),
1018+
'T',
1019+
new StringType(),
1020+
TemplateTypeVariance::createInvariant(),
1021+
),
1022+
TrinaryLogic::createMaybe(),
1023+
],
9821024
'accepts template-of-union containing a union member' => [
9831025
new UnionType([
9841026
new IntegerType(),

0 commit comments

Comments
 (0)