Keep the narrowed bound of a template type that TemplateTypeHelper::resolveTemplateTypes() maps to itself - #6613
phpstan-bot wants to merge 2 commits into
Conversation
…resolveTemplateTypes()` maps to itself - In the key of `array<K, V>`, TypeNodeResolver narrows `K of string|int|object` to a `K of int|string` occurrence. Methods, parameters and properties of the declaring class are then resolved against the class's active template type map, which maps `K` to the declared `K` and so widened the key bound back to `int|string|object`. `TypehintHelper::decideType()` then found the PHPDoc type not to be a subtype of the native `array` and fell back to `array`. - When the standin is the same template type (same name and scope), resolveTemplateTypes() now keeps the occurrence (converted to its argument form when the standin is an argument), so the narrowed bound survives. - Mirrored the change in turbo-ext/src/TemplateTypeHelper.cpp. - Covered: plain and conditional return types, non-empty-array, @PARAM, @var and promoted properties inside the class, inherited interface methods and trait methods. Function-level templates were not affected (they are not resolved against a class map).
|
//cc @SanderMuller |
|
I checked this against 2.3.x
To look for over-reach, I compared all errors before and after on real generic code. Tempest (1519 files, level 8) reports the same 2821 errors, and laravel/framework's I also tried a collection class that uses its own
It adds one: for Performance: Tempest cold with turbo, 3 interleaved rounds, takes 19.1-19.8 s CPU on 2.3.x and 19.2-19.4 s here. I see no difference. Two questions:
CI: 13 of the 15 red checks also fail on #6609, #6604, #6611 or #6612 today. The other two are the Mutation Testing jobs. The runner cancelled both with a shutdown signal while Infection was still generating mutants, so they have no result yet and need a re-run. |
Summary
A class template bounded to
string|int|objectused as an array key (array<K, V>) resolved to plainarrayfor methods declared on the class, both for@return array<K, V>and for conditional return types like(K is array-key ? array<K, V> : ...)(the latter regressed in 2.2.16). With this change,Map<string, int>::toArray()isarray<string, int>again.Changes
src/Type/Generic/TemplateTypeHelper.php: inresolveTemplateTypes(), when the standin for a template type is the same template type (same name and scope), the occurrence is kept (converted to its argument form if the standin is an argument), instead of being replaced by the declared template type with its wider bound.turbo-ext/src/TemplateTypeHelper.cpp: the same change in the native mirror. The extension was built locally,tests/smoke.phpprints ALL OK, and the full test suite passes with the extension loaded. The regression test also passes with the extension loaded and only the PHP change reverted.make bump-turbostill has to run after this commit lands.Root cause
TypeNodeResolvermakes the key ofarray<K, V>safe by intersecting it withint|string. ForK of string|int|object, that produces aK of int|stringoccurrence. Methods, parameters and properties of a class are then resolved against the class's active template type map. Outside a generic instantiation, that map sendsKto the declaredK of int|string|object, which widened the key's bound again.TypehintHelper::decideType()resolves the PHPDoc type to its bounds (array<int|object|string, mixed>), finds it is not a subtype of the nativearray, and falls back to the native type. So every PHPDoc type with such a key went through the same substitution: return types, parameters,@varproperties and promoted properties. Before 8411207 the conditional-return variant happened to be re-normalized and hid this.Other places I checked:
@extends) and trait methods (@use) are covered by the fix, and the test includes them.Test
tests/PHPStan/Analyser/nsrt/bug-15321.phpcovers:array<K, V>non-empty-array<K, V>@param, an@varproperty and a promoted propertyBefore the fix, the issue's reproducer,
non-empty-array, the parameter, the@varproperty and the instantiated-class assertions failed witharrayor with a widenedK of int|object|stringkey.Fixes phpstan/phpstan#15321
🤖 Generated with Claude Code