diff --git a/docs/running_psalm/issues/InvalidEnumCaseValue.md b/docs/running_psalm/issues/InvalidEnumCaseValue.md index 86c7f621c4e..86e413aa101 100644 --- a/docs/running_psalm/issues/InvalidEnumCaseValue.md +++ b/docs/running_psalm/issues/InvalidEnumCaseValue.md @@ -75,3 +75,28 @@ enum Status: string case Open = "open"; } ``` + +## Case with a type that cannot back an enum + +Case type should be either `int` or `string`. + +```php +scalarType) { if ($node->scalarType->name === 'string' || $node->scalarType->name === 'int') { $storage->enum_type = $node->scalarType->name; + $storage->class_implements['backedenum'] = 'BackedEnum'; + $storage->direct_class_interfaces['backedenum'] = 'BackedEnum'; + $this->file_storage->required_interfaces['backedenum'] = 'BackedEnum'; + $this->codebase->scanner->queueClassLikeForScanning('BackedEnum'); + $storage->declaring_method_ids['from'] = new MethodIdentifier('BackedEnum', 'from'); + $storage->appearing_method_ids['from'] = $storage->declaring_method_ids['from']; + $storage->declaring_method_ids['tryfrom'] = new MethodIdentifier( + 'BackedEnum', + 'tryfrom' + ); + $storage->appearing_method_ids['tryfrom'] = $storage->declaring_method_ids['tryfrom']; } else { IssueBuffer::maybeAdd( new InvalidEnumBackingType( @@ -325,17 +337,6 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool $this->file_storage->has_visitor_issues = true; $storage->has_visitor_issues = true; } - $storage->class_implements['backedenum'] = 'BackedEnum'; - $storage->direct_class_interfaces['backedenum'] = 'BackedEnum'; - $this->file_storage->required_interfaces['backedenum'] = 'BackedEnum'; - $this->codebase->scanner->queueClassLikeForScanning('BackedEnum'); - $storage->declaring_method_ids['from'] = new MethodIdentifier('BackedEnum', 'from'); - $storage->appearing_method_ids['from'] = $storage->declaring_method_ids['from']; - $storage->declaring_method_ids['tryfrom'] = new MethodIdentifier( - 'BackedEnum', - 'tryfrom' - ); - $storage->appearing_method_ids['tryfrom'] = $storage->declaring_method_ids['tryfrom']; } $this->codebase->scanner->queueClassLikeForScanning('UnitEnum'); @@ -710,7 +711,11 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool } elseif ($node_stmt instanceof PhpParser\Node\Stmt\EnumCase && $node instanceof PhpParser\Node\Stmt\Enum_ ) { - $this->visitEnumDeclaration($node_stmt, $storage, $fq_classlike_name); + $this->visitEnumDeclaration( + $node_stmt, + $storage, + $fq_classlike_name + ); } } @@ -1364,6 +1369,8 @@ private function visitEnumDeclaration( $enum_value = null; + $case_location = new CodeLocation($this->file_scanner, $stmt); + if ($stmt->expr !== null) { $case_type = SimpleTypeInferer::infer( $this->codebase, @@ -1381,8 +1388,12 @@ private function visitEnumDeclaration( } elseif ($case_type->isSingleStringLiteral()) { $enum_value = $case_type->getSingleStringLiteral()->value; } else { - throw new RuntimeException( - 'Unexpected: case value for ' . $stmt->name->name . ' is ' . $case_type->getId() + IssueBuffer::maybeAdd( + new InvalidEnumCaseValue( + 'Case of a backed enum should have either string or int value', + $case_location, + $fq_classlike_name + ) ); } } else { @@ -1390,7 +1401,6 @@ private function visitEnumDeclaration( } } - $case_location = new CodeLocation($this->file_scanner, $stmt); if (!isset($storage->enum_cases[$stmt->name->name])) { $case = new EnumCaseStorage( diff --git a/tests/EnumTest.php b/tests/EnumTest.php index b65d840ffd7..19e0e401b2f 100644 --- a/tests/EnumTest.php +++ b/tests/EnumTest.php @@ -559,6 +559,16 @@ enum Status: array {} 'ignored_issues' => [], 'php_version' => '8.1', ], + 'invalidCaseTypeForBackedEnum' => [ + 'code' => ' 'InvalidEnumCaseValue', + 'ignored_issues' => [], + 'php_version' => '8.1', + ], 'duplicateValues' => [ 'code' => '