Skip to content

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

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

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

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When building deeply nested arrays (3+ levels of nesting) via sequential assignments like $final[$i][$j][$k]['def'] = $i;, PHPStan incorrectly marked intermediate keys as optional. With 2 levels of nesting, the same pattern worked correctly.

Changes

  • Added a recursive case in src/Type/ArrayType.php setExistingOffsetValueType() method to handle non-constant array item types
  • New regression test in tests/PHPStan/Analyser/nsrt/bug-13637.php covering both the 3-level (broken) and 2-level (working) cases
  • Updated expected error count in tests/PHPStan/Analyser/AnalyserIntegrationTest.php for testBug7903 (39→36) as the fix resolves 3 false positives in that deeply nested array test

Root cause

ArrayType::setExistingOffsetValueType() had a special case for when both the item type and value type were constant arrays — it would recursively delegate key-by-key updates. However, when the item type was a non-constant array (e.g., array<int, array{abc: int, def?: int}> — an ArrayType wrapping a ConstantArrayType), this special case didn't apply. The fallback used TypeCombinator::union() to merge the old and new types, which re-introduced optionality for keys that had been made required by the inner assignment.

The fix adds a recursive case: when both item type and value type are arrays (but not constant arrays), it recursively calls setExistingOffsetValueType on the inner types. This allows the recursion to eventually reach the constant array level where the existing special case correctly handles key optionality.

Test

Added tests/PHPStan/Analyser/nsrt/bug-13637.php which verifies that after sequential assignments to $final[$i][$j][$k]['abc'], $final[$i][$j][$k]['def'], and $final[$i][$j][$k]['ghi'], the inferred type for $final[$i][$j][$k] has all three keys as required (not optional). The test confirmed the failure before the fix and passes after.

Fixes phpstan/phpstan#13637

- Added recursive handling in ArrayType::setExistingOffsetValueType() for
  non-constant array item types, so nested arrays at any depth correctly
  propagate key optionality updates
- New regression test in tests/PHPStan/Analyser/nsrt/bug-13637.php
- Updated bug-7903 expected error count (39→36) as fix resolves 3 false positives
- Root cause: when itemType was a non-constant array (e.g. array<int, array{...}>),
  setExistingOffsetValueType fell through to TypeCombinator::union which re-merged
  the old optional keys with the updated required 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