Preserve constant array shape when spreading a union of constant arrays in array literals#5774
Open
phpstan-bot wants to merge 1 commit into
Open
Conversation
…ys in array literals
- In `InitializerExprTypeResolver::getArrayType()`, change the
`count($constantArrays) === 1` check to `count($constantArrays) > 0`
to handle unions of constant arrays (e.g. `array{key: T}|array{}`)
- For string-key arrays: merge keys across all constant arrays, marking
keys not present in all branches as optional, with value types unioned
- For integer-key arrays: merge by position across all constant arrays,
with positions not present in all branches marked optional
- Update `$hasOffsetValueTypes` tracking to correctly handle merged keys
that overlap with previously-set keys
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 using the spread operator in array literals with a value that is a union of constant arrays (e.g.
...($flag ? ['key' => true] : [])), PHPStan was degrading the result to a general array type likenon-empty-array<'key'|'other', bool>instead of preserving the precise array shapearray{other: bool, key?: true}.Changes
InitializerExprTypeResolver::getArrayType()insrc/Reflection/InitializerExprTypeResolver.phpto handle unions of multiple constant arrays when processing spread itemscount($constantArrays) === 1tocount($constantArrays) > 0$hasOffsetValueTypestracking when merged spread keys overlap with previously-set keysRoot cause
In
InitializerExprTypeResolver::getArrayType(), when a spread item's value was a union type likearray{spread: true}|array{},getConstantArrays()returned 2 constant arrays. The conditioncount($constantArrays) === 1failed, causing the code to fall through to the general fallback that called$arrayBuilder->degradeToGeneralArray(), losing the array shape information entirely.Analogous cases probed
OversizedArrayBuilder: Uses$valueType instanceof ConstantArrayType(single type only) — affects only arrays with >256 items, a rare edge case. Not fixed here.FuncCallHandlerarg unpacking ($callArg->unpack): Similarcount($constantArrays) === 1pattern for function call argument unpacking (used byarray_pushetc.). Different context with different semantics — not fixed here.count($constantArrays) === 1sites (NodeScopeResolver foreach, ConstantArrayType list-ness, ArrayType truncation): Inspected and confirmed to be unrelated to array literal spreading.Test
Added
tests/PHPStan/Analyser/nsrt/bug-14708.phpwith 9 test functions covering:Fixes phpstan/phpstan#14708