Skip to content

Commit

Permalink
Flag invalid enum case value types
Browse files Browse the repository at this point in the history
Fixes #8267
  • Loading branch information
weirdan committed Nov 10, 2022
1 parent 0cd1f13 commit 0579828
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 15 deletions.
25 changes: 25 additions & 0 deletions docs/running_psalm/issues/InvalidEnumCaseValue.md
Expand Up @@ -75,3 +75,28 @@ enum Status: string
case Open = "open";
}
```

## Case with a type that back an enum

Case type should be either `int` or `string`.

```php
<?php

enum Status: int {
case Open = [];
}
```

### How to fix

Change the case value so that it's one of the allowed types (and matches the backing type)

```php
<?php

enum Status: int
{
case Open = 1;
}
```
40 changes: 25 additions & 15 deletions src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php
Expand Up @@ -43,6 +43,7 @@
use Psalm\Issue\DuplicateEnumCase;
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\InvalidEnumBackingType;
use Psalm\Issue\InvalidEnumCaseValue;
use Psalm\Issue\InvalidTypeImport;
use Psalm\Issue\MissingDocblockType;
use Psalm\Issue\ParseError;
Expand Down Expand Up @@ -314,6 +315,17 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool
if ($node->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(
Expand All @@ -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');
Expand Down Expand Up @@ -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
);
}
}

Expand Down Expand Up @@ -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,
Expand All @@ -1381,16 +1388,19 @@ 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 {
throw new RuntimeException('Failed to infer case value for ' . $stmt->name->name);
}
}

$case_location = new CodeLocation($this->file_scanner, $stmt);

if (!isset($storage->enum_cases[$stmt->name->name])) {
$case = new EnumCaseStorage(
Expand Down
10 changes: 10 additions & 0 deletions tests/EnumTest.php
Expand Up @@ -559,6 +559,16 @@ enum Status: array {}
'ignored_issues' => [],
'php_version' => '8.1',
],
'invalidCaseTypeForBackedEnum' => [
'code' => '<?php
enum Status: int {
case Open = [];
}
',
'error_message' => 'InvalidEnumCaseValue',
'ignored_issues' => [],
'php_version' => '8.1',
],
'duplicateValues' => [
'code' => '<?php
enum Status: string
Expand Down

0 comments on commit 0579828

Please sign in to comment.