diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 9f31a7a44d4..815d4ea315a 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -2519,7 +2519,7 @@ 'enchant_dict_store_replacement' => ['void', 'dictionary'=>'resource', 'misspelled'=>'string', 'correct'=>'string'], 'enchant_dict_suggest' => ['array', 'dictionary'=>'resource', 'word'=>'string'], 'end' => ['mixed|false', '&r_array'=>'array|object'], -'enum_exists' => ['bool', 'class' => 'class-string', 'autoload=' => 'bool'], +'enum_exists' => ['bool', 'class' => 'string', 'autoload=' => 'bool'], 'Error::__clone' => ['void'], 'Error::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?Error'], 'Error::__toString' => ['string'], @@ -7504,7 +7504,7 @@ 'MessageFormatter::parseMessage' => ['array|false', 'locale'=>'string', 'pattern'=>'string', 'source'=>'string'], 'MessageFormatter::setPattern' => ['bool', 'pattern'=>'string'], 'metaphone' => ['string|false', 'string'=>'string', 'max_phonemes='=>'int'], -'method_exists' => ['bool', 'object_or_class'=>'object|class-string|interface-string', 'method'=>'string'], +'method_exists' => ['bool', 'object_or_class'=>'object|class-string|interface-string|enum-string', 'method'=>'string'], 'mhash' => ['string', 'algo'=>'int', 'data'=>'string', 'key='=>'string'], 'mhash_count' => ['int'], 'mhash_get_block_size' => ['int|false', 'algo'=>'int'], diff --git a/dictionaries/CallMap_81_delta.php b/dictionaries/CallMap_81_delta.php index 81bc19a890f..350368df08a 100644 --- a/dictionaries/CallMap_81_delta.php +++ b/dictionaries/CallMap_81_delta.php @@ -17,7 +17,7 @@ return [ 'added' => [ 'array_is_list' => ['bool', 'array' => 'array'], - 'enum_exists' => ['bool', 'class' => 'class-string', 'autoload=' => 'bool'], + 'enum_exists' => ['bool', 'class' => 'string', 'autoload=' => 'bool'], 'fsync' => ['bool', 'stream' => 'resource'], 'fdatasync' => ['bool', 'stream' => 'resource'], 'imageavif' => ['bool', 'image'=>'GdImage', 'file='=>'resource|string|null', 'quality='=>'int', 'speed='=>'int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 362a181db8c..05b9964f8cd 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -12998,7 +12998,7 @@ 'memory_get_peak_usage' => ['int', 'real_usage='=>'bool'], 'memory_get_usage' => ['int', 'real_usage='=>'bool'], 'metaphone' => ['string|false', 'string'=>'string', 'max_phonemes='=>'int'], - 'method_exists' => ['bool', 'object_or_class'=>'object|class-string|interface-string', 'method'=>'string'], + 'method_exists' => ['bool', 'object_or_class'=>'object|class-string|interface-string|enum-string', 'method'=>'string'], 'mhash' => ['string', 'algo'=>'int', 'data'=>'string', 'key='=>'string'], 'mhash_count' => ['int'], 'mhash_get_block_size' => ['int|false', 'algo'=>'int'], diff --git a/docs/annotating_code/type_syntax/atomic_types.md b/docs/annotating_code/type_syntax/atomic_types.md index 5fa957be454..b203957ed61 100644 --- a/docs/annotating_code/type_syntax/atomic_types.md +++ b/docs/annotating_code/type_syntax/atomic_types.md @@ -10,6 +10,7 @@ Atomic types are the basic building block of all type information used in Psalm. - [string](scalar_types.md) - [class-string and class-string<Foo>](scalar_types.md#class-string-interface-string) - [trait-string](scalar_types.md#trait-string) +- [enum-string](scalar_types.md#enum-string) - [callable-string](scalar_types.md#callable-string) - [numeric-string](scalar_types.md#numeric-string) - [literal-string](scalar_types.md#literal-string) diff --git a/docs/annotating_code/type_syntax/scalar_types.md b/docs/annotating_code/type_syntax/scalar_types.md index ff99acc7e2a..6aed0a5212d 100644 --- a/docs/annotating_code/type_syntax/scalar_types.md +++ b/docs/annotating_code/type_syntax/scalar_types.md @@ -42,6 +42,10 @@ You can also parameterize `class-string` with an object name e.g. [`class-string Psalm also supports a `trait-string` annotation denote a trait that exists. +### enum-string + +Psalm also supports a `enum-string` annotation denote an enum that exists. + ### callable-string `callable-string` denotes a string value that has passed an `is_callable` check. diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 123a6cfbc8f..7e60faecaef 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -787,6 +787,14 @@ public static function processFunctionCall( $if_types[$first_var_name] = [['=trait-string']]; } } + } elseif ($class_exists_check_type = self::hasEnumExistsCheck($expr)) { + if ($first_var_name) { + if ($class_exists_check_type === 2) { + $if_types[$first_var_name] = [['enum-string']]; + } else { + $if_types[$first_var_name] = [['=enum-string']]; + } + } } elseif (self::hasInterfaceExistsCheck($expr)) { if ($first_var_name) { $if_types[$first_var_name] = [['interface-string']]; diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index d1611accfa8..df873178cc6 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1026,6 +1026,7 @@ private static function handleLiteralEquality( || $scalar_type === 'interface-string' || $scalar_type === 'callable-string' || $scalar_type === 'trait-string' + || $scalar_type === 'enum-string' ) { return self::handleLiteralEqualityWithString( $statements_analyzer, @@ -1313,6 +1314,7 @@ private static function handleLiteralEqualityWithString( if ($scalar_type === 'class-string' || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' + || $scalar_type === 'enum-string' ) { return $literal_asserted_type_classstring; } @@ -1324,6 +1326,7 @@ private static function handleLiteralEqualityWithString( if ($scalar_type === 'class-string' || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' + || $scalar_type === 'enum-string' ) { return $literal_asserted_type_classstring; } @@ -1347,6 +1350,7 @@ private static function handleLiteralEqualityWithString( if ($scalar_type === 'class-string' || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' + || $scalar_type === 'enum-string' ) { return $literal_asserted_type_classstring; } @@ -1615,6 +1619,7 @@ private static function getCompatibleStringType( if ($scalar_type === 'class-string' || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' + || $scalar_type === 'enum-string' ) { $asserted_type = new Union([new TLiteralClassString($value)]); $asserted_type->from_docblock = $existing_var_type->from_docblock; diff --git a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php index b3387f378d7..82290aca1ba 100644 --- a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php @@ -313,6 +313,7 @@ private static function handleLiteralNegatedEquality( || $scalar_type === 'interface-string' || $scalar_type === 'trait-string' || $scalar_type === 'callable-string' + || $scalar_type === 'enum-string' ) { if ($existing_var_type->hasString()) { if ($existing_string_types = $existing_var_type->getLiteralStrings()) { diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index 20c9308b2bd..a2443527640 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -617,7 +617,10 @@ private static function getTypeFromGenericTree( return new TNonEmptyList($generic_params[0]); } - if ($generic_type_value === 'class-string' || $generic_type_value === 'interface-string') { + if ($generic_type_value === 'class-string' + || $generic_type_value === 'interface-string' + || $generic_type_value === 'enum-string' + ) { $class_name = (string)$generic_params[0]; if (isset($template_type_map[$class_name])) { diff --git a/src/Psalm/Internal/Type/TypeTokenizer.php b/src/Psalm/Internal/Type/TypeTokenizer.php index 020a3b270ca..275aba4fb40 100644 --- a/src/Psalm/Internal/Type/TypeTokenizer.php +++ b/src/Psalm/Internal/Type/TypeTokenizer.php @@ -44,6 +44,7 @@ class TypeTokenizer 'numeric-string' => true, 'class-string' => true, 'interface-string' => true, + 'enum-string' => true, 'trait-string' => true, 'callable-string' => true, 'callable-array' => true, diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 91378836e53..0726c74b037 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -271,6 +271,7 @@ public static function create( case 'class-string': case 'interface-string': + case 'enum-string': return new TClassString(); case 'trait-string': diff --git a/stubs/Reflection.phpstub b/stubs/Reflection.phpstub index 15df6c68d3b..b573c8d17df 100644 --- a/stubs/Reflection.phpstub +++ b/stubs/Reflection.phpstub @@ -13,7 +13,7 @@ class ReflectionClass implements Reflector { public $name; /** - * @param T|class-string|interface-string|trait-string $argument + * @param T|class-string|interface-string|trait-string|enum-string $argument */ public function __construct($argument) {}