Fix phpstan/phpstan#9045: Narrowed template on an interface is ignored when generics is not specified#5339
Merged
staabm merged 2 commits intophpstan:2.1.xfrom Mar 30, 2026
Conversation
VincentLanglet
approved these changes
Mar 30, 2026
staabm
reviewed
Mar 30, 2026
Contributor
staabm
left a comment
There was a problem hiding this comment.
add a regression test for
Collaborator
Author
|
Done. Added regression tests for both issues:
Both tests pass, and the full test suite (11691 tests) and phpstan analysis are clean. |
staabm
approved these changes
Mar 30, 2026
… generics not specified - When a child interface narrows a template bound (e.g. `@template T of SpecificType`) and passes it to a parent via `@extends Parent<T>`, using the child without specifying generics now correctly resolves to the narrowed bound instead of the parent's wider bound - Added `getActiveTemplateTypeMapForAncestorResolution()` in ClassReflection that resolves ErrorType entries to template bounds when the bound is not mixed - Applied this resolution in both `getImmediateInterfaces()` and `getParentClass()` - Updated bug-2676 test expectation: Collection's TKey now correctly resolves to `(int|string)` (array-key bound) instead of `mixed` when not explicitly specified - New regression test in tests/PHPStan/Analyser/nsrt/bug-9045.php
Both issues involve template bounds not being correctly resolved when generic types are used without explicit type parameters. These are now fixed by the getActiveTemplateTypeMapForAncestorResolution() change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
adab3f5 to
f015f24
Compare
VincentLanglet
approved these changes
Mar 30, 2026
Contributor
|
thank you! |
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 child interface narrows a template bound and passes it to a parent interface via
@extends, using the child without explicit generic parameters incorrectly resolved the template to the parent's wider bound instead of the child's narrowed bound.For example:
Changes
getActiveTemplateTypeMapForAncestorResolution()method insrc/Reflection/ClassReflection.phpthat resolvesErrorTypeentries to their template bounds, but only when the bound is notmixed. This preserves the existing behavior for mixed-bounded templates (likeIterator<TKey>) while correctly resolving narrowed bounds.getImmediateInterfaces()andgetParentClass()inClassReflectionto use this new method when resolving@extendsand@implementstype declarationstests/PHPStan/Analyser/nsrt/bug-2676.php: Collection'sTKey of array-keynow correctly resolves to(int|string)instead ofmixedwhen not explicitly specifiedRoot cause
When a plain
ObjectType(without generics) referred to a generic class,ObjectType::getClassReflection()created aClassReflectionwith all template types set toErrorType. IngetImmediateInterfaces(), when resolving@extends TranslatableInterface<T>, theErrorTypeforTwas propagated to the parent, where it was resolved using the parent's own (wider) template bound (TranslationInterface) instead of the child's narrower bound (TransportTranslationInterface).The fix intercepts this propagation: before passing template types to ancestor resolution,
ErrorTypeentries with non-mixed bounds are resolved to their template bounds. This ensures the narrowed bound is correctly propagated through the inheritance chain.Test
Added
tests/PHPStan/Analyser/nsrt/bug-9045.phpthat verifiesTransportInterface::getTranslation()correctly returnsTransportTranslationInterfacewhen the interface is used without explicit generic parameters.Fixes phpstan/phpstan#9045
Fixes phpstan/phpstan#7185
Fixes phpstan/phpstan#13204