Skip to content

Fix phpstan/phpstan#9455: Bug: False report Cannot call method getId() on A|null#5447

Merged
staabm merged 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-iy55e8w
Apr 18, 2026
Merged

Fix phpstan/phpstan#9455: Bug: False report Cannot call method getId() on A|null#5447
staabm merged 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-iy55e8w

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

@phpstan-bot phpstan-bot commented Apr 13, 2026

Summary

When a null check on a method call result was stored in a boolean variable (e.g., $hasA = $b->getA() !== null), and that variable was later used in an if condition, PHPStan failed to narrow the type of $b->getA() inside the branch. The inline equivalent (if ($b->getA() !== null)) worked correctly.

Changes

  • Added MethodCall and Expr\StaticCall to the allowed expression types in processSureTypesForConditionalExpressionsAfterAssign() and processSureNotTypesForConditionalExpressionsAfterAssign() in src/Analyser/ExprHandler/AssignHandler.php
  • Added regression test in tests/PHPStan/Analyser/nsrt/bug-9455.php

Root cause

When PHPStan processes an assignment like $hasA = $b->getA() !== null, it creates ConditionalExpressionHolder objects that record type narrowing to apply when the assigned variable is later used in a condition. However, the methods processSureTypesForConditionalExpressionsAfterAssign and processSureNotTypesForConditionalExpressionsAfterAssign only allowed Variable, PropertyFetch, ArrayDimFetch, and FuncCall expressions to create these holders — MethodCall and StaticCall were filtered out. This meant that type narrowing from method call null checks (like $b->getA() !== null) was never stored and thus never applied when the boolean variable was checked.

Test

The regression test verifies that $b->getA() is correctly narrowed to Bug9455\A (not Bug9455\A|null) inside an if ($hasA) block where $hasA = $b->getA() !== null.

Fixes phpstan/phpstan#9455
Fixes phpstan/phpstan#5207

@staabm staabm force-pushed the create-pull-request/patch-iy55e8w branch from a634fcf to 2535811 Compare April 18, 2026 07:02
Copy link
Copy Markdown
Contributor

@staabm staabm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add regression test for phpstan/phpstan#5207

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Done. Added regression test tests/PHPStan/Analyser/nsrt/bug-5207.php for phpstan/phpstan#5207, which covers the same boolean-variable type narrowing bug with the exact code from that issue (abstract class with nullable getChild() method). All tests pass and PHPStan static analysis is clean.

@staabm staabm requested a review from VincentLanglet April 18, 2026 08:34
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test with an impure method, asserting the error is still reported

staabm and others added 3 commits April 18, 2026 10:57
…d boolean variable for method calls

- Added MethodCall and StaticCall to allowed expression types in processSureTypesForConditionalExpressionsAfterAssign and processSureNotTypesForConditionalExpressionsAfterAssign
- New regression test in tests/PHPStan/Analyser/nsrt/bug-9455.php
- The root cause was that conditional expression holders were not created for method call results when assigned to a boolean variable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses review feedback: adds test cases with @phpstan-impure methods
to both bug-9455 and bug-5207 regression tests, asserting that impure
method results are correctly NOT narrowed when stored in boolean variables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm staabm force-pushed the create-pull-request/patch-iy55e8w branch from 7877d19 to 656a527 Compare April 18, 2026 08:57
Comment thread tests/PHPStan/Analyser/nsrt/bug-5207.php
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm staabm merged commit 2112eee into phpstan:2.1.x Apr 18, 2026
653 of 656 checks passed
@staabm staabm deleted the create-pull-request/patch-iy55e8w branch April 18, 2026 09:38
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

I processed this review but have nothing to report.

@staabm
Copy link
Copy Markdown
Contributor

staabm commented Apr 18, 2026

since this change we get this error
https://phpstan.org/r/4421249a-1a56-4e0e-8b92-943c4977491d
(see integration tests)

Using nullsafe method call on non-nullable type Shopware\Core\Checkout\Customer\Service\OrderCustomerEntity. Use -> instead.

@VincentLanglet do we agree that this error is correct? if so, I would add a test for it

@staabm staabm mentioned this pull request Apr 18, 2026
@VincentLanglet
Copy link
Copy Markdown
Contributor

The error is correct

phpstan-bot pushed a commit to phpstan-bot/phpstan-src that referenced this pull request Apr 19, 2026
…ain sub-expressions through conditional expressions

- PR phpstan#5447 added MethodCall/StaticCall to the allowed expression types in
  processSureTypesForConditionalExpressionsAfterAssign and
  processSureNotTypesForConditionalExpressionsAfterAssign. This correctly
  fixed bug-9455 ($hasA = $b->getA() !== null; if ($hasA) { ... }).
- However, it also caused nullsafe chain sub-expressions to leak into the
  broader scope: $x = $a?->b()?->c() would narrow $a->b() to non-null via
  conditional expressions, causing "nullsafe on non-nullable type" false
  positives on subsequent $a->b()?->... calls.
- Fix: walk the assigned expression's nullsafe chain and collect exprStrings
  of MethodCall/StaticCall var nodes. Skip these in conditional expression
  propagation. Variable and PropertyFetch vars are not affected (they were
  already in the allowed list before PR phpstan#5447 and their narrowing is correct).
- Also tested the NullsafePropertyFetch analogous case (already fixed by the
  same change) and verified bug-9455/bug-5207 still pass.
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