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

Merge pull request #8283 from AndrolGenhald/feature/value-of-backed-enum

Allow `value-of` to work with backed enums (fixes #7874).
This commit is contained in:
orklah 2022-07-20 18:03:58 +02:00 committed by GitHub
commit 85fe7e8bcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 174 additions and 64 deletions

View File

@ -42,7 +42,7 @@
- `Psalm\Type\Atomic\TIntRange` - `Psalm\Type\Atomic\TIntRange`
- `Psalm\Type\Atomic\TIterable` - `Psalm\Type\Atomic\TIterable`
- `Psalm\Type\Atomic\TKeyedArray` - `Psalm\Type\Atomic\TKeyedArray`
- `Psalm\Type\Atomic\TKeyOfArray` - `Psalm\Type\Atomic\TKeyOf`
- `Psalm\Type\Atomic\TList` - `Psalm\Type\Atomic\TList`
- `Psalm\Type\Atomic\TLiteralClassString` - `Psalm\Type\Atomic\TLiteralClassString`
- `Psalm\Type\Atomic\TLowercaseString` - `Psalm\Type\Atomic\TLowercaseString`
@ -64,7 +64,7 @@
- `Psalm\Type\Atomic\TTraitString` - `Psalm\Type\Atomic\TTraitString`
- `Psalm\Type\Atomic\TTrue` - `Psalm\Type\Atomic\TTrue`
- `Psalm\Type\Atomic\TTypeAlias` - `Psalm\Type\Atomic\TTypeAlias`
- `Psalm\Type\Atomic\TValueOfArray` - `Psalm\Type\Atomic\TValueOf`
- `Psalm\Type\Atomic\TVoid` - `Psalm\Type\Atomic\TVoid`
- `Psalm\Type\Union` - `Psalm\Type\Union`
@ -92,7 +92,7 @@
- `Psalm\Type\Atomic\TInt` - `Psalm\Type\Atomic\TInt`
- `Psalm\Type\Atomic\TIterable` - `Psalm\Type\Atomic\TIterable`
- `Psalm\Type\Atomic\TKeyedArray` - `Psalm\Type\Atomic\TKeyedArray`
- `Psalm\Type\Atomic\TKeyOfArray` - `Psalm\Type\Atomic\TKeyOf`
- `Psalm\Type\Atomic\TList` - `Psalm\Type\Atomic\TList`
- `Psalm\Type\Atomic\TLiteralClassString` - `Psalm\Type\Atomic\TLiteralClassString`
- `Psalm\Type\Atomic\TMixed` - `Psalm\Type\Atomic\TMixed`
@ -109,7 +109,7 @@
- `Psalm\Type\Atomic\TTemplateParam` - `Psalm\Type\Atomic\TTemplateParam`
- `Psalm\Type\Atomic\TTraitString` - `Psalm\Type\Atomic\TTraitString`
- `Psalm\Type\Atomic\TTypeAlias` - `Psalm\Type\Atomic\TTypeAlias`
- `Psalm\Type\Atomic\TValueOfArray` - `Psalm\Type\Atomic\TValueOf`
- `Psalm\Type\Atomic\TVoid` - `Psalm\Type\Atomic\TVoid`
- `Psalm\Type\Union` - `Psalm\Type\Union`
- While not a BC break per se, all classes / interfaces / traits / enums under - While not a BC break per se, all classes / interfaces / traits / enums under
@ -155,8 +155,8 @@
- [BC] Atomic::getId() has now a first param $exact. Calling the method with false will return a less detailed version of the type in some cases (similarly to what `__toString` used to return) - [BC] Atomic::getId() has now a first param $exact. Calling the method with false will return a less detailed version of the type in some cases (similarly to what `__toString` used to return)
- [BC] To remove a variable from the context, use `Context::remove()`. Calling - [BC] To remove a variable from the context, use `Context::remove()`. Calling
`unset($context->vars_in_scope[$var_id])` can cause problems when using references. `unset($context->vars_in_scope[$var_id])` can cause problems when using references.
- [BC] `TKeyOfClassConstant` has been renamed to `TKeyOfArray`. - [BC] `TKeyOfClassConstant` has been renamed to `TKeyOf`.
- [BC] `TValueOfClassConstant` has been renamed to `TValueOfArray`. - [BC] `TValueOfClassConstant` has been renamed to `TValueOf`.
- [BC] `TKeyOfTemplate` base class has been changed from `Scalar` to `Atomic`. - [BC] `TKeyOfTemplate` base class has been changed from `Scalar` to `Atomic`.
- [BC] Class `Psalm\FileManipulation` became final - [BC] Class `Psalm\FileManipulation` became final
- [BC] Class `Psalm\Context` became final - [BC] Class `Psalm\Context` became final
@ -742,13 +742,13 @@
- [BC] Class `Psalm\Type\Atomic\TLiteralInt` became final - [BC] Class `Psalm\Type\Atomic\TLiteralInt` became final
- [BC] Class `Psalm\Type\Atomic\TTrue` became final - [BC] Class `Psalm\Type\Atomic\TTrue` became final
- [BC] Class `Psalm\Type\Atomic\TDependentGetClass` became final - [BC] Class `Psalm\Type\Atomic\TDependentGetClass` became final
- [BC] Class `Psalm\Type\Atomic\TValueOfArray` became final - [BC] Class `Psalm\Type\Atomic\TValueOf` became final
- [BC] Class `Psalm\Type\Atomic\TGenericObject` became final - [BC] Class `Psalm\Type\Atomic\TGenericObject` became final
- [BC] Class `Psalm\Type\Atomic\TNonEmptyLowercaseString` became final - [BC] Class `Psalm\Type\Atomic\TNonEmptyLowercaseString` became final
- [BC] Class `Psalm\Type\Atomic\TEnumCase` became final - [BC] Class `Psalm\Type\Atomic\TEnumCase` became final
- [BC] Class `Psalm\Type\Atomic\TCallableKeyedArray` became final - [BC] Class `Psalm\Type\Atomic\TCallableKeyedArray` became final
- [BC] Class `Psalm\Type\Atomic\TDependentGetDebugType` became final - [BC] Class `Psalm\Type\Atomic\TDependentGetDebugType` became final
- [BC] Class `Psalm\Type\Atomic\TKeyOfArray` became final - [BC] Class `Psalm\Type\Atomic\TKeyOf` became final
- [BC] Class `Psalm\Type\Atomic\TNonspecificLiteralInt` became final - [BC] Class `Psalm\Type\Atomic\TNonspecificLiteralInt` became final
- [BC] Class `Psalm\Type\Atomic\TObjectWithProperties` became final - [BC] Class `Psalm\Type\Atomic\TObjectWithProperties` became final
- [BC] Class `Psalm\Type\Atomic\TTemplateValueOf` became final - [BC] Class `Psalm\Type\Atomic\TTemplateValueOf` became final

View File

@ -49,15 +49,15 @@ The classes are as follows:
`TIntMaskOf` - as above, but used with a reference to constants in code `int-mask-of<MyClass::CLASS_CONSTANT_*>` will corresponds to `1|2|3|4|5|6|7` if there are three constant 1, 2 and 4 `TIntMaskOf` - as above, but used with a reference to constants in code `int-mask-of<MyClass::CLASS_CONSTANT_*>` will corresponds to `1|2|3|4|5|6|7` if there are three constant 1, 2 and 4
`TKeyOfArray` - Represents an offset of an array (e.g. `key-of<MyClass::CLASS_CONSTANT>`). `TKeyOf` - Represents an offset of an array (e.g. `key-of<MyClass::CLASS_CONSTANT>`).
`TValueOfArray` - Represents a value of an array (e.g. `value-of<MyClass::CLASS_CONSTANT>`). `TValueOf` - Represents a value of an array or enum (e.g. `value-of<MyClass::CLASS_CONSTANT>`).
`TTemplateIndexedAccess` - To be documented `TTemplateIndexedAccess` - To be documented
`TTemplateKeyOf` - Represents the type used when using TKeyOfArray when the type of the array is a template `TTemplateKeyOf` - Represents the type used when using TKeyOf when the type of the array is a template
`TTemplateValueOf` - Represents the type used when using TValueOfArray when the type of the array is a template `TTemplateValueOf` - Represents the type used when using TValueOf when the type of the array or enum is a template
`TPropertiesOf` - Represents properties and their types of a class as a keyed array (e.g. `properties-of<MyClass>`) `TPropertiesOf` - Represents properties and their types of a class as a keyed array (e.g. `properties-of<MyClass>`)

View File

@ -19,7 +19,7 @@ use Psalm\Type\Atomic\TEmptyMixed;
use Psalm\Type\Atomic\TEnumCase; use Psalm\Type\Atomic\TEnumCase;
use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TKeyOfArray; use Psalm\Type\Atomic\TKeyOf;
use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TList;
use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TLiteralString;
@ -36,7 +36,7 @@ use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateKeyOf; use Psalm\Type\Atomic\TTemplateKeyOf;
use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Atomic\TTemplateValueOf; use Psalm\Type\Atomic\TTemplateValueOf;
use Psalm\Type\Atomic\TValueOfArray; use Psalm\Type\Atomic\TValueOf;
use function array_merge; use function array_merge;
use function array_values; use function array_values;
@ -341,7 +341,7 @@ class AtomicTypeComparator
} }
if ($input_type_part instanceof TTemplateKeyOf) { if ($input_type_part instanceof TTemplateKeyOf) {
$array_key_type = TKeyOfArray::getArrayKeyType($input_type_part->as); $array_key_type = TKeyOf::getArrayKeyType($input_type_part->as);
if ($array_key_type === null) { if ($array_key_type === null) {
return false; return false;
} }
@ -375,7 +375,7 @@ class AtomicTypeComparator
} }
if ($input_type_part instanceof TTemplateValueOf) { if ($input_type_part instanceof TTemplateValueOf) {
$array_value_type = TValueOfArray::getArrayValueType($input_type_part->as); $array_value_type = TValueOf::getValueType($input_type_part->as, $codebase);
if ($array_value_type === null) { if ($array_value_type === null) {
return false; return false;
} }

View File

@ -11,7 +11,7 @@ use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TConditional; use Psalm\Type\Atomic\TConditional;
use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TKeyOfArray; use Psalm\Type\Atomic\TKeyOf;
use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TLiteralString;
@ -26,7 +26,7 @@ use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Atomic\TTemplateParamClass; use Psalm\Type\Atomic\TTemplateParamClass;
use Psalm\Type\Atomic\TTemplatePropertiesOf; use Psalm\Type\Atomic\TTemplatePropertiesOf;
use Psalm\Type\Atomic\TTemplateValueOf; use Psalm\Type\Atomic\TTemplateValueOf;
use Psalm\Type\Atomic\TValueOfArray; use Psalm\Type\Atomic\TValueOf;
use Psalm\Type\Union; use Psalm\Type\Union;
use UnexpectedValueException; use UnexpectedValueException;
@ -345,15 +345,15 @@ class TemplateInferredTypeReplacer
); );
if ($atomic_type instanceof TTemplateKeyOf if ($atomic_type instanceof TTemplateKeyOf
&& TKeyOfArray::isViableTemplateType($template_type) && TKeyOf::isViableTemplateType($template_type)
) { ) {
return new TKeyOfArray(clone $template_type); return new TKeyOf(clone $template_type);
} }
if ($atomic_type instanceof TTemplateValueOf if ($atomic_type instanceof TTemplateValueOf
&& TValueOfArray::isViableTemplateType($template_type) && TValueOf::isViableTemplateType($template_type)
) { ) {
return new TValueOfArray(clone $template_type); return new TValueOf(clone $template_type);
} }
return null; return null;

