From f1c3589856f128c069f5a426abe67153bc054141 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Sat, 2 Mar 2024 17:49:01 +0100 Subject: [PATCH] Initial support for named parameters for callables Fixes vimeo/psalm#10766 --- .../Type/ParseTree/CallableParamTree.php | 6 +++++ src/Psalm/Internal/Type/ParseTreeCreator.php | 6 ++++- src/Psalm/Internal/Type/TypeParser.php | 4 +++- src/Psalm/Storage/FunctionLikeParameter.php | 1 + tests/CallableTest.php | 24 +++++++++++++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php b/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php index 53bc98bbbc8..4fb4ca81592 100644 --- a/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php +++ b/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php @@ -12,4 +12,10 @@ final class CallableParamTree extends ParseTree public bool $variadic = false; public bool $has_default = false; + + /** + * Param name, without the $ prefix + * @var null|non-empty-string + */ + public ?string $name = null; } diff --git a/src/Psalm/Internal/Type/ParseTreeCreator.php b/src/Psalm/Internal/Type/ParseTreeCreator.php index 1b652f09758..cdafb9c0540 100644 --- a/src/Psalm/Internal/Type/ParseTreeCreator.php +++ b/src/Psalm/Internal/Type/ParseTreeCreator.php @@ -258,13 +258,17 @@ private function parseCallableParam(array $current_token, ParseTree $current_par $current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null; } - if (!$current_token || $current_token[0][0] !== '$') { + if (!$current_token || $current_token[0][0] !== '$' || strlen($current_token[0]) < 2) { throw new TypeParseTreeException('Unexpected token after space'); } $new_leaf = new CallableParamTree($current_parent); $new_leaf->has_default = $has_default; $new_leaf->variadic = $variadic; + $potential_name = substr($current_token[0], 1); + if ($potential_name !== false && $potential_name !== '') { + $new_leaf->name = $potential_name; + } if ($current_parent !== $this->current_leaf) { $new_leaf->children = [$this->current_leaf]; diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index fd807f584b1..f79cc3cfdc8 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -1277,6 +1277,7 @@ private static function getTypeFromCallableTree( foreach ($parse_tree->children as $child_tree) { $is_variadic = false; $is_optional = false; + $param_name = ''; if ($child_tree instanceof CallableParamTree) { if (isset($child_tree->children[0])) { @@ -1294,6 +1295,7 @@ private static function getTypeFromCallableTree( $is_variadic = $child_tree->variadic; $is_optional = $child_tree->has_default; + $param_name = $child_tree->name ?? ''; } else { if ($child_tree instanceof Value && strpos($child_tree->value, '$') > 0) { $child_tree->value = preg_replace('/(.+)\$.*/', '$1', $child_tree->value); @@ -1310,7 +1312,7 @@ private static function getTypeFromCallableTree( } $param = new FunctionLikeParameter( - '', + $param_name, false, $tree_type instanceof Union ? $tree_type : new Union([$tree_type]), null, diff --git a/src/Psalm/Storage/FunctionLikeParameter.php b/src/Psalm/Storage/FunctionLikeParameter.php index 39125c366bd..a96b7a1dc08 100644 --- a/src/Psalm/Storage/FunctionLikeParameter.php +++ b/src/Psalm/Storage/FunctionLikeParameter.php @@ -15,6 +15,7 @@ final class FunctionLikeParameter implements HasAttributesInterface, TypeNode use UnserializeMemoryUsageSuppressionTrait; /** + * Parameter name, without `$` * @var string */ public $name; diff --git a/tests/CallableTest.php b/tests/CallableTest.php index fc8c36d212f..e10784b0b3f 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -1908,6 +1908,18 @@ function bar($cb_arg) {} foo("bar");', ], + 'callableWithNamedArguments' => [ + 'code' => <<<'PHP' + [], + 'ignored_issues' => [], + 'php_version' => '8.0', + ], ]; } @@ -2494,6 +2506,18 @@ function int_int_int_int_string(Closure $f): void {} 'ignored_issues' => [], 'php_version' => '8.0', ], + 'callableWithInvalidNamedArguments' => [ + 'code' => <<<'PHP' + 'InvalidNamedArgument', + 'ignored_issues' => [], + 'php_version' => '8.0', + ], ]; } }