Skip to content

Commit 2112eee

Browse files
phpstan-botstaabmclaude
authored
Fix phpstan/phpstan#9455: Bug: False report Cannot call method getId() on A|null (#5447)
Co-authored-by: staabm <120441+staabm@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 436f8f4 commit 2112eee

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

src/Analyser/ExprHandler/AssignHandler.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,8 @@ private function processSureTypesForConditionalExpressionsAfterAssign(Scope $sco
865865
!$expr instanceof PropertyFetch
866866
&& !$expr instanceof ArrayDimFetch
867867
&& !$expr instanceof FuncCall
868+
&& !$expr instanceof MethodCall
869+
&& !$expr instanceof Expr\StaticCall
868870
) {
869871
continue;
870872
}
@@ -904,6 +906,8 @@ private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $
904906
!$expr instanceof PropertyFetch
905907
&& !$expr instanceof ArrayDimFetch
906908
&& !$expr instanceof FuncCall
909+
&& !$expr instanceof MethodCall
910+
&& !$expr instanceof Expr\StaticCall
907911
) {
908912
continue;
909913
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug5207;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
abstract class HelloWorld {
10+
abstract public function getChild(): ?HelloWorld;
11+
12+
public function sayHello(): void {
13+
$foo = null !== $this->getChild();
14+
if ($foo) {
15+
assertType('Bug5207\HelloWorld', $this->getChild());
16+
}
17+
}
18+
19+
public function sayHelloInline(): void {
20+
if (null !== $this->getChild()) {
21+
assertType('Bug5207\HelloWorld', $this->getChild());
22+
}
23+
}
24+
}
25+
26+
abstract class StaticWorld {
27+
abstract public static function getChild(): ?StaticWorld;
28+
29+
public static function sayHello(): void {
30+
$foo = null !== static::getChild();
31+
if ($foo) {
32+
assertType('Bug5207\StaticWorld', static::getChild());
33+
}
34+
}
35+
36+
public static function sayHelloInline(): void {
37+
if (null !== static::getChild()) {
38+
assertType('Bug5207\StaticWorld', static::getChild());
39+
}
40+
}
41+
}
42+
43+
abstract class ImpureStaticWorld {
44+
/**
45+
* @phpstan-impure
46+
*/
47+
abstract public static function getChild(): ?ImpureStaticWorld;
48+
49+
public static function sayHello(): void {
50+
$foo = null !== static::getChild();
51+
if ($foo) {
52+
assertType('Bug5207\ImpureStaticWorld|null', static::getChild());
53+
}
54+
}
55+
56+
public static function sayHelloInline(): void {
57+
if (null !== static::getChild()) {
58+
assertType('Bug5207\ImpureStaticWorld|null', static::getChild());
59+
}
60+
}
61+
}
62+
63+
abstract class ImpureWorld {
64+
/**
65+
* @phpstan-impure
66+
*/
67+
abstract public function getChild(): ?ImpureWorld;
68+
69+
public function sayHello(): void {
70+
$foo = null !== $this->getChild();
71+
if ($foo) {
72+
assertType('Bug5207\ImpureWorld|null', $this->getChild());
73+
}
74+
}
75+
76+
public function sayHelloInline(): void {
77+
if (null !== $this->getChild()) {
78+
assertType('Bug5207\ImpureWorld|null', $this->getChild());
79+
}
80+
}
81+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug9455;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class A {
10+
public function __construct(private int $id){}
11+
12+
public function getId(): int {
13+
return $this->id;
14+
}
15+
}
16+
17+
class B {
18+
public function __construct(private int $id, private ?A $a = null){}
19+
20+
public function getId(): int {
21+
return $this->id;
22+
}
23+
24+
public function getA(): ?A {
25+
return $this->a;
26+
}
27+
}
28+
29+
class HelloWorld
30+
{
31+
public function testFails(): void
32+
{
33+
$a = new A(1);
34+
$b = new B(1, $a);
35+
36+
$hasA = $b->getA() !== null;
37+
38+
if($hasA) {
39+
assertType('Bug9455\A', $b->getA());
40+
}
41+
}
42+
43+
public function testSucceeds(): void
44+
{
45+
$a = new A(1);
46+
$b = new B(1, $a);
47+
48+
if($b->getA() !== null) {
49+
assertType('Bug9455\A', $b->getA());
50+
}
51+
}
52+
}
53+
54+
class C {
55+
/**
56+
* @phpstan-impure
57+
*/
58+
public function getA(): ?A {
59+
return rand(0, 1) ? new A(1) : null;
60+
}
61+
}
62+
63+
class ImpureTest
64+
{
65+
public function testImpureMethodNotNarrowed(): void
66+
{
67+
$c = new C();
68+
69+
$hasA = $c->getA() !== null;
70+
71+
if($hasA) {
72+
assertType('Bug9455\A|null', $c->getA());
73+
}
74+
}
75+
76+
public function testImpureMethodInline(): void
77+
{
78+
$c = new C();
79+
80+
if($c->getA() !== null) {
81+
assertType('Bug9455\A|null', $c->getA());
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)