Skip to content

Commit 16049a0

Browse files
committed
Don't forget property-fetch expressions on $this after static method call
1 parent b4b3e73 commit 16049a0

File tree

5 files changed

+110
-3
lines changed

5 files changed

+110
-3
lines changed

src/Analyser/MutatingScope.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use PhpParser\Node\Stmt\Function_;
3333
use PhpParser\NodeFinder;
3434
use PHPStan\Node\ExecutionEndNode;
35+
use PHPStan\Node\Expr\AfterStaticMethodCall;
3536
use PHPStan\Node\Expr\AlwaysRememberedExpr;
3637
use PHPStan\Node\Expr\ExistingArrayDimFetch;
3738
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
@@ -4561,9 +4562,9 @@ private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr
45614562

45624563
$nodeFinder = new NodeFinder();
45634564
$expressionToInvalidateClass = get_class($exprToInvalidate);
4564-
$found = $nodeFinder->findFirst([$expr], function (Node $node) use ($expressionToInvalidateClass, $exprStringToInvalidate): bool {
4565+
$found = $nodeFinder->findFirst([$expr], function (Node $node) use ($expressionToInvalidateClass, $exprToInvalidate, $exprStringToInvalidate): bool {
45654566
if (
4566-
$exprStringToInvalidate === '$this'
4567+
($exprStringToInvalidate === '$this' || $exprToInvalidate instanceof AfterStaticMethodCall)
45674568
&& $node instanceof Name
45684569
&& (
45694570
in_array($node->toLowerString(), ['self', 'static', 'parent'], true)
@@ -4573,6 +4574,15 @@ private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr
45734574
return true;
45744575
}
45754576

4577+
if (
4578+
$exprToInvalidate instanceof AfterStaticMethodCall
4579+
&& $node instanceof MethodCall
4580+
&& $node->var instanceof Variable
4581+
&& $node->var->name === 'this'
4582+
) {
4583+
return true;
4584+
}
4585+
45764586
if (!$node instanceof $expressionToInvalidateClass) {
45774587
return false;
45784588
}

src/Analyser/NodeScopeResolver.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
use PHPStan\Node\ClosureReturnStatementsNode;
8585
use PHPStan\Node\DoWhileLoopConditionNode;
8686
use PHPStan\Node\ExecutionEndNode;
87+
use PHPStan\Node\Expr\AfterStaticMethodCall;
8788
use PHPStan\Node\Expr\AlwaysRememberedExpr;
8889
use PHPStan\Node\Expr\ExistingArrayDimFetch;
8990
use PHPStan\Node\Expr\ForeachValueByRefExpr;
@@ -3117,7 +3118,11 @@ static function (): void {
31173118
&& $scope->isInClass()
31183119
&& $scope->getClassReflection()->is($methodReflection->getDeclaringClass()->getName())
31193120
) {
3120-
$scope = $scope->invalidateExpression(new Variable('this'), true);
3121+
if ($methodReflection->isStatic()) {
3122+
$scope = $scope->invalidateExpression(new AfterStaticMethodCall(), true);
3123+
} else {
3124+
$scope = $scope->invalidateExpression(new Variable('this'), true);
3125+
}
31213126
}
31223127

31233128
if (
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Node\Expr;
4+
5+
use Override;
6+
use PhpParser\Node\Expr;
7+
use PHPStan\Node\VirtualNode;
8+
9+
final class AfterStaticMethodCall extends Expr implements VirtualNode
10+
{
11+
12+
#[Override]
13+
public function getType(): string
14+
{
15+
return 'PHPStan_Node_AfterStaticMethodCall';
16+
}
17+
18+
/**
19+
* @return string[]
20+
*/
21+
#[Override]
22+
public function getSubNodeNames(): array
23+
{
24+
return [];
25+
}
26+
27+
}

src/Node/Printer/Printer.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\PrettyPrinter\Standard;
66
use PHPStan\DependencyInjection\AutowiredService;
7+
use PHPStan\Node\Expr\AfterStaticMethodCall;
78
use PHPStan\Node\Expr\AlwaysRememberedExpr;
89
use PHPStan\Node\Expr\ExistingArrayDimFetch;
910
use PHPStan\Node\Expr\ForeachValueByRefExpr;
@@ -29,6 +30,11 @@
2930
final class Printer extends Standard
3031
{
3132

33+
protected function pPHPStan_Node_AfterStaticMethodCall(AfterStaticMethodCall $expr): string // phpcs:ignore
34+
{
35+
return sprintf('__phpstanAfterStaticMethodCall');
36+
}
37+
3238
protected function pPHPStan_Node_TypeExpr(TypeExpr $expr): string // phpcs:ignore
3339
{
3440
return sprintf('__phpstanType(%s)', $expr->getExprType()->describe(VerbosityLevel::precise()));
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Bug13735;
4+
5+
use function PHPStan\Testing\assertType;
6+
use function rand;
7+
8+
class Bug13735Test
9+
{
10+
private ?Foo $foo = null;
11+
public static ?Foo $staticFoo = null;
12+
13+
public function testFoo(): void
14+
{
15+
$this->foo = new Foo();
16+
assertType('Bug13735\Foo', $this->foo);
17+
self::assertTrue(true);
18+
assertType('Bug13735\Foo', $this->foo);
19+
20+
assertType('bool', $this->foo->aBool);
21+
$this->foo->aBool = true;
22+
assertType('true', $this->foo->aBool);
23+
self::assertTrue(true);
24+
assertType('true', $this->foo->aBool);
25+
}
26+
27+
public function testCallFoo(): void
28+
{
29+
if ($this->getFoo() === null) {
30+
return;
31+
}
32+
33+
// the getFoo() method could reference a static property in its body,
34+
// so self::assertTrue() still needs to invalidate $this->getFoo().
35+
assertType('Bug13735\Foo', $this->getFoo());
36+
self::assertTrue(true);
37+
assertType('Bug13735\Foo|null', $this->getFoo());
38+
}
39+
40+
public function testStaticFoo(): void
41+
{
42+
self::$staticFoo = new Foo();
43+
assertType('Bug13735\Foo', self::$staticFoo);
44+
self::assertTrue(true);
45+
assertType('Bug13735\Foo|null', self::$staticFoo);
46+
}
47+
48+
public static function assertTrue(mixed $condition, string $message = ''): void
49+
{
50+
}
51+
52+
public function getFoo(): ?Foo {
53+
return rand(0 ,1) ? null : new Foo();
54+
}
55+
}
56+
57+
class Foo {
58+
public bool $aBool = false;
59+
}

0 commit comments

Comments
 (0)