Narrow array key type after type-checking the key variable inside a foreach loop#5505
Merged
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom Apr 21, 2026
Conversation
…foreach` loop - Extend the existing value-type narrowing mechanism in NodeScopeResolver's foreach handling to also track and apply key-type narrowing - Collect key variable types from loop body end scopes and continue exit point scopes (same scopes used for value narrowing via OriginalForeachKeyExpr) - When the combined key type differs from the original iterable key type, create a new array type with the narrowed key type - Supports both key-only narrowing and combined key+value narrowing - Correctly does NOT narrow when: key variable is reassigned, break is used, continue without narrowing on all paths, or no explicit key variable
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 iterating over an array with
foreachand narrowing the key type inside the loop body (e.g. viais_string($key)+ throw), the narrowed key type was not reflected in the array type after the loop. This extends the value-type narrowing mechanism (added in #4534) to also narrow key types.Changes
src/Analyser/NodeScopeResolver.php: In the foreach post-processing block that already handles value-type narrowing, added parallel collection of key variable types from the same scopes. When either the value type or key type (or both) changed, the new array type uses the narrowed types.tests/PHPStan/Analyser/nsrt/bug-7076.phpwith comprehensive test cases covering:is_string()+ throwis_int()+ throwreturnassert()continuewithout narrowing,break, key reassignment, no key variable, partial narrowing with continueRoot cause
The existing foreach post-processing (from PR #4534) already collected scopes where the key variable hadn't been reassigned (
scopesWithIterableValueType) and used them to detect value-type changes via$scope->getType(new ArrayDimFetch($expr, $keyVar)). However, it did not also check whether the key variable's own type had been narrowed in those same scopes. The fix adds parallel key-type collection using$scope->getType($stmt->keyVar)and applies key narrowing when the combined key type differs from the original iterable key type.Analogous cases probed
foreachprovides direct key/value binding to an array's structure.continueexit points: Tested and correctly handled — when a non-narrowed continue path exists, the key type union includes the original type, so no narrowing occurs.breakexit points: Already blocked by the existingcount($breakExitPoints) === 0guard.OriginalForeachKeyExprmechanism.$this->prop— works correctly through theassignExpressionfallback path.Test
Regression test
tests/PHPStan/Analyser/nsrt/bug-7076.phpwith 13 test functions covering the reported bug and all analogous edge cases listed above.Fixes phpstan/phpstan#7076