1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Merge pull request #10165 from tuqqu/backed-enum-value-changed-to-atomic

This commit is contained in:
Bruce Weirdan 2023-08-31 23:13:53 +02:00 committed by GitHub
commit c50e822ff3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 60 additions and 49 deletions

View File

@ -33,6 +33,8 @@
- [BC] Properties `Psalm\Type\Atomic\TLiteralFloat::$value` and `Psalm\Type\Atomic\TLiteralInt::$value` became typed (`float` and `int` respectively)
- [BC] Property `Psalm\Storage\EnumCaseStorage::$value` changed from `int|string|null` to `TLiteralInt|TLiteralString|null`
# Upgrading from Psalm 4 to Psalm 5
## Changed

View File

@ -74,6 +74,8 @@ use Psalm\Storage\FunctionLikeParameter;
use Psalm\Storage\MethodStorage;
use Psalm\Type;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNull;
@ -93,8 +95,6 @@ use function count;
use function explode;
use function implode;
use function in_array;
use function is_int;
use function is_string;
use function preg_match;
use function preg_replace;
use function reset;
@ -2483,8 +2483,8 @@ class ClassAnalyzer extends ClassLikeAnalyzer
),
);
} elseif ($case_storage->value !== null) {
if ((is_int($case_storage->value) && $storage->enum_type === 'string')
|| (is_string($case_storage->value) && $storage->enum_type === 'int')
if (($case_storage->value instanceof TLiteralInt && $storage->enum_type === 'string')
|| ($case_storage->value instanceof TLiteralString && $storage->enum_type === 'int')
) {
IssueBuffer::maybeAdd(
new InvalidEnumCaseValue(
@ -2497,7 +2497,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
}
if ($case_storage->value !== null) {
if (in_array($case_storage->value, $seen_values, true)) {
if (in_array($case_storage->value->value, $seen_values, true)) {
IssueBuffer::maybeAdd(
new DuplicateEnumCaseValue(
'Enum case values should be unique',
@ -2506,7 +2506,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
),
);
} else {
$seen_values[] = $case_storage->value;
$seen_values[] = $case_storage->value->value;
}
}
}

View File

@ -51,7 +51,6 @@ use Psalm\Type\Atomic\TEnumCase;
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNull;
@ -67,8 +66,6 @@ use function array_map;
use function array_search;
use function count;
use function in_array;
use function is_int;
use function is_string;
use function strtolower;
use const ARRAY_FILTER_USE_KEY;
@ -1034,14 +1031,7 @@ class AtomicPropertyFetchAnalyzer
$case_values = [];
foreach ($enum_cases as $enum_case) {
if (is_string($enum_case->value)) {
$case_values[] = Type::getAtomicStringFromLiteral($enum_case->value);
} elseif (is_int($enum_case->value)) {
$case_values[] = new TLiteralInt($enum_case->value);
} else {
// this should never happen
$case_values[] = new TMixed();
}
$case_values[] = $enum_case->value ?? new TMixed();
}
/** @psalm-suppress ArgumentTypeCoercion */

View File

@ -340,10 +340,9 @@ class ConstantTypeResolver
if (isset($enum_storage->enum_cases[$c->case])) {
if ($c instanceof EnumValueFetch) {
$value = $enum_storage->enum_cases[$c->case]->value;
if (is_string($value)) {
return Type::getString($value)->getSingleAtomic();
} elseif (is_int($value)) {
return Type::getInt(false, $value)->getSingleAtomic();
if ($value !== null) {
return $value;
}
} elseif ($c instanceof EnumNameFetch) {
return Type::getString($c->case)->getSingleAtomic();

View File

@ -35,6 +35,7 @@ use Psalm\Type\Atomic\TEnumCase;
use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Union;
use UnexpectedValueException;
@ -44,7 +45,6 @@ use function assert;
use function count;
use function explode;
use function in_array;
use function is_int;
use function reset;
use function strtolower;
@ -629,9 +629,7 @@ class Methods
foreach ($original_class_storage->enum_cases as $case_name => $case_storage) {
if (UnionTypeComparator::isContainedBy(
$source_analyzer->getCodebase(),
is_int($case_storage->value) ?
Type::getInt(false, $case_storage->value) :
Type::getString($case_storage->value),
new Union([$case_storage->value ?? new TString()]),
$first_arg_type,
)) {
$types[] = new TEnumCase($original_fq_class_name, $case_name);

View File

@ -76,8 +76,6 @@ use function assert;
use function count;
use function get_class;
use function implode;
use function is_int;
use function is_string;
use function preg_match;
use function preg_replace;
use function preg_split;
@ -737,14 +735,11 @@ class ClassLikeNodeScanner
if ($storage->is_enum) {
$name_types = [];
$values_types = [];
foreach ($storage->enum_cases as $name => $enumCaseStorage) {
foreach ($storage->enum_cases as $name => $enum_case_storage) {
$name_types[] = Type::getAtomicStringFromLiteral($name);
if ($storage->enum_type !== null) {
if (is_string($enumCaseStorage->value)) {
$values_types[] = Type::getAtomicStringFromLiteral($enumCaseStorage->value);
} elseif (is_int($enumCaseStorage->value)) {
$values_types[] = new Type\Atomic\TLiteralInt($enumCaseStorage->value);
}
if ($storage->enum_type !== null
&& $enum_case_storage->value !== null) {
$values_types[] = $enum_case_storage->value;
}
}
if ($name_types !== []) {
@ -1441,9 +1436,9 @@ class ClassLikeNodeScanner
if ($case_type) {
if ($case_type->isSingleIntLiteral()) {
$enum_value = $case_type->getSingleIntLiteral()->value;
$enum_value = $case_type->getSingleIntLiteral();
} elseif ($case_type->isSingleStringLiteral()) {
$enum_value = $case_type->getSingleStringLiteral()->value;
$enum_value = $case_type->getSingleStringLiteral();
} else {
IssueBuffer::maybeAdd(
new InvalidEnumCaseValue(

View File

@ -23,8 +23,6 @@ use Psalm\Type\Union;
use UnitEnum;
use stdClass;
use function is_int;
use function is_string;
use function reset;
use function strtolower;
@ -63,11 +61,11 @@ class GetObjectVarsReturnTypeProvider implements FunctionReturnTypeProviderInter
return new TKeyedArray($properties);
}
$enum_case_storage = $enum_classlike_storage->enum_cases[$object_type->case_name];
if (is_int($enum_case_storage->value)) {
$properties['value'] = new Union([new Atomic\TLiteralInt($enum_case_storage->value)]);
} elseif (is_string($enum_case_storage->value)) {
$properties['value'] = new Union([Type::getAtomicStringFromLiteral($enum_case_storage->value)]);
if ($enum_case_storage->value !== null) {
$properties['value'] = new Union([$enum_case_storage->value]);
}
return new TKeyedArray($properties);
}

View File

@ -2978,7 +2978,7 @@ class SimpleAssertionReconciler extends Reconciler
$enum_case->value !== null,
'Verified enum type above, value can not contain `null` anymore.',
);
$reconciled_types[] = Type::getLiteral($enum_case->value);
$reconciled_types[] = $enum_case->value;
}
continue;
@ -2990,7 +2990,7 @@ class SimpleAssertionReconciler extends Reconciler
}
assert($enum_case->value !== null, 'Verified enum type above, value can not contain `null` anymore.');
$reconciled_types[] = Type::getLiteral($enum_case->value);
$reconciled_types[] = $enum_case->value;
}
if ($reconciled_types === []) {

View File

@ -3,11 +3,13 @@
namespace Psalm\Storage;
use Psalm\CodeLocation;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
final class EnumCaseStorage
{
/**
* @var int|string|null
* @var TLiteralString|TLiteralInt|null
*/
public $value;
@ -20,7 +22,7 @@ final class EnumCaseStorage
public $deprecated = false;
/**
* @param int|string|null $value
* @param TLiteralString|TLiteralInt|null $value
*/
public function __construct(
$value,

View File

@ -260,6 +260,7 @@ abstract class Type
}
/**
* @psalm-suppress PossiblyUnusedMethod
* @param int|string $value
* @return TLiteralString|TLiteralInt
*/

View File

@ -3,7 +3,6 @@
namespace Psalm\Type\Atomic;
use Psalm\Codebase;
use Psalm\Internal\Codebase\ConstantTypeResolver;
use Psalm\Storage\EnumCaseStorage;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
@ -37,13 +36,15 @@ final class TValueOf extends Atomic
assert(isset($cases[$atomic_type->case_name]), 'Should\'ve been verified in TValueOf#getValueType');
$value = $cases[$atomic_type->case_name]->value;
assert($value !== null, 'Backed enum must have a value.');
return new Union([ConstantTypeResolver::getLiteralTypeFromScalarValue($value)]);
return new Union([$value]);
}
return new Union(array_map(
function (EnumCaseStorage $case): Atomic {
assert($case->value !== null); // Backed enum must have a value
return ConstantTypeResolver::getLiteralTypeFromScalarValue($case->value);
return $case->value;
},
array_values($cases),
));

View File

@ -610,6 +610,31 @@ class EnumTest extends TestCase
'ignored_issues' => [],
'php_version' => '8.1',
],
'classStringAsBackedEnumValue' => [
'code' => <<<'PHP'
<?php
class Foo {}
enum FooEnum: string {
case Foo = Foo::class;
}
/**
* @param class-string $s
*/
function noop(string $s): string
{
return $s;
}
$foo = FooEnum::Foo->value;
noop($foo);
noop(FooEnum::Foo->value);
PHP,
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.1',
],
];
}