Recurse into parent expression in IssetCheck and MutatingScope::issetCheck when property has propagated error#5569
Merged
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom Apr 29, 2026
Conversation
…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).
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 chained property accesses follow array dim fetches in
??,isset(), or??=expressions, PHPStan incorrectly reported anullCoalesce.property/isset.propertyfalse 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 ?? nullwhere$arrayisarray<string, list<SomeDTO>>— the array offsets might not exist, making??valid, but PHPStan reportedProperty ValueObject::$value (string) on left side of ?? is not nullable.Changes
src/Rules/IssetCheck.php: In the property fetch branch ofcheck(), when$erroris already set from a deeper property, recurse into$expr->var/$expr->classinstead 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 inissetCheck()for the$resultparameter — recurse into$expr->var/$expr->classinstead of returning$resultimmediately.Root cause
In both
IssetCheck::check()andMutatingScope::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., anArrayDimFetch), which would have detected that an array offset might not exist and cleared the error. TheArrayDimFetchbranch already handled this correctly — it always recursed into$expr->varwhen an error was set.Analogous cases probed
??(null coalesce): affected, fixed and tested??=(null coalesce assign): affected (sameIssetCheck::check()path), fixed and testedisset(): affected (sameIssetCheck::check()path), fixed and testedempty(): uses the sameIssetCheck::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)$expr->class instanceof Exprbranch), fixed by the same change, testedMutatingScope::issetCheck(): affected (determines type of??expressions), fixed and tested via NSRTTest
tests/PHPStan/Rules/Variables/data/bug-14555.php: Test data with??,??=,isset(), and static property access on chained property + array dim fetch expressionstests/PHPStan/Rules/Variables/NullCoalesceRuleTest::testBug14555: Expects no errors for??and??=tests/PHPStan/Rules/Variables/IssetRuleTest::testBug14555: Expects no errors forisset()tests/PHPStan/Analyser/nsrt/bug-14555.php: NSRT test verifying$array['key'][0]->value->value ?? nullinfersstring|null(not juststring)Fixes phpstan/phpstan#14555