Skip to content

Don't widen a narrowed template type when it resolves to itself - #6603

Open
delacry wants to merge 1 commit into
phpstan:2.2.xfrom
delacry:keep-narrowed-template-occurrence
Open

delacry wants to merge 1 commit into
phpstan:2.2.xfrom
delacry:keep-narrowed-template-occurrence

Conversation

@delacry

@delacry delacry commented Sep 26, 2026

Copy link
Copy Markdown

Closes phpstan/phpstan#15321

resolveTemplateTypes() replaces template types by name. When the class's own template map resolves K to itself, a narrowed occurrence like the K of int|string key of array<K, V> gets replaced by the declared K of string|int|object. TypehintHelper::decideType() then resolves that to array<int|object|string, mixed>, which doesn't fit the native array, so we end up with plain array.

Plain @return array<K, V> has been broken like this for a while. The conditional return type only broke in 2.2.16: since 8411207, ConditionalType::traverse() keeps the traversed branches instead of normalizing them again, and that re-normalization used to narrow the key back.

The fix keeps the occurrence when the standin is the same template type (same name and scope, not an argument).

nsrt/bug-15321.php fails on all 5 assertions without the change.

resolveTemplateTypes() swaps template types by name, so when K resolves
to itself, a narrowed K got replaced by the declared one. For example,
with K of string|int|object the key of array<K, V> is K of int|string.
The PHPDoc return type then didn't fit the native array type and we fell
back to plain array.

This only started affecting conditional return types in 2.2.16. Before
that ConditionalType::traverse() re-normalized its branches, which
narrowed the key again and hid the problem.
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