diff --git a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php index a7dda5a4f67..375f0e916ce 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php @@ -1311,23 +1311,54 @@ private static function fleshOutAtomicType( return new Type\Atomic\TLiteralClassString($return_type->fq_classlike_name); } - try { - $class_constant = $codebase->classlikes->getConstantForClass( - $return_type->fq_classlike_name, - $return_type->const_name, - \ReflectionProperty::IS_PRIVATE - ); - } catch (\Psalm\Exception\CircularReferenceException $e) { - $class_constant = null; + if (strpos($return_type->const_name, '*') !== false) { + $class_storage = $codebase->classlike_storage_provider->get($return_type->fq_classlike_name); + + $matching_constants = \array_keys($class_storage->class_constant_locations); + + $const_name_part = \substr($return_type->const_name, 0, -1); + + if ($const_name_part) { + $matching_constants = \array_filter( + $matching_constants, + function ($constant_name) use ($const_name_part) { + return $constant_name !== $const_name_part + && \strpos($constant_name, $const_name_part) === 0; + } + ); + } + } else { + $matching_constants = [$return_type->const_name]; } - if ($class_constant) { - if ($class_constant->isSingle()) { - $class_constant = clone $class_constant; + $matching_constant_types = []; + + foreach ($matching_constants as $matching_constant) { + try { + $class_constant = $codebase->classlikes->getConstantForClass( + $return_type->fq_classlike_name, + $matching_constant, + \ReflectionProperty::IS_PRIVATE + ); + } catch (\Psalm\Exception\CircularReferenceException $e) { + $class_constant = null; + } + + if ($class_constant) { + if ($class_constant->isSingle()) { + $class_constant = clone $class_constant; - return array_values($class_constant->getAtomicTypes())[0]; + $matching_constant_types = \array_merge( + \array_values($class_constant->getAtomicTypes()), + $matching_constant_types + ); + } } } + + if ($matching_constant_types) { + return $matching_constant_types; + } } return $return_type; diff --git a/src/Psalm/Internal/Type/ParseTree.php b/src/Psalm/Internal/Type/ParseTree.php index 6030bd6a64b..a300ba2165e 100644 --- a/src/Psalm/Internal/Type/ParseTree.php +++ b/src/Psalm/Internal/Type/ParseTree.php @@ -547,7 +547,7 @@ public static function createFromTokens(array $type_tokens) $nexter_token = $i + 2 < $c ? $type_tokens[$i + 2] : null; if (!$nexter_token - || (!preg_match('/^[a-zA-Z_][a-zA-Z_0-9]*$/', $nexter_token[0]) + || (!preg_match('/^([a-zA-Z_][a-zA-Z_0-9]*\*?|\*)$/', $nexter_token[0]) && strtolower($nexter_token[0]) !== 'class') ) { throw new TypeParseTreeException( diff --git a/src/Psalm/Internal/Visitor/SimpleNameResolver.php b/src/Psalm/Internal/Visitor/SimpleNameResolver.php index 68f60a51290..f3ebec30c56 100644 --- a/src/Psalm/Internal/Visitor/SimpleNameResolver.php +++ b/src/Psalm/Internal/Visitor/SimpleNameResolver.php @@ -197,7 +197,7 @@ private function resolveType($node) * Resolve name, according to name resolver options. * * @param Name $name Function or constant name to resolve - * @param int $type One of Stmt\Use_::TYPE_* + * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* * * @return Name Resolved name, or original name with attribute */ diff --git a/tests/ConstantTest.php b/tests/ConstantTest.php index f5c1aaf312f..25e99a2b4d6 100644 --- a/tests/ConstantTest.php +++ b/tests/ConstantTest.php @@ -622,6 +622,42 @@ class A { public $foo = "a"; }', ], + 'wildcardEnum' => [ + ' [ + ' 'InvalidArgument', ], + 'wildcardEnumBadValue' => [ + ' 'InvalidArgument' + ], ]; } }