Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/Analyser/IntermediaryNameScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Analyser;

use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;

final class IntermediaryNameScope
{
Expand All @@ -13,7 +14,7 @@ final class IntermediaryNameScope
* @param array<string, string> $uses alias(string) => fullName(string)
* @param array<string, array{string, TemplateTagValueNode}> $templatePhpDocNodes
* @param array<string, string> $constUses alias(string) => fullName(string)
* @param array<string, true> $typeAliasesMap
* @param array<string, TypeNode|array{string, string}> $typeAliasesMap
* @param array{string, string, string, string|null, string|null}|null $traitData
*/
public function __construct(
Expand Down Expand Up @@ -129,7 +130,7 @@ public function getParent(): ?self
}

/**
* @return array<string, true>
* @return array<string, TypeNode|array{string, string}>
*/
public function getTypeAliasesMap(): array
{
Expand Down
12 changes: 11 additions & 1 deletion src/Analyser/NameScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Analyser;

use PHPStan\PhpDoc\Tag\TemplateTag;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Generic\TemplateTypeScope;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -31,7 +32,7 @@ final class NameScope
* @param array<string, string> $uses alias(string) => fullName(string)
* @param array<string, string> $constUses alias(string) => fullName(string)
* @param array<string, TemplateTag> $templateTags
* @param array<string, true> $typeAliasesMap
* @param array<string, Type> $typeAliasesMap
*/
public function __construct(
private ?string $namespace,
Expand Down Expand Up @@ -264,4 +265,13 @@ public function hasTypeAlias(string $alias): bool
return array_key_exists($alias, $this->typeAliasesMap);
}

public function getTypeAlias(string $alias): Type
{
if (!$this->hasTypeAlias($alias)) {
throw new ShouldNotHappenException(sprintf('Type alias %s not in NameScope', $alias));
}

return $this->typeAliasesMap[$alias];
}

}
5 changes: 5 additions & 0 deletions src/PhpDoc/Tag/TypeAliasTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public function getAliasName(): string
return $this->aliasName;
}

public function getTypeNode(): TypeNode
{
return $this->typeNode;
}

