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:
commit
c50e822ff3
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 === []) {
|
||||
|
@ -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,
|
||||
|
@ -260,6 +260,7 @@ abstract class Type
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
* @param int|string $value
|
||||
* @return TLiteralString|TLiteralInt
|
||||
*/
|
||||
|
@ -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),
|
||||
));
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user