diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 22a895e03c..16a9c15f05 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4379,6 +4379,17 @@ private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $nodeFinder = new NodeFinder(); $expressionToInvalidateClass = get_class($exprToInvalidate); $found = $nodeFinder->findFirst([$expr], function (Node $node) use ($expressionToInvalidateClass, $exprStringToInvalidate): bool { + if ( + $exprStringToInvalidate === '$this' + && $node instanceof Name + && ( + in_array($node->toLowerString(), ['self', 'static', 'parent'], true) + || ($this->getClassReflection() !== null && $this->getClassReflection()->is($this->resolveName($node))) + ) + ) { + return true; + } + if (!$node instanceof $expressionToInvalidateClass) { return false; } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 756ea8db31..b43f0298cc 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2597,6 +2597,13 @@ static function (): void { $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); } + if ( + $parametersAcceptor instanceof ClosureType && count($parametersAcceptor->getImpurePoints()) > 0 + && $scope->isInClass() + ) { + $scope = $scope->invalidateExpression(new Variable('this'), true); + } + if ( $functionReflection !== null && in_array($functionReflection->getName(), ['json_encode', 'json_decode'], true) @@ -3022,13 +3029,13 @@ static function (): void { if ( $methodReflection !== null - && !$methodReflection->isStatic() && ( $methodReflection->hasSideEffects()->yes() - || $methodReflection->getName() === '__construct' + || ( + !$methodReflection->isStatic() + && $methodReflection->getName() === '__construct' + ) ) - && $scopeFunction instanceof MethodReflection - && !$scopeFunction->isStatic() && $scope->isInClass() && $scope->getClassReflection()->is($methodReflection->getDeclaringClass()->getName()) ) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php b/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php index d294016ec5..33f8a11e26 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php @@ -72,8 +72,8 @@ public function __construct() assertNativeType('int', self::$i); $this->impureCall(); - assertType('int', self::$i); // should be float|int - assertNativeType('int', self::$i); // should be float|int + assertType('float|int', self::$i); + assertNativeType('float|int', self::$i); } public function doFoo(): void { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12902.php b/tests/PHPStan/Analyser/nsrt/bug-12902.php index cbdc816074..2330c0c130 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12902.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12902.php @@ -72,8 +72,8 @@ public function __construct() assertNativeType('int', self::$i); $this->impureCall(); - assertType('int', self::$i); // should be float|int - assertNativeType('int', self::$i); // should be float|int + assertType('float|int', self::$i); + assertNativeType('float|int', self::$i); } public function doFoo(): void { @@ -85,6 +85,33 @@ public function doFoo(): void { public function impureCall(): void {} } +class BaseClass +{ + static protected int|float $i; +} + +class UsesBaseClass extends BaseClass +{ + public function __construct() + { + parent::$i = getInt(); + assertType('int', parent::$i); + assertNativeType('int', parent::$i); + + $this->impureCall(); + assertType('float|int', parent::$i); + assertNativeType('float|int', parent::$i); + } + + public function doFoo(): void { + assertType('float|int', parent::$i); + assertNativeType('float|int', parent::$i); + } + + /** @phpstan-impure */ + public function impureCall(): void {} +} + function getInt(): int { return 1; } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 0198587347..941829a685 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -930,4 +930,9 @@ public function testBug12593(): void $this->analyse([__DIR__ . '/data/bug-12593.php'], []); } + public function testBug3747(): void + { + $this->analyse([__DIR__ . '/data/bug-3747.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-3747.php b/tests/PHPStan/Rules/Arrays/data/bug-3747.php new file mode 100644 index 0000000000..d1dea20e05 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-3747.php @@ -0,0 +1,27 @@ + $x */ + private static array $x; + + public function y(): void { + + self::$x = []; + + $this->z(); + + echo self::$x['foo']; + + } + + private function z(): void { + self::$x['foo'] = 'bar'; + } + +} + +$x = new X(); +$x->y(); diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index 1f03a122bd..25e362f6cc 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -173,4 +173,16 @@ public function testBug4912(): void $this->analyse([__DIR__ . '/data/bug-4912.php'], []); } + public function testBug4864(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4864.php'], []); + } + + public function testBug8926(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8926.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 4c72f04c61..68cd2cc059 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1011,4 +1011,9 @@ public function testBug12748(): void $this->analyse([__DIR__ . '/data/bug-12748.php'], []); } + public function testBug11019(): void + { + $this->analyse([__DIR__ . '/data/bug-11019.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-11019.php b/tests/PHPStan/Rules/Comparison/data/bug-11019.php new file mode 100644 index 0000000000..c6a64cec05 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-11019.php @@ -0,0 +1,21 @@ +reset(); + assert(static::$a === 1); + $this->reset(); + assert(static::$a === 1); + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-4864.php b/tests/PHPStan/Rules/Comparison/data/bug-4864.php new file mode 100644 index 0000000000..288e19c21f --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-4864.php @@ -0,0 +1,25 @@ +isHandled = false; + $this->value = null; + + (function () { + $this->isHandled = true; + $this->value = 'value'; + })(); + + if ($this->isHandled) { + $f($this->value); + } + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8926.php b/tests/PHPStan/Rules/Comparison/data/bug-8926.php new file mode 100644 index 0000000000..c5d92bd1e0 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-8926.php @@ -0,0 +1,32 @@ +test = false; + (function($arr) { + $this->test = count($arr) == 1; + })($arr); + + + if ($this->test) { + echo "...\n"; + } + } +} diff --git a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php index b954794c6e..5c308ea5ad 100644 --- a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php @@ -55,4 +55,19 @@ public function testBug6922b(): void $this->analyse([__DIR__ . '/data/bug-6922b.php'], []); } + public function testBug8523(): void + { + $this->analyse([__DIR__ . '/data/bug-8523.php'], []); + } + + public function testBug8523b(): void + { + $this->analyse([__DIR__ . '/data/bug-8523b.php'], []); + } + + public function testBug8523c(): void + { + $this->analyse([__DIR__ . '/data/bug-8523c.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 1c62efa0c0..bc3a1b1fae 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1232,4 +1232,18 @@ public function testBug1O580(): void ]); } + public function testBug4443(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/bug-4443.php'], [ + [ + 'Method Bug4443\HelloWorld::getArray() should return array but returns array|null.', + 22, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4443.php b/tests/PHPStan/Rules/Methods/data/bug-4443.php new file mode 100644 index 0000000000..9f7ff6a28e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4443.php @@ -0,0 +1,26 @@ + */ + private static ?array $arr = null; + + private static function setup(): void + { + self::$arr = null; + } + + /** @return array */ + public static function getArray(): array + { + if (self::$arr === null) { + self::$arr = []; + self::setup(); + } + return self::$arr; + } +} + +HelloWorld::getArray(); diff --git a/tests/PHPStan/Rules/Methods/data/bug-8523.php b/tests/PHPStan/Rules/Methods/data/bug-8523.php new file mode 100644 index 0000000000..0cc8b3ad0a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-8523.php @@ -0,0 +1,36 @@ +foo(); + } + + public function bar(): void + { + self::$instance = null; + } + + public function baz(): void + { + self::$instance = new HelloWorld(); + + $this->bar(); + + self::$instance?->foo(); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-8523b.php b/tests/PHPStan/Rules/Methods/data/bug-8523b.php new file mode 100644 index 0000000000..a007fd2661 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-8523b.php @@ -0,0 +1,24 @@ +save(); + } +} + +(new HelloWorld())->save(); diff --git a/tests/PHPStan/Rules/Methods/data/bug-8523c.php b/tests/PHPStan/Rules/Methods/data/bug-8523c.php new file mode 100644 index 0000000000..ea88940446 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-8523c.php @@ -0,0 +1,26 @@ +save(); + } +} + +(new HelloWorld())->save();