From 5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 13 Nov 2023 22:48:39 +0100 Subject: [PATCH] MethodSignatureRule - look at abstract trait method in Bleeding Edge --- conf/bleedingEdge.neon | 1 + conf/config.neon | 2 + conf/parametersSchema.neon | 1 + src/PhpDoc/StubValidator.php | 2 +- src/Rules/Methods/MethodSignatureRule.php | 20 ++++++++++ .../Rules/Methods/MethodSignatureRuleTest.php | 15 +++++++- .../Methods/OverridingMethodRuleTest.php | 2 +- .../data/overriding-trait-methods-phpdoc.php | 38 +++++++++++++++++++ 8 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/overriding-trait-methods-phpdoc.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 4c915913e6..3c571c3b33 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -20,6 +20,7 @@ parameters: notAnalysedTrait: true curlSetOptTypes: true listType: true + abstractTraitMethod: true missingMagicSerializationRule: true nullContextForVoidReturningFunctions: true unescapeStrings: true diff --git a/conf/config.neon b/conf/config.neon index 2bf57cc945..9cbbed5bee 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -54,6 +54,7 @@ parameters: notAnalysedTrait: false curlSetOptTypes: false listType: false + abstractTraitMethod: false missingMagicSerializationRule: false nullContextForVoidReturningFunctions: false unescapeStrings: false @@ -916,6 +917,7 @@ services: arguments: reportMaybes: %reportMaybesInMethodSignatures% reportStatic: %reportStaticMethodSignatures% + abstractTraitMethod: %featureToggles.abstractTraitMethod% - class: PHPStan\Rules\Methods\MethodParameterComparisonHelper diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index b4f792cdc0..e7579611c1 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -50,6 +50,7 @@ parametersSchema: notAnalysedTrait: bool() curlSetOptTypes: bool() listType: bool() + abstractTraitMethod: bool() missingMagicSerializationRule: bool() nullContextForVoidReturningFunctions: bool() unescapeStrings: bool() diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index f0e3ff2f8f..adaf2e9718 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -164,7 +164,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), new ExistingClassesInPropertiesRule($reflectionProvider, $classCaseSensitivityCheck, $unresolvableTypeHelper, $phpVersion, true, false), - new OverridingMethodRule($phpVersion, new MethodSignatureRule(true, true), true, new MethodParameterComparisonHelper($phpVersion, $container->getParameter('featureToggles')['genericPrototypeMessage']), $container->getParameter('featureToggles')['genericPrototypeMessage'], $container->getParameter('featureToggles')['finalByPhpDoc']), + new OverridingMethodRule($phpVersion, new MethodSignatureRule(true, true, $container->getParameter('featureToggles')['abstractTraitMethod']), true, new MethodParameterComparisonHelper($phpVersion, $container->getParameter('featureToggles')['genericPrototypeMessage']), $container->getParameter('featureToggles')['genericPrototypeMessage'], $container->getParameter('featureToggles')['finalByPhpDoc']), new DuplicateDeclarationRule(), new LocalTypeAliasesRule($localTypeAliasesCheck), new LocalTypeTraitAliasesRule($localTypeAliasesCheck, $reflectionProvider), diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index 7c5126a821..adf1c6bbb2 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -22,6 +22,7 @@ use PHPStan\Type\TypeTraverser; use PHPStan\Type\VerbosityLevel; use function count; +use function is_bool; use function min; use function sprintf; @@ -34,6 +35,7 @@ class MethodSignatureRule implements Rule public function __construct( private bool $reportMaybes, private bool $reportStatic, + private bool $abstractTraitMethod, ) { } @@ -132,6 +134,24 @@ private function collectParentMethods(string $methodName, ClassReflection $class $parentMethods[] = $interface->getNativeMethod($methodName); } + if ($this->abstractTraitMethod) { + foreach ($class->getTraits(true) as $trait) { + if (!$trait->hasNativeMethod($methodName)) { + continue; + } + + $method = $trait->getNativeMethod($methodName); + $isAbstract = $method->isAbstract(); + if (is_bool($isAbstract)) { + if ($isAbstract) { + $parentMethods[] = $method; + } + } elseif ($isAbstract->yes()) { + $parentMethods[] = $method; + } + } + } + return $parentMethods; } diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 8d29ae1059..b0da1c037f 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -23,7 +23,7 @@ protected function getRule(): Rule return new OverridingMethodRule( $phpVersion, - new MethodSignatureRule($this->reportMaybes, $this->reportStatic), + new MethodSignatureRule($this->reportMaybes, $this->reportStatic, true), true, new MethodParameterComparisonHelper($phpVersion, true), true, @@ -423,4 +423,17 @@ public function testBug9905(): void $this->analyse([__DIR__ . '/data/bug-9905.php'], []); } + public function testTraits(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + + $this->analyse([__DIR__ . '/data/overriding-trait-methods-phpdoc.php'], [ + [ + 'Parameter #1 $i (non-empty-string) of method OverridingTraitMethodsPhpDoc\Bar::doBar() should be contravariant with parameter $i (string) of method OverridingTraitMethodsPhpDoc\Foo::doBar()', + 33, + ] + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 310e848fda..eef272ca49 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -23,7 +23,7 @@ protected function getRule(): Rule return new OverridingMethodRule( $phpVersion, - new MethodSignatureRule(true, true), + new MethodSignatureRule(true, true, true), false, new MethodParameterComparisonHelper($phpVersion, true), true, diff --git a/tests/PHPStan/Rules/Methods/data/overriding-trait-methods-phpdoc.php b/tests/PHPStan/Rules/Methods/data/overriding-trait-methods-phpdoc.php new file mode 100644 index 0000000000..8a84a36c65 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/overriding-trait-methods-phpdoc.php @@ -0,0 +1,38 @@ += 8.0 + +namespace OverridingTraitMethodsPhpDoc; + +trait Foo +{ + + public function doFoo(int $i): int + { + + } + + abstract public function doBar(string $i): int; + +} + +class Bar +{ + + use Foo; + + /** + * @param positive-int $i + */ + public function doFoo(int $i): string + { + // ok, trait method not abstract + } + + /** + * @param non-empty-string $i + */ + public function doBar(string $i): int + { + // error + } + +}