Emit virtual Assign node for List_ destructuring in foreach value position#5504
Merged
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom Apr 21, 2026
Merged
Conversation
…ue position - When a `foreach` uses array destructuring in the value position (e.g. `foreach ($arr as ['key' => $val])`), `ArrayDestructuringRule` was never invoked because no `Assign` node was emitted for it to match. - In `NodeScopeResolver::processStmtNode`, alongside the existing `VariableAssignNode` emission for simple variable foreach values, emit a virtual `Assign(List_, GetIterableValueTypeExpr)` node when the value var is a `List_`. This causes `ArrayDestructuringRule` to fire and report nonexistent offsets. - The virtual node is emitted once before the loop convergence iterations, avoiding duplicate error reports. - Covers both `[]` and `list()` syntax, string/integer keys, and nested destructuring patterns. - Analogous cases probed and found not broken: foreach key destructuring (not valid PHP), regular assignment destructuring (already worked), assignment inside foreach body (already worked).
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 array destructuring is used in the
foreachvalue position (e.g.foreach ($arr as ['b' => $val])), PHPStan did not report nonexistent offsets. The same destructuring via regular assignment (['b' => $val] = $arr[0]) or assignment inside the foreach body (['b' => $val] = $item) correctly reported errors.Changes
src/Analyser/NodeScopeResolver.php: In the foreach handling code, added emission of a virtualAssignnode when$stmt->valueVaris aList_. This is placed alongside the existingVariableAssignNodeemission (before the loop convergence iterations) to avoid duplicate reports. The virtual node pairs theList_with aGetIterableValueTypeExprsoArrayDestructuringRulecan resolve the iterable value type and check offset existence.tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php: AddedtestBug8075test method.tests/PHPStan/Rules/Arrays/data/bug-8075.php: Regression test data covering:Root cause
ArrayDestructuringRuleis registered asRule<Assign>— it only fires when the node callback receives anAssignnode. For regular assignments like['b' => $val] = $expr, the parser produces anAssign(List_, expr)which the rule catches. But forforeach ($arr as ['b' => $val]), the parser produces aForeach_node withvalueVar = List_, andNodeScopeResolver::enterForeachcalledprocessVirtualAssignto handle scope updates without ever emitting anAssignnode for rules to process.Analogous cases probed
list()vs[]syntax: Both produceList_AST nodes — handled by the same fixArrayDestructuringRule::getErrors()already recurses for nestedList_nodes — works with the fixAssign: OnlyArrayDestructuringRuledoes — no unintended side effectsTest
Regression test
testBug8075inArrayDestructuringRuleTestcovers the reported bug and several variations: string keys, integer keys, PHPDoc-typed arrays, nested destructuring, and mixed existent/nonexistent keys.Fixes phpstan/phpstan#8075