Skip to content

Commit 768bfab

Browse files
committed
Support int and string as template type bound
1 parent 756af18 commit 768bfab

19 files changed

+307
-24
lines changed

src/Rules/Generics/TemplateTypeCheck.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
use PHPStan\Rules\ClassNameNodePair;
99
use PHPStan\Rules\RuleErrorBuilder;
1010
use PHPStan\Type\Generic\TemplateTypeScope;
11+
use PHPStan\Type\IntegerType;
1112
use PHPStan\Type\MixedType;
1213
use PHPStan\Type\ObjectType;
1314
use PHPStan\Type\ObjectWithoutClassType;
15+
use PHPStan\Type\StringType;
1416
use PHPStan\Type\Type;
1517
use PHPStan\Type\TypeTraverser;
1618
use PHPStan\Type\UnionType;
@@ -107,6 +109,8 @@ public function check(
107109
$boundClass = get_class($type);
108110
if (
109111
$boundClass === MixedType::class
112+
|| $boundClass === StringType::class
113+
|| $boundClass === IntegerType::class
110114
|| $boundClass === ObjectWithoutClassType::class
111115
|| $boundClass === ObjectType::class
112116
|| $type instanceof UnionType
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Generic;
4+
5+
use PHPStan\Type\IntegerType;
6+
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
7+
use PHPStan\Type\Type;
8+
9+
final class TemplateIntegerType extends IntegerType implements TemplateType
10+
{
11+
12+
use TemplateTypeTrait;
13+
use UndecidedComparisonCompoundTypeTrait;
14+
15+
public function __construct(
16+
TemplateTypeScope $scope,
17+
TemplateTypeStrategy $templateTypeStrategy,
18+
TemplateTypeVariance $templateTypeVariance,
19+
string $name
20+
)
21+
{
22+
$this->scope = $scope;
23+
$this->strategy = $templateTypeStrategy;
24+
$this->variance = $templateTypeVariance;
25+
$this->name = $name;
26+
$this->bound = new IntegerType();
27+
}
28+
29+
public function toArgument(): TemplateType
30+
{
31+
return new self(
32+
$this->scope,
33+
new TemplateTypeArgumentStrategy(),
34+
$this->variance,
35+
$this->name
36+
);
37+
}
38+
39+
protected function shouldGeneralizeInferredType(): bool
40+
{
41+
return false;
42+
}
43+
44+
/**
45+
* @param mixed[] $properties
46+
* @return Type
47+
*/
48+
public static function __set_state(array $properties): Type
49+
{
50+
return new self(
51+
$properties['scope'],
52+
$properties['strategy'],
53+
$properties['variance'],
54+
$properties['name']
55+
);
56+
}
57+
58+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Generic;
4+
5+
use PHPStan\Type\StringType;
6+
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
7+
use PHPStan\Type\Type;
8+
9+
final class TemplateStringType extends StringType implements TemplateType
10+
{
11+
12+
use TemplateTypeTrait;
13+
use UndecidedComparisonCompoundTypeTrait;
14+
15+
public function __construct(
16+
TemplateTypeScope $scope,
17+
TemplateTypeStrategy $templateTypeStrategy,
18+
TemplateTypeVariance $templateTypeVariance,
19+
string $name
20+
)
21+
{
22+
$this->scope = $scope;
23+
$this->strategy = $templateTypeStrategy;
24+
$this->variance = $templateTypeVariance;
25+
$this->name = $name;
26+
$this->bound = new StringType();
27+
}
28+
29+
public function toArgument(): TemplateType
30+
{
31+
return new self(
32+
$this->scope,
33+
new TemplateTypeArgumentStrategy(),
34+
$this->variance,
35+
$this->name
36+
);
37+
}
38+
39+
protected function shouldGeneralizeInferredType(): bool
40+
{
41+
return false;
42+
}
43+
44+
/**
45+
* @param mixed[] $properties
46+
* @return Type
47+
*/
48+
public static function __set_state(array $properties): Type
49+
{
50+
return new self(
51+
$properties['scope'],
52+
$properties['strategy'],
53+
$properties['variance'],
54+
$properties['name']
55+
);
56+
}
57+
58+
}

src/Type/Generic/TemplateTypeFactory.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44

55
use PHPStan\PhpDoc\Tag\TemplateTag;
66
use PHPStan\Type\BenevolentUnionType;
7+
use PHPStan\Type\IntegerType;
78
use PHPStan\Type\MixedType;
89
use PHPStan\Type\ObjectType;
910
use PHPStan\Type\ObjectWithoutClassType;
11+
use PHPStan\Type\StringType;
1012
use PHPStan\Type\Type;
13+
use PHPStan\Type\TypeWithClassName;
1114
use PHPStan\Type\UnionType;
1215

1316
final class TemplateTypeFactory
1417
{
1518

16-
public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance): Type
19+
public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance): TemplateType
1720
{
1821
$strategy = new TemplateTypeParameterStrategy();
1922

@@ -22,13 +25,21 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou
2225
}
2326

2427
$boundClass = get_class($bound);
25-
if ($boundClass === ObjectType::class) {
28+
if ($bound instanceof TypeWithClassName && $boundClass === ObjectType::class) {
2629
return new TemplateObjectType($scope, $strategy, $variance, $name, $bound->getClassName());
2730
}
2831
if ($boundClass === ObjectWithoutClassType::class) {
2932
return new TemplateObjectWithoutClassType($scope, $strategy, $variance, $name);
3033
}
3134

35+
if ($boundClass === StringType::class) {
36+
return new TemplateStringType($scope, $strategy, $variance, $name);
37+
}
38+
39+
if ($boundClass === IntegerType::class) {
40+
return new TemplateIntegerType($scope, $strategy, $variance, $name);
41+
}
42+
3243
if ($boundClass === MixedType::class) {
3344
return new TemplateMixedType($scope, $strategy, $variance, $name);
3445
}
@@ -46,7 +57,7 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou
4657
return new TemplateMixedType($scope, $strategy, $variance, $name);
4758
}
4859

49-
public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): Type
60+
public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): TemplateType
5061
{
5162
return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance());
5263
}

