Skip to content

Commit

Permalink
Split TemplateType::isSubTypeOf() and TemplateType::isAcceptedBy()
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-lb committed Feb 7, 2022
1 parent b464b21 commit 57c0562
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 6 deletions.
10 changes: 8 additions & 2 deletions src/Type/Generic/TemplateTypeArgumentStrategy.php
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion src/Type/Generic/TemplateTypeTrait.php
Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php
Expand Up @@ -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
Expand Down Expand Up @@ -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,
],
]);
}

}
33 changes: 33 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-6589.php
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace Bug6589;

class Field {}

class Field2 extends Field {}

/**
* @template TField of Field2
*/
class HelloWorldTemplated
{
/** @return TField */
public function getField()
{
return new Field();
}

/** @return TField */
public function getField2()
{
return new Field2();
}
}

class HelloWorldSimple
{
public function getField(string $name): Field2
{
return new Field();
}
}
2 changes: 1 addition & 1 deletion tests/PHPStan/Type/ObjectTypeTest.php
Expand Up @@ -412,7 +412,7 @@ public function dataAccepts(): array
new ObjectType(DateTimeInterface::class),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createMaybe(),
TrinaryLogic::createNo(),
],
];
}
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Type/TemplateTypeTest.php
Expand Up @@ -85,6 +85,12 @@ public function dataAccepts(): array
TrinaryLogic::createYes(),
TrinaryLogic::createYes(),
],
'does not accept ObjectType that is a super type of bound' => [
$templateType('T', new ObjectType(Iterable::class)),
new ObjectType(Traversable::class),
TrinaryLogic::createNo(),
TrinaryLogic::createNo(),
],
];
}

Expand Down
26 changes: 25 additions & 1 deletion tests/PHPStan/Type/UnionTypeTest.php
Expand Up @@ -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'),
Expand All @@ -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' => [
Expand Down

0 comments on commit 57c0562

Please sign in to comment.