From a634fcfa1a24cb9558d132ebf481c0e4f4b7ddc5 Mon Sep 17 00:00:00 2001 From: staabm <120441+staabm@users.noreply.github.com> Date: Mon, 13 Apr 2026 10:00:17 +0000 Subject: [PATCH] Fix phpstan/phpstan#9455: Type narrowing not propagated through stored 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 --- src/Analyser/ExprHandler/AssignHandler.php | 4 ++ tests/PHPStan/Analyser/nsrt/bug-9455.php | 52 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-9455.php diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 51995ac6657..3ea0196e4aa 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -865,6 +865,8 @@ private function processSureTypesForConditionalExpressionsAfterAssign(Scope $sco !$expr instanceof PropertyFetch && !$expr instanceof ArrayDimFetch && !$expr instanceof FuncCall + && !$expr instanceof MethodCall + && !$expr instanceof Expr\StaticCall ) { continue; } @@ -904,6 +906,8 @@ private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $ !$expr instanceof PropertyFetch && !$expr instanceof ArrayDimFetch && !$expr instanceof FuncCall + && !$expr instanceof MethodCall + && !$expr instanceof Expr\StaticCall ) { continue; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-9455.php b/tests/PHPStan/Analyser/nsrt/bug-9455.php new file mode 100644 index 00000000000..58c2d625234 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9455.php @@ -0,0 +1,52 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug9455; + +use function PHPStan\Testing\assertType; + +class A { + public function __construct(private int $id){} + + public function getId(): int { + return $this->id; + } +} + +class B { + public function __construct(private int $id, private ?A $a = null){} + + public function getId(): int { + return $this->id; + } + + public function getA(): ?A { + return $this->a; + } +} + +class HelloWorld +{ + public function testFails(): void + { + $a = new A(1); + $b = new B(1, $a); + + $hasA = $b->getA() !== null; + + if($hasA) { + assertType('Bug9455\A', $b->getA()); + } + } + + public function testSucceeds(): void + { + $a = new A(1); + $b = new B(1, $a); + + if($b->getA() !== null) { + assertType('Bug9455\A', $b->getA()); + } + } +}