Skip to content

Fix phpstan/phpstan#11314: Template of imported type breaks imported type#5360

Merged
VincentLanglet merged 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-xt25se8
Apr 26, 2026
Merged

Fix phpstan/phpstan#11314: Template of imported type breaks imported type#5360
VincentLanglet merged 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-xt25se8

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

@phpstan-bot phpstan-bot commented Mar 31, 2026

Summary

When a class has both @phpstan-import-type Breed from Cat and @template T of Breed in the same PHPDoc comment, the imported type alias Breed could not be resolved. Properties typed with @var Breed were incorrectly resolved to string (the native type) instead of the imported type alias.

Changes

  • Modified src/Type/FileTypeMapper.php to store a partial NameScope (with type aliases resolved but before template tag resolution) and return it when a cyclic dependency is detected during getNameScope(), instead of throwing NameScopeAlreadyBeingCreatedException
  • Added $inProcessNameScopes field to track partial NameScopes during resolution
  • Added regression test in tests/PHPStan/Analyser/nsrt/bug-11314.php

Root cause

When FileTypeMapper::getNameScope() resolves template tag bounds, it calls TypeNodeResolver::resolve() which triggers type alias resolution through UsefulTypeAliasResolver::resolveLocalTypeAlias(). This in turn calls ClassReflection::getTypeAliases(), which needs getResolvedPhpDoc(), which calls back to FileTypeMapper::getNameScope() for the same class. The cycle detection in getNameScope() threw NameScopeAlreadyBeingCreatedException, causing getResolvedPhpDoc() to return an empty PHPDoc block. With empty PHPDoc, getTypeAliases() returned an empty array, so the imported type alias could not be found.

The fix stores the NameScope built so far (which already has type alias information) before template resolution begins. When the cycle is detected, this partial NameScope is returned instead of throwing. This allows ClassReflection::getTypeAliases() to correctly extract type alias information from the PHPDoc, breaking the cycle while preserving the data needed for type alias resolution.

Test

Added tests/PHPStan/Analyser/nsrt/bug-11314.php which verifies that Cat2::$breed (with both @phpstan-import-type and @template T of Breed) correctly resolves to 'British Shorthair'|'Maine Coon'|'Siamese' instead of string.

Fixes phpstan/phpstan#11314
Closes phpstan/phpstan#7152
Closes phpstan/phpstan#13332

@VincentLanglet VincentLanglet self-assigned this Apr 23, 2026
…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
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add non regression test for
phpstan/phpstan#7152
and phpstan/phpstan#13332

- bug-7152: @phpstan-type used as template bound with class inheritance
- bug-13332: @phpstan-type used as template bound with generic return types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Done. Added two regression test files and pushed:

  • tests/PHPStan/Analyser/nsrt/bug-7152.php — Tests that @phpstan-type can be used as a @template bound when extending a generic class (@extends Root<T>)
  • tests/PHPStan/Analyser/nsrt/bug-13332.php — Tests that @phpstan-type used as a @template bound doesn't cause false positive generics.notSubtype when used with generic return types like @return self<TestEnum>

Both make tests (11919 tests, all passing) and make phpstan (no errors) verified clean.

@VincentLanglet VincentLanglet requested a review from staabm April 25, 2026 21:50
Comment thread tests/PHPStan/Analyser/nsrt/bug-11314.php
phpstan-bot and others added 2 commits April 26, 2026 06:46
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm
Copy link
Copy Markdown
Contributor

staabm commented Apr 26, 2026

@VincentLanglet did you double check the integration test errors?

@VincentLanglet
Copy link
Copy Markdown
Contributor

@VincentLanglet did you double check the integration test errors?

I don't explain all of them yet, but they are unrelated to this PR since we saw them on other PR like #5530.

  • doctrine/orm errors is unclear to me
  • nextra/orm is valid error
  • shopware/shopware too
  • PMMP is unclear

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants