Fix phpstan/phpstan#4971: When combining templated class-string and Generics, the concrete class is missing.#5207
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
- When a template type K (bound to IFoo) is used as K<T> in a return type, PHPStan now correctly resolves to the concrete class (e.g., Foo<int>) instead of the bound interface (IFoo<int>) - Added TemplateAppliedGenericObjectType to distinguish usage-site generic args (K<T>) from template declarations with generic bounds (K of IFoo<T>) - Resolution in ResolvedFunctionVariantWithOriginal replaces the bound's class name with the resolved template's class name after inner type traversal - New regression test in tests/PHPStan/Analyser/nsrt/bug-4971.php Closes phpstan/phpstan#4971
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a templated class-string parameter is combined with generic type arguments in the return type (e.g.,
@return K<T>where@template K of IFoo), PHPStan incorrectly resolved to the bound interface type (IFoo<int>) instead of the concrete class (Foo<int>).Changes
src/Type/Generic/TemplateAppliedGenericObjectType.php- a newGenericObjectTypesubclass that tracks when generic args are applied to a template type at the usage site (e.g.,K<T>), as opposed to being part of the template's bound declaration (e.g.,@template K of IFoo<T>)src/PhpDoc/TypeNodeResolver.phpto createTemplateAppliedGenericObjectTypewhen a template type is used with explicit generic arguments in PHPDoc typessrc/Reflection/ResolvedFunctionVariantWithOriginal.phpto resolve the template name to its concrete class after inner type traversal, replacing the bound's class name with the resolved type's class nametests/PHPStan/Analyser/nsrt/bug-4971.phpRoot cause
When
K<T>was parsed inTypeNodeResolver::resolveGenericTypeNode(), the template typeKwas resolved to its bound's class name (IFoo) and a regularGenericObjectType(IFoo, [T])was created, completely losing the templateK. During template resolution,Twas correctly resolved toint, but the class name remainedIFooinstead of being replaced with the concrete resolved typeFoo.The fix preserves the template name via
TemplateAppliedGenericObjectType, which is a non-TemplateTypesubclass ofGenericObjectType. This ensures it doesn't interfere with existingTemplateGenericObjectTyperesolution (used for@template K of IFoo<T>declarations), while allowing the resolution code to detect and replace the class name after inner types are resolved.Test
Added
tests/PHPStan/Analyser/nsrt/bug-4971.phpwhich verifies:make1(1, Foo::class)returnsFoo(template without generic args - already worked)make2(1, Foo::class)returnsFoo<int>(template with generic args - was returningIFoo<int>)Fixes phpstan/phpstan#4971