diff --git a/Makefile b/Makefile index f5476d6067..0d4adf8b5f 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ lint: --exclude tests/PHPStan/Rules/Names/data \ --exclude tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php \ --exclude tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php \ + --exclude tests/PHPStan/Rules/Classes/data/bug-13768.php \ --exclude tests/PHPStan/Rules/Classes/data/duplicate-declarations.php \ --exclude tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php \ --exclude tests/PHPStan/Rules/Classes/data/enum-sanity.php \ diff --git a/src/Rules/Classes/EnumSanityRule.php b/src/Rules/Classes/EnumSanityRule.php index fc5d56ba8a..3d1e42a00e 100644 --- a/src/Rules/Classes/EnumSanityRule.php +++ b/src/Rules/Classes/EnumSanityRule.php @@ -8,6 +8,7 @@ use PHPStan\Node\InClassNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\ShouldNotHappenException; use PHPStan\Type\IntegerType; use PHPStan\Type\StringType; use PHPStan\Type\VerbosityLevel; @@ -16,6 +17,8 @@ use function count; use function implode; use function in_array; +use function is_int; +use function is_string; use function sprintf; /** @@ -145,27 +148,16 @@ public function processNode(Node $node, Scope $scope): array } $caseName = $stmt->name->name; - if ($stmt->expr instanceof Node\Scalar\Int_ || $stmt->expr instanceof Node\Scalar\String_) { - if ($enumNode->scalarType === null) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Enum %s is not backed, but case %s has value %s.', - $classReflection->getDisplayName(), - $caseName, - $stmt->expr->value, - )) - ->identifier('enum.caseWithValue') - ->line($stmt->getStartLine()) - ->nonIgnorable() - ->build(); - } else { - $caseValue = $stmt->expr->value; - - if (!isset($enumCases[$caseValue])) { - $enumCases[$caseValue] = []; - } - - $enumCases[$caseValue][] = $caseName; - } + if ($enumNode->scalarType === null && $stmt->expr !== null) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Enum %s is not backed, but case %s has value.', + $classReflection->getDisplayName(), + $caseName, + )) + ->identifier('enum.caseWithValue') + ->line($stmt->getStartLine()) + ->nonIgnorable() + ->build(); } if ($enumNode->scalarType === null) { @@ -189,6 +181,20 @@ public function processNode(Node $node, Scope $scope): array $exprType = $scope->getType($stmt->expr); $scalarType = $enumNode->scalarType->toLowerString() === 'int' ? new IntegerType() : new StringType(); if ($scalarType->isSuperTypeOf($exprType)->yes()) { + $constantValues = $exprType->getConstantScalarValues(); + if (count($constantValues) === 1) { + $caseValue = $constantValues[0]; + if (!is_string($caseValue) && !is_int($caseValue)) { + throw new ShouldNotHappenException(); + } + + if (!isset($enumCases[$caseValue])) { + $enumCases[$caseValue] = []; + } + + $enumCases[$caseValue][] = $caseName; + } + continue; } diff --git a/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php b/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php index 22bb10a00e..6b6f7c2f02 100644 --- a/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php +++ b/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php @@ -95,7 +95,7 @@ public function testRule(): void 110, ], [ - 'Enum EnumSanity\EnumWithValueButNotBacked is not backed, but case FOO has value 1.', + 'Enum EnumSanity\EnumWithValueButNotBacked is not backed, but case FOO has value.', 114, ], [ @@ -119,24 +119,28 @@ public function testBug9402(): void } #[RequiresPhp('>= 8.1')] - public function testBug11592(): void + public function testBug13768(): void { - $this->analyse([__DIR__ . '/data/bug-11592.php'], [ + $this->analyse([__DIR__ . '/data/bug-13768.php'], [ [ - 'Enum Bug11592\Test2 cannot redeclare native method cases().', - 22, + 'Enum Bug13768\Order is not backed, but case A has value.', + 7, ], [ - 'Enum Bug11592\BackedTest2 cannot redeclare native method cases().', - 37, + 'Enum Bug13768\Order is not backed, but case B has value.', + 8, ], [ - 'Enum Bug11592\BackedTest2 cannot redeclare native method from().', - 39, + 'Enum Bug13768\Order is not backed, but case C has value.', + 9, + ], + [ + 'Enum Bug13768\Order is not backed, but case D has value.', + 10, ], [ - 'Enum Bug11592\BackedTest2 cannot redeclare native method tryFrom().', - 41, + 'Enum Bug13768\Backed has duplicate value 1 for cases One, Two.', + 18, ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/bug-13768.php b/tests/PHPStan/Rules/Classes/data/bug-13768.php new file mode 100644 index 0000000000..7624cc59ca --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-13768.php @@ -0,0 +1,21 @@ += 8.1 + +namespace Bug13768; + +enum Order { + case U; + case A = 1.5; + case B = 2.5; + case C = 3; + case D = '3'; +} + +class Foo +{ + public const A = 1; +} + +enum Backed: int { + case One = Foo::A; + case Two = Foo::A; +}