Skip to content

Commit e671cc0

Browse files
committed
Nested generic types
1 parent d0e6683 commit e671cc0

33 files changed

+985
-42
lines changed

src/Analyser/NameScope.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,22 @@ public function withTemplateTypeMap(TemplateTypeMap $map): self
116116
);
117117
}
118118

119+
public function unsetTemplateType(string $name): self
120+
{
121+
$map = $this->templateTypeMap;
122+
if (!$map->hasType($name)) {
123+
return $this;
124+
}
125+
126+
return new self(
127+
$this->namespace,
128+
$this->uses,
129+
$this->className,
130+
$this->functionName,
131+
$this->templateTypeMap->unsetType($name)
132+
);
133+
}
134+
119135
/**
120136
* @param mixed[] $properties
121137
* @return self

src/PhpDoc/PhpDocNodeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ public function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScope
276276

277277
$resolved[$valueNode->name] = new TemplateTag(
278278
$valueNode->name,
279-
$valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope) : new MixedType(),
279+
$valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(),
280280
$variance
281281
);
282282
$resolvedPrefix[$valueNode->name] = $prefix;

src/Reflection/ClassReflection.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -843,11 +843,10 @@ public function getTemplateTypeMap(): TemplateTypeMap
843843
return $this->templateTypeMap;
844844
}
845845

846-
$templateTypeMap = new TemplateTypeMap(array_map(function (TemplateTag $tag): Type {
847-
return TemplateTypeFactory::fromTemplateTag(
848-
TemplateTypeScope::createWithClass($this->getName()),
849-
$tag
850-
);
846+
$templateTypeScope = TemplateTypeScope::createWithClass($this->getName());
847+
848+
$templateTypeMap = new TemplateTypeMap(array_map(static function (TemplateTag $tag) use ($templateTypeScope): Type {
849+
return TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag);
851850
}, $this->getTemplateTags()));
852851

853852
$this->templateTypeMap = $templateTypeMap;

src/Rules/Generics/ClassTemplateTypeRule.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\InClassNode;
88
use PHPStan\Rules\Rule;
9-
use PHPStan\Type\Generic\TemplateTypeScope;
109

1110
/**
1211
* @implements \PHPStan\Rules\Rule<InClassNode>
@@ -34,7 +33,6 @@ public function processNode(Node $node, Scope $scope): array
3433
return [];
3534
}
3635
$classReflection = $scope->getClassReflection();
37-
$className = $classReflection->getName();
3836
if ($classReflection->isAnonymous()) {
3937
$displayName = 'anonymous class';
4038
} else {
@@ -43,7 +41,6 @@ public function processNode(Node $node, Scope $scope): array
4341

4442
return $this->templateTypeCheck->check(
4543
$node,
46-
TemplateTypeScope::createWithClass($className),
4744
$classReflection->getTemplateTags(),
4845
sprintf('PHPDoc tag @template for %s cannot have existing class %%s as its name.', $displayName),
4946
sprintf('PHPDoc tag @template for %s cannot have existing type alias %%s as its name.', $displayName),

src/Rules/Generics/FunctionTemplateTypeRule.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Type\FileTypeMapper;
9-
use PHPStan\Type\Generic\TemplateTypeScope;
109

1110
/**
1211
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Function_>
@@ -54,7 +53,6 @@ public function processNode(Node $node, Scope $scope): array
5453

5554
return $this->templateTypeCheck->check(
5655
$node,
57-
TemplateTypeScope::createWithFunction($functionName),
5856
$resolvedPhpDoc->getTemplateTags(),
5957
sprintf('PHPDoc tag @template for function %s() cannot have existing class %%s as its name.', $functionName),
6058
sprintf('PHPDoc tag @template for function %s() cannot have existing type alias %%s as its name.', $functionName),

src/Rules/Generics/InterfaceTemplateTypeRule.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Type\FileTypeMapper;
9-
use PHPStan\Type\Generic\TemplateTypeScope;
109

1110
/**
1211
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Interface_>
@@ -54,7 +53,6 @@ public function processNode(Node $node, Scope $scope): array
5453

5554
return $this->templateTypeCheck->check(
5655
$node,
57-
TemplateTypeScope::createWithClass($interfaceName),
5856
$resolvedPhpDoc->getTemplateTags(),
5957
sprintf('PHPDoc tag @template for interface %s cannot have existing class %%s as its name.', $interfaceName),
6058
sprintf('PHPDoc tag @template for interface %s cannot have existing type alias %%s as its name.', $interfaceName),

src/Rules/Generics/MethodTemplateTypeRule.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PHPStan\Rules\Rule;
88
use PHPStan\Rules\RuleErrorBuilder;
99
use PHPStan\Type\FileTypeMapper;
10-
use PHPStan\Type\Generic\TemplateTypeScope;
1110
use PHPStan\Type\VerbosityLevel;
1211

1312
/**
@@ -59,7 +58,6 @@ public function processNode(Node $node, Scope $scope): array
5958
$methodTemplateTags = $resolvedPhpDoc->getTemplateTags();
6059
$messages = $this->templateTypeCheck->check(
6160
$node,
62-
TemplateTypeScope::createWithMethod($className, $methodName),
6361
$methodTemplateTags,
6462
sprintf('PHPDoc tag @template for method %s::%s() cannot have existing class %%s as its name.', $className, $methodName),
6563
sprintf('PHPDoc tag @template for method %s::%s() cannot have existing type alias %%s as its name.', $className, $methodName),

src/Rules/Generics/TemplateTypeCheck.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
use PHPStan\Rules\ClassCaseSensitivityCheck;
88
use PHPStan\Rules\ClassNameNodePair;
99
use PHPStan\Rules\RuleErrorBuilder;
10-
use PHPStan\Type\Generic\TemplateTypeScope;
10+
use PHPStan\Type\Generic\GenericObjectType;
11+
use PHPStan\Type\Generic\TemplateType;
1112
use PHPStan\Type\IntegerType;
1213
use PHPStan\Type\MixedType;
1314
use PHPStan\Type\ObjectType;
@@ -27,6 +28,8 @@ class TemplateTypeCheck
2728

2829
private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck;
2930

31+
private GenericObjectTypeCheck $genericObjectTypeCheck;
32+
3033
/** @var array<string, string> */
3134
private array $typeAliases;
3235

@@ -35,31 +38,32 @@ class TemplateTypeCheck
3538
/**
3639
* @param ReflectionProvider $reflectionProvider
3740
* @param ClassCaseSensitivityCheck $classCaseSensitivityCheck
41+
* @param GenericObjectTypeCheck $genericObjectTypeCheck
3842
* @param array<string, string> $typeAliases
3943
* @param bool $checkClassCaseSensitivity
4044
*/
4145
public function __construct(
4246
ReflectionProvider $reflectionProvider,
4347
ClassCaseSensitivityCheck $classCaseSensitivityCheck,
48+
GenericObjectTypeCheck $genericObjectTypeCheck,
4449
array $typeAliases,
4550
bool $checkClassCaseSensitivity
4651
)
4752
{
4853
$this->reflectionProvider = $reflectionProvider;
4954
$this->classCaseSensitivityCheck = $classCaseSensitivityCheck;
55+
$this->genericObjectTypeCheck = $genericObjectTypeCheck;
5056
$this->typeAliases = $typeAliases;
5157
$this->checkClassCaseSensitivity = $checkClassCaseSensitivity;
5258
}
5359

5460
/**
5561
* @param \PhpParser\Node $node
56-
* @param \PHPStan\Type\Generic\TemplateTypeScope $templateTypeScope
5762
* @param array<string, \PHPStan\PhpDoc\Tag\TemplateTag> $templateTags
5863
* @return \PHPStan\Rules\RuleError[]
5964
*/
6065
public function check(
6166
Node $node,
62-
TemplateTypeScope $templateTypeScope,
6367
array $templateTags,
6468
string $sameTemplateTypeNameAsClassMessage,
6569
string $sameTemplateTypeNameAsTypeMessage,
@@ -113,7 +117,9 @@ public function check(
113117
|| $boundClass === IntegerType::class
114118
|| $boundClass === ObjectWithoutClassType::class
115119
|| $boundClass === ObjectType::class
120+
|| $boundClass === GenericObjectType::class
116121
|| $type instanceof UnionType
122+
|| $type instanceof TemplateType
117123
) {
118124
return $traverse($type);
119125
}
@@ -122,6 +128,17 @@ public function check(
122128

123129
return $type;
124130
});
131+
132+
$genericObjectErrors = $this->genericObjectTypeCheck->check(
133+
$boundType,
134+
sprintf('PHPDoc tag @template %s bound contains generic type %%s but class %%s is not generic.', $templateTagName),
135+
sprintf('PHPDoc tag @template %s bound has type %%s which does not specify all template types of class %%s: %%s', $templateTagName),
136+
sprintf('PHPDoc tag @template %s bound has type %%s which specifies %%d template types, but class %%s supports only %%d: %%s', $templateTagName),
137+
sprintf('Type %%s in generic type %%s in PHPDoc tag @template %s is not subtype of template type %%s of class %%s.', $templateTagName),
138+
);
139+
foreach ($genericObjectErrors as $genericObjectError) {
140+
$messages[] = $genericObjectError;
141+
}
125142
}
126143

127144
return $messages;

src/Rules/Generics/TraitTemplateTypeRule.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Type\FileTypeMapper;
9-
use PHPStan\Type\Generic\TemplateTypeScope;
109

1110
/**
1211
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Trait_>
@@ -54,7 +53,6 @@ public function processNode(Node $node, Scope $scope): array
5453

5554
return $this->templateTypeCheck->check(
5655
$node,
57-
TemplateTypeScope::createWithClass($traitName),
5856
$resolvedPhpDoc->getTemplateTags(),
5957
sprintf('PHPDoc tag @template for trait %s cannot have existing class %%s as its name.', $traitName),
6058
sprintf('PHPDoc tag @template for trait %s cannot have existing type alias %%s as its name.', $traitName),

src/Type/FileTypeMapper.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ private function createResolvedPhpDocBlock(string $phpDocKey, NameScopedPhpDocSt
135135
$templateTypeMap->getTypes()
136136
))
137137
);
138+
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
139+
$templateTypeMap = new TemplateTypeMap(array_map(static function (TemplateTag $tag) use ($templateTypeScope): Type {
140+
return TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag);
141+
}, $templateTags));
142+
$nameScope = $nameScope->withTemplateTypeMap(
143+
new TemplateTypeMap(array_merge(
144+
$nameScope->getTemplateTypeMap()->getTypes(),
145+
$templateTypeMap->getTypes()
146+
))
147+
);
138148
} else {
139149
$templateTypeMap = TemplateTypeMap::createEmpty();
140150
}

0 commit comments

Comments
 (0)