diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index 4e34e0991d..6891c3bb29 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -217,7 +217,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex && $scope->isInClass() && $scope->getClassReflection()->is($methodReflection->getDeclaringClass()->getName()) ) { - $scope = $scope->invalidateExpression(new Variable('this'), true, $methodReflection->getDeclaringClass()); + $scope = $scope->invalidateExpression(new Variable('this'), true, $methodReflection->getDeclaringClass(), $methodReflection->isStatic()); } elseif ( $methodReflection !== null && $this->rememberPossiblyImpureFunctionValues diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 404097352b..fbd980fbae 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2857,7 +2857,7 @@ public function assignInitializedProperty(Type $fetchedOnType, string $propertyN return $this->assignExpression(new PropertyInitializationExpr($propertyName), new MixedType(), new MixedType()); } - public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null): self + public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null, bool $fromStaticCall = false): self { $expressionTypes = $this->expressionTypes; $nativeExpressionTypes = $this->nativeExpressionTypes; @@ -2866,7 +2866,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require foreach ($expressionTypes as $exprString => $exprTypeHolder) { $exprExpr = $exprTypeHolder->getExpr(); - if (!$this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $exprExpr, $exprString, $requireMoreCharacters, $invalidatingClass)) { + if (!$this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $exprExpr, $exprString, $requireMoreCharacters, $invalidatingClass, $fromStaticCall)) { continue; } @@ -2881,7 +2881,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require continue; } $firstExpr = $holders[array_key_first($holders)]->getTypeHolder()->getExpr(); - if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $firstExpr, $this->getNodeKey($firstExpr), false, $invalidatingClass)) { + if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $firstExpr, $this->getNodeKey($firstExpr), false, $invalidatingClass, $fromStaticCall)) { $invalidated = true; continue; } @@ -2890,7 +2890,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require $shouldKeep = true; $conditionalTypeHolders = $holder->getConditionExpressionTypeHolders(); foreach ($conditionalTypeHolders as $conditionalTypeHolderExprString => $conditionalTypeHolder) { - if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $conditionalTypeHolder->getExpr(), $conditionalTypeHolderExprString, false, $invalidatingClass)) { + if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $conditionalTypeHolder->getExpr(), $conditionalTypeHolderExprString, false, $invalidatingClass, $fromStaticCall)) { $invalidated = true; $shouldKeep = false; break; @@ -2944,7 +2944,7 @@ private function getIntertwinedRefRootVariableName(Expr $expr): ?string return null; } - private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $exprToInvalidate, Expr $expr, string $exprString, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null): bool + private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $exprToInvalidate, Expr $expr, string $exprString, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null, bool $fromStaticCall = false): bool { if ( $expr instanceof IntertwinedVariableByReferenceWithExpr @@ -3011,6 +3011,13 @@ private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr return false; } + if ( + $fromStaticCall + && $this->isThisInstancePropertyAccessChain($expr) + ) { + return false; + } + return true; } @@ -3030,6 +3037,21 @@ private function isPrivatePropertyOfDifferentClass(Expr $expr, ClassReflection $ return false; } + private function isThisInstancePropertyAccessChain(Expr $expr): bool + { + if ($expr instanceof Variable && is_string($expr->name) && $expr->name === 'this') { + return true; + } + if ($expr instanceof PropertyFetch) { + return $this->isThisInstancePropertyAccessChain($expr->var); + } + if ($expr instanceof Expr\ArrayDimFetch) { + return $this->isThisInstancePropertyAccessChain($expr->var); + } + + return false; + } + private function invalidateMethodsOnExpression(Expr $expressionToInvalidate): self { $exprStringToInvalidate = null; diff --git a/tests/PHPStan/Analyser/nsrt/bug-13735.php b/tests/PHPStan/Analyser/nsrt/bug-13735.php new file mode 100644 index 0000000000..690d8a2c37 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13735.php @@ -0,0 +1,31 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug13735; + +use function PHPStan\Testing\assertType; + +class Bug13735Test +{ + private ?Foo $foo = null; + + public function testFoo(): void + { + $this->foo = new Foo(); + assertType('Bug13735\Foo', $this->foo); + self::assertTrue(true); + assertType('Bug13735\Foo', $this->foo); + } + + public static function assertTrue(mixed $condition, string $message = ''): void + { + + } +} + +class Foo { + public function doSomething(): bool { + return true; + } +}