Fix ErrorType leaking from array_key_exists with union key types in loops#5487
Merged
VincentLanglet merged 1 commit intophpstan:2.1.xfrom Apr 17, 2026
Merged
Conversation
…oops
When array_key_exists() was called with a union constant key type (e.g.,
'c1'|'c2') on an empty array inside a loop, the NonEmptyArrayType
narrowing was skipped due to a count(getConstantScalarTypes()) <= 1
guard. This caused the isIterableAtLeastOnce()->no() early return to
produce empty SpecifiedTypes, leaving the array typed as array{} even
in the true branch. Subsequent access to $arr[$key] on the empty array
type produced ErrorType, which propagated through loop iterations and
eventually caused false "Unable to resolve template type" errors when
the array was passed to generic functions like array_values().
The fix removes the <= 1 guard so NonEmptyArrayType narrowing always
applies when array_key_exists returns true, which is semantically
correct regardless of key type.
Closes phpstan/phpstan#14489
VincentLanglet
approved these changes
Apr 17, 2026
staabm
approved these changes
Apr 17, 2026
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
array_key_exists()type-specifying extension to always narrow the array tonon-emptywhen the function returns true, regardless of whether the key type is a union of constant scalarscount($keyType->getConstantScalarTypes()) <= 1guard prevented theNonEmptyArrayTypenarrowing for union keys like'c1'|'c2', causingErrorTypeto leak into array value types during loop analysisarray_values()Root cause
In
ArrayKeyExistsFunctionTypeSpecifyingExtension, the non-constant key path had two guards:count($keyType->getConstantScalarTypes()) <= 1— only applyNonEmptyArrayTypenarrowing for single-valued keysisIterableAtLeastOnce()->no()— early return for empty arraysWhen the key was a union (e.g.,
'c1'|'c2'), guard #1 skipped the narrowing, and guard #2 returned emptySpecifiedTypes. This left the array typed asarray{}even in the true branch ofarray_key_exists(). Accessing$arr[$key]onarray{}returnsErrorType(viaConstantArrayType::getOffsetValueType()), which then propagated through assignments and loop merging.Test plan
tests/PHPStan/Analyser/nsrt/bug-14489.phpcovering both the original issue (nested foreach with array_values) and the minimal reproduction (while loop with union key)bug-7000btest expectation (array correctly narrowed tonon-empty-arrayafterarray_key_exists)Closes phpstan/phpstan#14489