Skip to content

Commit 17b5748

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Fix phpstan/phpstan#13735: Don't forget $this property types after static method call
- Static methods cannot modify instance properties on $this, so property fetch expressions on $this should not be invalidated by static calls - Added fromStaticCall parameter to invalidateExpression/shouldInvalidateExpression - Added isThisInstancePropertyAccessChain helper to detect $this->prop chains - Method calls on $this (e.g. $this->getFoo()) are still invalidated since the method body could reference static state - parent::__construct() correctly still invalidates $this properties since the method is not static
1 parent c36922b commit 17b5748

File tree

3 files changed

+59
-6
lines changed

3 files changed

+59
-6
lines changed

src/Analyser/ExprHandler/StaticCallHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
217217
&& $scope->isInClass()
218218
&& $scope->getClassReflection()->is($methodReflection->getDeclaringClass()->getName())
219219
) {
220-
$scope = $scope->invalidateExpression(new Variable('this'), true, $methodReflection->getDeclaringClass());
220+
$scope = $scope->invalidateExpression(new Variable('this'), true, $methodReflection->getDeclaringClass(), $methodReflection->isStatic());
221221
} elseif (
222222
$methodReflection !== null
223223
&& $this->rememberPossiblyImpureFunctionValues

src/Analyser/MutatingScope.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,7 +2857,7 @@ public function assignInitializedProperty(Type $fetchedOnType, string $propertyN
28572857
return $this->assignExpression(new PropertyInitializationExpr($propertyName), new MixedType(), new MixedType());
28582858
}
28592859

2860-
public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null): self
2860+
public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null, bool $fromStaticCall = false): self
28612861
{
28622862
$expressionTypes = $this->expressionTypes;
28632863
$nativeExpressionTypes = $this->nativeExpressionTypes;
@@ -2866,7 +2866,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
28662866

28672867
foreach ($expressionTypes as $exprString => $exprTypeHolder) {
28682868
$exprExpr = $exprTypeHolder->getExpr();
2869-
if (!$this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $exprExpr, $exprString, $requireMoreCharacters, $invalidatingClass)) {
2869+
if (!$this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $exprExpr, $exprString, $requireMoreCharacters, $invalidatingClass, $fromStaticCall)) {
28702870
continue;
28712871
}
28722872

@@ -2881,7 +2881,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
28812881
continue;
28822882
}
28832883
$firstExpr = $holders[array_key_first($holders)]->getTypeHolder()->getExpr();
2884-
if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $firstExpr, $this->getNodeKey($firstExpr), false, $invalidatingClass)) {
2884+
if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $firstExpr, $this->getNodeKey($firstExpr), false, $invalidatingClass, $fromStaticCall)) {
28852885
$invalidated = true;
28862886
continue;
28872887
}
@@ -2890,7 +2890,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
28902890
$shouldKeep = true;
28912891
$conditionalTypeHolders = $holder->getConditionExpressionTypeHolders();
28922892
foreach ($conditionalTypeHolders as $conditionalTypeHolderExprString => $conditionalTypeHolder) {
2893-
if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $conditionalTypeHolder->getExpr(), $conditionalTypeHolderExprString, false, $invalidatingClass)) {
2893+
if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $conditionalTypeHolder->getExpr(), $conditionalTypeHolderExprString, false, $invalidatingClass, $fromStaticCall)) {
28942894
$invalidated = true;
28952895
$shouldKeep = false;
28962896
break;
@@ -2944,7 +2944,7 @@ private function getIntertwinedRefRootVariableName(Expr $expr): ?string
29442944
return null;
29452945
}
29462946

2947-
private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $exprToInvalidate, Expr $expr, string $exprString, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null): bool
2947+
private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $exprToInvalidate, Expr $expr, string $exprString, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null, bool $fromStaticCall = false): bool
29482948
{
29492949
if (
29502950
$expr instanceof IntertwinedVariableByReferenceWithExpr
@@ -3011,6 +3011,13 @@ private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr
30113011
return false;
30123012
}
30133013

3014+
if (
3015+
$fromStaticCall
3016+
&& $this->isThisInstancePropertyAccessChain($expr)
3017+
) {
3018+
return false;
3019+
}
3020+
30143021
return true;
30153022
}
30163023

@@ -3030,6 +3037,21 @@ private function isPrivatePropertyOfDifferentClass(Expr $expr, ClassReflection $
30303037
return false;
30313038
}
30323039

3040+
private function isThisInstancePropertyAccessChain(Expr $expr): bool
3041+
{
3042+
if ($expr instanceof Variable && is_string($expr->name) && $expr->name === 'this') {
3043+
return true;
3044+
}
3045+
if ($expr instanceof PropertyFetch) {
3046+
return $this->isThisInstancePropertyAccessChain($expr->var);
3047+
}
3048+
if ($expr instanceof Expr\ArrayDimFetch) {
3049+
return $this->isThisInstancePropertyAccessChain($expr->var);
3050+
}
3051+
3052+
return false;
3053+
}
3054+
30333055
private function invalidateMethodsOnExpression(Expr $expressionToInvalidate): self
30343056
{
30353057
$exprStringToInvalidate = null;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug13735;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class Bug13735Test
10+
{
11+
private ?Foo $foo = 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+
21+
public static function assertTrue(mixed $condition, string $message = ''): void
22+
{
23+
24+
}
25+
}
26+
27+
class Foo {
28+
public function doSomething(): bool {
29+
return true;
30+
}
31+
}

0 commit comments

Comments
 (0)