Skip to content

Recurse into parent expression in IssetCheck and MutatingScope::issetCheck when property has propagated error#5569

Merged
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-t8chqqh
Apr 29, 2026
Merged

Recurse into parent expression in IssetCheck and MutatingScope::issetCheck when property has propagated error#5569
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-t8chqqh

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When chained property accesses follow array dim fetches in ??, isset(), or ??= expressions, PHPStan incorrectly reported a nullCoalesce.property / isset.property false positive on the innermost non-nullable property, ignoring that an array access further up the chain might not exist and thus the ?? / isset() is valid.

For example, $array['key'][0]->value->value ?? null where $array is array<string, list<SomeDTO>> — the array offsets might not exist, making ?? valid, but PHPStan reported Property ValueObject::$value (string) on left side of ?? is not nullable.

Changes

  • src/Rules/IssetCheck.php: In the property fetch branch of check(), when $error is already set from a deeper property, recurse into $expr->var / $expr->class instead of returning the error immediately. This mirrors what the code already does when it generates the error at the current level (lines 221–228).
  • src/Analyser/MutatingScope.php: Same fix in issetCheck() for the $result parameter — recurse into $expr->var / $expr->class instead of returning $result immediately.

Root cause

In both IssetCheck::check() and MutatingScope::issetCheck(), the property fetch branch had an early return when a non-null error/result was propagated from a deeper recursive call. This prevented recursing into the parent expression (e.g., an ArrayDimFetch), which would have detected that an array offset might not exist and cleared the error. The ArrayDimFetch branch already handled this correctly — it always recursed into $expr->var when an error was set.

Analogous cases probed

  • ?? (null coalesce): affected, fixed and tested
  • ??= (null coalesce assign): affected (same IssetCheck::check() path), fixed and tested
  • isset(): affected (same IssetCheck::check() path), fixed and tested
  • empty(): uses the same IssetCheck::check() path so benefits from the fix, but doesn't trigger the specific false positive for the reproducer's types (string has uncertain falsiness, so no error is generated)
  • Static properties: affected (the $expr->class instanceof Expr branch), fixed by the same change, tested
  • MutatingScope::issetCheck(): affected (determines type of ?? expressions), fixed and tested via NSRT

Test

  • tests/PHPStan/Rules/Variables/data/bug-14555.php: Test data with ??, ??=, isset(), and static property access on chained property + array dim fetch expressions
  • tests/PHPStan/Rules/Variables/NullCoalesceRuleTest::testBug14555: Expects no errors for ?? and ??=
  • tests/PHPStan/Rules/Variables/IssetRuleTest::testBug14555: Expects no errors for isset()
  • tests/PHPStan/Analyser/nsrt/bug-14555.php: NSRT test verifying $array['key'][0]->value->value ?? null infers string|null (not just string)

Fixes phpstan/phpstan#14555

…setCheck` when property has propagated error

- In `IssetCheck::check()`, when a non-null `$error` is passed in from a deeper
  property check, recurse into `$expr->var` (or `$expr->class` for static
  properties) instead of returning the error immediately. This lets array dim
  fetches further up the chain clear the error when the offset might not exist.
- Apply the same fix in `MutatingScope::issetCheck()` for the `$result` parameter,
  ensuring type inference for `??` expressions also considers parent array accesses.
- Affects `??`, `??=`, `isset()`, and `empty()` — all share the same `IssetCheck::check()` code path.
  `empty()` was probed but doesn't trigger the false positive for scalar types
  because the falsiness callback returns null before an error is generated.
- Static property access (`$array[0]::$prop->value ?? null`) also fixed by the
  same change (the `$expr->class instanceof Expr` branch).
@ondrejmirtes ondrejmirtes merged commit 22923ac into phpstan:2.1.x Apr 29, 2026
654 of 657 checks passed
@ondrejmirtes ondrejmirtes deleted the create-pull-request/patch-t8chqqh branch April 29, 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.

2 participants