Preserve constant scalar types in same-key constant array generalization during loop widening#5470
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Open
Conversation
…ion during loop widening - In `MutatingScope::generalizeType()`, when generalizing constant arrays with matching keys, skip recursive `generalizeType` for non-integer, non-array value types when one side's type already encompasses the other (convergence check via `isSuperTypeOf`) - This prevents constant string and float values from being over-generalized to `literal-string&non-falsy-string` or `float` when they come from a bounded set of class constants - Integer values still use the full `generalizeType` path for proper range-based widening (e.g. counters → `int<min, max>`) - Array values still use the full path for proper structure generalization (e.g. growing lists) - Updated one existing test assertion that now correctly infers more precise constant string types instead of the previous over-generalized `literal-string` type
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 constant string values in a fixed-shape array are conditionally reassigned inside a loop (using a boolean flag pattern), PHPStan's loop widening over-generalizes the value types from specific constants like
'xxx'|'yyy'|'zzz'toliteral-string&non-falsy-string, causing false positive return type errors.Changes
src/Analyser/MutatingScope.phpin thegeneralizeType()method's constant array same-keys handlergeneralizeTypefor each key's value type, check whether the value types are converging (one side encompasses the other viaisSuperTypeOf)int<min, max>)tests/PHPStan/Analyser/nsrt/array-keys-branches.php— one assertion now reflects more precise constant string inference ('bar'|'baz'|'foo'instead of the previousliteral-string&lowercase-string&non-falsy-string)Root cause
During loop analysis,
NodeScopeResolvercallsgeneralizeWith()to widen scope types and ensure convergence. For constant arrays with fixed keys,generalizeType()recursed into each key's value type. When the value types were constant strings that differed slightly between iterations (due to conditional assignments controlled by a boolean flag), the constant string generalization converted them toliteral-string&non-falsy-string, losing all constant information.The fix adds a convergence check (
isSuperTypeOf) specifically in the constant array same-keys handler. When one iteration's value type already encompasses the other's (indicating the types are converging from a bounded set), the broader type is kept without further generalization. This check is guarded to only apply for non-integer, non-array types, ensuring that integer counters and nested array structures continue to use the standard widening behavior.Analogous cases probed
generalizeWith()code path — verified with regression tests (same fix applies)isArray()->no()guard — verified not affectedTest
tests/PHPStan/Analyser/nsrt/bug-12653.php— regression test for the reported bug: constant string array values with flag-based conditional assignment in a foreach looptests/PHPStan/Analyser/nsrt/bug-12653b.php— analogous cases: while loop, for loop, and float constants in arraysFixes phpstan/phpstan#12653