Skip to content

Commit 52475fb

Browse files
committed
Fix type alias resolution in template bound
Closes phpstan/phpstan#11314 Closes phpstan/phpstan#7152 Closes phpstan/phpstan#13332
1 parent ed5c392 commit 52475fb

8 files changed

Lines changed: 212 additions & 72 deletions

File tree

src/Analyser/IntermediaryNameScope.php

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

55
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
6+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
67

78
final class IntermediaryNameScope
89
{
@@ -13,7 +14,7 @@ final class IntermediaryNameScope
1314
* @param array<string, string> $uses alias(string) => fullName(string)
1415
* @param array<string, array{string, TemplateTagValueNode}> $templatePhpDocNodes
1516
* @param array<string, string> $constUses alias(string) => fullName(string)
16-
* @param array<string, true> $typeAliasesMap
17+
* @param array<string, TypeNode|array{string, string}> $typeAliasesMap
1718
* @param array{string, string, string, string|null, string|null}|null $traitData
1819
*/
1920
public function __construct(
@@ -129,7 +130,7 @@ public function getParent(): ?self
129130
}
130131

131132
/**
132-
* @return array<string, true>
133+
* @return array<string, TypeNode|array{string, string}>
133134
*/
134135
public function getTypeAliasesMap(): array
135136
{

src/Analyser/NameScope.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Analyser;
44

55
use PHPStan\PhpDoc\Tag\TemplateTag;
6+
use PHPStan\ShouldNotHappenException;
67
use PHPStan\Type\Generic\TemplateTypeMap;
78
use PHPStan\Type\Generic\TemplateTypeScope;
89
use PHPStan\Type\Type;
@@ -31,7 +32,7 @@ final class NameScope
3132
* @param array<string, string> $uses alias(string) => fullName(string)
3233
* @param array<string, string> $constUses alias(string) => fullName(string)
3334
* @param array<string, TemplateTag> $templateTags
34-
* @param array<string, true> $typeAliasesMap
35+
* @param array<string, Type> $typeAliasesMap
3536
*/
3637
public function __construct(
3738
private ?string $namespace,
@@ -264,4 +265,13 @@ public function hasTypeAlias(string $alias): bool
264265
return array_key_exists($alias, $this->typeAliasesMap);
265266
}
266267

268+
public function getTypeAlias(string $alias): Type
269+
{
270+
if (!$this->hasTypeAlias($alias)) {
271+
throw new ShouldNotHappenException(sprintf('Type alias %s not in NameScope', $alias));
272+
}
273+
274+
return $this->typeAliasesMap[$alias];
275+
}
276+
267277
}

src/PhpDoc/Tag/TypeAliasTag.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public function getAliasName(): string
2525
return $this->aliasName;
2626
}
2727

