Skip to content

Fix #14081: infer offset after array_key_first#4992

Closed
phpstan-bot wants to merge 1 commit into2.1.xfrom
create-pull-request/patch-jt1hy00
Closed

Fix #14081: infer offset after array_key_first#4992
phpstan-bot wants to merge 1 commit into2.1.xfrom
create-pull-request/patch-jt1hy00

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

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 for array_key_first/array_key_last assignments 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 type
  • tests/PHPStan/Rules/Arrays/data/bug-14081.php: New regression test data file with test cases for list, array map, and reversed null checks
  • tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php: Added testBug14081 method
  • tests/PHPStan/Analyser/nsrt/bug-14081.php: New NSRT test verifying type narrowing for array, key, and dim fetch
  • tests/PHPStan/Analyser/nsrt/bug-13546.php: Updated existing assertion from list<string> to non-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 !== null check would narrow $key to int<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 $key has its non-null type, then $arr is non-empty and $arr[$key] has the value type." When $key !== null is later checked, these holders fire and the type narrowing is applied.

Test

  • Rule test (testBug14081): Verifies no errors for $list[$key] after array_key_first/array_key_last with null check, covering list<string>, array<string, int>, and reversed null check patterns
  • NSRT test: Verifies that after the null check, the array is narrowed to non-empty, the key type is correct, and the dim fetch type is the value type

Fixes phpstan/phpstan#14081

- 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
@staabm staabm closed this Feb 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants