diff --git a/src/Psalm/Internal/Type/ParseTreeCreator.php b/src/Psalm/Internal/Type/ParseTreeCreator.php index ca2eea6cdff..0ea26080433 100644 --- a/src/Psalm/Internal/Type/ParseTreeCreator.php +++ b/src/Psalm/Internal/Type/ParseTreeCreator.php @@ -235,6 +235,46 @@ private function createMethodParam(array $current_token, ParseTree $current_pare $this->current_leaf = $new_parent_leaf; } + /** + * @param array{0: string, 1: int, 2?: string} $current_token + */ + private function parseCallableParam(array $current_token, ParseTree $current_parent): void + { + $variadic = false; + $has_default = false; + + if ($current_token[0] === '&') { + ++$this->t; + $current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null; + } elseif ($current_token[0] === '...') { + $variadic = true; + + ++$this->t; + $current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null; + } elseif ($current_token[0] === '=') { + $has_default = true; + + ++$this->t; + $current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null; + } + + if (!$current_token || $current_token[0][0] !== '$') { + throw new TypeParseTreeException('Unexpected token after space'); + } + + $new_leaf = new CallableParamTree($current_parent); + $new_leaf->has_default = $has_default; + $new_leaf->variadic = $variadic; + + if ($current_parent !== $this->current_leaf) { + $new_leaf->children = [$this->current_leaf]; + array_pop($current_parent->children); + } + $current_parent->children[] = $new_leaf; + + $this->current_leaf = $new_leaf; + } + private function handleLessThan(): void { if (!$this->current_leaf instanceof FieldEllipsis) { @@ -565,11 +605,15 @@ private function handleSpace(): void throw new TypeParseTreeException('Unexpected space'); } - ++$this->t; if ($current_parent instanceof MethodTree) { + ++$this->t; $this->createMethodParam($next_token, $current_parent); } + if ($current_parent instanceof CallableTree) { + ++$this->t; + $this->parseCallableParam($next_token, $current_parent); + } } private function handleQuestionMark(): void diff --git a/tests/TypeAnnotationTest.php b/tests/TypeAnnotationTest.php index 459cc803489..29ee8f6581f 100644 --- a/tests/TypeAnnotationTest.php +++ b/tests/TypeAnnotationTest.php @@ -686,11 +686,14 @@ class Foo { * @psalm-type B callable(int, int=): string * @psalm-type C callable(int $a, string $b): void * @psalm-type D callable(string $c): mixed - * @psalm-type E callable(float...): (int|null) - * @psalm-type F callable(float ...$d): (int|null) - * @psalm-type G callable(array): array - * @psalm-type H callable(array $e): array - * @psalm-type I \Closure(int, int): string + * @psalm-type E callable(string $c): mixed + * @psalm-type F callable(float...): (int|null) + * @psalm-type G callable(float ...$d): (int|null) + * @psalm-type H callable(array): array + * @psalm-type I callable(array $e): array + * @psalm-type J callable(array ...): string + * @psalm-type K callable(array ...$e): string + * @psalm-type L \Closure(int, int): string * * @method ma(): A * @method mb(): B @@ -701,6 +704,9 @@ class Foo { * @method mg(): G * @method mh(): H * @method mi(): I + * @method mj(): J + * @method mk(): K + * @method ml(): L */ class Foo { public function __call(string $method, array $params) { return 1; } @@ -716,17 +722,23 @@ public function __call(string $method, array $params) { return 1; } $output_mg = $foo->mg(); $output_mh = $foo->mh(); $output_mi = $foo->mi(); + $output_mj = $foo->mj(); + $output_mk = $foo->mk(); + $output_ml = $foo->ml(); ', 'assertions' => [ '$output_ma===' => 'callable(int, int):string', '$output_mb===' => 'callable(int, int=):string', '$output_mc===' => 'callable(int, string):void', '$output_md===' => 'callable(string):mixed', - '$output_me===' => 'callable(float...):(int|null)', + '$output_me===' => 'callable(string):mixed', '$output_mf===' => 'callable(float...):(int|null)', - '$output_mg===' => 'callable(array):array', - '$output_mh===' => 'callable(array):array', - '$output_mi===' => 'Closure(int, int):string', + '$output_mg===' => 'callable(float...):(int|null)', + '$output_mh===' => 'callable(array):array', + '$output_mi===' => 'callable(array):array', + '$output_mj===' => 'callable(array...):string', + '$output_mk===' => 'callable(array...):string', + '$output_ml===' => 'Closure(int, int):string', ], ], 'unionOfStringsContainingBraceChar' => [