diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index e8e0152c6a1..76afb8bdd54 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -17,3 +17,4 @@ parameters: assignToByRefForeachExpr: true curlSetOptArrayTypes: true checkDateIntervalConstructor: true + reportMethodPurityOverride: true diff --git a/conf/config.neon b/conf/config.neon index fcbb79b1d81..1628b4b83a0 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -44,6 +44,7 @@ parameters: assignToByRefForeachExpr: false curlSetOptArrayTypes: false checkDateIntervalConstructor: false + reportMethodPurityOverride: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 7cff420cc3f..15e6e02c215 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -46,6 +46,7 @@ parametersSchema: assignToByRefForeachExpr: bool() curlSetOptArrayTypes: bool() checkDateIntervalConstructor: bool() + reportMethodPurityOverride: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/conf/services.neon b/conf/services.neon index c5c1d4019ca..948baee6a76 100644 --- a/conf/services.neon +++ b/conf/services.neon @@ -98,6 +98,7 @@ services: arguments: reportMaybes: %reportMaybesInMethodSignatures% reportStatic: %reportStaticMethodSignatures% + reportMethodPurityOverride: %featureToggles.reportMethodPurityOverride% phpstanDiagnoseExtension: class: PHPStan\Diagnose\PHPStanDiagnoseExtension diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index 267b6ae5189..3c476d14b87 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -41,6 +41,7 @@ public function __construct( private ParentMethodHelper $parentMethodHelper, private bool $reportMaybes, private bool $reportStatic, + private bool $reportMethodPurityOverride, ) { } @@ -66,6 +67,16 @@ public function processNode(Node $node, Scope $scope): array $errors = []; $declaringClass = $method->getDeclaringClass(); foreach ($this->parentMethodHelper->collectParentMethods($methodName, $method->getDeclaringClass()) as [$parentMethod, $parentMethodDeclaringClass]) { + if ($this->reportMethodPurityOverride && $method->isPure()->no() && $parentMethod->isPure()->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Impure method %s::%s() overrides pure method %s::%s().', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $parentMethodDeclaringClass->getDisplayName(), + $parentMethod->getName(), + ))->identifier('method.impure')->build(); + } + $parentVariants = $parentMethod->getVariants(); if (count($parentVariants) !== 1) { continue; diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 39efdf4aeb7..b016afd0a48 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -19,6 +19,8 @@ class MethodSignatureRuleTest extends RuleTestCase private bool $reportStatic; + private bool $reportMethodPurityOverride = false; + protected function getRule(): Rule { $phpVersion = new PhpVersion(PHP_VERSION_ID); @@ -27,7 +29,7 @@ protected function getRule(): Rule return new OverridingMethodRule( $phpVersion, - new MethodSignatureRule(new ParentMethodHelper($phpClassReflectionExtension), $this->reportMaybes, $this->reportStatic), + new MethodSignatureRule(new ParentMethodHelper($phpClassReflectionExtension), $this->reportMaybes, $this->reportStatic, $this->reportMethodPurityOverride), true, new MethodParameterComparisonHelper($phpVersion), new MethodVisibilityComparisonHelper(), @@ -565,4 +567,67 @@ public function testBug14320(): void $this->analyse([__DIR__ . '/data/bug-14320.php'], []); } + public function testBug14563(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->reportMethodPurityOverride = true; + $this->analyse([__DIR__ . '/data/bug-14563.php'], [ + [ + 'Impure method Bug14563\ChildImpureOverridesPure::pure() overrides pure method Bug14563\Foo::pure().', + 31, + ], + [ + 'Impure method Bug14563\ImpureImplementation::pureMethod() overrides pure method Bug14563\PureInterface::pureMethod().', + 93, + ], + [ + 'Impure method Bug14563\ImpureChildOfAllMethodsPure::method() overrides pure method Bug14563\AllMethodsPureParent::method().', + 126, + ], + [ + 'Impure method Bug14563\ChildImpureOverridesPureExtended::pure() overrides pure method Bug14563\Foo::pure().', + 137, + ], + [ + 'Impure method Bug14563\GrandchildImpureOverridesPure::pure() overrides pure method Bug14563\ChildPureOverridesPure::pure().', + 148, + ], + [ + 'Impure method Bug14563\ImpureMultipleInterfaces::sharedMethod() overrides pure method Bug14563\PureInterfaceA::sharedMethod().', + 186, + ], + [ + 'Impure method Bug14563\ImpureMultipleInterfaces::sharedMethod() overrides pure method Bug14563\PureInterfaceB::sharedMethod().', + 186, + ], + [ + 'Impure method Bug14563\ChildImpureOverridesPureVoid::pureVoid() overrides pure method Bug14563\VoidFoo::pureVoid().', + 211, + ], + [ + 'Impure method Bug14563\StaticChildImpureOverridesPure::pure() overrides pure method Bug14563\StaticFoo::pure().', + 284, + ], + [ + 'Impure method Bug14563\StaticImpureImplementation::pureMethod() overrides pure method Bug14563\StaticPureInterface::pureMethod().', + 335, + ], + ]); + } + + #[RequiresPhp('>= 8.0.0')] + public function testBug14563Trait(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->reportMethodPurityOverride = true; + $this->analyse([__DIR__ . '/data/bug-14563-trait.php'], [ + [ + 'Impure method Bug14563Trait\ImpureTraitUser::pureTraitMethod() overrides pure method Bug14563Trait\PureTrait::pureTraitMethod().', + 19, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 8f2b1d488ff..b8c2954417b 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -30,7 +30,7 @@ protected function getRule(): Rule return new OverridingMethodRule( $phpVersion, - new MethodSignatureRule(new ParentMethodHelper($phpClassReflectionExtension), true, true), + new MethodSignatureRule(new ParentMethodHelper($phpClassReflectionExtension), true, true, false), false, new MethodParameterComparisonHelper($phpVersion), new MethodVisibilityComparisonHelper(), diff --git a/tests/PHPStan/Rules/Methods/data/bug-14563-trait.php b/tests/PHPStan/Rules/Methods/data/bug-14563-trait.php new file mode 100644 index 00000000000..a9000b7e6e5 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14563-trait.php @@ -0,0 +1,24 @@ +