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

Accept derived template types #999

Merged
merged 1 commit into from Feb 5, 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
2 changes: 1 addition & 1 deletion src/Type/Generic/TemplateTypeArgumentStrategy.php
Expand Up @@ -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())));
}

Expand Down
14 changes: 5 additions & 9 deletions src/Type/Generic/TemplateTypeTrait.php
Expand Up @@ -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);
}

Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php
Expand Up @@ -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'], []);
}

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

namespace Bug5706;

/**
* @template T of string|int
* @param T $key
* @return T
*/
function bar($key)
{
if (is_int($key)) {
return $key;
}

// String
return $key;
}
17 changes: 17 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-5844.php
@@ -0,0 +1,17 @@
<?php

namespace Bug5844;

/**
* @template T of string|int
* @param T $value
* @return T
*/
function value($value)
{
if (is_string($value)) {
return $value;
}

return $value;
}
2 changes: 1 addition & 1 deletion tests/PHPStan/Type/StringTypeTest.php
Expand Up @@ -149,7 +149,7 @@ public function dataAccepts(): iterable
TemplateTypeVariance::createInvariant(),
)->toArgument(),
new StringType(),
TrinaryLogic::createNo(),
TrinaryLogic::createMaybe(),
];

yield [
Expand Down
57 changes: 36 additions & 21 deletions tests/PHPStan/Type/TemplateTypeTest.php
Expand Up @@ -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(),
Expand All @@ -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(),
],
];
}

Expand Down Expand Up @@ -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')),
Expand Down Expand Up @@ -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(),
Expand Down