diff --git a/conf/config.neon b/conf/config.neon index 664145f443..1385e6ba94 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -54,6 +54,7 @@ parameters: checkPhpDocMethodSignatures: false checkExtraArguments: false checkMissingTypehints: false + checkTooWideParameterOutInProtectedAndPublicMethods: false checkTooWideReturnTypesInProtectedAndPublicMethods: false checkUninitializedProperties: false checkDynamicProperties: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 76d8e86b40..04cc77b836 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -58,6 +58,7 @@ parametersSchema: checkPhpDocMethodSignatures: bool() checkExtraArguments: bool() checkMissingTypehints: bool() + checkTooWideParameterOutInProtectedAndPublicMethods: bool() checkTooWideReturnTypesInProtectedAndPublicMethods: bool() checkUninitializedProperties: bool() checkDynamicProperties: bool() diff --git a/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php b/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php index 6b580f0888..791ac033de 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\RegisteredRule; use PHPStan\Node\MethodReturnStatementsNode; use PHPStan\Rules\Rule; @@ -18,6 +19,8 @@ final class TooWideMethodParameterOutTypeRule implements Rule public function __construct( private TooWideParameterOutTypeCheck $check, + #[AutowiredParameter(ref: '%checkTooWideParameterOutInProtectedAndPublicMethods%')] + private bool $checkProtectedAndPublicMethods, ) { } @@ -31,6 +34,14 @@ public function processNode(Node $node, Scope $scope): array { $inMethod = $node->getMethodReflection(); + if (!$inMethod->isPrivate()) { + if (!$inMethod->getDeclaringClass()->isFinal() && !$inMethod->isFinal()->yes()) { + if (!$this->checkProtectedAndPublicMethods) { + return []; + } + } + } + return $this->check->check( $node->getExecutionEnds(), $node->getReturnStatements(), diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRuleTest.php index cc2ef739c8..1897737bb1 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRuleTest.php @@ -11,9 +11,11 @@ class TooWideMethodParameterOutTypeRuleTest extends RuleTestCase { + private bool $checkProtectedAndPublicMethods = true; + protected function getRule(): TRule { - return new TooWideMethodParameterOutTypeRule(new TooWideParameterOutTypeCheck()); + return new TooWideMethodParameterOutTypeRule(new TooWideParameterOutTypeCheck(), $this->checkProtectedAndPublicMethods); } public function testRule(): void @@ -37,6 +39,69 @@ public function testRule(): void 'Method TooWideMethodParameterOut\Foo::bug10699() never assigns 20 to &$out so it can be removed from the @param-out type.', 37, ], + [ + 'Method TooWideMethodParameterOut\Foo::finalDoBaz() never assigns null to &$p so it can be removed from the @param-out type.', + 45, + ], + [ + 'Method TooWideMethodParameterOut\Foo::doBazProtected() never assigns null to &$p so it can be removed from the @param-out type.', + 53, + ], + [ + 'Method TooWideMethodParameterOut\Foo::doBazPrivate() never assigns null to &$p so it can be removed from the @param-out type.', + 61, + ], + [ + 'Method TooWideMethodParameterOut\FinalFoo::doBar() never assigns null to &$p so it can be removed from the by-ref type.', + 76, + 'You can narrow the parameter out type with @param-out PHPDoc tag.', + ], + [ + 'Method TooWideMethodParameterOut\FinalFoo::doBaz() never assigns null to &$p so it can be removed from the @param-out type.', + 84, + ], + [ + 'Method TooWideMethodParameterOut\FinalFoo::doLorem() never assigns null to &$p so it can be removed from the by-ref type.', + 89, + 'You can narrow the parameter out type with @param-out PHPDoc tag.', + ], + [ + 'Method TooWideMethodParameterOut\FinalFoo::bug10699() never assigns 20 to &$out so it can be removed from the @param-out type.', + 100, + ], + ]); + } + + public function testRuleWithoutProtectedAndPublic(): void + { + $this->checkProtectedAndPublicMethods = false; + $this->analyse([__DIR__ . '/data/too-wide-method-parameter-out.php'], [ + [ + 'Method TooWideMethodParameterOut\Foo::finalDoBaz() never assigns null to &$p so it can be removed from the @param-out type.', + 45, + ], + [ + 'Method TooWideMethodParameterOut\Foo::doBazPrivate() never assigns null to &$p so it can be removed from the @param-out type.', + 61, + ], + [ + 'Method TooWideMethodParameterOut\FinalFoo::doBar() never assigns null to &$p so it can be removed from the by-ref type.', + 76, + 'You can narrow the parameter out type with @param-out PHPDoc tag.', + ], + [ + 'Method TooWideMethodParameterOut\FinalFoo::doBaz() never assigns null to &$p so it can be removed from the @param-out type.', + 84, + ], + [ + 'Method TooWideMethodParameterOut\FinalFoo::doLorem() never assigns null to &$p so it can be removed from the by-ref type.', + 89, + 'You can narrow the parameter out type with @param-out PHPDoc tag.', + ], + [ + 'Method TooWideMethodParameterOut\FinalFoo::bug10699() never assigns 20 to &$out so it can be removed from the @param-out type.', + 100, + ], ]); } @@ -50,4 +115,10 @@ public function testBug10687(): void $this->analyse([__DIR__ . '/data/bug-10687.php'], []); } + public function testBug12080(): void + { + $this->checkProtectedAndPublicMethods = false; + $this->analyse([__DIR__ . '/data/bug-12080.php'], []); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-12080.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-12080.php new file mode 100644 index 0000000000..6d6acf06b4 --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-12080.php @@ -0,0 +1,19 @@ +