Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split TemplateType::isSubTypeOf() and TemplateType::isAcceptedBy() #1001

Merged
merged 1 commit into from Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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