Narrow types of maybe-set array keys through conditional expression holders#5764
Open
phpstan-bot wants to merge 1 commit into
Open
Narrow types of maybe-set array keys through conditional expression holders#5764phpstan-bot wants to merge 1 commit into
phpstan-bot wants to merge 1 commit into
Conversation
…olders
When a condition like `array_key_exists('a', $data) && !is_string($data['a'])`
is false, PHPStan now knows that if key 'a' exists, its value is a string.
This allows `$data['a'] ?? 'fallback'` to resolve to `string`.
Three changes make this work:
1. processBooleanSureConditionalTypes and processBooleanNotSureConditionalTypes
now cross-process sureTypes with sureNotTypes (and vice versa) when building
conditional expression holders. Previously they only matched sure-to-sure and
sureNot-to-sureNot, missing the cross-arm implications.
2. The isset handler now produces HasOffsetType narrowing for non-constant
general arrays with non-nullable values in the falsey context, matching what
array_key_exists already does.
3. Replaced array_merge with a proper deep merge for conditional holder arrays
to prevent later holders from overwriting earlier ones sharing the same
expression key.
Closes phpstan/phpstan#11918
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 code guards a maybe-set array key with a pattern like:
PHPStan should know that after this block, if key
'a'exists, its value isstring. This enables$options['a'] ?? 'fallback'to resolve tostring.Three changes make this work:
Cross-process sureTypes/sureNotTypes in conditional holder builders:
processBooleanSureConditionalTypesnow also iteratesrightTypes->getSureNotTypes(), andprocessBooleanNotSureConditionalTypesnow also iteratesrightTypes->getSureTypes(). Previously, each method only paired sure-to-sure or sureNot-to-sureNot, missing cross-arm implications like "if array has offset X (from sureNotType), then value at X is string (from sureType)".Isset handler produces HasOffsetType for general arrays: In the falsey branch,
isset($data['key'])on a non-constant array with non-nullable values now produces aHasOffsetTypespecification, matching whatarray_key_existsalready does. This enables conditional holders to fire for theissetvariant of the pattern. Gated to non-constant arrays to avoid interfering with the existing constant-array handling of optional keys.Deep merge for conditional holder arrays: Replaced
array_mergewithmergeConditionalHolderArrayswhen combining holders from the fourprocessBooleanXxxConditionalTypescalls. PHP'sarray_mergeoverwrites string keys, so a holder from one call would silently replace a valid holder from an earlier call when they targeted the same expression.Test plan
tests/PHPStan/Analyser/nsrt/bug-11918.phpcovers:array_key_exists+!is_stringpatternarray_key_exists+!is_intpattern (no assertion on coalesce since int is not in original type)isset+!is_stringpattern!array_key_exists || is_string)instanceofpattern (array_key_exists+!instanceof)make phpstanreports no errorsmake cs-fixreports no violationsCloses phpstan/phpstan#11918