Skip to content

Fix phpstan/phpstan#13637: Array might not have offset, if array is deep#5203

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-gfgpey3
Open

Fix phpstan/phpstan#13637: Array might not have offset, if array is deep#5203
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-gfgpey3

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When assigning to deeply nested arrays (3+ levels of ArrayDimFetch), e.g. $final[$i][$j][$k]['abc'] = $i, PHPStan incorrectly marked subsequent keys like 'def' as optional. This worked correctly for 2-level nesting but not for 3+.

Changes

  • Added a recursive path in src/Type/ArrayType.php in setExistingOffsetValueType() that handles the case where the item type is an array whose value type is a constant array
  • When this condition is met, the method recursively delegates to the inner array's setExistingOffsetValueType() instead of falling through to the naive TypeCombinator::union() fallback
  • Added regression test in tests/PHPStan/Analyser/nsrt/bug-13637.php

Root cause

ArrayType::setExistingOffsetValueType() had special handling (lines 377-411) for when $this->itemType is a constant array — it would iterate the keys and properly merge them. However, for 3+ levels of nesting, $this->itemType is a general ArrayType (not a constant array), so this path was skipped. The fallback at line 413 performed a raw TypeCombinator::union($this->itemType, $valueType) which re-introduced intermediate type states where keys were missing, causing them to appear as optional.

The fix adds an intermediate check: if both the current item type and the value type are arrays whose value types are constant arrays, recursively call setExistingOffsetValueType() on the item type with the value's key/value types.

Test

Added tests/PHPStan/Analyser/nsrt/bug-13637.php with two functions:

  • doesNotWork() — 3-level deep nesting that was previously broken (keys marked as optional)
  • thisWorks() — 2-level deep nesting that already worked correctly

Both use assertType() to verify all keys are required (not optional).

Fixes phpstan/phpstan#13637

- Added recursive handling in ArrayType::setExistingOffsetValueType() for arrays
  whose item type is itself an array with constant array values (3+ nesting levels)
- New regression test in tests/PHPStan/Analyser/nsrt/bug-13637.php
- The root cause was that setExistingOffsetValueType() only handled the case where
  the item type was directly a constant array, falling through to a naive union for
  deeper nesting which re-introduced intermediate states with optional keys

Closes phpstan/phpstan#13637
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