Skip to content

Track $arr[$key] existence across array_key_first/last + !== null#5537

Merged
ondrejmirtes merged 1 commit into2.1.xfrom
array-key-last-existing
Apr 26, 2026
Merged

Track $arr[$key] existence across array_key_first/last + !== null#5537
ondrejmirtes merged 1 commit into2.1.xfrom
array-key-last-existing

Conversation

@ondrejmirtes
Copy link
Copy Markdown
Member

Summary

  • Attaches a conditional holder when $key = array_key_first/last($arr) runs against a possibly-empty array, so if ($key !== null) lands $arr[$key] back in scope (matching what isset($arr[$key]) already does).
  • Lets AssignHandler's precise setExistingOffsetValueType path fire on deep writes like $arr[$key]['comment'] = $val, so untouched inner keys stay required instead of being generalised to optional.
  • Drops the @phpstan-ignore return.type workaround in RichParser::parseIdentifiers() that this exact pattern needed.

Test plan

  • make phpstan (was reporting Sealed array shape can only accept a constant array on RichParser::parseIdentifiers()'s return; now clean)
  • make tests (no new failures vs 2.1.x)
  • make cs
  • New regression nsrt: tests/PHPStan/Analyser/nsrt/array-key-last-existing.php
  • User-supplied reproduction from test.php now returns the expected shape on the if (\$key !== null) form

🤖 Generated with Claude Code

When `$key = array_key_first/last($arr)` runs against an array that
might be empty, `$arr[$key]` was only registered in scope if the array
was already known non-empty at assignment time. Once the user narrowed
`$key` separately with `if ($key !== null)`, AssignHandler's deep-write
path couldn't see the dim-fetch and fell back to a lossy
`setOffsetValueType`, generalising untouched keys to optional. Attach a
conditional holder at assignment time so the dim-fetch lands in scope
once `$key` is narrowed, matching what `isset($arr[$key])` already does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ondrejmirtes ondrejmirtes merged commit 7604335 into 2.1.x Apr 26, 2026
652 of 657 checks passed
@ondrejmirtes ondrejmirtes deleted the array-key-last-existing branch April 26, 2026 08:05
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.

1 participant