28+
public function getTypeNode(): TypeNode
29+
{
30+
return $this->typeNode;
31+
}
32+
2833
public function getTypeAlias(): TypeAlias
2934
{
3035
return new TypeAlias(

src/Testing/PHPStanTestCase.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use PHPStan\Parser\Parser;
2424
use PHPStan\Php\ComposerPhpVersionFactory;
2525
use PHPStan\Php\PhpVersion;
26-
use PHPStan\PhpDoc\TypeNodeResolver;
2726
use PHPStan\PhpDoc\TypeStringResolver;
2827
use PHPStan\Reflection\AttributeReflectionFactory;
2928
use PHPStan\Reflection\InitializerExprTypeResolver;
@@ -183,7 +182,6 @@ public static function createTypeAliasResolver(array $globalTypeAliases, Reflect
183182
return new UsefulTypeAliasResolver(
184183
$globalTypeAliases,
185184
$container->getByType(TypeStringResolver::class),
186-
$container->getByType(TypeNodeResolver::class),
187185
$reflectionProvider,
188186
);
189187
}

src/Type/FileTypeMapper.php

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
use PHPStan\PhpDoc\PhpDocStringResolver;
2020
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
2121
use PHPStan\PhpDoc\Tag\TemplateTag;
22+
use PHPStan\PhpDoc\TypeNodeResolver;
2223
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
2324
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
2425
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
26+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
2527
use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
2628
use PHPStan\ShouldNotHappenException;
2729
use PHPStan\Type\Generic\GenericObjectType;
@@ -76,6 +78,7 @@ public function __construct(
7678
private Parser $phpParser,
7779
private PhpDocStringResolver $phpDocStringResolver,
7880
private PhpDocNodeResolver $phpDocNodeResolver,
81+
private TypeNodeResolver $typeNodeResolver,
7982
private AnonymousClassNameHelper $anonymousClassNameHelper,
8083
private FileHelper $fileHelper,
8184
private Cache $cache,
@@ -217,6 +220,7 @@ public function getNameScope(
217220

218221
$phpDocTemplateTypes = [];
219222
$templateTags = [];
223+
$typeAliases = [];
220224
$reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
221225
foreach (array_reverse($parents) as $parent) {
222226
$nameScope = new NameScope(
@@ -226,7 +230,21 @@ public function getNameScope(
226230
$parent->getFunctionName(),
227231
new TemplateTypeMap($phpDocTemplateTypes),
228232
$templateTags,
229-
$parent->getTypeAliasesMap(),
233+
$typeAliases,
234+
$parent->shouldBypassTypeAliases(),
235+
$parent->getConstUses(),
236+
$parent->getClassNameForTypeAlias(),
237+
);
238+
$resolvedTypeAliases = $this->resolveTypeAliases($parent->getTypeAliasesMap(), $nameScope);
239+
$typeAliases = array_merge($typeAliases, $resolvedTypeAliases);
240+
$nameScope = new NameScope(
241+
$parent->getNamespace(),
242+
$parent->getUses(),
243+
$parent->getClassName(),
244+
$parent->getFunctionName(),
245+
new TemplateTypeMap($phpDocTemplateTypes),
246+
$templateTags,
247+
$typeAliases,
230248
$parent->shouldBypassTypeAliases(),
231249
$parent->getConstUses(),
232250
$parent->getClassNameForTypeAlias(),
@@ -307,7 +325,7 @@ public function getNameScope(
307325
$intermediaryNameScope->getFunctionName(),
308326
new TemplateTypeMap($phpDocTemplateTypes),
309327
$templateTags,
310-
$intermediaryNameScope->getTypeAliasesMap(),
328+
$typeAliases,
311329
$intermediaryNameScope->shouldBypassTypeAliases(),
312330
$intermediaryNameScope->getConstUses(),
313331
$intermediaryNameScope->getClassNameForTypeAlias(),
@@ -317,14 +335,44 @@ public function getNameScope(
317335
}
318336
}
319337

338+
/**
339+
* @param array<string, TypeNode|array{string, string}> $typeAliasesMap
340+
* @return array<string, Type>
341+
*/
342+
private function resolveTypeAliases(array $typeAliasesMap, NameScope $nameScope): array
343+
{
344+
$aliases = [];
345+
foreach ($typeAliasesMap as $localAliasName => $alias) {
346+
if (is_array($alias)) {
347+
[$aliasName, $importedFrom] = $alias;
348+
$importedFrom = $nameScope->resolveStringName($importedFrom);
349+
$reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
350+
if (!$reflectionProvider->hasClass($importedFrom)) {
351+
continue;
352+
}
353+
$importedFromClassReflection = $reflectionProvider->getClass($importedFrom);
354+
$classTypeAliaseses = $importedFromClassReflection->getTypeAliases();
355+
if (!array_key_exists($aliasName, $classTypeAliaseses)) {
356+
continue;
357+
}
358+
359+
$aliases[$localAliasName] = $classTypeAliaseses[$aliasName]->resolve($this->typeNodeResolver);
360+
continue;
361+
}
362+
363+
$aliases[$localAliasName] = $this->typeNodeResolver->resolve($alias, $nameScope);
364+
}
365+
return $aliases;
366+
}
367+
320368
/**
321369
* @return array{array<string, IntermediaryNameScope>}
322370
*/
323371
private function getNameScopeMap(string $fileName): array
324372
{
325373
if (!isset($this->memoryCache[$fileName])) {
326374
$cacheKey = sprintf('ftm-%s', $fileName);
327-
$variableCacheKey = 'v2';
375+
$variableCacheKey = 'v3';
328376
$cached = $this->loadCachedPhpDocNodeMap($cacheKey, $variableCacheKey);
329377
if ($cached === null) {
330378
[$nameScopeMap, $files] = $this->createPhpDocNodeMap($fileName, null, null, [], $fileName);
@@ -399,7 +447,7 @@ private function createPhpDocNodeMap(string $fileName, ?string $lookForTrait, ?s
399447
/** @var array<int, IntermediaryNameScope> $typeMapStack */
400448
$typeMapStack = [];
401449

402-
/** @var array<int, array<string, true>> $typeAliasStack */
450+
/** @var array<int, array<string, TypeNode|array{string, string}>> $typeAliasStack */
403451
$typeAliasStack = [];
404452

405453
/** @var string[] $classStack */
@@ -735,19 +783,19 @@ private function chooseTemplateTagValueNodesByPriority(array $tags): array
735783
}
736784

737785
/**
738-
* @return array<string, true>
786+
* @return array<string, TypeNode|array{string, string}>
739787
*/
740788
private function getTypeAliasesMap(PhpDocNode $phpDocNode): array
741789
{
742790
$nameScope = new NameScope(null, []);
743791

744792
$aliasesMap = [];
745-
foreach (array_keys($this->phpDocNodeResolver->resolveTypeAliasImportTags($phpDocNode, $nameScope)) as $key) {
746-
$aliasesMap[$key] = true;
793+
foreach ($this->phpDocNodeResolver->resolveTypeAliasImportTags($phpDocNode, $nameScope) as $key => $typeAliasImportTag) {
794+
$aliasesMap[$key] = [$typeAliasImportTag->getImportedAlias(), $typeAliasImportTag->getImportedFrom()];
747795
}
748796

749-
foreach (array_keys($this->phpDocNodeResolver->resolveTypeAliasTags($phpDocNode, $nameScope)) as $key) {
750-
$aliasesMap[$key] = true;
797+
foreach ($this->phpDocNodeResolver->resolveTypeAliasTags($phpDocNode, $nameScope) as $key => $typeAlias) {
798+
$aliasesMap[$key] = $typeAlias->getTypeNode();
751799
}
752800

753801
return $aliasesMap;

src/Type/UsefulTypeAliasResolver.php

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PHPStan\Analyser\NameScope;
66
use PHPStan\DependencyInjection\AutowiredParameter;
77
use PHPStan\DependencyInjection\AutowiredService;
8-
use PHPStan\PhpDoc\TypeNodeResolver;
98
use PHPStan\PhpDoc\TypeStringResolver;
109
use PHPStan\Reflection\ReflectionProvider;
1110
use PHPStan\ShouldNotHappenException;
@@ -19,12 +18,6 @@ final class UsefulTypeAliasResolver implements TypeAliasResolver
1918
/** @var array<string, Type> */
2019
private array $resolvedGlobalTypeAliases = [];
2120

22-
/** @var array<string, Type> */
23-
private array $resolvedLocalTypeAliases = [];
24-
25-
/** @var array<string, true> */
26-
private array $resolvingClassTypeAliases = [];
27-
2821
/** @var array<string, true> */
2922
private array $inProcess = [];
3023

@@ -35,7 +28,6 @@ public function __construct(
3528
#[AutowiredParameter(ref: '%typeAliases%')]
3629
private array $globalTypeAliases,
3730
private TypeStringResolver $typeStringResolver,
38-
private TypeNodeResolver $typeNodeResolver,
3931
private ReflectionProvider $reflectionProvider,
4032
)
4133
{
@@ -73,56 +65,7 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
7365
return null;
7466
}
7567

76-
$className = $nameScope->getClassNameForTypeAlias();
77-
if ($className === null) {
78-
return null;
79-
}
80-
81-
$aliasNameInClassScope = $className . '::' . $aliasName;
82-
83-
if (array_key_exists($aliasNameInClassScope, $this->resolvedLocalTypeAliases)) {
84-
return $this->resolvedLocalTypeAliases[$aliasNameInClassScope];
85-
}
86-
87-
// prevent infinite recursion
88-
if (array_key_exists($className, $this->resolvingClassTypeAliases)) {
89-
return null;
90-
}
91-
92-
$this->resolvingClassTypeAliases[$className] = true;
93-
94-
if (!$this->reflectionProvider->hasClass($className)) {
95-
unset($this->resolvingClassTypeAliases[$className]);
96-
return null;
97-
}
98-
99-
$classReflection = $this->reflectionProvider->getClass($className);
100-
$localTypeAliases = $classReflection->getTypeAliases();
101-
102-
unset($this->resolvingClassTypeAliases[$className]);
103-
104-
if (!array_key_exists($aliasName, $localTypeAliases)) {
105-
return null;
106-
}
107-
108-
if (array_key_exists($aliasNameInClassScope, $this->inProcess)) {
109-
// resolve circular reference as ErrorType to make it easier to detect
110-
throw new CircularTypeAliasDefinitionException();
111-
}
112-
113-
$this->inProcess[$aliasNameInClassScope] = true;
114-
115-
try {
116-
$unresolvedAlias = $localTypeAliases[$aliasName];
117-
$resolvedAliasType = $unresolvedAlias->resolve($this->typeNodeResolver);
118-
} catch (CircularTypeAliasDefinitionException) {
119-
$resolvedAliasType = new CircularTypeAliasErrorType();
120-
}
121-
122-
$this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType;
123-
unset($this->inProcess[$aliasNameInClassScope]);
124-
125-
return $resolvedAliasType;
68+
return $nameScope->getTypeAlias($aliasName);
12669
}
12770

12871
private function resolveGlobalTypeAlias(string $aliasName, NameScope $nameScope): ?Type

0 commit comments

Comments
 (0)