From 2aae123c4a22cda091e4caad05d645ee67371bb6 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 1/4] 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()); + } + } +} From 39403713838910a88509c4a0ae1b09212abb1469 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 18 Apr 2026 08:16:48 +0000 Subject: [PATCH 2/4] Add regression test for phpstan/phpstan#5207 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-5207.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-5207.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-5207.php b/tests/PHPStan/Analyser/nsrt/bug-5207.php new file mode 100644 index 00000000000..d3bdd1acc7e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-5207.php @@ -0,0 +1,24 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug5207; + +use function PHPStan\Testing\assertType; + +abstract class HelloWorld { + abstract public function getChild(): ?HelloWorld; + + public function sayHello(): void { + $foo = null !== $this->getChild(); + if ($foo) { + assertType('Bug5207\HelloWorld', $this->getChild()); + } + } + + public function sayHelloInline(): void { + if (null !== $this->getChild()) { + assertType('Bug5207\HelloWorld', $this->getChild()); + } + } +} From 656a5276e3f33c8e4e6cb479a53fba8f7dc26a51 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 18 Apr 2026 08:53:40 +0000 Subject: [PATCH 3/4] Add impure method tests to verify type narrowing is not applied 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 --- tests/PHPStan/Analyser/nsrt/bug-5207.php | 20 +++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-9455.php | 32 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-5207.php b/tests/PHPStan/Analyser/nsrt/bug-5207.php index d3bdd1acc7e..1729ff3df1d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-5207.php +++ b/tests/PHPStan/Analyser/nsrt/bug-5207.php @@ -22,3 +22,23 @@ public function sayHelloInline(): void { } } } + +abstract class ImpureWorld { + /** + * @phpstan-impure + */ + abstract public function getChild(): ?ImpureWorld; + + public function sayHello(): void { + $foo = null !== $this->getChild(); + if ($foo) { + assertType('Bug5207\ImpureWorld|null', $this->getChild()); + } + } + + public function sayHelloInline(): void { + if (null !== $this->getChild()) { + assertType('Bug5207\ImpureWorld|null', $this->getChild()); + } + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-9455.php b/tests/PHPStan/Analyser/nsrt/bug-9455.php index 58c2d625234..2a0fe4f23fa 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9455.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9455.php @@ -50,3 +50,35 @@ public function testSucceeds(): void } } } + +class C { + /** + * @phpstan-impure + */ + public function getA(): ?A { + return rand(0, 1) ? new A(1) : null; + } +} + +class ImpureTest +{ + public function testImpureMethodNotNarrowed(): void + { + $c = new C(); + + $hasA = $c->getA() !== null; + + if($hasA) { + assertType('Bug9455\A|null', $c->getA()); + } + } + + public function testImpureMethodInline(): void + { + $c = new C(); + + if($c->getA() !== null) { + assertType('Bug9455\A|null', $c->getA()); + } + } +} From 62cf3748fc8aed7dc5b330a79d3a9e70cb62fb2f Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 18 Apr 2026 09:14:46 +0000 Subject: [PATCH 4/4] Add static method tests to bug-5207 regression test Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-5207.php | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-5207.php b/tests/PHPStan/Analyser/nsrt/bug-5207.php index 1729ff3df1d..87f1f8157b5 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-5207.php +++ b/tests/PHPStan/Analyser/nsrt/bug-5207.php @@ -23,6 +23,43 @@ public function sayHelloInline(): void { } } +abstract class StaticWorld { + abstract public static function getChild(): ?StaticWorld; + + public static function sayHello(): void { + $foo = null !== static::getChild(); + if ($foo) { + assertType('Bug5207\StaticWorld', static::getChild()); + } + } + + public static function sayHelloInline(): void { + if (null !== static::getChild()) { + assertType('Bug5207\StaticWorld', static::getChild()); + } + } +} + +abstract class ImpureStaticWorld { + /** + * @phpstan-impure + */ + abstract public static function getChild(): ?ImpureStaticWorld; + + public static function sayHello(): void { + $foo = null !== static::getChild(); + if ($foo) { + assertType('Bug5207\ImpureStaticWorld|null', static::getChild()); + } + } + + public static function sayHelloInline(): void { + if (null !== static::getChild()) { + assertType('Bug5207\ImpureStaticWorld|null', static::getChild()); + } + } +} + abstract class ImpureWorld { /** * @phpstan-impure