Skip to content

Commit cfcc764

Browse files
arnaud-lbondrejmirtes
authored andcommitted
Generic variance
1 parent 01fd3c6 commit cfcc764

24 files changed

+398
-70
lines changed

src/PhpDoc/PhpDocNodeResolver.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use PHPStan\Type\ArrayType;
2323
use PHPStan\Type\Generic\TemplateTypeFactory;
2424
use PHPStan\Type\Generic\TemplateTypeMap;
25+
use PHPStan\Type\Generic\TemplateTypeVariance;
2526
use PHPStan\Type\IntegerType;
2627
use PHPStan\Type\MixedType;
2728
use PHPStan\Type\Type;
@@ -263,7 +264,18 @@ private function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScop
263264
foreach ($phpDocNode->getTemplateTagValues($tagName) as $tagValue) {
264265
$resolved[$tagValue->name] = new TemplateTag(
265266
$tagValue->name,
266-
$tagValue->bound !== null ? $this->typeNodeResolver->resolve($tagValue->bound, $nameScope) : new MixedType()
267+
$tagValue->bound !== null ? $this->typeNodeResolver->resolve($tagValue->bound, $nameScope) : new MixedType(),
268+
TemplateTypeVariance::createInvariant()
269+
);
270+
}
271+
}
272+
273+
foreach (['@template-covariant', '@psalm-template-covariant', '@phpstan-template-covariant'] as $tagName) {
274+
foreach ($phpDocNode->getTemplateTagValues($tagName) as $tagValue) {
275+
$resolved[$tagValue->name] = new TemplateTag(
276+
$tagValue->name,
277+
$tagValue->bound !== null ? $this->typeNodeResolver->resolve($tagValue->bound, $nameScope) : new MixedType(),
278+
TemplateTypeVariance::createCovariant()
267279
);
268280
}
269281
}

src/PhpDoc/Tag/TemplateTag.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\PhpDoc\Tag;
44

5+
use PHPStan\Type\Generic\TemplateTypeVariance;
56
use PHPStan\Type\Type;
67

78
class TemplateTag
@@ -13,10 +14,14 @@ class TemplateTag
1314
/** @var \PHPStan\Type\Type */
1415
private $bound;
1516

16-
public function __construct(string $name, Type $bound)
17+
/** @var TemplateTypeVariance */
18+
private $variance;
19+
20+
public function __construct(string $name, Type $bound, TemplateTypeVariance $variance)
1721
{
1822
$this->name = $name;
1923
$this->bound = $bound;
24+
$this->variance = $variance;
2025
}
2126

2227
public function getName(): string
@@ -29,6 +34,11 @@ public function getBound(): Type
2934
return $this->bound;
3035
}
3136

