Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

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
phpstan-bot:create-pull-request/patch-whq10lk
Open

Fix phpstan/phpstan#4971: When combining templated class-string and Generics, the concrete class is missing.#5207
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-whq10lk

Conversation

@phpstan-bot
Copy link
Collaborator

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

  • Added src/Type/Generic/TemplateAppliedGenericObjectType.php - a new GenericObjectType subclass 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>)
  • Modified src/PhpDoc/TypeNodeResolver.php to create TemplateAppliedGenericObjectType when a template type is used with explicit generic arguments in PHPDoc types
  • Modified src/Reflection/ResolvedFunctionVariantWithOriginal.php to resolve the template name to its concrete class after inner type traversal, replacing the bound's class name with the resolved type's class name
  • Added regression test tests/PHPStan/Analyser/nsrt/bug-4971.php

Root cause

When K<T> was parsed in TypeNodeResolver::resolveGenericTypeNode(), the template type K was resolved to its bound's class name (IFoo) and a regular GenericObjectType(IFoo, [T]) was created, completely losing the template K. During template resolution, T was correctly resolved to int, but the class name remained IFoo instead of being replaced with the concrete resolved type Foo.

The fix preserves the template name via TemplateAppliedGenericObjectType, which is a non-TemplateType subclass of GenericObjectType. This ensures it doesn't interfere with existing TemplateGenericObjectType resolution (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.php which verifies:

  • make1(1, Foo::class) returns Foo (template without generic args - already worked)
  • make2(1, Foo::class) returns Foo<int> (template with generic args - was returning IFoo<int>)

Fixes phpstan/phpstan#4971

- 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
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.

1 participant