Skip to content

Commit

Permalink
TemplateType should accept a TemplateType derived from itself
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-lb committed Feb 5, 2022
1 parent b6bbbaf commit b9a2703
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 32 deletions.
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
15 changes: 6 additions & 9 deletions src/Type/Generic/TemplateTypeTrait.php
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Type\Generic;

use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Traits\NonRemoveableTypeTrait;
Expand Down Expand Up @@ -124,7 +125,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 +150,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
58 changes: 37 additions & 21 deletions tests/PHPStan/Type/TemplateTypeTest.php
Expand Up @@ -12,6 +12,7 @@
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateTypeScope;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\ObjectWithoutClassType;
use stdClass;
use Throwable;
use function array_map;
Expand All @@ -31,37 +32,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 +71,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 +162,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 +227,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

0 comments on commit b9a2703

Please sign in to comment.