37+
public function getVariance(): TemplateTypeVariance
38+
{
39+
return $this->variance;
40+
}
41+
3242
/**
3343
* @param mixed[] $properties
3444
* @return self
@@ -37,7 +47,8 @@ public static function __set_state(array $properties): self
3747
{
3848
return new self(
3949
$properties['name'],
40-
$properties['bound']
50+
$properties['bound'],
51+
$properties['variance']
4152
);
4253
}
4354

src/Type/Generic/GenericObjectType.php

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use PHPStan\Type\CompoundType;
1414
use PHPStan\Type\ErrorType;
1515
use PHPStan\Type\IntersectionType;
16-
use PHPStan\Type\MixedType;
1716
use PHPStan\Type\ObjectType;
1817
use PHPStan\Type\Type;
1918
use PHPStan\Type\TypeWithClassName;
@@ -116,17 +115,24 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): Trinar
116115
return TrinaryLogic::createNo();
117116
}
118117

119-
foreach ($this->types as $i => $t) {
118+
$classReflection = $this->getClassReflection();
119+
if ($classReflection === null) {
120+
return $nakedSuperTypeOf;
121+
}
122+
123+
$typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap());
124+
125+
foreach ($typeList as $i => $templateType) {
120126
if (!isset($ancestor->types[$i])) {
121127
throw new \PHPStan\ShouldNotHappenException();
122128
}
123-
if (!$t->equals($ancestor->types[$i])) {
124-
if ($t instanceof MixedType) {
125-
continue;
126-
}
127-
if ($ancestor->types[$i] instanceof MixedType) {
128-
continue;
129-
}
129+
if (!isset($this->types[$i])) {
130+
throw new \PHPStan\ShouldNotHappenException();
131+
}
132+
if (!$templateType instanceof TemplateType) {
133+
throw new \PHPStan\ShouldNotHappenException();
134+
}
135+
if (!$templateType->isValidVariance($this->types[$i], $ancestor->types[$i])) {
130136
return TrinaryLogic::createNo();
131137
}
132138
}
@@ -218,6 +224,11 @@ public function traverse(callable $cb): Type
218224
return $this;
219225
}
220226

227+
public function changeSubtractedType(?Type $subtractedType): Type
228+
{
229+
return new self($this->getClassName(), $this->types, $subtractedType);
230+
}
231+
221232
/**
222233
* @param mixed[] $properties
223234
* @return Type

src/Type/Generic/TemplateMixedType.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ final class TemplateMixedType extends MixedType implements TemplateType
2525
/** @var TemplateTypeStrategy */
2626
private $strategy;
2727

28+
/** @var TemplateTypeVariance */
29+
private $variance;
30+
2831
public function __construct(
2932
TemplateTypeScope $scope,
3033
TemplateTypeStrategy $templateTypeStrategy,
34+
TemplateTypeVariance $templateTypeVariance,
3135
string $name,
3236
?Type $subtractedType = null
3337
)
@@ -36,6 +40,7 @@ public function __construct(
3640

3741
$this->scope = $scope;
3842
$this->strategy = $templateTypeStrategy;
43+
$this->variance = $templateTypeVariance;
3944
$this->name = $name;
4045
}
4146

@@ -143,11 +148,17 @@ public function toArgument(): TemplateType
143148
return new self(
144149
$this->scope,
145150
new TemplateTypeArgumentStrategy(),
151+
$this->variance,
146152
$this->name,
147153
$this->getSubtractedType()
148154
);
149155
}
150156

