Fix #14081: infer offset after array_key_first#4992
Closed
phpstan-bot wants to merge 1 commit into2.1.xfrom
Closed
Fix #14081: infer offset after array_key_first#4992phpstan-bot wants to merge 1 commit into2.1.xfrom
phpstan-bot wants to merge 1 commit into2.1.xfrom
Conversation
- Added conditional expression holders in NodeScopeResolver for $key = array_key_first($arr) / array_key_last($arr) assignments - When $key !== null is checked, the array is narrowed to non-empty and $arr[$key] is recognized as having the array's value type - New regression tests in tests/PHPStan/Rules/Arrays/data/bug-14081.php and tests/PHPStan/Analyser/nsrt/bug-14081.php - Updated bug-13546 test assertion to reflect improved narrowing Fixes phpstan/phpstan#14081
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
$key = array_key_first($list)is assigned and then checked with$key !== null, PHPStan was not recognizing that$list[$key]is a valid offset access, producing a false positive "Offset int<0, max> might not exist on list."This PR adds conditional expression holders so that after the null check, the array is narrowed to non-empty and the dim fetch
$arr[$key]is recognized as valid.Changes
src/Analyser/NodeScopeResolver.php: Added handling forarray_key_first/array_key_lastassignments to create conditional expression holders that link the non-null state of the key variable to (1) the array being non-empty and (2) the dim fetch$arr[$key]having the array's value typetests/PHPStan/Rules/Arrays/data/bug-14081.php: New regression test data file with test cases for list, array map, and reversed null checkstests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php: AddedtestBug14081methodtests/PHPStan/Analyser/nsrt/bug-14081.php: New NSRT test verifying type narrowing for array, key, and dim fetchtests/PHPStan/Analyser/nsrt/bug-13546.php: Updated existing assertion fromlist<string>tonon-empty-list<string>to reflect the improved narrowing (the old test even had a comment noting this could be improved)Root cause
When
$key = array_key_first($list)is a standalone assignment (not inside a condition), the existing TypeSpecifier code at lines 758-780 only registers the dim fetch expression type when the array is already known to be non-empty (isIterableAtLeastOnce()->yes()). For possibly-empty arrays, no type information was registered, so a subsequent$key !== nullcheck would narrow$keytoint<0, max>but not establish that$list[$key]is valid.The fix uses PHPStan's conditional expression holder mechanism (the same system used for
$count = count($arr); if ($count > 0)patterns) to create holders during the assignment that say: "if$keyhas its non-null type, then$arris non-empty and$arr[$key]has the value type." When$key !== nullis later checked, these holders fire and the type narrowing is applied.Test
testBug14081): Verifies no errors for$list[$key]afterarray_key_first/array_key_lastwith null check, coveringlist<string>,array<string, int>, and reversed null check patternsFixes phpstan/phpstan#14081