From c50b71fd961e9009419b8fddac835b15696f4ff5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 20:28:48 +0200 Subject: [PATCH] Do not report missing implementation abstract method from trait when it's implicitly implemented by enum --- src/Rules/Classes/EnumSanityRule.php | 13 ----- .../AbstractMethodInNonAbstractClassRule.php | 13 +++++ .../Rules/Classes/EnumSanityRuleTest.php | 31 +++++++++++- .../PHPStan/Rules/Classes/data/bug-11592.php | 47 +++++++++++++++++++ ...stractMethodInNonAbstractClassRuleTest.php | 30 ++++++++++++ 5 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-11592.php diff --git a/src/Rules/Classes/EnumSanityRule.php b/src/Rules/Classes/EnumSanityRule.php index e9aebc1c26..2d193095d4 100644 --- a/src/Rules/Classes/EnumSanityRule.php +++ b/src/Rules/Classes/EnumSanityRule.php @@ -47,20 +47,7 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($enumNode->getMethods() as $methodNode) { - if ($methodNode->isAbstract()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Enum %s contains abstract method %s().', - $classReflection->getDisplayName(), - $methodNode->name->name, - )) - ->identifier('enum.abstractMethod') - ->line($methodNode->getStartLine()) - ->nonIgnorable() - ->build(); - } - $lowercasedMethodName = $methodNode->name->toLowerString(); - if ($methodNode->isMagic()) { if ($lowercasedMethodName === '__construct') { $errors[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php b/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php index 04ca707933..82c92a4ecd 100644 --- a/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php +++ b/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php @@ -7,6 +7,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use function in_array; use function sprintf; /** @@ -29,6 +30,18 @@ public function processNode(Node $node, Scope $scope): array $class = $scope->getClassReflection(); if (!$class->isAbstract() && $node->isAbstract()) { + if ($class->isEnum()) { + $lowercasedMethodName = $node->name->toLowerString(); + if ($lowercasedMethodName === 'cases') { + return []; + } + if ($class->isBackedEnum()) { + if (in_array($lowercasedMethodName, ['from', 'tryfrom'], true)) { + return []; + } + } + } + $description = $class->getClassTypeDescription(); return [ RuleErrorBuilder::message(sprintf( diff --git a/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php b/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php index c1e85d214f..af02a79635 100644 --- a/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php +++ b/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php @@ -24,10 +24,11 @@ public function testRule(): void } $expected = [ - [ + /*[ + // reported by AbstractMethodInNonAbstractClassRule 'Enum EnumSanity\EnumWithAbstractMethod contains abstract method foo().', 7, - ], + ],*/ [ 'Enum EnumSanity\EnumWithConstructorAndDestructor contains constructor.', 12, @@ -123,4 +124,30 @@ public function testBug9402(): void ]); } + public function testBug11592(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1'); + } + + $this->analyse([__DIR__ . '/data/bug-11592.php'], [ + [ + 'Enum Bug11592\Test2 cannot redeclare native method cases().', + 22, + ], + [ + 'Enum Bug11592\BackedTest2 cannot redeclare native method cases().', + 37, + ], + [ + 'Enum Bug11592\BackedTest2 cannot redeclare native method from().', + 39, + ], + [ + 'Enum Bug11592\BackedTest2 cannot redeclare native method tryFrom().', + 41, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-11592.php b/tests/PHPStan/Rules/Classes/data/bug-11592.php new file mode 100644 index 0000000000..b94251a495 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-11592.php @@ -0,0 +1,47 @@ += 8.1 + +namespace Bug11592; + +trait HelloWorld +{ + abstract public static function cases(): array; + + abstract public static function from(): self; + + abstract public static function tryFrom(): ?self; +} + +enum Test +{ + use HelloWorld; +} + +enum Test2 +{ + + abstract public static function cases(): array; + + abstract public static function from(): self; + + abstract public static function tryFrom(): ?self; + +} + +enum BackedTest: int +{ + use HelloWorld; +} + +enum BackedTest2: int +{ + abstract public static function cases(): array; + + abstract public static function from(): self; + + abstract public static function tryFrom(): ?self; +} + +enum EnumWithAbstractMethod +{ + abstract function foo(); +} diff --git a/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php b/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php index 581d512890..7ec5a8d7f4 100644 --- a/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php +++ b/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php @@ -89,4 +89,34 @@ public function testEnum(): void ]); } + public function testBug11592(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1'); + } + + $this->analyse([__DIR__ . '/../Classes/data/bug-11592.php'], [ + [ + 'Enum Bug11592\Test contains abstract method from().', + 9, + ], + [ + 'Enum Bug11592\Test contains abstract method tryFrom().', + 11, + ], + [ + 'Enum Bug11592\Test2 contains abstract method from().', + 24, + ], + [ + 'Enum Bug11592\Test2 contains abstract method tryFrom().', + 26, + ], + [ + 'Enum Bug11592\EnumWithAbstractMethod contains abstract method foo().', + 46, + ], + ]); + } + }