157+
public function isValidVariance(Type $a, Type $b): bool
158+
{
159+
return $this->variance->isValidVariance($a, $b);
160+
}
161+
151162
public function subtract(Type $type): Type
152163
{
153164
if ($type instanceof self) {
@@ -160,6 +171,7 @@ public function subtract(Type $type): Type
160171
return new self(
161172
$this->scope,
162173
$this->strategy,
174+
$this->variance,
163175
$this->name,
164176
$type
165177
);
@@ -170,6 +182,7 @@ public function getTypeWithoutSubtractedType(): Type
170182
return new self(
171183
$this->scope,
172184
$this->strategy,
185+
$this->variance,
173186
$this->name,
174187
null
175188
);
@@ -180,6 +193,7 @@ public function changeSubtractedType(?Type $subtractedType): Type
180193
return new self(
181194
$this->scope,
182195
$this->strategy,
196+
$this->variance,
183197
$this->name,
184198
$subtractedType
185199
);
@@ -194,6 +208,7 @@ public static function __set_state(array $properties): Type
194208
return new self(
195209
$properties['scope'],
196210
$properties['strategy'],
211+
$properties['variance'],
197212
$properties['name'],
198213
$properties['subtractedType']
199214
);

src/Type/Generic/TemplateObjectType.php

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use PHPStan\Type\ObjectType;
99
use PHPStan\Type\ObjectWithoutClassType;
1010
use PHPStan\Type\Type;
11-
use PHPStan\Type\TypeCombinator;
1211
use PHPStan\Type\TypeUtils;
1312
use PHPStan\Type\UnionType;
1413
use PHPStan\Type\VerbosityLevel;
@@ -28,9 +27,13 @@ final class TemplateObjectType extends ObjectType implements TemplateType
2827
/** @var ObjectType */
2928
private $bound;
3029

30+
/** @var TemplateTypeVariance */
31+
private $variance;
32+
3133
public function __construct(
3234
TemplateTypeScope $scope,
3335
TemplateTypeStrategy $templateTypeStrategy,
36+
TemplateTypeVariance $templateTypeVariance,
3437
string $name,
3538
string $class,
3639
?Type $subtractedType = null
@@ -40,6 +43,7 @@ public function __construct(
4043

4144
$this->scope = $scope;
4245
$this->strategy = $templateTypeStrategy;
46+
$this->variance = $templateTypeVariance;
4347
$this->name = $name;
4448
$this->bound = new ObjectType($class, $subtractedType);
4549
}
@@ -163,43 +167,24 @@ public function toArgument(): TemplateType
163167
return new self(
164168
$this->scope,
165169
new TemplateTypeArgumentStrategy(),
170+
TemplateTypeVariance::createInvariant(),
166171
$this->name,
167172
$this->getClassName(),
168173
$this->getSubtractedType()
169174
);
170175
}
171176

172-
public function subtract(Type $type): Type
173-
{
174-
if ($this->getSubtractedType() !== null) {
175-
$type = TypeCombinator::union($this->getSubtractedType(), $type);
176-
}
177-
178-
return new self(
179-
$this->scope,
180-
$this->strategy,
181-
$this->name,
182-
$this->getClassName(),
183-
$type
184-
);
185-
}
186-
187-
public function getTypeWithoutSubtractedType(): Type
177+
public function isValidVariance(Type $a, Type $b): bool
188178
{
189-
return new self(
190-
$this->scope,
191-
$this->strategy,
192-
$this->name,
193-
$this->getClassName(),
194-
null
195-
);
179+
return $this->variance->isValidVariance($a, $b);
196180
}
197181

198182
public function changeSubtractedType(?Type $subtractedType): Type
199183
{
200184
return new self(
201185
$this->scope,
202186
$this->strategy,
187+
$this->variance,
203188
$this->name,
204189
$this->getClassName(),
205190
$subtractedType
@@ -215,6 +200,7 @@ public static function __set_state(array $properties): Type
215200
return new self(
216201
$properties['scope'],
217202
$properties['strategy'],
203+
$properties['variance'],
218204
$properties['name'],
219205
$properties['className'],
220206
$properties['subtractedType']

src/Type/Generic/TemplateType.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ public function toArgument(): TemplateType;
1818

1919
public function isArgument(): bool;
2020

21+
public function isValidVariance(Type $a, Type $b): bool;
22+
2123
}

src/Type/Generic/TemplateTypeFactory.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@
1111
final class TemplateTypeFactory
1212
{
1313

14-
public static function create(TemplateTypeScope $scope, string $name, ?Type $bound): Type
14+
public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance): Type
1515
{
1616
$strategy = new TemplateTypeParameterStrategy();
1717

1818
if ($bound instanceof ObjectType) {
19-
return new TemplateObjectType($scope, $strategy, $name, $bound->getClassName());
19+
return new TemplateObjectType($scope, $strategy, $variance, $name, $bound->getClassName());
2020
}
2121

2222
if ($bound === null || get_class($bound) === MixedType::class) {
23-
return new TemplateMixedType($scope, $strategy, $name);
23+
return new TemplateMixedType($scope, $strategy, $variance, $name);
2424
}
2525

2626
return new ErrorType();
2727
}
2828

2929
public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): Type
3030
{
31-
return self::create($scope, $tag->getName(), $tag->getBound());
31+
return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance());
3232
}
3333

3434
}

0 commit comments

Comments
 (0)