Skip to content

Do not record conditional expressions for MethodCall/StaticCall when assigned expression contains nullsafe chain#5496

Closed
phpstan-bot wants to merge 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-f3d4r2x
Closed

Do not record conditional expressions for MethodCall/StaticCall when assigned expression contains nullsafe chain#5496
phpstan-bot wants to merge 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-f3d4r2x

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

PR #5447 added MethodCall and StaticCall to the allowed expression types in processSureTypesForConditionalExpressionsAfterAssign, enabling type narrowing for method call results assigned to variables (e.g. $hasA = $b->getA() !== null; if ($hasA) { $b->getA(); }). However, this also caused false positives when the assigned expression was a nullsafe chain: the TypeSpecifier's chain decomposition produces sure types for intermediate sub-expressions (like $order->getOrderCustomer() from $order->getOrderCustomer()?->getCustomer()?->getAccountType()), and these were incorrectly persisted as conditional expressions.

Changes

  • Added exprContainsNullsafe() helper method in src/Analyser/ExprHandler/AssignHandler.php that recursively checks whether an expression contains any NullsafeMethodCall or NullsafePropertyFetch
  • Added $assignedExprContainsNullsafe parameter to processSureTypesForConditionalExpressionsAfterAssign() and processSureNotTypesForConditionalExpressionsAfterAssign()
  • When the flag is true, MethodCall and StaticCall expressions in the sure types are skipped (not recorded as conditional expressions)
  • All call sites updated to pass the flag

Analogous cases probed

  • NullsafePropertyFetch with MethodCall var: Same issue — a chain like $a->getB()?->prop?->subProp would narrow $a->getB() through conditional expressions. Fixed by the same mechanism (the assigned expression contains NullsafePropertyFetch). Added test.
  • StaticCall in nullsafe chain: Already covered in the reproduction test ($order::getStaticOrderCustomer()?->...).
  • Direct method call narrowing (bug #9455): Verified still working — $hasA = $b->getA() !== null does NOT contain any nullsafe operator, so the conditional expression is still recorded correctly.
  • PropertyFetch: Not affected — PropertyFetch was in the allowed list before PR Fix phpstan/phpstan#9455: Bug: False report Cannot call method getId() on A|null #5447 and property accesses are stable within a scope.
  • FuncCall: Not affected — FuncCall was in the allowed list before PR Fix phpstan/phpstan#9455: Bug: False report Cannot call method getId() on A|null #5447 and is not skipped by the new filter.

Root cause

The TypeSpecifier decomposes NullsafeMethodCall chains into BooleanAnd(var !== null, MethodCall(var, name, args)) conditions. This produces sure types for intermediate expressions (e.g., $order->getOrderCustomer() is not null when the chain result is non-null). PR #5447 allowed these MethodCall/StaticCall sure types to be recorded as conditional expressions tied to the assigned variable. After a subsequent check narrowed the assigned variable, the conditional expressions fired and incorrectly narrowed the intermediate method call for all future uses — triggering the nullsafe.neverNull rule.

Test

  • tests/PHPStan/Rules/Methods/data/bug-14493.php — reproduces the reported false positive with both instance method calls and static method calls in nullsafe chains
  • tests/PHPStan/Rules/Properties/data/bug-14493.php — reproduces the analogous false positive with NullsafePropertyFetch chains where the var is a method call
  • Both tests expect zero errors (false positives eliminated)

Fixes phpstan/phpstan#14493

phpstan-bot and others added 4 commits April 19, 2026 06:43
…hen assigned expression contains nullsafe chain

- When assigning from a nullsafe method/property chain (e.g. `$x = $a->b()?->c()?->d()`),
  the TypeSpecifier decomposes the chain and produces sure types for intermediate
  sub-expressions like `$a->b()`. These were being recorded as conditional expressions
  tied to the assigned variable, causing false positives in `NullsafeMethodCallRule`
  and `NullsafePropertyFetchRule` when the same method was called again later.
- Add `exprContainsNullsafe()` helper to detect nullsafe operators in the assigned expression
- Skip recording MethodCall/StaticCall conditional expressions when the assigned
  expression contains any NullsafeMethodCall or NullsafePropertyFetch
- The fix for #9455 (direct method call narrowing like `$hasA = $b->getA() !== null`)
  is preserved because those assigned expressions do not contain nullsafe operators
- Also fixes the analogous case with NullsafePropertyFetch chains
@staabm staabm closed this Apr 19, 2026
@staabm staabm deleted the create-pull-request/patch-f3d4r2x branch April 19, 2026 09:07
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