Skip to content

Commit

Permalink
Merge pull request #10165 from tuqqu/backed-enum-value-changed-to-atomic
Browse files Browse the repository at this point in the history
  • Loading branch information
weirdan authored Aug 31, 2023
2 parents 77650e7 + 76f03cc commit c50e822
Show file tree
Hide file tree
Showing 12 changed files with 60 additions and 49 deletions.
2 changes: 2 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 6 additions & 6 deletions src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
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;
Expand All @@ -93,8 +95,6 @@
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;
Expand Down Expand Up @@ -2483,8 +2483,8 @@ private function checkEnum(): void
),
);
} 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(
Expand All @@ -2497,7 +2497,7 @@ private function checkEnum(): void
}

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',
Expand All @@ -2506,7 +2506,7 @@ private function checkEnum(): void
),
);
} else {
$seen_values[] = $case_storage->value;
$seen_values[] = $case_storage->value->value;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
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;
Expand All @@ -67,8 +66,6 @@
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;
Expand Down Expand Up @@ -1034,14 +1031,7 @@ private static function handleEnumValue(
$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 */
Expand Down
7 changes: 3 additions & 4 deletions src/Psalm/Internal/Codebase/ConstantTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,9 @@ public static function resolve(
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();
Expand Down
6 changes: 2 additions & 4 deletions src/Psalm/Internal/Codebase/Methods.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
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;
Expand All @@ -44,7 +45,6 @@
use function count;
use function explode;
use function in_array;
use function is_int;
use function reset;
use function strtolower;

Expand Down Expand Up @@ -629,9 +629,7 @@ public function getMethodReturnType(
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);
Expand Down
17 changes: 6 additions & 11 deletions src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@
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;
Expand Down Expand Up @@ -737,14 +735,11 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool
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 !== []) {
Expand Down Expand Up @@ -1441,9 +1436,9 @@ private function visitEnumDeclaration(

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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
use UnitEnum;
use stdClass;

use function is_int;
use function is_string;
use function reset;
use function strtolower;

Expand Down Expand Up @@ -63,11 +61,11 @@ public static function getGetObjectVarsReturnType(
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);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Psalm/Internal/Type/SimpleAssertionReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2978,7 +2978,7 @@ private static function reconcileValueOf(
$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;
Expand All @@ -2990,7 +2990,7 @@ private static function reconcileValueOf(
}

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 === []) {
Expand Down
6 changes: 4 additions & 2 deletions src/Psalm/Storage/EnumCaseStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,7 +22,7 @@ final class EnumCaseStorage
public $deprecated = false;

/**
* @param int|string|null $value
* @param TLiteralString|TLiteralInt|null $value
*/
public function __construct(
$value,
Expand Down
1 change: 1 addition & 0 deletions src/Psalm/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ public static function getNumericString(): Union
}

/**
* @psalm-suppress PossiblyUnusedMethod
* @param int|string $value
* @return TLiteralString|TLiteralInt
*/
Expand Down
7 changes: 4 additions & 3 deletions src/Psalm/Type/Atomic/TValueOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -37,13 +36,15 @@ private static function getValueTypeForNamedObject(array $cases, TNamedObject $a
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),
));
Expand Down
25 changes: 25 additions & 0 deletions tests/EnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,31 @@ function f(Transport $e): void {
'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',
],
];
}

Expand Down

0 comments on commit c50e822

Please sign in to comment.