Fix phpstan/phpstan#13773: Index for loop inference should only work in lists#5215
Conversation
…for lists - Added isList() check in inferForLoopExpressions() before assigning $array[$i] type - The inference that $array[$i] exists in `for ($i=0; $i<count($array); $i++)` is only valid when $array is a list (sequential integer keys starting from 0) - New regression test in tests/PHPStan/Rules/Arrays/data/bug-13773.php
src/Analyser/NodeScopeResolver.php
Outdated
| $arrayType = $bodyScope->getType($arrayArg); | ||
| $arrayNativeType = $bodyScope->getNativeType($arrayArg); | ||
| if ($arrayType->isList()->yes() || $arrayNativeType->isList()->yes()) { |
There was a problem hiding this comment.
Checking the arrayType (and not the arrayNative type) should be enough
There was a problem hiding this comment.
Everything is already up to date. The reviewer's feedback was already addressed by the da0f51464 ("simplify") commit, which removed the $arrayNativeType check and only uses $arrayType->isList()->yes() — exactly what VincentLanglet suggested.
src/Analyser/NodeScopeResolver.php
Outdated
| $arrayType = $bodyScope->getType($arrayArg); | ||
| $arrayNativeType = $bodyScope->getNativeType($arrayArg); | ||
| if ($arrayType->isList()->yes() || $arrayNativeType->isList()->yes()) { |
There was a problem hiding this comment.
Checking the arrayType( and not the arrayNativeType) should be enough
|
The review is an APPROVED review with no new change requests. The previous feedback from VincentLanglet (suggesting that checking only There are no remaining changes to make - the PR is approved and ready to merge. |
Summary
For loops like
for ($i = 0; $i < count($array); $i++)were incorrectly inferring that$array[$i]always exists, even for non-list arrays likearray<int, string>. This is only valid for lists where keys are sequential integers starting from 0. A non-listarray<int, string>could have keys like[100 => "hey"], making$array[0]undefined.Changes
isList()check insrc/Analyser/NodeScopeResolver.phpininferForLoopExpressions()for both$i < count($items)andcount($items) > $ipatternstests/PHPStan/Rules/Arrays/data/bug-13773.phpcovering both list and non-list cases in both condition orderingsRoot cause
The
inferForLoopExpressions()method (introduced in PR #4403) unconditionally assigned the array's iterable value type to$array[$i]inside for loops with the$i = 0; $i < count($array); $i++pattern. This made PHPStan believe the offset always exists, suppressing "offset might not exist" errors. However, this assumption is only correct for lists (arrays with sequential integer keys 0, 1, 2, ...), not for generalarray<int, string>where keys can be arbitrary integers.Test
Added
tests/PHPStan/Rules/Arrays/data/bug-13773.phpwith four test cases:testNonListArray()-array<int, string>with$i < count(...)- should report errortestList()-list<string>with$i < count(...)- no error (inference is correct)testListReversed()-list<string>withcount(...) > $i- no errortestNonListReversed()-array<int, string>withcount(...) > $i- should report errorFixes phpstan/phpstan#13773