From 8fe8b4fd52e1e73fb0e149807f0451ba7cf750ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:42:31 +0000 Subject: [PATCH] Fix phpstan/phpstan#11314: Template of imported type breaks imported type - When a class has both @phpstan-import-type and @template with the imported type as bound, resolving the template bound triggered a cyclic dependency in FileTypeMapper - The cycle occurred because resolving the type alias went through ClassReflection::getTypeAliases() which needed the full resolved PHPDoc, but the PHPDoc was still being built - Fix: store a partial NameScope (with type aliases but before template resolution) and return it on cycle detection instead of throwing NameScopeAlreadyBeingCreatedException - New regression test in tests/PHPStan/Analyser/nsrt/bug-11314.php --- src/Type/FileTypeMapper.php | 9 ++++ tests/PHPStan/Analyser/nsrt/bug-11314.php | 51 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11314.php diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 2c281f58cad..de0981858d6 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -66,6 +66,9 @@ final class FileTypeMapper /** @var array */ private array $inProcess = []; + /** @var array */ + private array $inProcessNameScopes = []; + /** @var array */ private array $resolvedPhpDocBlockCache = []; @@ -200,6 +203,9 @@ public function getNameScope( { $nameScopeKey = $this->getNameScopeKey($fileName, $className, $traitName, $functionName); if (isset($this->inProcess[$nameScopeKey])) { + if (isset($this->inProcessNameScopes[$nameScopeKey])) { + return $this->inProcessNameScopes[$nameScopeKey]; + } throw new NameScopeAlreadyBeingCreatedException(); } @@ -288,6 +294,8 @@ public function getNameScope( continue; } + $this->inProcessNameScopes[$nameScopeKey] = $nameScope; + $templateTags = $this->phpDocNodeResolver->resolveTemplateTags($parent->getTemplatePhpDocNodes(), $nameScope); $templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $templateTags)); $nameScope = $nameScope->withTemplateTypeMap($templateTypeMap, $templateTags); @@ -319,6 +327,7 @@ public function getNameScope( ); } finally { unset($this->inProcess[$nameScopeKey]); + unset($this->inProcessNameScopes[$nameScopeKey]); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11314.php b/tests/PHPStan/Analyser/nsrt/bug-11314.php new file mode 100644 index 00000000000..e541bb8691c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11314.php @@ -0,0 +1,51 @@ +breed); + + $cat2 = new Cat2(); + assertType("'British Shorthair'|'Maine Coon'|'Siamese'", $cat2->breed); + + $cat3 = new Cat3(); + assertType("'British Shorthair'|'Maine Coon'|'Siamese'", $cat3->breed); +};