From f1896494ecbd2d83010dae19cd3c115b07074bd8 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:30:12 +0200 Subject: [PATCH] Reject `p(p a, &block => value)` and similar They were being parsed as `p((p a, &block) => value)`. When we get to this point, we must not just have parsed a command call, always consuming the `=>` is not correct. Closes [Bug #21622] --- snapshots/command_method_call.txt | 255 +++++++++++++++++--- src/prism.c | 21 +- test/prism/errors/command_calls_35.txt | 41 ++++ test/prism/fixtures/command_method_call.txt | 6 + 4 files changed, 285 insertions(+), 38 deletions(-) create mode 100644 test/prism/errors/command_calls_35.txt diff --git a/snapshots/command_method_call.txt b/snapshots/command_method_call.txt index 87db5a9acf..df77e7ac51 100644 --- a/snapshots/command_method_call.txt +++ b/snapshots/command_method_call.txt @@ -1,10 +1,10 @@ -@ ProgramNode (location: (1,0)-(41,10)) +@ ProgramNode (location: (1,0)-(47,27)) ├── flags: ∅ ├── locals: [:foo, :bar] └── statements: - @ StatementsNode (location: (1,0)-(41,10)) + @ StatementsNode (location: (1,0)-(47,27)) ├── flags: ∅ - └── body: (length: 21) + └── body: (length: 24) ├── @ CallNode (location: (1,0)-(1,5)) │ ├── flags: newline, ignore_visibility │ ├── receiver: ∅ @@ -737,39 +737,220 @@ │ │ ├── closing_loc: ∅ │ │ └── block: ∅ │ └── operator_loc: (39,7)-(39,9) = "or" - └── @ CallNode (location: (41,0)-(41,10)) - ├── flags: newline - ├── receiver: - │ @ CallNode (location: (41,4)-(41,10)) - │ ├── flags: ∅ - │ ├── receiver: - │ │ @ CallNode (location: (41,5)-(41,10)) - │ │ ├── flags: ignore_visibility - │ │ ├── receiver: ∅ - │ │ ├── call_operator_loc: ∅ - │ │ ├── name: :foo - │ │ ├── message_loc: (41,5)-(41,8) = "foo" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: - │ │ │ @ ArgumentsNode (location: (41,9)-(41,10)) - │ │ │ ├── flags: ∅ - │ │ │ └── arguments: (length: 1) - │ │ │ └── @ IntegerNode (location: (41,9)-(41,10)) - │ │ │ ├── flags: static_literal, decimal - │ │ │ └── value: 1 - │ │ ├── closing_loc: ∅ - │ │ └── block: ∅ - │ ├── call_operator_loc: ∅ - │ ├── name: :! - │ ├── message_loc: (41,4)-(41,5) = "!" - │ ├── opening_loc: ∅ - │ ├── arguments: ∅ - │ ├── closing_loc: ∅ - │ └── block: ∅ + ├── @ CallNode (location: (41,0)-(41,10)) + │ ├── flags: newline + │ ├── receiver: + │ │ @ CallNode (location: (41,4)-(41,10)) + │ │ ├── flags: ∅ + │ │ ├── receiver: + │ │ │ @ CallNode (location: (41,5)-(41,10)) + │ │ │ ├── flags: ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :foo + │ │ │ ├── message_loc: (41,5)-(41,8) = "foo" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (41,9)-(41,10)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ IntegerNode (location: (41,9)-(41,10)) + │ │ │ │ ├── flags: static_literal, decimal + │ │ │ │ └── value: 1 + │ │ │ ├── closing_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :! + │ │ ├── message_loc: (41,4)-(41,5) = "!" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :! + │ ├── message_loc: (41,0)-(41,3) = "not" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (43,0)-(43,26)) + │ ├── flags: newline, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (43,0)-(43,3) = "foo" + │ ├── opening_loc: (43,3)-(43,4) = "(" + │ ├── arguments: + │ │ @ ArgumentsNode (location: (43,4)-(43,25)) + │ │ ├── flags: ∅ + │ │ └── arguments: (length: 1) + │ │ └── @ CallNode (location: (43,4)-(43,25)) + │ │ ├── flags: ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :bar + │ │ ├── message_loc: (43,4)-(43,7) = "bar" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (43,8)-(43,25)) + │ │ │ ├── flags: contains_keywords + │ │ │ └── arguments: (length: 2) + │ │ │ ├── @ CallNode (location: (43,8)-(43,11)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :baz + │ │ │ │ ├── message_loc: (43,8)-(43,11) = "baz" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ └── @ KeywordHashNode (location: (43,13)-(43,25)) + │ │ │ ├── flags: ∅ + │ │ │ └── elements: (length: 1) + │ │ │ └── @ AssocNode (location: (43,13)-(43,25)) + │ │ │ ├── flags: ∅ + │ │ │ ├── key: + │ │ │ │ @ CallNode (location: (43,13)-(43,16)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :key + │ │ │ │ ├── message_loc: (43,13)-(43,16) = "key" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── value: + │ │ │ │ @ CallNode (location: (43,20)-(43,25)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :value + │ │ │ │ ├── message_loc: (43,20)-(43,25) = "value" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ └── operator_loc: (43,17)-(43,19) = "=>" + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── closing_loc: (43,25)-(43,26) = ")" + │ └── block: ∅ + ├── @ CallNode (location: (45,0)-(45,26)) + │ ├── flags: newline, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (45,0)-(45,3) = "foo" + │ ├── opening_loc: (45,3)-(45,4) = "(" + │ ├── arguments: + │ │ @ ArgumentsNode (location: (45,4)-(45,25)) + │ │ ├── flags: ∅ + │ │ └── arguments: (length: 1) + │ │ └── @ CallNode (location: (45,4)-(45,25)) + │ │ ├── flags: ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :bar + │ │ ├── message_loc: (45,4)-(45,7) = "bar" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (45,8)-(45,25)) + │ │ │ ├── flags: contains_keywords + │ │ │ └── arguments: (length: 2) + │ │ │ ├── @ CallNode (location: (45,8)-(45,11)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :baz + │ │ │ │ ├── message_loc: (45,8)-(45,11) = "baz" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ └── @ KeywordHashNode (location: (45,13)-(45,25)) + │ │ │ ├── flags: ∅ + │ │ │ └── elements: (length: 1) + │ │ │ └── @ AssocNode (location: (45,13)-(45,25)) + │ │ │ ├── flags: ∅ + │ │ │ ├── key: + │ │ │ │ @ ConstantReadNode (location: (45,13)-(45,16)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── name: :KEY + │ │ │ ├── value: + │ │ │ │ @ CallNode (location: (45,20)-(45,25)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :value + │ │ │ │ ├── message_loc: (45,20)-(45,25) = "value" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ └── operator_loc: (45,17)-(45,19) = "=>" + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ ├── closing_loc: (45,25)-(45,26) = ")" + │ └── block: ∅ + └── @ CallNode (location: (47,0)-(47,27)) + ├── flags: newline, ignore_visibility + ├── receiver: ∅ ├── call_operator_loc: ∅ - ├── name: :! - ├── message_loc: (41,0)-(41,3) = "not" - ├── opening_loc: ∅ - ├── arguments: ∅ - ├── closing_loc: ∅ + ├── name: :foo + ├── message_loc: (47,0)-(47,3) = "foo" + ├── opening_loc: (47,3)-(47,4) = "(" + ├── arguments: + │ @ ArgumentsNode (location: (47,4)-(47,26)) + │ ├── flags: ∅ + │ └── arguments: (length: 1) + │ └── @ CallNode (location: (47,4)-(47,26)) + │ ├── flags: ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :bar + │ ├── message_loc: (47,4)-(47,7) = "bar" + │ ├── opening_loc: ∅ + │ ├── arguments: + │ │ @ ArgumentsNode (location: (47,8)-(47,26)) + │ │ ├── flags: contains_keywords + │ │ └── arguments: (length: 2) + │ │ ├── @ CallNode (location: (47,8)-(47,11)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :baz + │ │ │ ├── message_loc: (47,8)-(47,11) = "baz" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ └── block: ∅ + │ │ └── @ KeywordHashNode (location: (47,13)-(47,26)) + │ │ ├── flags: symbol_keys + │ │ └── elements: (length: 1) + │ │ └── @ AssocNode (location: (47,13)-(47,26)) + │ │ ├── flags: ∅ + │ │ ├── key: + │ │ │ @ SymbolNode (location: (47,13)-(47,17)) + │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ ├── opening_loc: (47,13)-(47,14) = ":" + │ │ │ ├── value_loc: (47,14)-(47,17) = "key" + │ │ │ ├── closing_loc: ∅ + │ │ │ └── unescaped: "key" + │ │ ├── value: + │ │ │ @ CallNode (location: (47,21)-(47,26)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :value + │ │ │ ├── message_loc: (47,21)-(47,26) = "value" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ └── block: ∅ + │ │ └── operator_loc: (47,18)-(47,20) = "=>" + │ ├── closing_loc: ∅ + │ └── block: ∅ + ├── closing_loc: (47,26)-(47,27) = ")" └── block: ∅ diff --git a/src/prism.c b/src/prism.c index 22ac19b5e4..771719b55c 100644 --- a/src/prism.c +++ b/src/prism.c @@ -14221,6 +14221,25 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod return contains_keyword_splat; } +static inline bool +argument_allowed_for_bare_hash(pm_parser_t *parser, pm_node_t *argument) { + if (pm_symbol_node_label_p(argument)) { + return true; + } + + switch (PM_NODE_TYPE(argument)) { + case PM_CALL_NODE: { + pm_call_node_t *cast = (pm_call_node_t *) argument; + if (cast->opening_loc.start == NULL && cast->arguments != NULL) { + return false; + } + break; + } + default: break; + } + return accept1(parser, PM_TOKEN_EQUAL_GREATER); +} + /** * Append an argument to a list of arguments. */ @@ -14378,7 +14397,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for bool contains_keywords = false; bool contains_keyword_splat = false; - if (pm_symbol_node_label_p(argument) || accept1(parser, PM_TOKEN_EQUAL_GREATER)) { + if (argument_allowed_for_bare_hash(parser, argument)){ if (parsed_bare_hash) { pm_parser_err_previous(parser, PM_ERR_ARGUMENT_BARE_HASH); } diff --git a/test/prism/errors/command_calls_35.txt b/test/prism/errors/command_calls_35.txt new file mode 100644 index 0000000000..9eb011cd86 --- /dev/null +++ b/test/prism/errors/command_calls_35.txt @@ -0,0 +1,41 @@ +p(p a, x: b => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, x: => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, &block => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, *args => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, **kwargs => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p p 1, &block => 2, &block + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + ^ unexpected ',', expecting end-of-input + ^ unexpected ',', ignoring it + ^ unexpected '&', ignoring it + +p p p 1 => 2 => 3 => 4 + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + +p[p a, x: b => value] + ^ expected a matching `]` + ^ unexpected ']', expecting end-of-input + ^ unexpected ']', ignoring it + diff --git a/test/prism/fixtures/command_method_call.txt b/test/prism/fixtures/command_method_call.txt index 182b87948b..3f510efa69 100644 --- a/test/prism/fixtures/command_method_call.txt +++ b/test/prism/fixtures/command_method_call.txt @@ -39,3 +39,9 @@ def foo = bar 1 !foo 1 or !bar 2 not !foo 1 + +foo(bar baz, key => value) + +foo(bar baz, KEY => value) + +foo(bar baz, :key => value)