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 authored and ondrejmirtes committed Feb 7, 2022
1 parent bd4f179 commit 35606f9
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 8 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
22 changes: 21 additions & 1 deletion src/Type/Generic/TemplateTypeTrait.php
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Type/UnionType.php
Expand Up @@ -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);
}

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
8 changes: 8 additions & 0 deletions tests/PHPStan/Type/TemplateTypeTest.php
Expand Up @@ -5,6 +5,7 @@
use DateTime;
use DateTimeInterface;
use Exception;
use Iterator;
use PHPStan\Testing\PHPStanTestCase;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\TemplateType;
Expand All @@ -14,6 +15,7 @@
use PHPStan\Type\Generic\TemplateTypeVariance;
use stdClass;
use Throwable;
use Traversable;
use function array_map;
use function assert;
use function sprintf;
Expand Down Expand Up @@ -85,6 +87,12 @@ public function dataAccepts(): array
TrinaryLogic::createYes(),
TrinaryLogic::createYes(),
],
'does not accept ObjectType that is a super type of bound' => [
$templateType('T', new ObjectType(Iterator::class)),
new ObjectType(Traversable::class),
TrinaryLogic::createNo(),
TrinaryLogic::createNo(),
],
];
}

Expand Down
46 changes: 44 additions & 2 deletions 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 All @@ -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'),
Expand All @@ -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(),
Expand Down

0 comments on commit 35606f9

Please sign in to comment.