public function getTypeAlias(): TypeAlias
{
return new TypeAlias(
Expand Down
2 changes: 0 additions & 2 deletions src/Testing/PHPStanTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
use PHPStan\Parser\Parser;
use PHPStan\Php\ComposerPhpVersionFactory;
use PHPStan\Php\PhpVersion;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDoc\TypeStringResolver;
use PHPStan\Reflection\AttributeReflectionFactory;
use PHPStan\Reflection\InitializerExprTypeResolver;
Expand Down Expand Up @@ -183,7 +182,6 @@ public static function createTypeAliasResolver(array $globalTypeAliases, Reflect
return new UsefulTypeAliasResolver(
$globalTypeAliases,
$container->getByType(TypeStringResolver::class),
$container->getByType(TypeNodeResolver::class),
$reflectionProvider,
);
}
Expand Down
66 changes: 57 additions & 9 deletions src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
use PHPStan\PhpDoc\PhpDocStringResolver;
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
use PHPStan\PhpDoc\Tag\TemplateTag;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Generic\GenericObjectType;
Expand Down Expand Up @@ -76,6 +78,7 @@ public function __construct(
private Parser $phpParser,
private PhpDocStringResolver $phpDocStringResolver,
private PhpDocNodeResolver $phpDocNodeResolver,
private TypeNodeResolver $typeNodeResolver,
private AnonymousClassNameHelper $anonymousClassNameHelper,
private FileHelper $fileHelper,
private Cache $cache,
Expand Down Expand Up @@ -217,6 +220,7 @@ public function getNameScope(

$phpDocTemplateTypes = [];
$templateTags = [];
$typeAliases = [];
$reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
foreach (array_reverse($parents) as $parent) {
$nameScope = new NameScope(
Expand All @@ -226,7 +230,21 @@ public function getNameScope(
$parent->getFunctionName(),
new TemplateTypeMap($phpDocTemplateTypes),
$templateTags,
$parent->getTypeAliasesMap(),
$typeAliases,
$parent->shouldBypassTypeAliases(),
$parent->getConstUses(),
$parent->getClassNameForTypeAlias(),
);
$resolvedTypeAliases = $this->resolveTypeAliases($parent->getTypeAliasesMap(), $nameScope);
$typeAliases = array_merge($typeAliases, $resolvedTypeAliases);
$nameScope = new NameScope(
$parent->getNamespace(),
$parent->getUses(),
$parent->getClassName(),
$parent->getFunctionName(),
new TemplateTypeMap($phpDocTemplateTypes),
$templateTags,
$typeAliases,
$parent->shouldBypassTypeAliases(),
$parent->getConstUses(),
$parent->getClassNameForTypeAlias(),
Expand Down Expand Up @@ -307,7 +325,7 @@ public function getNameScope(
$intermediaryNameScope->getFunctionName(),
new TemplateTypeMap($phpDocTemplateTypes),
$templateTags,
$intermediaryNameScope->getTypeAliasesMap(),
$typeAliases,
$intermediaryNameScope->shouldBypassTypeAliases(),
$intermediaryNameScope->getConstUses(),
$intermediaryNameScope->getClassNameForTypeAlias(),
Expand All @@ -317,14 +335,44 @@ public function getNameScope(
}
}

/**
* @param array<string, TypeNode|array{string, string}> $typeAliasesMap
* @return array<string, Type>
*/
private function resolveTypeAliases(array $typeAliasesMap, NameScope $nameScope): array
{
$aliases = [];
foreach ($typeAliasesMap as $localAliasName => $alias) {
if (is_array($alias)) {
[$aliasName, $importedFrom] = $alias;
$importedFrom = $nameScope->resolveStringName($importedFrom);
$reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
if (!$reflectionProvider->hasClass($importedFrom)) {
continue;
}
$importedFromClassReflection = $reflectionProvider->getClass($importedFrom);
$classTypeAliaseses = $importedFromClassReflection->getTypeAliases();
if (!array_key_exists($aliasName, $classTypeAliaseses)) {
continue;
}

$aliases[$localAliasName] = $classTypeAliaseses[$aliasName]->resolve($this->typeNodeResolver);
continue;
}

$aliases[$localAliasName] = $this->typeNodeResolver->resolve($alias, $nameScope);
}
return $aliases;
}

/**
* @return array{array<string, IntermediaryNameScope>}
*/
private function getNameScopeMap(string $fileName): array
{
if (!isset($this->memoryCache[$fileName])) {
$cacheKey = sprintf('ftm-%s', $fileName);
$variableCacheKey = 'v2';
$variableCacheKey = 'v3';
$cached = $this->loadCachedPhpDocNodeMap($cacheKey, $variableCacheKey);
if ($cached === null) {
[$nameScopeMap, $files] = $this->createPhpDocNodeMap($fileName, null, null, [], $fileName);
Expand Down Expand Up @@ -399,7 +447,7 @@ private function createPhpDocNodeMap(string $fileName, ?string $lookForTrait, ?s
/** @var array<int, IntermediaryNameScope> $typeMapStack */
$typeMapStack = [];

/** @var array<int, array<string, true>> $typeAliasStack */
/** @var array<int, array<string, TypeNode|array{string, string}>> $typeAliasStack */
$typeAliasStack = [];

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

/**
* @return array<string, true>
* @return array<string, TypeNode|array{string, string}>
*/
private function getTypeAliasesMap(PhpDocNode $phpDocNode): array
{
$nameScope = new NameScope(null, []);

$aliasesMap = [];
foreach (array_keys($this->phpDocNodeResolver->resolveTypeAliasImportTags($phpDocNode, $nameScope)) as $key) {
$aliasesMap[$key] = true;
foreach ($this->phpDocNodeResolver->resolveTypeAliasImportTags($phpDocNode, $nameScope) as $key => $typeAliasImportTag) {
$aliasesMap[$key] = [$typeAliasImportTag->getImportedAlias(), $typeAliasImportTag->getImportedFrom()];
}

foreach (array_keys($this->phpDocNodeResolver->resolveTypeAliasTags($phpDocNode, $nameScope)) as $key) {
$aliasesMap[$key] = true;
foreach ($this->phpDocNodeResolver->resolveTypeAliasTags($phpDocNode, $nameScope) as $key => $typeAlias) {
$aliasesMap[$key] = $typeAlias->getTypeNode();
}

return $aliasesMap;
Expand Down
59 changes: 1 addition & 58 deletions src/Type/UsefulTypeAliasResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use PHPStan\Analyser\NameScope;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDoc\TypeStringResolver;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\ShouldNotHappenException;
Expand All @@ -19,12 +18,6 @@ final class UsefulTypeAliasResolver implements TypeAliasResolver
/** @var array<string, Type> */
private array $resolvedGlobalTypeAliases = [];

/** @var array<string, Type> */
private array $resolvedLocalTypeAliases = [];

/** @var array<string, true> */
private array $resolvingClassTypeAliases = [];

/** @var array<string, true> */
private array $inProcess = [];

Expand All @@ -35,7 +28,6 @@ public function __construct(
#[AutowiredParameter(ref: '%typeAliases%')]
private array $globalTypeAliases,
private TypeStringResolver $typeStringResolver,
private TypeNodeResolver $typeNodeResolver,
private ReflectionProvider $reflectionProvider,
)
{
Expand Down Expand Up @@ -73,56 +65,7 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
return null;
}

$className = $nameScope->getClassNameForTypeAlias();
if ($className === null) {
return null;
}

$aliasNameInClassScope = $className . '::' . $aliasName;

if (array_key_exists($aliasNameInClassScope, $this->resolvedLocalTypeAliases)) {
return $this->resolvedLocalTypeAliases[$aliasNameInClassScope];
}

// prevent infinite recursion
if (array_key_exists($className, $this->resolvingClassTypeAliases)) {
return null;
}

$this->resolvingClassTypeAliases[$className] = true;

if (!$this->reflectionProvider->hasClass($className)) {
unset($this->resolvingClassTypeAliases[$className]);
return null;
}

$classReflection = $this->reflectionProvider->getClass($className);
$localTypeAliases = $classReflection->getTypeAliases();

unset($this->resolvingClassTypeAliases[$className]);

if (!array_key_exists($aliasName, $localTypeAliases)) {
return null;
}

if (array_key_exists($aliasNameInClassScope, $this->inProcess)) {
// resolve circular reference as ErrorType to make it easier to detect
throw new CircularTypeAliasDefinitionException();
}

$this->inProcess[$aliasNameInClassScope] = true;

try {
$unresolvedAlias = $localTypeAliases[$aliasName];
$resolvedAliasType = $unresolvedAlias->resolve($this->typeNodeResolver);
} catch (CircularTypeAliasDefinitionException) {
$resolvedAliasType = new CircularTypeAliasErrorType();
}

$this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType;
unset($this->inProcess[$aliasNameInClassScope]);

return $resolvedAliasType;
return $nameScope->getTypeAlias($aliasName);
}

private function resolveGlobalTypeAlias(string $aliasName, NameScope $nameScope): ?Type
Expand Down
Loading
Loading