Repository navigation
Keep an explicit key of an unsealed shape as an optional never key when subtracting its offset - #6792
Merged
Merged
Conversation
…en subtracting its offset
ConstantArrayType::tryRemove() subtracted HasOffsetType and
HasOffsetValueType by unsetting the offset. On an unsealed shape whose
extras can hold that offset, dropping the explicit key lets the extras
supply it again with their value type, so the "subtracted" shape is wider
than the original: array{a?: string, ...} minus hasOffset('a') was
array{...}, and array{a?: string, b: int, ...<string, int>} re-admitted
'a' as int. The falsey isset() narrowing from 77527df and the falsey
array_key_exists() narrowing both go through this subtraction, and merging
the widened branch back collapsed the subject to array.
The key now stays as an optional never key instead - absent, but still
fencing the offset off from the extras - and branch merges restore the
original shape.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xj1fBpXNVT34mRk1GQBxJF
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.
ConstantArrayType::tryRemove()subtractedHasOffsetType/HasOffsetValueTypeby unsetting the offset. On an unsealed shape whose extras can hold that offset, dropping the explicit key lets the extras supply it again with their value type, so the result of the subtraction is wider than the original:array{a?: string, ...}minushasOffset('a')wasarray{...}array{a?: string, b: int, ...<string, int>}minushasOffset('a')wasarray{b: int, ...<string, int>}, which allows'a'to be anintThe falsey
isset()narrowing from 77527df (and??, which reuses it) and the falseyarray_key_exists()narrowing both go through this subtraction. When the widened branch merged back, the subject collapsed toarray.The key now stays as an optional
neverkey, the same representation as the PHPDocarray{a?: never, ...}. The key is absent, and the extras still can't supply it, so branch merges restore the original shape:!isset($c['a'])array{...}array{a?: never, ...}$c['a'] ?? ''/ the ifarrayarray{a?: string, ...}!array_key_exists('a', $c), after the ifarrayarray{a?: string, ...}!isset($d['a'])onarray{a?: string, b: int, ...<string, int>}array{b: int, ...<string, int>}array{a?: never, b: int, ...<string, int>}Sealed shapes, shapes whose extras cannot hold the offset (e.g.
...<int, int>), and legacy shapes without sealedness information behave exactly as before. A key that is only implied by the extras is still left alone, because unsetting it never widened anything.This is an alternative to #6711. That PR skips the
isset()narrowing for unsealed shapes inDefaultNarrowingHelper, buttryRemove()itself still widens, soarray_key_exists()and the typed-extras case above stay broken.The turbo mirror in
turbo-ext/src/ConstantArrayType.cppis updated the same way. Test results are identical with and without the extension loaded.Closes phpstan/phpstan#15428
🤖 Generated with Claude Code
https://claude.ai/code/session_01Xj1fBpXNVT34mRk1GQBxJF