1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 13:51:54 +01:00

Merge pull request #8693 from weirdan/fix-8267

Fixes https://github.com/vimeo/psalm/issues/8267
This commit is contained in:
Bruce Weirdan 2022-11-10 21:13:12 -04:00 committed by GitHub
commit c613e47c55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 15 deletions

View File

@ -75,3 +75,28 @@ enum Status: string
case Open = "open"; case Open = "open";
} }
``` ```
## Case with a type that cannot 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;
}
```

View File

@ -43,6 +43,7 @@ use Psalm\Issue\DuplicateConstant;
use Psalm\Issue\DuplicateEnumCase; use Psalm\Issue\DuplicateEnumCase;
use Psalm\Issue\InvalidDocblock; use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\InvalidEnumBackingType; use Psalm\Issue\InvalidEnumBackingType;
use Psalm\Issue\InvalidEnumCaseValue;
use Psalm\Issue\InvalidTypeImport; use Psalm\Issue\InvalidTypeImport;
use Psalm\Issue\MissingDocblockType; use Psalm\Issue\MissingDocblockType;
use Psalm\Issue\ParseError; use Psalm\Issue\ParseError;
@ -314,17 +315,6 @@ class ClassLikeNodeScanner
if ($node->scalarType) { if ($node->scalarType) {
if ($node->scalarType->name === 'string' || $node->scalarType->name === 'int') { if ($node->scalarType->name === 'string' || $node->scalarType->name === 'int') {
$storage->enum_type = $node->scalarType->name; $storage->enum_type = $node->scalarType->name;
} else {
IssueBuffer::maybeAdd(
new InvalidEnumBackingType(
'Enums cannot be backed by ' . $node->scalarType->name . ', string or int expected',
new CodeLocation($this->file_scanner, $node->scalarType),
$fq_classlike_name
)
);
$this->file_storage->has_visitor_issues = true;
$storage->has_visitor_issues = true;
}
$storage->class_implements['backedenum'] = 'BackedEnum'; $storage->class_implements['backedenum'] = 'BackedEnum';
$storage->direct_class_interfaces['backedenum'] = 'BackedEnum'; $storage->direct_class_interfaces['backedenum'] = 'BackedEnum';
$this->file_storage->required_interfaces['backedenum'] = 'BackedEnum'; $this->file_storage->required_interfaces['backedenum'] = 'BackedEnum';
@ -336,6 +326,17 @@ class ClassLikeNodeScanner
'tryfrom' 'tryfrom'
); );
$storage->appearing_method_ids['tryfrom'] = $storage->declaring_method_ids['tryfrom']; $storage->appearing_method_ids['tryfrom'] = $storage->declaring_method_ids['tryfrom'];
} else {
IssueBuffer::maybeAdd(
new InvalidEnumBackingType(
'Enums cannot be backed by ' . $node->scalarType->name . ', string or int expected',
new CodeLocation($this->file_scanner, $node->scalarType),
$fq_classlike_name
)
);
$this->file_storage->has_visitor_issues = true;
$storage->has_visitor_issues = true;
}
} }
$this->codebase->scanner->queueClassLikeForScanning('UnitEnum'); $this->codebase->scanner->queueClassLikeForScanning('UnitEnum');
@ -710,7 +711,11 @@ class ClassLikeNodeScanner
} elseif ($node_stmt instanceof PhpParser\Node\Stmt\EnumCase } elseif ($node_stmt instanceof PhpParser\Node\Stmt\EnumCase
&& $node instanceof PhpParser\Node\Stmt\Enum_ && $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 @@ class ClassLikeNodeScanner
$enum_value = null; $enum_value = null;
$case_location = new CodeLocation($this->file_scanner, $stmt);
if ($stmt->expr !== null) { if ($stmt->expr !== null) {
$case_type = SimpleTypeInferer::infer( $case_type = SimpleTypeInferer::infer(
$this->codebase, $this->codebase,
@ -1381,8 +1388,12 @@ class ClassLikeNodeScanner
} elseif ($case_type->isSingleStringLiteral()) { } elseif ($case_type->isSingleStringLiteral()) {
$enum_value = $case_type->getSingleStringLiteral()->value; $enum_value = $case_type->getSingleStringLiteral()->value;
} else { } else {
throw new RuntimeException( IssueBuffer::maybeAdd(
'Unexpected: case value for ' . $stmt->name->name . ' is ' . $case_type->getId() new InvalidEnumCaseValue(
'Case of a backed enum should have either string or int value',
$case_location,
$fq_classlike_name
)
); );
} }
} else { } else {
@ -1390,7 +1401,6 @@ class ClassLikeNodeScanner
} }
} }
$case_location = new CodeLocation($this->file_scanner, $stmt);
if (!isset($storage->enum_cases[$stmt->name->name])) { if (!isset($storage->enum_cases[$stmt->name->name])) {
$case = new EnumCaseStorage( $case = new EnumCaseStorage(

View File

@ -575,6 +575,16 @@ class EnumTest extends TestCase
'ignored_issues' => [], 'ignored_issues' => [],
'php_version' => '8.1', 'php_version' => '8.1',
], ],
'invalidCaseTypeForBackedEnum' => [
'code' => '<?php
enum Status: int {
case Open = [];
}
',
'error_message' => 'InvalidEnumCaseValue',
'ignored_issues' => [],
'php_version' => '8.1',
],
'duplicateValues' => [ 'duplicateValues' => [
'code' => '<?php 'code' => '<?php
enum Status: string enum Status: string