Track array value type overwrites in by-reference foreach without key variable#5542
Open
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
Open
Track array value type overwrites in by-reference foreach without key variable#5542phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
foreach without key variable#5542phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
Conversation
…ey variable - Extend post-loop array type rewrite in NodeScopeResolver to handle `foreach ($arr as &$v)` (without `$key =>`) by tracking the value variable's type via `OriginalForeachValueExpr` instead of relying on `OriginalForeachKeyExpr` + `ArrayDimFetch` - When the value is always overwritten (OriginalForeachValueExpr absent in all paths), replace the array's value type with the final value variable type — matching the existing behavior for the key-based case - Handle continue exit points for the by-ref without key case with the same logic: check OriginalForeachValueExpr presence in each continue scope - Update assertions in existing tests (bug-13809, bug-14083, bug-14084, bug-14124, bug-14124b, overwritten-arrays) that were asserting the old imprecise behavior
Contributor
VincentLanglet
left a comment
There was a problem hiding this comment.
I don't understand how it's related to the fact it's "by ref without key" since the same issue exists when a key is used
foreach ($a as $k => &$testArray) {
$testArray['two'] = 'two';
}
…works The reviewer pointed out that modifying array sub-elements through a by-reference foreach with a key variable also had issues. Testing confirms this case already works correctly on the current branch — both with and without a key variable. The reviewer's example was likely tested against the stable phpstan.org release which runs an older version. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Collaborator
Author
|
I processed this review but have nothing to report. |
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 an array by reference without a key variable (
foreach ($arr as &$v) { $v = X; }), PHPStan did not track that the array's element type was being overwritten. The same construct WITH a key variable (foreach ($arr as $k => &$v) { $v = X; }) already worked correctly.This PR extends the post-loop array type rewrite mechanism in
NodeScopeResolverto also handle the by-reference-without-key case.Changes
src/Analyser/NodeScopeResolver.php: Extended the foreach post-loop array type rewrite (around line 1293-1475) to handle$stmt->byRef && $stmt->keyVar === null:$byRefWithoutKeyflag detectionOriginalForeachValueExpr(value was overwritten) instead ofOriginalForeachKeyExpr(key was not modified)ArrayDimFetch($expr, $keyVar))$stmt->keyVar !== nullto$stmt->keyVar !== null || $byRefWithoutKeyUpdated test assertions in existing files that were asserting the old imprecise behavior:
tests/PHPStan/Analyser/nsrt/overwritten-arrays.php—doFoo7:array<int, 1|string>→array<int, 1>tests/PHPStan/Analyser/nsrt/bug-13809.php—foo():list<mixed>→list<'foo'>,bar3():list<mixed>→list<'foo'|'maybe'>,baz():list<string>→list<'bar'>tests/PHPStan/Analyser/nsrt/bug-14083.php—example():list<string>→list<uppercase-string>tests/PHPStan/Analyser/nsrt/bug-14084.php—example()andexample2():list<string>→list<uppercase-string>tests/PHPStan/Rules/Variables/data/bug-14124.php—example3a():list<string>→list<uppercase-string>tests/PHPStan/Rules/Variables/data/bug-14124b.php—example3a():list<list<string>>→list<list<uppercase-string>>Root cause
The post-loop array type rewrite in
NodeScopeResolverwas gated on$stmt->keyVar !== null. This gate exists because the mechanism relies on:OriginalForeachKeyExprto verify the key wasn't modified$arr[$key]from the final scope to determine the new element typeWithout a key variable, neither of these steps is possible. The fix adds a parallel mechanism for the by-reference case: it uses
OriginalForeachValueExpr(already tracked for other purposes) to verify the value was overwritten, and reads the value variable's type directly instead of through anArrayDimFetch.Test
tests/PHPStan/Analyser/nsrt/bug-1940.php: Comprehensive regression test covering:$this->prop)intval)Fixes phpstan/phpstan#1940