View File

@ -22,7 +22,7 @@ use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIntMask; use Psalm\Type\Atomic\TIntMask;
use Psalm\Type\Atomic\TIntMaskOf; use Psalm\Type\Atomic\TIntMaskOf;
use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TKeyOfArray; use Psalm\Type\Atomic\TKeyOf;
use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TList;
use Psalm\Type\Atomic\TLiteralClassString; use Psalm\Type\Atomic\TLiteralClassString;
@ -34,7 +34,7 @@ use Psalm\Type\Atomic\TObjectWithProperties;
use Psalm\Type\Atomic\TPropertiesOf; use Psalm\Type\Atomic\TPropertiesOf;
use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Atomic\TTypeAlias; use Psalm\Type\Atomic\TTypeAlias;
use Psalm\Type\Atomic\TValueOfArray; use Psalm\Type\Atomic\TValueOf;
use Psalm\Type\Atomic\TVoid; use Psalm\Type\Atomic\TVoid;
use Psalm\Type\Union; use Psalm\Type\Union;
use ReflectionProperty; use ReflectionProperty;
@ -363,10 +363,10 @@ class TypeExpander
return [$return_type]; return [$return_type];
} }
if ($return_type instanceof TKeyOfArray if ($return_type instanceof TKeyOf
|| $return_type instanceof TValueOfArray || $return_type instanceof TValueOf
) { ) {
return self::expandKeyOfValueOfArray( return self::expandKeyOfValueOf(
$codebase, $codebase,
$return_type, $return_type,
$self_class, $self_class,
@ -965,11 +965,11 @@ class TypeExpander
} }
/** /**
* @param TKeyOfArray|TValueOfArray $return_type * @param TKeyOf|TValueOf $return_type
* @param string|TNamedObject|TTemplateParam|null $static_class_type * @param string|TNamedObject|TTemplateParam|null $static_class_type
* @return non-empty-list<Atomic> * @return non-empty-list<Atomic>
*/ */
private static function expandKeyOfValueOfArray( private static function expandKeyOfValueOf(
Codebase $codebase, Codebase $codebase,
Atomic &$return_type, Atomic &$return_type,
?string $self_class, ?string $self_class,
@ -1025,12 +1025,12 @@ class TypeExpander
if (!$constant_type if (!$constant_type
|| ( || (
$return_type instanceof TKeyOfArray $return_type instanceof TKeyOf
&& !TKeyOfArray::isViableTemplateType($constant_type) && !TKeyOf::isViableTemplateType($constant_type)
) )
|| ( || (
$return_type instanceof TValueOfArray $return_type instanceof TValueOf
&& !TValueOfArray::isViableTemplateType($constant_type) && !TValueOf::isViableTemplateType($constant_type)
) )
) { ) {
if ($throw_on_unresolvable_constant) { if ($throw_on_unresolvable_constant) {
@ -1049,10 +1049,10 @@ class TypeExpander
return [$return_type]; return [$return_type];
} }
if ($return_type instanceof TKeyOfArray) { if ($return_type instanceof TKeyOf) {
$new_return_types = TKeyOfArray::getArrayKeyType(new Union($type_atomics)); $new_return_types = TKeyOf::getArrayKeyType(new Union($type_atomics));
} else { } else {
$new_return_types = TValueOfArray::getArrayValueType(new Union($type_atomics)); $new_return_types = TValueOf::getValueType(new Union($type_atomics), $codebase);
} }
if ($new_return_types === null) { if ($new_return_types === null) {
return [$return_type]; return [$return_type];

View File

@ -41,7 +41,7 @@ use Psalm\Type\Atomic\TIntMask;
use Psalm\Type\Atomic\TIntMaskOf; use Psalm\Type\Atomic\TIntMaskOf;
use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TIntRange;
use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TKeyOfArray; use Psalm\Type\Atomic\TKeyOf;
use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TList;
use Psalm\Type\Atomic\TLiteralClassString; use Psalm\Type\Atomic\TLiteralClassString;
@ -63,7 +63,7 @@ use Psalm\Type\Atomic\TTemplateParamClass;
use Psalm\Type\Atomic\TTemplatePropertiesOf; use Psalm\Type\Atomic\TTemplatePropertiesOf;
use Psalm\Type\Atomic\TTemplateValueOf; use Psalm\Type\Atomic\TTemplateValueOf;
use Psalm\Type\Atomic\TTypeAlias; use Psalm\Type\Atomic\TTypeAlias;
use Psalm\Type\Atomic\TValueOfArray; use Psalm\Type\Atomic\TValueOf;
use Psalm\Type\TypeNode; use Psalm\Type\TypeNode;
use Psalm\Type\Union; use Psalm\Type\Union;
@ -743,13 +743,13 @@ class TypeParser
); );
} }
if (!TKeyOfArray::isViableTemplateType($generic_params[0])) { if (!TKeyOf::isViableTemplateType($generic_params[0])) {
throw new TypeParseTreeException( throw new TypeParseTreeException(
'Untemplated key-of param ' . $param_name . ' should be an array' 'Untemplated key-of param ' . $param_name . ' should be an array'
); );
} }
return new TKeyOfArray($generic_params[0]); return new TKeyOf($generic_params[0]);
} }
if ($generic_type_value === 'value-of') { if ($generic_type_value === 'value-of') {
@ -765,13 +765,13 @@ class TypeParser
); );
} }
if (!TValueOfArray::isViableTemplateType($generic_params[0])) { if (!TValueOf::isViableTemplateType($generic_params[0])) {
throw new TypeParseTreeException( throw new TypeParseTreeException(
'Untemplated value-of param ' . $param_name . ' should be an array' 'Untemplated value-of param ' . $param_name . ' should be an array'
); );
} }
return new TValueOfArray($generic_params[0]); return new TValueOf($generic_params[0]);
} }
if ($generic_type_value === 'int-mask') { if ($generic_type_value === 'int-mask') {
@ -842,8 +842,8 @@ class TypeParser
$param_type = $param_union_types[0]; $param_type = $param_union_types[0];
if (!$param_type instanceof TClassConstant if (!$param_type instanceof TClassConstant
&& !$param_type instanceof TValueOfArray && !$param_type instanceof TValueOf
&& !$param_type instanceof TKeyOfArray && !$param_type instanceof TKeyOf
) { ) {
throw new TypeParseTreeException( throw new TypeParseTreeException(
'Invalid reference passed to int-mask-of' 'Invalid reference passed to int-mask-of'

View File

@ -11,11 +11,11 @@ use Psalm\Type\Atomic;
*/ */
final class TIntMaskOf extends TInt final class TIntMaskOf extends TInt
{ {
/** @var TClassConstant|TKeyOfArray|TValueOfArray */ /** @var TClassConstant|TKeyOf|TValueOf */
public $value; public $value;
/** /**
* @param TClassConstant|TKeyOfArray|TValueOfArray $value * @param TClassConstant|TKeyOf|TValueOf $value
*/ */
public function __construct(Atomic $value) public function __construct(Atomic $value)
{ {

View File

@ -11,7 +11,7 @@ use function array_values;
/** /**
* Represents an offset of an array. * Represents an offset of an array.
*/ */
final class TKeyOfArray extends TArrayKey final class TKeyOf extends TArrayKey
{ {
/** @var Union */ /** @var Union */
public $type; public $type;

View File

@ -9,7 +9,7 @@ use Psalm\Type\Atomic;
use Psalm\Type\Union; use Psalm\Type\Union;
/** /**
* Represents the type used when using TKeyOfArray when the type of the array is a template * Represents the type used when using TKeyOf when the type of the array is a template
*/ */
final class TTemplateKeyOf extends Atomic final class TTemplateKeyOf extends Atomic
{ {

View File

@ -9,7 +9,7 @@ use Psalm\Type\Atomic;
use Psalm\Type\Union; use Psalm\Type\Union;
/** /**
* Represents the type used when using TValueOfArray when the type of the array is a template * Represents the type used when using TValueOf when the type of the array or enum is a template
*/ */
final class TTemplateValueOf extends Atomic final class TTemplateValueOf extends Atomic
{ {

View File

@ -2,16 +2,21 @@
namespace Psalm\Type\Atomic; namespace Psalm\Type\Atomic;
use Psalm\Codebase;
use Psalm\Internal\Codebase\ConstantTypeResolver;
use Psalm\Storage\EnumCaseStorage;
use Psalm\Type\Atomic; use Psalm\Type\Atomic;
use Psalm\Type\Union; use Psalm\Type\Union;
use function array_merge; use function array_map;
use function array_values; use function array_values;
use function assert;
use function count;
/** /**
* Represents a value of an array. * Represents a value of an array or enum.
*/ */
final class TValueOfArray extends Atomic final class TValueOf extends Atomic
{ {
/** @var Union */ /** @var Union */
public $type; public $type;
@ -56,6 +61,7 @@ final class TValueOfArray extends Atomic
&& !$type instanceof TKeyedArray && !$type instanceof TKeyedArray
&& !$type instanceof TList && !$type instanceof TList
&& !$type instanceof TPropertiesOf && !$type instanceof TPropertiesOf
&& !$type instanceof TNamedObject
) { ) {
return false; return false;
} }
@ -63,39 +69,58 @@ final class TValueOfArray extends Atomic
return true; return true;
} }
public static function getArrayValueType( public static function getValueType(
Union $type, Union $type,
Codebase $codebase,
bool $keep_template_params = false bool $keep_template_params = false
): ?Union { ): ?Union {
$value_types = []; $value_types = [];
foreach ($type->getAtomicTypes() as $atomic_type) { foreach ($type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof TArray) { if ($atomic_type instanceof TArray) {
$array_value_atomics = $atomic_type->type_params[1]; $value_atomics = $atomic_type->type_params[1];
} elseif ($atomic_type instanceof TList) { } elseif ($atomic_type instanceof TList) {
$array_value_atomics = $atomic_type->type_param; $value_atomics = $atomic_type->type_param;
} elseif ($atomic_type instanceof TKeyedArray) { } elseif ($atomic_type instanceof TKeyedArray) {
$array_value_atomics = $atomic_type->getGenericValueType(); $value_atomics = $atomic_type->getGenericValueType();
} elseif ($atomic_type instanceof TTemplateParam) { } elseif ($atomic_type instanceof TTemplateParam) {
if ($keep_template_params) { if ($keep_template_params) {
$array_value_atomics = new Union([$atomic_type]); $value_atomics = new Union([$atomic_type]);
} else { } else {
$array_value_atomics = static::getArrayValueType( $value_atomics = static::getValueType(
$atomic_type->as, $atomic_type->as,
$codebase,
$keep_template_params $keep_template_params
); );
if ($array_value_atomics === null) { if ($value_atomics === null) {
continue; continue;
} }
} }
} elseif ($atomic_type instanceof TNamedObject
&& $codebase->classlike_storage_provider->has($atomic_type->value)
) {
$class_storage = $codebase->classlike_storage_provider->get($atomic_type->value);
$cases = $class_storage->enum_cases;
if (!$class_storage->is_enum
|| $class_storage->enum_type === null
|| count($cases) === 0
) {
// Invalid value-of, skip
continue;
}
$value_atomics = new Union(array_map(
function (EnumCaseStorage $case): Atomic {
assert($case->value !== null); // Backed enum must have a value
return ConstantTypeResolver::getLiteralTypeFromScalarValue($case->value);
},
array_values($cases),
));
} else { } else {
continue; continue;
} }
$value_types = array_merge( $value_types = [...$value_types, ...array_values($value_atomics->getAtomicTypes())];
$value_types,
array_values($array_value_atomics->getAtomicTypes())
);
} }
if ($value_types === []) { if ($value_types === []) {

View File

@ -7,7 +7,7 @@ namespace Psalm\Tests;
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait; use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait; use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
class ValueOfArrayTest extends TestCase class ValueOfTest extends TestCase
{ {
use InvalidCodeAnalysisTestTrait; use InvalidCodeAnalysisTestTrait;
use ValidCodeAnalysisTestTrait; use ValidCodeAnalysisTestTrait;
@ -141,6 +141,74 @@ class ValueOfArrayTest extends TestCase
} }
', ',
], ],
'valueOfStringEnum' => [
'code' => '<?php
enum Foo: string
{
case Foo = "foo";
case Bar = "bar";
}
/** @param value-of<Foo> $arg */
function foobar(string $arg): void
{
/** @psalm-check-type-exact $arg = "foo"|"bar" */;
}
/** @var Foo */
$foo = Foo::Foo;
foobar($foo->value);
',
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.1',
],
'valueOfIntEnum' => [
'code' => '<?php
enum Foo: int
{
case Foo = 2;
case Bar = 3;
}
/** @param value-of<Foo> $arg */
function foobar(int $arg): void
{
/** @psalm-check-type-exact $arg = 2|3 */;
}
/** @var Foo */
$foo = Foo::Foo;
foobar($foo->value);
',
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.1',
],
'valueOfEnumUnion' => [
'code' => '<?php
enum Foo: int
{
case Foo = 2;
case Bar = 3;
}
enum Bar: string
{
case Foo = "foo";
case Bar = "bar";
}
/** @param value-of<Foo|Bar> $arg */
function foobar(int|string $arg): void
{
/** @psalm-check-type-exact $arg = 2|3|"foo"|"bar" */;
}
',
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.1',
],
]; ];
} }
@ -212,6 +280,23 @@ class ValueOfArrayTest extends TestCase
', ',
'error_message' => 'InvalidReturnStatement' 'error_message' => 'InvalidReturnStatement'
], ],
'valueOfUnitEnum' => [
'code' => '<?php
enum Foo
{
case Foo;
case Bar;
}
/** @param value-of<Foo> $arg */
function foobar(string $arg): void {}
',
// TODO turn this into an InvalidDocblock with a better error message. This is difficult because it
// has to happen after scanning has finished, otherwise the class might not have been scanned yet.
'error_message' => 'MismatchingDocblockParamType',
'ignored_issues' => [],
'php_version' => '8.1',
],
]; ];
} }
} }