diff --git a/src/Rules/Pure/FunctionPurityCheck.php b/src/Rules/Pure/FunctionPurityCheck.php index ccc85a1c24..a799cd4351 100644 --- a/src/Rules/Pure/FunctionPurityCheck.php +++ b/src/Rules/Pure/FunctionPurityCheck.php @@ -92,6 +92,11 @@ public function check( count($throwPoints) === 0 && count($impurePoints) === 0 && count($functionReflection->getAsserts()->getAll()) === 0 + && ( + !$functionReflection instanceof ExtendedMethodReflection + || $functionReflection->isFinal()->yes() + || $functionReflection->getDeclaringClass()->isFinal() + ) ) { $errors[] = RuleErrorBuilder::message(sprintf( '%s is marked as impure but does not have any side effects.', diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index a483c6d580..c31874629d 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -220,4 +220,27 @@ public function testBug12224(): void ]); } + public function testBug12382(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12382.php'], [ + [ + 'Method Bug12382\FinalHelloWorld1::dummy() is marked as impure but does not have any side effects.', + 25, + ], + [ + 'Method Bug12382\FinalHelloWorld2::dummy() is marked as impure but does not have any side effects.', + 33, + ], + [ + 'Method Bug12382\FinalHelloWorld3::dummy() is marked as impure but does not have any side effects.', + 42, + ], + [ + 'Method Bug12382\FinalHelloWorld4::dummy() is marked as impure but does not have any side effects.', + 53, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Pure/data/bug-12382.php b/tests/PHPStan/Rules/Pure/data/bug-12382.php new file mode 100644 index 0000000000..7a66941da8 --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/bug-12382.php @@ -0,0 +1,56 @@ +prop++; + return $this; + } +} + +final class FinalHelloWorld1 +{ + /** @phpstan-impure */ + public function dummy() : self{ + return $this; + } +} + +class FinalHelloWorld2 +{ + /** @phpstan-impure */ + final public function dummy() : self{ + return $this; + } +} + +/** @final */ +class FinalHelloWorld3 +{ + /** @phpstan-impure */ + public function dummy() : self{ + return $this; + } +} + +class FinalHelloWorld4 +{ + /** + * @final + * @phpstan-impure + */ + public function dummy() : self{ + return $this; + } +} diff --git a/tests/PHPStan/Rules/Pure/data/pure-constructor.php b/tests/PHPStan/Rules/Pure/data/pure-constructor.php index 71045fd3ed..baa1f755cf 100644 --- a/tests/PHPStan/Rules/Pure/data/pure-constructor.php +++ b/tests/PHPStan/Rules/Pure/data/pure-constructor.php @@ -2,7 +2,7 @@ namespace PureConstructor; -class Foo +final class Foo { private string $prop; @@ -21,7 +21,7 @@ public function __construct( } -class Bar +final class Bar { private string $prop; @@ -37,7 +37,7 @@ public function __construct( } -class AssignOtherThanThis +final class AssignOtherThanThis { private int $i = 0; diff --git a/tests/PHPStan/Rules/Pure/data/pure-method.php b/tests/PHPStan/Rules/Pure/data/pure-method.php index efa83c9191..ba2ce7e3be 100644 --- a/tests/PHPStan/Rules/Pure/data/pure-method.php +++ b/tests/PHPStan/Rules/Pure/data/pure-method.php @@ -2,7 +2,7 @@ namespace PureMethod; -class Foo +final class Foo { /** @@ -92,7 +92,7 @@ public function doFoo5() } -class PureConstructor +final class PureConstructor { /** @@ -105,7 +105,7 @@ public function __construct() } -class ImpureConstructor +final class ImpureConstructor { /** @@ -118,7 +118,7 @@ public function __construct() } -class PossiblyImpureConstructor +final class PossiblyImpureConstructor { public function __construct() @@ -128,7 +128,7 @@ public function __construct() } -class TestConstructors +final class TestConstructors { /** @@ -144,7 +144,7 @@ public function doFoo(string $s) } -class ActuallyPure +final class ActuallyPure { /** @@ -175,7 +175,7 @@ public function impure(): int } -class ExtendingClass extends ToBeExtended +final class ExtendingClass extends ToBeExtended { public function pure(): int @@ -191,7 +191,7 @@ public function impure(): int } -class ClassWithVoidMethods +final class ClassWithVoidMethods { public function voidFunctionThatThrows(): void @@ -235,12 +235,12 @@ public function purePostGetAssign(array $post = [], array $get = []): int } -class NoMagicMethods +final class NoMagicMethods { } -class PureMagicMethods +final class PureMagicMethods { /** @@ -253,7 +253,7 @@ public function __toString(): string } -class MaybePureMagicMethods +final class MaybePureMagicMethods { public function __toString(): string @@ -263,7 +263,7 @@ public function __toString(): string } -class ImpureMagicMethods +final class ImpureMagicMethods { /** @@ -277,7 +277,7 @@ public function __toString(): string } -class TestMagicMethods +final class TestMagicMethods { /** @@ -298,12 +298,12 @@ public function doFoo( } -class NoConstructor +final class NoConstructor { } -class TestNoConstructor +final class TestNoConstructor { /** @@ -318,7 +318,7 @@ public function doFoo(): int } -class MaybeCallableFromUnion +final class MaybeCallableFromUnion { /** @@ -334,7 +334,7 @@ public function doFoo($p): int } -class VoidMethods +final class VoidMethods { private function doFoo(): void @@ -361,7 +361,7 @@ private function doBaz(): void } -class AssertingImpureVoidMethod +final class AssertingImpureVoidMethod { /** @@ -376,7 +376,7 @@ public function assertSth($value): void } -class StaticMethodAccessingStaticProperty +final class StaticMethodAccessingStaticProperty { /** @var int */ public static $a = 0; @@ -397,7 +397,7 @@ public static function getB(): int } } -class StaticMethodAssigningStaticProperty +final class StaticMethodAssigningStaticProperty { /** @var int */ public static $a = 0;