Skip to content

Propagate ArrayDimFetch type narrowing to parent array in BooleanAnd/BooleanOr compound conditions#5592

Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ehiudw6
Closed

Propagate ArrayDimFetch type narrowing to parent array in BooleanAnd/BooleanOr compound conditions#5592
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ehiudw6

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When isset($arr['key']) && is_string($arr['key']) (or similar compound conditions) was used with an early return, PHPStan failed to narrow the parent array type in the else/falsy branch. For example, with $test: array{}|array{hi: 'hello'}|array{hi: array{0: 42, 1?: 42}}, after if (isset($test['hi']) && is_string($test['hi'])) { return; }, PHPStan still inferred $test as the full union instead of array{}|array{hi: array{0: 42, 1?: 42}}.

Changes

  • Added propagateArrayDimFetchToParentArray() method in src/Analyser/TypeSpecifier.php that enriches normalized SpecifiedTypes with parent array narrowing using HasOffsetValueType when a dim fetch specifier exists but its parent variable has no specifier
  • Applied propagation before intersectWith() in two locations:
    • BooleanAnd falsy path (line ~737): handles isset($x['k']) && is_type($x['k']) in else branches
    • BooleanOr truthy path (line ~797): handles !isset($x['k']) || !is_type($x['k']) in if branches
  • Restricted propagation to cases where the parent expression is a simple Expr\Variable (not another ArrayDimFetch) to avoid cascading side effects with nested dim fetches

Root cause

In the BooleanAnd falsy context (!(A && B) = !A || !B), TypeSpecifier normalizes left and right specifiers then intersects them. The intersection (SpecifiedTypes::intersectWith()) only retains specifiers for expressions present in both sides. When the left side (isset) narrows $test and the right side (is_string) narrows $test['hi'], these are different expression keys, so the intersection produces empty specifiers — no narrowing occurs.

The fix propagates the dim-fetch narrowing to the parent array (via HasOffsetValueType) before the intersection, ensuring both sides have specifiers for the same variable ($test). The intersection then correctly computes the union of the two path types.

Test

  • tests/PHPStan/Analyser/nsrt/bug-14566.php:
    • Original reproducer: isset($test['hi']) && is_string($test['hi']) with early return
    • else branch variant
    • is_array variant
    • is_int variant
    • instanceof variant
    • Strict comparison (=== 'hello') variant
    • BooleanOr equivalent (!isset || !is_string) variant

Probed and found already correct:

  • Simple is_string($test['hi']) without isset (existing propagation in specifyExpressionType handles this)
  • Non-constant array keys and mixed parent types (guard clauses correctly skip these)

Fixes phpstan/phpstan#14566

…nd`/`BooleanOr` compound conditions

- Add `propagateArrayDimFetchToParentArray()` in TypeSpecifier that
  enriches normalized SpecifiedTypes with parent array narrowing when
  a dim fetch on a variable is present but the variable itself has no
  specifier
- Apply the propagation before `intersectWith()` in BooleanAnd falsy
  and BooleanOr truthy paths, where the intersection previously
  dropped non-overlapping dim-fetch vs variable specifiers
- Use `HasOffsetValueType` to compute the narrowed parent array type
- Restrict propagation to cases where the parent is a simple variable
  (not a nested dim fetch) to avoid cascading side effects
- Covers `is_string`, `is_int`, `is_array`, `instanceof`, and strict
  comparisons on array offsets combined with `isset` via `&&`
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-ehiudw6 branch May 3, 2026 17:40
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.

2 participants