src/Type/Generic/TemplateTypeTrait.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
155155

156156
if ($this->getBound()->isSuperTypeOf($receivedType)->yes()) {
157157
return new TemplateTypeMap([
158-
$this->name => TemplateTypeHelper::generalizeType($receivedType),
158+
$this->name => $this->shouldGeneralizeInferredType() ? TemplateTypeHelper::generalizeType($receivedType) : $receivedType,
159159
]);
160160
}
161161

@@ -172,4 +172,9 @@ public function getVariance(): TemplateTypeVariance
172172
return $this->variance;
173173
}
174174

175+
protected function shouldGeneralizeInferredType(): bool
176+
{
177+
return true;
178+
}
179+
175180
}

tests/PHPStan/Generics/TemplateTypeFactoryTest.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace PHPStan\Generics;
44

55
use PHPStan\Type\ErrorType;
6-
use PHPStan\Type\Generic\TemplateType;
76
use PHPStan\Type\Generic\TemplateTypeFactory;
87
use PHPStan\Type\Generic\TemplateTypeScope;
98
use PHPStan\Type\Generic\TemplateTypeVariance;
@@ -36,7 +35,11 @@ public function dataCreate(): array
3635
],
3736
[
3837
new StringType(),
39-
new MixedType(),
38+
new StringType(),
39+
],
40+
[
41+
new IntegerType(),
42+
new IntegerType(),
4043
],
4144
[
4245
new ErrorType(),
@@ -77,7 +80,6 @@ public function testCreate(?Type $bound, Type $expectedBound): void
7780
TemplateTypeVariance::createInvariant()
7881
);
7982

80-
$this->assertInstanceOf(TemplateType::class, $templateType);
8183
$this->assertTrue(
8284
$expectedBound->equals($templateType->getBound()),
8385
sprintf('%s -> equals(%s)', $expectedBound->describe(VerbosityLevel::precise()), $templateType->getBound()->describe(VerbosityLevel::precise()))

tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function testRule(): void
3333
16,
3434
],
3535
[
36-
'PHPDoc tag @template T for class ClassTemplateType\Baz with bound type int is not supported.',
36+
'PHPDoc tag @template T for class ClassTemplateType\Baz with bound type float is not supported.',
3737
24,
3838
],
3939
[
@@ -53,7 +53,7 @@ public function testRule(): void
5353
50,
5454
],
5555
[
56-
'PHPDoc tag @template T for anonymous class with bound type int is not supported.',
56+
'PHPDoc tag @template T for anonymous class with bound type float is not supported.',
5757
55,
5858
],
5959
[

tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function testRule(): void
3434
16,
3535
],
3636
[
37-
'PHPDoc tag @template T for function FunctionTemplateType\baz() with bound type int is not supported.',
37+
'PHPDoc tag @template T for function FunctionTemplateType\baz() with bound type float is not supported.',
3838
24,
3939
],
4040
[

tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function testRule(): void
3434
16,
3535
],
3636
[
37-
'PHPDoc tag @template T for interface InterfaceTemplateType\Baz with bound type int is not supported.',
37+
'PHPDoc tag @template T for interface InterfaceTemplateType\Baz with bound type float is not supported.',
3838
24,
3939
],
4040
[

tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function testRule(): void
3838
37,
3939
],
4040
[
41-
'PHPDoc tag @template T for method MethodTemplateType\Baz::doFoo() with bound type int is not supported.',
41+
'PHPDoc tag @template T for method MethodTemplateType\Baz::doFoo() with bound type float is not supported.',
4242
50,
4343
],
4444
[

0 commit comments

Comments
 (0)