Skip to content

Fix ErrorType leaking from array_key_exists with union key types in loops#5487

Merged
VincentLanglet merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-c61unyw
Apr 17, 2026
Merged

Fix ErrorType leaking from array_key_exists with union key types in loops#5487
VincentLanglet merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-c61unyw

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

  • Fix array_key_exists() type-specifying extension to always narrow the array to non-empty when the function returns true, regardless of whether the key type is a union of constant scalars
  • Previously, a count($keyType->getConstantScalarTypes()) <= 1 guard prevented the NonEmptyArrayType narrowing for union keys like 'c1'|'c2', causing ErrorType to leak into array value types during loop analysis
  • This manifested as false positive "Unable to resolve template type" errors when the corrupted array was passed to generic functions like array_values()

Root cause

In ArrayKeyExistsFunctionTypeSpecifyingExtension, the non-constant key path had two guards:

  1. count($keyType->getConstantScalarTypes()) <= 1 — only apply NonEmptyArrayType narrowing for single-valued keys
  2. isIterableAtLeastOnce()->no() — early return for empty arrays

When the key was a union (e.g., 'c1'|'c2'), guard #1 skipped the narrowing, and guard #2 returned empty SpecifiedTypes. This left the array typed as array{} even in the true branch of array_key_exists(). Accessing $arr[$key] on array{} returns ErrorType (via ConstantArrayType::getOffsetValueType()), which then propagated through assignments and loop merging.

Test plan

  • Added NSRT regression test tests/PHPStan/Analyser/nsrt/bug-14489.php covering both the original issue (nested foreach with array_values) and the minimal reproduction (while loop with union key)
  • Verified test fails without the fix (ErrorType appears in assertions)
  • Updated bug-7000b test expectation (array correctly narrowed to non-empty-array after array_key_exists)
  • Full test suite passes (11867 tests, 79377 assertions)
  • PHPStan self-analysis passes
  • Coding standards check passes

Closes phpstan/phpstan#14489

…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 VincentLanglet requested a review from staabm April 17, 2026 15:17
@VincentLanglet VincentLanglet merged commit e905481 into phpstan:2.1.x Apr 17, 2026
653 of 657 checks passed
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-c61unyw branch April 17, 2026 15:52
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.

3 participants