From 950d0fe0e6f6da1bbc52d5ce20c3e9cbfca219ee Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Jun 2026 13:49:56 -0400 Subject: [PATCH] Parse rescue modifier values as statements The value of a `rescue` modifier is a full `stmt` in the grammar in three of its six productions, but we parsed it as a plain expression in every case. This rejected valid syntax such as `a rescue b, c = 1` and accepted invalid syntax such as `a = 1 rescue not b`. These are the relevant grammar rules: stmt : stmt modifier_rescue stmt | mlhs '=' mrhs_arg modifier_rescue stmt command_rhs : command_call_value modifier_rescue stmt arg_rhs : arg modifier_rescue arg endless_arg : endless_arg modifier_rescue arg endless_command : endless_command modifier_rescue arg parse_rescue_modifier_value will now parse either a stmt or an arg depending on the caller. --- lib/prism/translation/parser/lexer.rb | 22 +- snapshots/rescue_modifier_assignment.txt | 156 +++++++ .../rescue_modifier_block_command_value.txt | 317 +++++++++++++++ snapshots/rescue_modifier_command_value.txt | 357 ++++++++++++++++ .../rescue_modifier_multiple_assignment.txt | 381 ++++++++++++++++++ snapshots/rescue_modifier_multiple_value.txt | 269 +++++++++++++ snapshots/rescue_modifier_statement_value.txt | 254 ++++++++++++ src/prism.c | 223 ++++++++-- .../rescue_modifier_argument_value_alias.txt | 3 + .../rescue_modifier_argument_value_not.txt | 4 + ...ier_endless_method_multiple_assignment.txt | 4 + ...r_multiple_assignment_logical_operator.txt | 3 + ...ier_multiple_assignment_trailing_comma.txt | 3 + ..._single_assignment_multiple_assignment.txt | 4 + .../fixtures/rescue_modifier_assignment.txt | 3 + .../rescue_modifier_block_command_value.txt | 5 + .../rescue_modifier_command_value.txt | 6 + .../rescue_modifier_multiple_assignment.txt | 9 + .../rescue_modifier_multiple_value.txt | 5 + .../rescue_modifier_statement_value.txt | 8 + test/prism/ruby/parser_test.rb | 1 - 21 files changed, 1990 insertions(+), 47 deletions(-) create mode 100644 snapshots/rescue_modifier_assignment.txt create mode 100644 snapshots/rescue_modifier_block_command_value.txt create mode 100644 snapshots/rescue_modifier_command_value.txt create mode 100644 snapshots/rescue_modifier_multiple_assignment.txt create mode 100644 snapshots/rescue_modifier_multiple_value.txt create mode 100644 snapshots/rescue_modifier_statement_value.txt create mode 100644 test/prism/errors/rescue_modifier_argument_value_alias.txt create mode 100644 test/prism/errors/rescue_modifier_argument_value_not.txt create mode 100644 test/prism/errors/rescue_modifier_endless_method_multiple_assignment.txt create mode 100644 test/prism/errors/rescue_modifier_multiple_assignment_logical_operator.txt create mode 100644 test/prism/errors/rescue_modifier_multiple_assignment_trailing_comma.txt create mode 100644 test/prism/errors/rescue_modifier_single_assignment_multiple_assignment.txt create mode 100644 test/prism/fixtures/rescue_modifier_assignment.txt create mode 100644 test/prism/fixtures/rescue_modifier_block_command_value.txt create mode 100644 test/prism/fixtures/rescue_modifier_command_value.txt create mode 100644 test/prism/fixtures/rescue_modifier_multiple_assignment.txt create mode 100644 test/prism/fixtures/rescue_modifier_multiple_value.txt create mode 100644 test/prism/fixtures/rescue_modifier_statement_value.txt diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb index e82042867f..dadc53d38e 100644 --- a/lib/prism/translation/parser/lexer.rb +++ b/lib/prism/translation/parser/lexer.rb @@ -192,17 +192,25 @@ class Lexer # :nodoc: EXPR_BEG = 0x1 EXPR_LABEL = 0x400 - # It is used to determine whether `do` is of the token type `kDO` or `kDO_LAMBDA`. + # It is used to determine whether `do` is of the token type `kDO` or + # `kDO_LAMBDA`. # - # NOTE: In edge cases like `-> (foo = -> (bar) {}) do end`, please note that `kDO` is still returned - # instead of `kDO_LAMBDA`, which is expected: https://github.com/ruby/prism/pull/3046 + # NOTE: In edge cases like `-> (foo = -> (bar) {}) do end`, please note + # that `kDO` is still returned instead of `kDO_LAMBDA`, which is + # expected: https://github.com/ruby/prism/pull/3046 LAMBDA_TOKEN_TYPES = Set.new([:kDO_LAMBDA, :tLAMBDA, :tLAMBEG]) - # The `PARENTHESIS_LEFT` token in Prism is classified as either `tLPAREN` or `tLPAREN2` in the Parser gem. - # The following token types are listed as those classified as `tLPAREN`. + # The `PARENTHESIS_LEFT` token in Prism is classified as either + # `tLPAREN` or `tLPAREN2` in the Parser gem. The following token types + # are listed as those classified as `tLPAREN`. LPAREN_CONVERSION_TOKEN_TYPES = Set.new([ - :kBREAK, :tCARET, :kCASE, :tDIVIDE, :kFOR, :kIF, :kNEXT, :kRETURN, :kUNTIL, :kWHILE, :tAMPER, :tANDOP, :tBANG, :tCOMMA, :tDOT2, :tDOT3, - :tEQL, :tLPAREN, :tLPAREN2, :tLPAREN_ARG, :tLSHFT, :tNL, :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS, :tLCURLY + :kAND, :kBEGIN, :kBREAK, :kCASE, :kDO_COND, :kDO_LAMBDA, :kDO, :kELSE, + :kELSIF, :kENSURE, :kFOR, :kIF_MOD, :kIF, :kIN, :kNEXT, :kOR, + :kRESCUE_MOD, :kRESCUE, :kRETURN, :kTHEN, :kUNLESS_MOD, :kUNLESS, + :kUNTIL_MOD, :kUNTIL, :kWHEN, :kWHILE_MOD, :kWHILE, + :tAMPER, :tANDOP, :tBANG, :tCARET, :tCOMMA, :tDIVIDE, :tDOT2, :tDOT3, + :tEQL, :tLCURLY, :tLPAREN_ARG, :tLPAREN, :tLPAREN2, :tLSHFT, :tNL, + :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS ]) # Types of tokens that are allowed to continue a method call with comments in-between. diff --git a/snapshots/rescue_modifier_assignment.txt b/snapshots/rescue_modifier_assignment.txt new file mode 100644 index 0000000000..91b23bee90 --- /dev/null +++ b/snapshots/rescue_modifier_assignment.txt @@ -0,0 +1,156 @@ +@ ProgramNode (location: (1,0)-(3,23)) +├── flags: ∅ +├── locals: [:x, :y, :a, :b, :c] +└── statements: + @ StatementsNode (location: (1,0)-(3,23)) + ├── flags: ∅ + └── body: (length: 3) + ├── @ MultiWriteNode (location: (1,0)-(1,24)) + │ ├── flags: newline + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (1,0)-(1,1)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :x + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (1,3)-(1,4)) + │ │ ├── flags: ∅ + │ │ ├── name: :y + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (1,5)-(1,6) = "=" + │ └── value: + │ @ RescueModifierNode (location: (1,7)-(1,24)) + │ ├── flags: ∅ + │ ├── expression: + │ │ @ IntegerNode (location: (1,7)-(1,8)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 1 + │ ├── keyword_loc: (1,9)-(1,15) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (1,16)-(1,24)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (1,16)-(1,17)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :a + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (1,19)-(1,20)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (1,21)-(1,22) = "=" + │ └── value: + │ @ IntegerNode (location: (1,23)-(1,24)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ MultiWriteNode (location: (2,0)-(2,24)) + │ ├── flags: newline + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (2,0)-(2,1)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :x + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (2,3)-(2,4)) + │ │ ├── flags: ∅ + │ │ ├── name: :y + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (2,5)-(2,6) = "=" + │ └── value: + │ @ RescueModifierNode (location: (2,7)-(2,24)) + │ ├── flags: ∅ + │ ├── expression: + │ │ @ LocalVariableReadNode (location: (2,7)-(2,8)) + │ │ ├── flags: ∅ + │ │ ├── name: :a + │ │ └── depth: 0 + │ ├── keyword_loc: (2,9)-(2,15) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (2,16)-(2,24)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (2,16)-(2,17)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :b + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (2,19)-(2,20)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (2,21)-(2,22) = "=" + │ └── value: + │ @ IntegerNode (location: (2,23)-(2,24)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + └── @ MultiWriteNode (location: (3,0)-(3,23)) + ├── flags: newline + ├── lefts: (length: 2) + │ ├── @ LocalVariableTargetNode (location: (3,0)-(3,1)) + │ │ ├── flags: ∅ + │ │ ├── name: :x + │ │ └── depth: 0 + │ └── @ LocalVariableTargetNode (location: (3,3)-(3,4)) + │ ├── flags: ∅ + │ ├── name: :y + │ └── depth: 0 + ├── rest: ∅ + ├── rights: (length: 0) + ├── lparen_loc: ∅ + ├── rparen_loc: ∅ + ├── operator_loc: (3,5)-(3,6) = "=" + └── value: + @ RescueModifierNode (location: (3,7)-(3,23)) + ├── flags: ∅ + ├── expression: + │ @ LocalVariableReadNode (location: (3,7)-(3,8)) + │ ├── flags: ∅ + │ ├── name: :a + │ └── depth: 0 + ├── keyword_loc: (3,9)-(3,15) = "rescue" + └── rescue_expression: + @ LocalVariableWriteNode (location: (3,16)-(3,23)) + ├── flags: ∅ + ├── name: :b + ├── depth: 0 + ├── name_loc: (3,16)-(3,17) = "b" + ├── value: + │ @ CallNode (location: (3,20)-(3,23)) + │ ├── flags: ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :c + │ ├── message_loc: (3,20)-(3,21) = "c" + │ ├── opening_loc: ∅ + │ ├── arguments: + │ │ @ ArgumentsNode (location: (3,22)-(3,23)) + │ │ ├── flags: ∅ + │ │ └── arguments: (length: 1) + │ │ └── @ CallNode (location: (3,22)-(3,23)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :d + │ │ ├── message_loc: (3,22)-(3,23) = "d" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + └── operator_loc: (3,18)-(3,19) = "=" diff --git a/snapshots/rescue_modifier_block_command_value.txt b/snapshots/rescue_modifier_block_command_value.txt new file mode 100644 index 0000000000..58d8ed5024 --- /dev/null +++ b/snapshots/rescue_modifier_block_command_value.txt @@ -0,0 +1,317 @@ +@ ProgramNode (location: (1,0)-(5,33)) +├── flags: ∅ +├── locals: [:x, :b, :c, :d, :y] +└── statements: + @ StatementsNode (location: (1,0)-(5,33)) + ├── flags: ∅ + └── body: (length: 5) + ├── @ LocalVariableWriteNode (location: (1,0)-(1,25)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (1,0)-(1,1) = "x" + │ ├── value: + │ │ @ RescueModifierNode (location: (1,4)-(1,25)) + │ │ ├── flags: ∅ + │ │ ├── expression: + │ │ │ @ CallNode (location: (1,4)-(1,16)) + │ │ │ ├── flags: ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :foo + │ │ │ ├── message_loc: (1,4)-(1,7) = "foo" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (1,8)-(1,9)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ CallNode (location: (1,8)-(1,9)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :a + │ │ │ │ ├── message_loc: (1,8)-(1,9) = "a" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: + │ │ │ @ BlockNode (location: (1,10)-(1,16)) + │ │ │ ├── flags: ∅ + │ │ │ ├── locals: [] + │ │ │ ├── parameters: ∅ + │ │ │ ├── body: ∅ + │ │ │ ├── opening_loc: (1,10)-(1,12) = "do" + │ │ │ └── closing_loc: (1,13)-(1,16) = "end" + │ │ ├── keyword_loc: (1,17)-(1,23) = "rescue" + │ │ └── rescue_expression: + │ │ @ CallNode (location: (1,24)-(1,25)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :b + │ │ ├── message_loc: (1,24)-(1,25) = "b" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ └── operator_loc: (1,2)-(1,3) = "=" + ├── @ LocalVariableWriteNode (location: (2,0)-(2,32)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (2,0)-(2,1) = "x" + │ ├── value: + │ │ @ RescueModifierNode (location: (2,4)-(2,32)) + │ │ ├── flags: ∅ + │ │ ├── expression: + │ │ │ @ CallNode (location: (2,4)-(2,16)) + │ │ │ ├── flags: ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :foo + │ │ │ ├── message_loc: (2,4)-(2,7) = "foo" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (2,8)-(2,9)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ CallNode (location: (2,8)-(2,9)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :a + │ │ │ │ ├── message_loc: (2,8)-(2,9) = "a" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: + │ │ │ @ BlockNode (location: (2,10)-(2,16)) + │ │ │ ├── flags: ∅ + │ │ │ ├── locals: [] + │ │ │ ├── parameters: ∅ + │ │ │ ├── body: ∅ + │ │ │ ├── opening_loc: (2,10)-(2,12) = "do" + │ │ │ └── closing_loc: (2,13)-(2,16) = "end" + │ │ ├── keyword_loc: (2,17)-(2,23) = "rescue" + │ │ └── rescue_expression: + │ │ @ MultiWriteNode (location: (2,24)-(2,32)) + │ │ ├── flags: ∅ + │ │ ├── lefts: (length: 2) + │ │ │ ├── @ LocalVariableTargetNode (location: (2,24)-(2,25)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ └── @ LocalVariableTargetNode (location: (2,27)-(2,28)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :c + │ │ │ └── depth: 0 + │ │ ├── rest: ∅ + │ │ ├── rights: (length: 0) + │ │ ├── lparen_loc: ∅ + │ │ ├── rparen_loc: ∅ + │ │ ├── operator_loc: (2,29)-(2,30) = "=" + │ │ └── value: + │ │ @ IntegerNode (location: (2,31)-(2,32)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 1 + │ └── operator_loc: (2,2)-(2,3) = "=" + ├── @ LocalVariableWriteNode (location: (3,0)-(3,34)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (3,0)-(3,1) = "x" + │ ├── value: + │ │ @ RescueModifierNode (location: (3,4)-(3,34)) + │ │ ├── flags: ∅ + │ │ ├── expression: + │ │ │ @ CallNode (location: (3,4)-(3,18)) + │ │ │ ├── flags: ∅ + │ │ │ ├── receiver: + │ │ │ │ @ CallNode (location: (3,4)-(3,5)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :a + │ │ │ │ ├── message_loc: (3,4)-(3,5) = "a" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── call_operator_loc: (3,5)-(3,6) = "." + │ │ │ ├── name: :foo + │ │ │ ├── message_loc: (3,6)-(3,9) = "foo" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (3,10)-(3,11)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ LocalVariableReadNode (location: (3,10)-(3,11)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: + │ │ │ @ BlockNode (location: (3,12)-(3,18)) + │ │ │ ├── flags: ∅ + │ │ │ ├── locals: [] + │ │ │ ├── parameters: ∅ + │ │ │ ├── body: ∅ + │ │ │ ├── opening_loc: (3,12)-(3,14) = "do" + │ │ │ └── closing_loc: (3,15)-(3,18) = "end" + │ │ ├── keyword_loc: (3,19)-(3,25) = "rescue" + │ │ └── rescue_expression: + │ │ @ MultiWriteNode (location: (3,26)-(3,34)) + │ │ ├── flags: ∅ + │ │ ├── lefts: (length: 2) + │ │ │ ├── @ LocalVariableTargetNode (location: (3,26)-(3,27)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :c + │ │ │ │ └── depth: 0 + │ │ │ └── @ LocalVariableTargetNode (location: (3,29)-(3,30)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :d + │ │ │ └── depth: 0 + │ │ ├── rest: ∅ + │ │ ├── rights: (length: 0) + │ │ ├── lparen_loc: ∅ + │ │ ├── rparen_loc: ∅ + │ │ ├── operator_loc: (3,31)-(3,32) = "=" + │ │ └── value: + │ │ @ IntegerNode (location: (3,33)-(3,34)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 1 + │ └── operator_loc: (3,2)-(3,3) = "=" + ├── @ RescueModifierNode (location: (4,0)-(4,28)) + │ ├── flags: newline + │ ├── expression: + │ │ @ MultiWriteNode (location: (4,0)-(4,19)) + │ │ ├── flags: ∅ + │ │ ├── lefts: (length: 2) + │ │ │ ├── @ LocalVariableTargetNode (location: (4,0)-(4,1)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :x + │ │ │ │ └── depth: 0 + │ │ │ └── @ LocalVariableTargetNode (location: (4,3)-(4,4)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :y + │ │ │ └── depth: 0 + │ │ ├── rest: ∅ + │ │ ├── rights: (length: 0) + │ │ ├── lparen_loc: ∅ + │ │ ├── rparen_loc: ∅ + │ │ ├── operator_loc: (4,5)-(4,6) = "=" + │ │ └── value: + │ │ @ CallNode (location: (4,7)-(4,19)) + │ │ ├── flags: ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :foo + │ │ ├── message_loc: (4,7)-(4,10) = "foo" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (4,11)-(4,12)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ CallNode (location: (4,11)-(4,12)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (4,11)-(4,12) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: + │ │ @ BlockNode (location: (4,13)-(4,19)) + │ │ ├── flags: ∅ + │ │ ├── locals: [] + │ │ ├── parameters: ∅ + │ │ ├── body: ∅ + │ │ ├── opening_loc: (4,13)-(4,15) = "do" + │ │ └── closing_loc: (4,16)-(4,19) = "end" + │ ├── keyword_loc: (4,20)-(4,26) = "rescue" + │ └── rescue_expression: + │ @ LocalVariableReadNode (location: (4,27)-(4,28)) + │ ├── flags: ∅ + │ ├── name: :b + │ └── depth: 0 + └── @ LocalVariableOperatorWriteNode (location: (5,0)-(5,33)) + ├── flags: newline + ├── name_loc: (5,0)-(5,1) = "x" + ├── binary_operator_loc: (5,2)-(5,4) = "+=" + ├── value: + │ @ RescueModifierNode (location: (5,5)-(5,33)) + │ ├── flags: ∅ + │ ├── expression: + │ │ @ CallNode (location: (5,5)-(5,17)) + │ │ ├── flags: ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :foo + │ │ ├── message_loc: (5,5)-(5,8) = "foo" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (5,9)-(5,10)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ CallNode (location: (5,9)-(5,10)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (5,9)-(5,10) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: + │ │ @ BlockNode (location: (5,11)-(5,17)) + │ │ ├── flags: ∅ + │ │ ├── locals: [] + │ │ ├── parameters: ∅ + │ │ ├── body: ∅ + │ │ ├── opening_loc: (5,11)-(5,13) = "do" + │ │ └── closing_loc: (5,14)-(5,17) = "end" + │ ├── keyword_loc: (5,18)-(5,24) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (5,25)-(5,33)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (5,25)-(5,26)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :b + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (5,28)-(5,29)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (5,30)-(5,31) = "=" + │ └── value: + │ @ IntegerNode (location: (5,32)-(5,33)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── name: :x + ├── binary_operator: :+ + └── depth: 0 diff --git a/snapshots/rescue_modifier_command_value.txt b/snapshots/rescue_modifier_command_value.txt new file mode 100644 index 0000000000..b810287711 --- /dev/null +++ b/snapshots/rescue_modifier_command_value.txt @@ -0,0 +1,357 @@ +@ ProgramNode (location: (1,0)-(6,30)) +├── flags: ∅ +├── locals: [:b, :x, :c, :y] +└── statements: + @ StatementsNode (location: (1,0)-(6,30)) + ├── flags: ∅ + └── body: (length: 6) + ├── @ RescueModifierNode (location: (1,0)-(1,16)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (1,0)-(1,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (1,0)-(1,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (1,2)-(1,8) = "rescue" + │ └── rescue_expression: + │ @ LocalVariableWriteNode (location: (1,9)-(1,16)) + │ ├── flags: ∅ + │ ├── name: :b + │ ├── depth: 0 + │ ├── name_loc: (1,9)-(1,10) = "b" + │ ├── value: + │ │ @ CallNode (location: (1,13)-(1,16)) + │ │ ├── flags: ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :c + │ │ ├── message_loc: (1,13)-(1,14) = "c" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (1,15)-(1,16)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ CallNode (location: (1,15)-(1,16)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :d + │ │ │ ├── message_loc: (1,15)-(1,16) = "d" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ └── operator_loc: (1,11)-(1,12) = "=" + ├── @ LocalVariableWriteNode (location: (2,0)-(2,27)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (2,0)-(2,1) = "x" + │ ├── value: + │ │ @ RescueModifierNode (location: (2,4)-(2,27)) + │ │ ├── flags: ∅ + │ │ ├── expression: + │ │ │ @ CallNode (location: (2,4)-(2,11)) + │ │ │ ├── flags: ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :foo + │ │ │ ├── message_loc: (2,4)-(2,7) = "foo" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (2,8)-(2,11)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ CallNode (location: (2,8)-(2,11)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :bar + │ │ │ │ ├── message_loc: (2,8)-(2,11) = "bar" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── keyword_loc: (2,12)-(2,18) = "rescue" + │ │ └── rescue_expression: + │ │ @ MultiWriteNode (location: (2,19)-(2,27)) + │ │ ├── flags: ∅ + │ │ ├── lefts: (length: 2) + │ │ │ ├── @ LocalVariableTargetNode (location: (2,19)-(2,20)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ └── @ LocalVariableTargetNode (location: (2,22)-(2,23)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :c + │ │ │ └── depth: 0 + │ │ ├── rest: ∅ + │ │ ├── rights: (length: 0) + │ │ ├── lparen_loc: ∅ + │ │ ├── rparen_loc: ∅ + │ │ ├── operator_loc: (2,24)-(2,25) = "=" + │ │ └── value: + │ │ @ IntegerNode (location: (2,26)-(2,27)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 1 + │ └── operator_loc: (2,2)-(2,3) = "=" + ├── @ LocalVariableWriteNode (location: (3,0)-(3,26)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (3,0)-(3,1) = "x" + │ ├── value: + │ │ @ RescueModifierNode (location: (3,4)-(3,26)) + │ │ ├── flags: ∅ + │ │ ├── expression: + │ │ │ @ CallNode (location: (3,4)-(3,11)) + │ │ │ ├── flags: ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :foo + │ │ │ ├── message_loc: (3,4)-(3,7) = "foo" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (3,8)-(3,11)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ CallNode (location: (3,8)-(3,11)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :bar + │ │ │ │ ├── message_loc: (3,8)-(3,11) = "bar" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── keyword_loc: (3,12)-(3,18) = "rescue" + │ │ └── rescue_expression: + │ │ @ LocalVariableWriteNode (location: (3,19)-(3,26)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ ├── depth: 0 + │ │ ├── name_loc: (3,19)-(3,20) = "b" + │ │ ├── value: + │ │ │ @ CallNode (location: (3,23)-(3,26)) + │ │ │ ├── flags: ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :c + │ │ │ ├── message_loc: (3,23)-(3,24) = "c" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (3,25)-(3,26)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ CallNode (location: (3,25)-(3,26)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :d + │ │ │ │ ├── message_loc: (3,25)-(3,26) = "d" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ └── operator_loc: (3,21)-(3,22) = "=" + │ └── operator_loc: (3,2)-(3,3) = "=" + ├── @ LocalVariableOperatorWriteNode (location: (4,0)-(4,28)) + │ ├── flags: newline + │ ├── name_loc: (4,0)-(4,1) = "x" + │ ├── binary_operator_loc: (4,2)-(4,4) = "+=" + │ ├── value: + │ │ @ RescueModifierNode (location: (4,5)-(4,28)) + │ │ ├── flags: ∅ + │ │ ├── expression: + │ │ │ @ CallNode (location: (4,5)-(4,12)) + │ │ │ ├── flags: ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :foo + │ │ │ ├── message_loc: (4,5)-(4,8) = "foo" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (4,9)-(4,12)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ CallNode (location: (4,9)-(4,12)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :bar + │ │ │ │ ├── message_loc: (4,9)-(4,12) = "bar" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── keyword_loc: (4,13)-(4,19) = "rescue" + │ │ └── rescue_expression: + │ │ @ MultiWriteNode (location: (4,20)-(4,28)) + │ │ ├── flags: ∅ + │ │ ├── lefts: (length: 2) + │ │ │ ├── @ LocalVariableTargetNode (location: (4,20)-(4,21)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ └── @ LocalVariableTargetNode (location: (4,23)-(4,24)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :c + │ │ │ └── depth: 0 + │ │ ├── rest: ∅ + │ │ ├── rights: (length: 0) + │ │ ├── lparen_loc: ∅ + │ │ ├── rparen_loc: ∅ + │ │ ├── operator_loc: (4,25)-(4,26) = "=" + │ │ └── value: + │ │ @ IntegerNode (location: (4,27)-(4,28)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 1 + │ ├── name: :x + │ ├── binary_operator: :+ + │ └── depth: 0 + ├── @ RescueModifierNode (location: (5,0)-(5,23)) + │ ├── flags: newline + │ ├── expression: + │ │ @ MultiWriteNode (location: (5,0)-(5,14)) + │ │ ├── flags: ∅ + │ │ ├── lefts: (length: 2) + │ │ │ ├── @ LocalVariableTargetNode (location: (5,0)-(5,1)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :x + │ │ │ │ └── depth: 0 + │ │ │ └── @ LocalVariableTargetNode (location: (5,3)-(5,4)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :y + │ │ │ └── depth: 0 + │ │ ├── rest: ∅ + │ │ ├── rights: (length: 0) + │ │ ├── lparen_loc: ∅ + │ │ ├── rparen_loc: ∅ + │ │ ├── operator_loc: (5,5)-(5,6) = "=" + │ │ └── value: + │ │ @ CallNode (location: (5,7)-(5,14)) + │ │ ├── flags: ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :foo + │ │ ├── message_loc: (5,7)-(5,10) = "foo" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (5,11)-(5,14)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ CallNode (location: (5,11)-(5,14)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :bar + │ │ │ ├── message_loc: (5,11)-(5,14) = "bar" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (5,15)-(5,21) = "rescue" + │ └── rescue_expression: + │ @ LocalVariableReadNode (location: (5,22)-(5,23)) + │ ├── flags: ∅ + │ ├── name: :b + │ └── depth: 0 + └── @ RescueModifierNode (location: (6,0)-(6,30)) + ├── flags: newline + ├── expression: + │ @ MultiWriteNode (location: (6,0)-(6,14)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (6,0)-(6,1)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :x + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (6,3)-(6,4)) + │ │ ├── flags: ∅ + │ │ ├── name: :y + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (6,5)-(6,6) = "=" + │ └── value: + │ @ CallNode (location: (6,7)-(6,14)) + │ ├── flags: ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (6,7)-(6,10) = "foo" + │ ├── opening_loc: ∅ + │ ├── arguments: + │ │ @ ArgumentsNode (location: (6,11)-(6,14)) + │ │ ├── flags: ∅ + │ │ └── arguments: (length: 1) + │ │ └── @ CallNode (location: (6,11)-(6,14)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :bar + │ │ ├── message_loc: (6,11)-(6,14) = "bar" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── keyword_loc: (6,15)-(6,21) = "rescue" + └── rescue_expression: + @ MultiWriteNode (location: (6,22)-(6,30)) + ├── flags: ∅ + ├── lefts: (length: 2) + │ ├── @ LocalVariableTargetNode (location: (6,22)-(6,23)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ └── @ LocalVariableTargetNode (location: (6,25)-(6,26)) + │ ├── flags: ∅ + │ ├── name: :c + │ └── depth: 0 + ├── rest: ∅ + ├── rights: (length: 0) + ├── lparen_loc: ∅ + ├── rparen_loc: ∅ + ├── operator_loc: (6,27)-(6,28) = "=" + └── value: + @ IntegerNode (location: (6,29)-(6,30)) + ├── flags: static_literal, decimal + └── value: 1 diff --git a/snapshots/rescue_modifier_multiple_assignment.txt b/snapshots/rescue_modifier_multiple_assignment.txt new file mode 100644 index 0000000000..8085f66019 --- /dev/null +++ b/snapshots/rescue_modifier_multiple_assignment.txt @@ -0,0 +1,381 @@ +@ ProgramNode (location: (1,0)-(9,20)) +├── flags: ∅ +├── locals: [:b, :c, :d] +└── statements: + @ StatementsNode (location: (1,0)-(9,20)) + ├── flags: ∅ + └── body: (length: 9) + ├── @ RescueModifierNode (location: (1,0)-(1,17)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (1,0)-(1,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (1,0)-(1,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (1,2)-(1,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (1,9)-(1,17)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ LocalVariableTargetNode (location: (1,9)-(1,10)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :b + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (1,12)-(1,13)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (1,14)-(1,15) = "=" + │ └── value: + │ @ IntegerNode (location: (1,16)-(1,17)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (2,0)-(2,20)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (2,0)-(2,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (2,0)-(2,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (2,2)-(2,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (2,9)-(2,20)) + │ ├── flags: ∅ + │ ├── lefts: (length: 3) + │ │ ├── @ LocalVariableTargetNode (location: (2,9)-(2,10)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :b + │ │ │ └── depth: 0 + │ │ ├── @ LocalVariableTargetNode (location: (2,12)-(2,13)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :c + │ │ │ └── depth: 0 + │ │ └── @ LocalVariableTargetNode (location: (2,15)-(2,16)) + │ │ ├── flags: ∅ + │ │ ├── name: :d + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (2,17)-(2,18) = "=" + │ └── value: + │ @ IntegerNode (location: (2,19)-(2,20)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (3,0)-(3,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (3,0)-(3,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (3,0)-(3,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (3,2)-(3,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (3,9)-(3,18)) + │ ├── flags: ∅ + │ ├── lefts: (length: 0) + │ ├── rest: + │ │ @ SplatNode (location: (3,9)-(3,11)) + │ │ ├── flags: ∅ + │ │ ├── operator_loc: (3,9)-(3,10) = "*" + │ │ └── expression: + │ │ @ LocalVariableTargetNode (location: (3,10)-(3,11)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ ├── rights: (length: 1) + │ │ └── @ LocalVariableTargetNode (location: (3,13)-(3,14)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (3,15)-(3,16) = "=" + │ └── value: + │ @ IntegerNode (location: (3,17)-(3,18)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (4,0)-(4,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (4,0)-(4,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (4,0)-(4,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (4,2)-(4,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (4,9)-(4,18)) + │ ├── flags: ∅ + │ ├── lefts: (length: 1) + │ │ └── @ LocalVariableTargetNode (location: (4,9)-(4,10)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ ├── rest: + │ │ @ SplatNode (location: (4,12)-(4,14)) + │ │ ├── flags: ∅ + │ │ ├── operator_loc: (4,12)-(4,13) = "*" + │ │ └── expression: + │ │ @ LocalVariableTargetNode (location: (4,13)-(4,14)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (4,15)-(4,16) = "=" + │ └── value: + │ @ IntegerNode (location: (4,17)-(4,18)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (5,0)-(5,21)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (5,0)-(5,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (5,0)-(5,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (5,2)-(5,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (5,9)-(5,21)) + │ ├── flags: ∅ + │ ├── lefts: (length: 1) + │ │ └── @ LocalVariableTargetNode (location: (5,9)-(5,10)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ ├── rest: + │ │ @ SplatNode (location: (5,12)-(5,14)) + │ │ ├── flags: ∅ + │ │ ├── operator_loc: (5,12)-(5,13) = "*" + │ │ └── expression: + │ │ @ LocalVariableTargetNode (location: (5,13)-(5,14)) + │ │ ├── flags: ∅ + │ │ ├── name: :c + │ │ └── depth: 0 + │ ├── rights: (length: 1) + │ │ └── @ LocalVariableTargetNode (location: (5,16)-(5,17)) + │ │ ├── flags: ∅ + │ │ ├── name: :d + │ │ └── depth: 0 + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (5,18)-(5,19) = "=" + │ └── value: + │ @ IntegerNode (location: (5,20)-(5,21)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (6,0)-(6,15)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (6,0)-(6,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (6,0)-(6,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (6,2)-(6,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (6,9)-(6,15)) + │ ├── flags: ∅ + │ ├── lefts: (length: 0) + │ ├── rest: + │ │ @ SplatNode (location: (6,9)-(6,11)) + │ │ ├── flags: ∅ + │ │ ├── operator_loc: (6,9)-(6,10) = "*" + │ │ └── expression: + │ │ @ LocalVariableTargetNode (location: (6,10)-(6,11)) + │ │ ├── flags: ∅ + │ │ ├── name: :b + │ │ └── depth: 0 + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (6,12)-(6,13) = "=" + │ └── value: + │ @ IntegerNode (location: (6,14)-(6,15)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (7,0)-(7,22)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (7,0)-(7,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (7,0)-(7,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (7,2)-(7,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (7,9)-(7,22)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ MultiTargetNode (location: (7,9)-(7,15)) + │ │ │ ├── flags: ∅ + │ │ │ ├── lefts: (length: 2) + │ │ │ │ ├── @ LocalVariableTargetNode (location: (7,10)-(7,11)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :b + │ │ │ │ │ └── depth: 0 + │ │ │ │ └── @ LocalVariableTargetNode (location: (7,13)-(7,14)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :c + │ │ │ │ └── depth: 0 + │ │ │ ├── rest: ∅ + │ │ │ ├── rights: (length: 0) + │ │ │ ├── lparen_loc: (7,9)-(7,10) = "(" + │ │ │ └── rparen_loc: (7,14)-(7,15) = ")" + │ │ └── @ LocalVariableTargetNode (location: (7,17)-(7,18)) + │ │ ├── flags: ∅ + │ │ ├── name: :d + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (7,19)-(7,20) = "=" + │ └── value: + │ @ IntegerNode (location: (7,21)-(7,22)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + ├── @ RescueModifierNode (location: (8,0)-(8,19)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (8,0)-(8,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (8,0)-(8,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (8,2)-(8,8) = "rescue" + │ └── rescue_expression: + │ @ MultiWriteNode (location: (8,9)-(8,19)) + │ ├── flags: ∅ + │ ├── lefts: (length: 2) + │ │ ├── @ CallTargetNode (location: (8,9)-(8,12)) + │ │ │ ├── flags: ∅ + │ │ │ ├── receiver: + │ │ │ │ @ LocalVariableReadNode (location: (8,9)-(8,10)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ ├── call_operator_loc: (8,10)-(8,11) = "." + │ │ │ ├── name: :c= + │ │ │ └── message_loc: (8,11)-(8,12) = "c" + │ │ └── @ LocalVariableTargetNode (location: (8,14)-(8,15)) + │ │ ├── flags: ∅ + │ │ ├── name: :d + │ │ └── depth: 0 + │ ├── rest: ∅ + │ ├── rights: (length: 0) + │ ├── lparen_loc: ∅ + │ ├── rparen_loc: ∅ + │ ├── operator_loc: (8,16)-(8,17) = "=" + │ └── value: + │ @ IntegerNode (location: (8,18)-(8,19)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + └── @ RescueModifierNode (location: (9,0)-(9,20)) + ├── flags: newline + ├── expression: + │ @ CallNode (location: (9,0)-(9,1)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :a + │ ├── message_loc: (9,0)-(9,1) = "a" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── keyword_loc: (9,2)-(9,8) = "rescue" + └── rescue_expression: + @ MultiWriteNode (location: (9,9)-(9,20)) + ├── flags: ∅ + ├── lefts: (length: 2) + │ ├── @ IndexTargetNode (location: (9,9)-(9,13)) + │ │ ├── flags: attribute_write + │ │ ├── receiver: + │ │ │ @ LocalVariableReadNode (location: (9,9)-(9,10)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :b + │ │ │ └── depth: 0 + │ │ ├── opening_loc: (9,10)-(9,11) = "[" + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (9,11)-(9,12)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ IntegerNode (location: (9,11)-(9,12)) + │ │ │ ├── flags: static_literal, decimal + │ │ │ └── value: 0 + │ │ ├── closing_loc: (9,12)-(9,13) = "]" + │ │ └── block: ∅ + │ └── @ LocalVariableTargetNode (location: (9,15)-(9,16)) + │ ├── flags: ∅ + │ ├── name: :c + │ └── depth: 0 + ├── rest: ∅ + ├── rights: (length: 0) + ├── lparen_loc: ∅ + ├── rparen_loc: ∅ + ├── operator_loc: (9,17)-(9,18) = "=" + └── value: + @ IntegerNode (location: (9,19)-(9,20)) + ├── flags: static_literal, decimal + └── value: 1 diff --git a/snapshots/rescue_modifier_multiple_value.txt b/snapshots/rescue_modifier_multiple_value.txt new file mode 100644 index 0000000000..6485747c11 --- /dev/null +++ b/snapshots/rescue_modifier_multiple_value.txt @@ -0,0 +1,269 @@ +@ ProgramNode (location: (1,0)-(5,18)) +├── flags: ∅ +├── locals: [:b] +└── statements: + @ StatementsNode (location: (1,0)-(5,18)) + ├── flags: ∅ + └── body: (length: 5) + ├── @ RescueModifierNode (location: (1,0)-(1,17)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (1,0)-(1,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (1,0)-(1,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (1,2)-(1,8) = "rescue" + │ └── rescue_expression: + │ @ LocalVariableWriteNode (location: (1,9)-(1,17)) + │ ├── flags: ∅ + │ ├── name: :b + │ ├── depth: 0 + │ ├── name_loc: (1,9)-(1,10) = "b" + │ ├── value: + │ │ @ ArrayNode (location: (1,13)-(1,17)) + │ │ ├── flags: ∅ + │ │ ├── elements: (length: 2) + │ │ │ ├── @ CallNode (location: (1,13)-(1,14)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :c + │ │ │ │ ├── message_loc: (1,13)-(1,14) = "c" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ └── @ CallNode (location: (1,16)-(1,17)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :d + │ │ │ ├── message_loc: (1,16)-(1,17) = "d" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── opening_loc: ∅ + │ │ └── closing_loc: ∅ + │ └── operator_loc: (1,11)-(1,12) = "=" + ├── @ RescueModifierNode (location: (2,0)-(2,20)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (2,0)-(2,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (2,0)-(2,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (2,2)-(2,8) = "rescue" + │ └── rescue_expression: + │ @ LocalVariableWriteNode (location: (2,9)-(2,20)) + │ ├── flags: ∅ + │ ├── name: :b + │ ├── depth: 0 + │ ├── name_loc: (2,9)-(2,10) = "b" + │ ├── value: + │ │ @ ArrayNode (location: (2,13)-(2,20)) + │ │ ├── flags: ∅ + │ │ ├── elements: (length: 3) + │ │ │ ├── @ CallNode (location: (2,13)-(2,14)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :c + │ │ │ │ ├── message_loc: (2,13)-(2,14) = "c" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── @ CallNode (location: (2,16)-(2,17)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :d + │ │ │ │ ├── message_loc: (2,16)-(2,17) = "d" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ └── @ CallNode (location: (2,19)-(2,20)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :e + │ │ │ ├── message_loc: (2,19)-(2,20) = "e" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── opening_loc: ∅ + │ │ └── closing_loc: ∅ + │ └── operator_loc: (2,11)-(2,12) = "=" + ├── @ RescueModifierNode (location: (3,0)-(3,15)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (3,0)-(3,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (3,0)-(3,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (3,2)-(3,8) = "rescue" + │ └── rescue_expression: + │ @ LocalVariableWriteNode (location: (3,9)-(3,15)) + │ ├── flags: ∅ + │ ├── name: :b + │ ├── depth: 0 + │ ├── name_loc: (3,9)-(3,10) = "b" + │ ├── value: + │ │ @ ArrayNode (location: (3,13)-(3,15)) + │ │ ├── flags: contains_splat + │ │ ├── elements: (length: 1) + │ │ │ └── @ SplatNode (location: (3,13)-(3,15)) + │ │ │ ├── flags: ∅ + │ │ │ ├── operator_loc: (3,13)-(3,14) = "*" + │ │ │ └── expression: + │ │ │ @ CallNode (location: (3,14)-(3,15)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :c + │ │ │ ├── message_loc: (3,14)-(3,15) = "c" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── opening_loc: ∅ + │ │ └── closing_loc: ∅ + │ └── operator_loc: (3,11)-(3,12) = "=" + ├── @ RescueModifierNode (location: (4,0)-(4,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (4,0)-(4,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (4,0)-(4,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (4,2)-(4,8) = "rescue" + │ └── rescue_expression: + │ @ LocalVariableWriteNode (location: (4,9)-(4,18)) + │ ├── flags: ∅ + │ ├── name: :b + │ ├── depth: 0 + │ ├── name_loc: (4,9)-(4,10) = "b" + │ ├── value: + │ │ @ ArrayNode (location: (4,13)-(4,18)) + │ │ ├── flags: contains_splat + │ │ ├── elements: (length: 2) + │ │ │ ├── @ CallNode (location: (4,13)-(4,14)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :c + │ │ │ │ ├── message_loc: (4,13)-(4,14) = "c" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ └── @ SplatNode (location: (4,16)-(4,18)) + │ │ │ ├── flags: ∅ + │ │ │ ├── operator_loc: (4,16)-(4,17) = "*" + │ │ │ └── expression: + │ │ │ @ CallNode (location: (4,17)-(4,18)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :d + │ │ │ ├── message_loc: (4,17)-(4,18) = "d" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── opening_loc: ∅ + │ │ └── closing_loc: ∅ + │ └── operator_loc: (4,11)-(4,12) = "=" + └── @ RescueModifierNode (location: (5,0)-(5,18)) + ├── flags: newline + ├── expression: + │ @ CallNode (location: (5,0)-(5,1)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :a + │ ├── message_loc: (5,0)-(5,1) = "a" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── keyword_loc: (5,2)-(5,8) = "rescue" + └── rescue_expression: + @ LocalVariableWriteNode (location: (5,9)-(5,18)) + ├── flags: ∅ + ├── name: :b + ├── depth: 0 + ├── name_loc: (5,9)-(5,10) = "b" + ├── value: + │ @ ArrayNode (location: (5,13)-(5,18)) + │ ├── flags: contains_splat + │ ├── elements: (length: 2) + │ │ ├── @ SplatNode (location: (5,13)-(5,15)) + │ │ │ ├── flags: ∅ + │ │ │ ├── operator_loc: (5,13)-(5,14) = "*" + │ │ │ └── expression: + │ │ │ @ CallNode (location: (5,14)-(5,15)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :c + │ │ │ ├── message_loc: (5,14)-(5,15) = "c" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ └── @ CallNode (location: (5,17)-(5,18)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :d + │ │ ├── message_loc: (5,17)-(5,18) = "d" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── opening_loc: ∅ + │ └── closing_loc: ∅ + └── operator_loc: (5,11)-(5,12) = "=" diff --git a/snapshots/rescue_modifier_statement_value.txt b/snapshots/rescue_modifier_statement_value.txt new file mode 100644 index 0000000000..59e531555a --- /dev/null +++ b/snapshots/rescue_modifier_statement_value.txt @@ -0,0 +1,254 @@ +@ ProgramNode (location: (1,0)-(8,24)) +├── flags: ∅ +├── locals: [:x] +└── statements: + @ StatementsNode (location: (1,0)-(8,24)) + ├── flags: ∅ + └── body: (length: 8) + ├── @ RescueModifierNode (location: (1,0)-(1,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (1,0)-(1,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (1,0)-(1,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (1,2)-(1,8) = "rescue" + │ └── rescue_expression: + │ @ AliasMethodNode (location: (1,9)-(1,18)) + │ ├── flags: ∅ + │ ├── new_name: + │ │ @ SymbolNode (location: (1,15)-(1,16)) + │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ ├── opening_loc: ∅ + │ │ ├── value_loc: (1,15)-(1,16) = "x" + │ │ ├── closing_loc: ∅ + │ │ └── unescaped: "x" + │ ├── old_name: + │ │ @ SymbolNode (location: (1,17)-(1,18)) + │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ ├── opening_loc: ∅ + │ │ ├── value_loc: (1,17)-(1,18) = "y" + │ │ ├── closing_loc: ∅ + │ │ └── unescaped: "y" + │ └── keyword_loc: (1,9)-(1,14) = "alias" + ├── @ RescueModifierNode (location: (2,0)-(2,16)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (2,0)-(2,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (2,0)-(2,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (2,2)-(2,8) = "rescue" + │ └── rescue_expression: + │ @ UndefNode (location: (2,9)-(2,16)) + │ ├── flags: ∅ + │ ├── names: (length: 1) + │ │ └── @ SymbolNode (location: (2,15)-(2,16)) + │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ ├── opening_loc: ∅ + │ │ ├── value_loc: (2,15)-(2,16) = "m" + │ │ ├── closing_loc: ∅ + │ │ └── unescaped: "m" + │ └── keyword_loc: (2,9)-(2,14) = "undef" + ├── @ RescueModifierNode (location: (3,0)-(3,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (3,0)-(3,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (3,0)-(3,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (3,2)-(3,8) = "rescue" + │ └── rescue_expression: + │ @ PostExecutionNode (location: (3,9)-(3,18)) + │ ├── flags: ∅ + │ ├── statements: + │ │ @ StatementsNode (location: (3,15)-(3,16)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ CallNode (location: (3,15)-(3,16)) + │ │ ├── flags: newline, variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :b + │ │ ├── message_loc: (3,15)-(3,16) = "b" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (3,9)-(3,12) = "END" + │ ├── opening_loc: (3,13)-(3,14) = "{" + │ └── closing_loc: (3,17)-(3,18) = "}" + ├── @ RescueModifierNode (location: (4,0)-(4,14)) + │ ├── flags: newline + │ ├── expression: + │ │ @ CallNode (location: (4,0)-(4,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (4,0)-(4,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (4,2)-(4,8) = "rescue" + │ └── rescue_expression: + │ @ RetryNode (location: (4,9)-(4,14)) + │ └── flags: ∅ + ├── @ RescueModifierNode (location: (5,0)-(5,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ AliasMethodNode (location: (5,0)-(5,9)) + │ │ ├── flags: ∅ + │ │ ├── new_name: + │ │ │ @ SymbolNode (location: (5,6)-(5,7)) + │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── value_loc: (5,6)-(5,7) = "x" + │ │ │ ├── closing_loc: ∅ + │ │ │ └── unescaped: "x" + │ │ ├── old_name: + │ │ │ @ SymbolNode (location: (5,8)-(5,9)) + │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── value_loc: (5,8)-(5,9) = "y" + │ │ │ ├── closing_loc: ∅ + │ │ │ └── unescaped: "y" + │ │ └── keyword_loc: (5,0)-(5,5) = "alias" + │ ├── keyword_loc: (5,10)-(5,16) = "rescue" + │ └── rescue_expression: + │ @ CallNode (location: (5,17)-(5,18)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :b + │ ├── message_loc: (5,17)-(5,18) = "b" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ RescueModifierNode (location: (6,0)-(6,16)) + │ ├── flags: newline + │ ├── expression: + │ │ @ UndefNode (location: (6,0)-(6,7)) + │ │ ├── flags: ∅ + │ │ ├── names: (length: 1) + │ │ │ └── @ SymbolNode (location: (6,6)-(6,7)) + │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── value_loc: (6,6)-(6,7) = "m" + │ │ │ ├── closing_loc: ∅ + │ │ │ └── unescaped: "m" + │ │ └── keyword_loc: (6,0)-(6,5) = "undef" + │ ├── keyword_loc: (6,8)-(6,14) = "rescue" + │ └── rescue_expression: + │ @ CallNode (location: (6,15)-(6,16)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :b + │ ├── message_loc: (6,15)-(6,16) = "b" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ RescueModifierNode (location: (7,0)-(7,18)) + │ ├── flags: newline + │ ├── expression: + │ │ @ PostExecutionNode (location: (7,0)-(7,9)) + │ │ ├── flags: ∅ + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (7,6)-(7,7)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ CallNode (location: (7,6)-(7,7)) + │ │ │ ├── flags: newline, variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :b + │ │ │ ├── message_loc: (7,6)-(7,7) = "b" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── keyword_loc: (7,0)-(7,3) = "END" + │ │ ├── opening_loc: (7,4)-(7,5) = "{" + │ │ └── closing_loc: (7,8)-(7,9) = "}" + │ ├── keyword_loc: (7,10)-(7,16) = "rescue" + │ └── rescue_expression: + │ @ CallNode (location: (7,17)-(7,18)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :b + │ ├── message_loc: (7,17)-(7,18) = "b" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + └── @ LocalVariableWriteNode (location: (8,0)-(8,24)) + ├── flags: newline + ├── name: :x + ├── depth: 0 + ├── name_loc: (8,0)-(8,1) = "x" + ├── value: + │ @ RescueModifierNode (location: (8,4)-(8,24)) + │ ├── flags: ∅ + │ ├── expression: + │ │ @ CallNode (location: (8,4)-(8,11)) + │ │ ├── flags: ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :foo + │ │ ├── message_loc: (8,4)-(8,7) = "foo" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (8,8)-(8,11)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ CallNode (location: (8,8)-(8,11)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :bar + │ │ │ ├── message_loc: (8,8)-(8,11) = "bar" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── keyword_loc: (8,12)-(8,18) = "rescue" + │ └── rescue_expression: + │ @ RetryNode (location: (8,19)-(8,24)) + │ └── flags: ∅ + └── operator_loc: (8,2)-(8,3) = "=" diff --git a/src/prism.c b/src/prism.c index a2e04ed106..f3a9dc4684 100644 --- a/src/prism.c +++ b/src/prism.c @@ -12735,6 +12735,17 @@ expect1_opening(pm_parser_t *parser, pm_token_type_t type, pm_diagnostic_id_t di #define PM_PARSE_ACCEPTS_DO_BLOCK ((uint8_t) 0x4) #define PM_PARSE_IN_ENDLESS_DEF ((uint8_t) 0x8) +/** + * Indicates that we are parsing a statement even though the binding power is + * below PM_BINDING_POWER_STATEMENT. Only used for the value of a `rescue` + * modifier, whose CRuby grammar is a `stmt` in statement, multiple-assignment, + * and command-call contexts. Permits statement-only constructs that the + * binding power would otherwise reject: a multiple-value right-hand side + * (`b = c, d`), a leading splat value (`b = *c`), a parenthesized target list + * (`(b, c), d = 1`), and `alias`/`undef`/`END {}`. + */ +#define PM_PARSE_ACCEPTS_STATEMENT ((uint8_t) 0x10) + static pm_node_t * parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth); @@ -18856,7 +18867,7 @@ parse_symbol_array(pm_parser_t *parser, uint16_t depth) { * assignment, or a set of statements. */ static pm_node_t * -parse_parentheses(pm_parser_t *parser, pm_binding_power_t binding_power, uint16_t depth) { +parse_parentheses(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags, uint16_t depth) { pm_token_t opening = parser->current; pm_node_flags_t paren_flags = 0; @@ -18951,6 +18962,13 @@ parse_parentheses(pm_parser_t *parser, pm_binding_power_t binding_power, uint16_ } else if (context_p(parser, PM_CONTEXT_FOR_INDEX) && match1(parser, PM_TOKEN_KEYWORD_IN)) { /* All set, we're inside a for loop and we're parsing multiple * targets. */ + } else if (flags & PM_PARSE_ACCEPTS_STATEMENT) { + /* The rescue-modifier value parser promotes this target on a + * following `=` or comma. Reject any other binary operator that + * would otherwise consume the target list (e.g. `(a, b) + c`). */ + if (pm_binding_powers[parser->current.type].binary && !match1(parser, PM_TOKEN_EQUAL)) { + pm_parser_err_node(parser, result, PM_ERR_WRITE_TARGET_UNEXPECTED); + } } else if (binding_power != PM_BINDING_POWER_STATEMENT) { /* Multi targets are not allowed when it's not a statement * level. */ @@ -19056,6 +19074,23 @@ parse_parentheses(pm_parser_t *parser, pm_binding_power_t binding_power, uint16_ return UP(pm_parentheses_node_create(parser, &opening, UP(statements), &parser->previous, paren_flags)); } +/** + * Parse a splat (`*expression`) whose `*` operator has just been lexed and is + * sitting in parser->previous. The expression is optional, since an anonymous + * splat is just `*` with no following name. + */ +static pm_node_t * +parse_splat(pm_parser_t *parser, uint8_t flags, uint16_t depth) { + pm_token_t operator = parser->previous; + pm_node_t *name = NULL; + + if (token_begins_expression_p(parser->current.type)) { + name = parse_expression(parser, PM_BINDING_POWER_INDEX, flags & PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); + } + + return UP(pm_splat_node_create(parser, &operator, name)); +} + /** * Parse an expression that begins with the previous node that we just lexed. */ @@ -19177,7 +19212,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u } case PM_TOKEN_PARENTHESIS_LEFT: case PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES: - return parse_parentheses(parser, binding_power, depth); + return parse_parentheses(parser, binding_power, flags, depth); case PM_TOKEN_BRACE_LEFT: { // If we were passed a current_hash_keys via the parser, then that // means we're already parsing a hash and we want to share the set @@ -19579,7 +19614,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u parser_lex(parser); return UP(pm_source_line_node_create(parser, &parser->previous)); case PM_TOKEN_KEYWORD_ALIAS: { - if (binding_power != PM_BINDING_POWER_STATEMENT) { + if (binding_power != PM_BINDING_POWER_STATEMENT && !(flags & PM_PARSE_ACCEPTS_STATEMENT)) { pm_parser_err_current(parser, PM_ERR_STATEMENT_ALIAS); } @@ -19807,7 +19842,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u )); } case PM_TOKEN_KEYWORD_END_UPCASE: { - if (binding_power != PM_BINDING_POWER_STATEMENT) { + if (binding_power != PM_BINDING_POWER_STATEMENT && !(flags & PM_PARSE_ACCEPTS_STATEMENT)) { pm_parser_err_current(parser, PM_ERR_STATEMENT_POSTEXE_END); } @@ -19839,14 +19874,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u // First, parse out the first index expression. if (accept1(parser, PM_TOKEN_USTAR)) { - pm_token_t star_operator = parser->previous; - pm_node_t *name = NULL; - - if (token_begins_expression_p(parser->current.type)) { - name = parse_expression(parser, PM_BINDING_POWER_INDEX, flags & PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); - } - - index = UP(pm_splat_node_create(parser, &star_operator, name)); + index = parse_splat(parser, flags, depth); } else if (token_begins_expression_p(parser->current.type)) { index = parse_expression(parser, PM_BINDING_POWER_INDEX, flags & PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1)); } else { @@ -19900,7 +19928,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u return parse_conditional(parser, PM_CONTEXT_IF, opening_newline_index, if_after_else, (uint16_t) (depth + 1)); case PM_TOKEN_KEYWORD_UNDEF: { - if (binding_power != PM_BINDING_POWER_STATEMENT) { + if (binding_power != PM_BINDING_POWER_STATEMENT && !(flags & PM_PARSE_ACCEPTS_STATEMENT)) { pm_parser_err_current(parser, PM_ERR_STATEMENT_UNDEF); } @@ -20360,14 +20388,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u return UP(pm_error_recovery_node_create(parser, PM_TOKEN_START(parser, &parser->previous), PM_TOKEN_LENGTH(&parser->previous))); } - pm_token_t operator = parser->previous; - pm_node_t *name = NULL; - - if (token_begins_expression_p(parser->current.type)) { - name = parse_expression(parser, PM_BINDING_POWER_INDEX, flags & PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); - } - - pm_node_t *splat = UP(pm_splat_node_create(parser, &operator, name)); + pm_node_t *splat = parse_splat(parser, flags, depth); if (match1(parser, PM_TOKEN_COMMA)) { return parse_targets_validate(parser, splat, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); @@ -20573,6 +20594,25 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u } } +static pm_node_t * +parse_rescue_modifier_value(pm_parser_t *parser, uint8_t flags, bool statement, uint16_t depth); + +/** + * When a `rescue` modifier's handler is itself a statement (a multiple or + * implicit-array assignment, a pattern match, or a command call before + * `=>`/`in`), its parse stops before any trailing operator above the modifier + * level. Such an operator cannot follow a statement — only statement modifiers + * (`if`/`unless`/...) can — so report it and skip past it. + */ +static void +parse_rescue_modifier_terminator(pm_parser_t *parser, uint8_t flags, uint16_t depth) { + if (pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, &parser->current, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_str(parser->current.type)); + parser_lex(parser); + parse_expression(parser, pm_binding_powers[parser->previous.type].right, flags & PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); + } +} + /** * Parse a value that is going to be written to some kind of variable or method * call. We need to handle this separately because the rescue modifier is @@ -20604,9 +20644,22 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_ pm_token_t rescue = parser->current; parser_lex(parser); - pm_node_t *right = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, flags & PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + // As in parse_assignment_values, the resbody is a `stmt` (permitting a + // multiple assignment / command call) when the rescued value is itself a + // command call, and a plain `arg` otherwise. + bool statement_value = pm_command_call_value_p(value) || pm_block_call_p(value); + uint8_t rescue_flags = (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (statement_value ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0)); + + pm_node_t *right = parse_rescue_modifier_value(parser, rescue_flags, statement_value, (uint16_t) (depth + 1)); context_pop(parser); + // A pattern-match resbody is a statement, but here the rescue is nested + // in an assignment value where parse_expression_terminator cannot see + // it, so reject a trailing operator above the modifier level directly. + if (PM_NODE_TYPE_P(right, PM_MATCH_REQUIRED_NODE) || PM_NODE_TYPE_P(right, PM_MATCH_PREDICATE_NODE)) { + parse_rescue_modifier_terminator(parser, flags, depth); + } + return UP(pm_rescue_modifier_node_create(parser, value, &rescue, right)); } @@ -20663,10 +20716,19 @@ parse_assignment_value_local(pm_parser_t *parser, const pm_node_t *node) { */ static pm_node_t * parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth) { + bool statement_level = (previous_binding_power == PM_BINDING_POWER_STATEMENT) || (flags & PM_PARSE_ACCEPTS_STATEMENT); + bool permitted = true; - if (previous_binding_power != PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_USTAR)) permitted = false; + if (!statement_level && match1(parser, PM_TOKEN_USTAR)) permitted = false; - pm_node_t *value = parse_starred_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? (flags & PM_PARSE_ACCEPTS_COMMAND_CALL) : (previous_binding_power < PM_BINDING_POWER_MODIFIER ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0))), diag_id, (uint16_t) (depth + 1)); + // A command call (e.g. `x = y z`) is permitted as the value when assigning + // directly (carrying the caller's flag), or in any statement-level context + // — which includes a rescue modifier value via the flag. + uint8_t command_call_flag = (previous_binding_power == PM_BINDING_POWER_ASSIGNMENT) + ? (uint8_t) (flags & PM_PARSE_ACCEPTS_COMMAND_CALL) + : ((previous_binding_power < PM_BINDING_POWER_MODIFIER || statement_level) ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0); + + pm_node_t *value = parse_starred_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | command_call_flag), diag_id, (uint16_t) (depth + 1)); if (!permitted) pm_parser_err_node(parser, value, PM_ERR_UNEXPECTED_MULTI_WRITE); parse_assignment_value_local(parser, value); @@ -20675,7 +20737,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding // Block calls (command call + do block, e.g., `foo bar do end`) cannot // be followed by a comma to form a multi-value RHS because each element // of a multi-value assignment must be an `arg`, not a `block_call`. - if (previous_binding_power == PM_BINDING_POWER_STATEMENT && !pm_block_call_p(value) && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) { + if (statement_level && !pm_block_call_p(value) && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) { single_value = false; pm_array_node_t *array = pm_array_node_create(parser, NULL); @@ -20704,32 +20766,115 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding // Contradicting binding powers, the right-hand-side value of the assignment // allows the `rescue` modifier. - if ((single_value || (binding_power == (PM_BINDING_POWER_MULTI_ASSIGNMENT + 1))) && match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { + bool multiple_assignment = (binding_power == (PM_BINDING_POWER_MULTI_ASSIGNMENT + 1)); + if ((single_value || multiple_assignment) && match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { + bool command_value = pm_command_call_value_p(value) || pm_block_call_p(value); + + // A multiple assignment whose value is a command call (`x, y = foo + // bar`) is a complete statement (parse.y: `mlhs '=' + // command_call_value`, which has no rescue), so a trailing `rescue` + // modifies the whole assignment rather than the value. Leave it for the + // statement-level rescue instead of binding it to the value here. For a + // non-command value the rescue does bind to the value (parse.y: + // `mlhs '=' mrhs_arg modifier_rescue stmt`). + if (multiple_assignment && command_value) return value; + context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); pm_token_t rescue = parser->current; parser_lex(parser); - bool accepts_command_call_inner = false; + // The resbody is a `stmt` (parse.y: `command_rhs`/`mlhs '=' mrhs_arg`), + // which permits a multiple assignment and a command call, when this is a + // multiple assignment or the rescued value is itself a command call. + // Otherwise it is a plain `arg` (parse.y: `arg_rhs`). + bool statement_value = multiple_assignment || command_value; + uint8_t rescue_flags = (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (statement_value ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0)); - // RHS can accept command call iff the value is a call with arguments - // but without parenthesis. - if (PM_NODE_TYPE_P(value, PM_CALL_NODE)) { - pm_call_node_t *call_node = (pm_call_node_t *) value; - if ((call_node->arguments != NULL) && (call_node->opening_loc.length == 0)) { - accepts_command_call_inner = true; - } - } - - pm_node_t *right = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (accepts_command_call_inner ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0)), PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + pm_node_t *right = parse_rescue_modifier_value(parser, rescue_flags, statement_value, (uint16_t) (depth + 1)); context_pop(parser); + // A pattern-match resbody is a statement, but here the rescue is nested + // in an assignment value where parse_expression_terminator cannot see + // it, so reject a trailing operator above the modifier level directly. + if (PM_NODE_TYPE_P(right, PM_MATCH_REQUIRED_NODE) || PM_NODE_TYPE_P(right, PM_MATCH_PREDICATE_NODE)) { + parse_rescue_modifier_terminator(parser, flags, depth); + } + return UP(pm_rescue_modifier_node_create(parser, value, &rescue, right)); } return value; } +/** + * Parse the value of a `rescue` modifier. When `statement` is true the value + * is a `stmt` in CRuby's grammar (parse.y: `stmt modifier_rescue stmt`, + * `mlhs '=' mrhs_arg modifier_rescue stmt`, and `command_rhs`), which means it + * may be a multiple assignment, e.g. `a rescue b, c = 1`. We parse it at the + * rescue modifier's right binding power so that trailing statement modifiers + * (if/unless/while/until) stay outside the value, and promote to a multiple + * assignment exactly as the statement-level parser does when a comma (or + * leading splat) appears. Otherwise the value is a plain `arg` (parse.y: + * `arg modifier_rescue arg`), parsed above the `and`/`or`/`not` level so that + * those stay outside it. + */ +static pm_node_t * +parse_rescue_modifier_value(pm_parser_t *parser, uint8_t flags, bool statement, uint16_t depth) { + if (statement) { + pm_node_t *value; + bool multiple; + + if (match1(parser, PM_TOKEN_USTAR)) { + // A leading splat can only begin a multiple assignment target list. + parser_lex(parser); + value = parse_splat(parser, flags, depth); + multiple = true; + } else { + // The flag lets a single-target assignment take a multiple-value or + // splat right-hand side (`b = c, d` / `b = *c`); a comma _before_ an + // `=`, or a parenthesized target list (`(b, c), d = 1`), instead + // promotes to a multiple assignment target list below. + value = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, flags | PM_PARSE_ACCEPTS_STATEMENT, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + multiple = match1(parser, PM_TOKEN_COMMA) || PM_NODE_TYPE_P(value, PM_MULTI_TARGET_NODE); + } + + if (multiple) { + pm_node_t *target = parse_targets_validate(parser, value, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1)); + + // A promoted target list is only a valid rescue value as part of a + // complete `targets = values`. parse_targets_validate already + // reports a missing `=` for every terminator except `)` (which it + // permits for an enclosing mlhs paren that does not apply here), so + // reject that case. + if (!match1(parser, PM_TOKEN_EQUAL)) { + if (match1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) pm_parser_err_node(parser, target, PM_ERR_WRITE_TARGET_UNEXPECTED); + return target; + } + + pm_token_t operator = parser->current; + parser_lex(parser); + + pm_node_t *values = parse_assignment_values(parser, PM_BINDING_POWER_STATEMENT, PM_BINDING_POWER_MULTI_ASSIGNMENT + 1, flags, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL, (uint16_t) (depth + 1)); + value = parse_write(parser, target, &operator, values); + } + + // Reject a trailing operator that cannot follow a statement resbody. + // Pattern-match handlers are statements too, but for the bare statement + // form they are reported by parse_expression_terminator instead (which + // keeps its existing error-recovery), so they are excluded here. + if (!PM_NODE_TYPE_P(value, PM_MATCH_REQUIRED_NODE) && !PM_NODE_TYPE_P(value, PM_MATCH_PREDICATE_NODE)) { + parse_rescue_modifier_terminator(parser, flags, depth); + } + + return value; + } + + // Otherwise the resbody is a plain `arg` (parse.y: `arg modifier_rescue + // arg`), parsed above the `and`/`or`/`not` level so those stay outside it. + return parse_expression(parser, PM_BINDING_POWER_DEFINED, flags, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); +} + /** * Ensure a call node that is about to become a call operator node does not * have arguments or a block attached. If it does, then we'll need to add an @@ -21013,7 +21158,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_values(parser, previous_binding_power, PM_NODE_TYPE_P(node, PM_MULTI_TARGET_NODE) ? PM_BINDING_POWER_MULTI_ASSIGNMENT + 1 : binding_power, flags, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL, (uint16_t) (depth + 1)); - if (PM_NODE_TYPE_P(node, PM_MULTI_TARGET_NODE) && previous_binding_power != PM_BINDING_POWER_STATEMENT) { + if (PM_NODE_TYPE_P(node, PM_MULTI_TARGET_NODE) && previous_binding_power != PM_BINDING_POWER_STATEMENT && !(flags & PM_PARSE_ACCEPTS_STATEMENT)) { pm_parser_err_node(parser, node, PM_ERR_UNEXPECTED_MULTI_WRITE); } @@ -21805,7 +21950,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); accept1(parser, PM_TOKEN_NEWLINE); - pm_node_t *value = parse_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | PM_PARSE_ACCEPTS_COMMAND_CALL), PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1)); + pm_node_t *value = parse_rescue_modifier_value(parser, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | PM_PARSE_ACCEPTS_COMMAND_CALL), previous_binding_power == PM_BINDING_POWER_STATEMENT, (uint16_t) (depth + 1)); context_pop(parser); return UP(pm_rescue_modifier_node_create(parser, node, &token, value)); diff --git a/test/prism/errors/rescue_modifier_argument_value_alias.txt b/test/prism/errors/rescue_modifier_argument_value_alias.txt new file mode 100644 index 0000000000..e4d0b9dc90 --- /dev/null +++ b/test/prism/errors/rescue_modifier_argument_value_alias.txt @@ -0,0 +1,3 @@ +x = 1 rescue alias a b + ^~~~~ unexpected an `alias` at a non-statement position + diff --git a/test/prism/errors/rescue_modifier_argument_value_not.txt b/test/prism/errors/rescue_modifier_argument_value_not.txt new file mode 100644 index 0000000000..5d066eb43a --- /dev/null +++ b/test/prism/errors/rescue_modifier_argument_value_not.txt @@ -0,0 +1,4 @@ +x = 1 rescue not b + ^ expected a `(` after `not` + ^ unexpected local variable or method, expecting end-of-input + diff --git a/test/prism/errors/rescue_modifier_endless_method_multiple_assignment.txt b/test/prism/errors/rescue_modifier_endless_method_multiple_assignment.txt new file mode 100644 index 0000000000..892f78cc9b --- /dev/null +++ b/test/prism/errors/rescue_modifier_endless_method_multiple_assignment.txt @@ -0,0 +1,4 @@ +def m = foo bar rescue a, b = 1 + ^ unexpected ',', expecting end-of-input + ^ unexpected ',', ignoring it + diff --git a/test/prism/errors/rescue_modifier_multiple_assignment_logical_operator.txt b/test/prism/errors/rescue_modifier_multiple_assignment_logical_operator.txt new file mode 100644 index 0000000000..faca9bf49b --- /dev/null +++ b/test/prism/errors/rescue_modifier_multiple_assignment_logical_operator.txt @@ -0,0 +1,3 @@ +a rescue b, c = d and e + ^~~ unexpected 'and', expecting end-of-input + diff --git a/test/prism/errors/rescue_modifier_multiple_assignment_trailing_comma.txt b/test/prism/errors/rescue_modifier_multiple_assignment_trailing_comma.txt new file mode 100644 index 0000000000..bffd9e7280 --- /dev/null +++ b/test/prism/errors/rescue_modifier_multiple_assignment_trailing_comma.txt @@ -0,0 +1,3 @@ +(a rescue b,) + ^~ unexpected write target + diff --git a/test/prism/errors/rescue_modifier_single_assignment_multiple_assignment.txt b/test/prism/errors/rescue_modifier_single_assignment_multiple_assignment.txt new file mode 100644 index 0000000000..be666ef886 --- /dev/null +++ b/test/prism/errors/rescue_modifier_single_assignment_multiple_assignment.txt @@ -0,0 +1,4 @@ +z = 1 rescue a, b = 1 + ^ unexpected ',', expecting end-of-input + ^ unexpected ',', ignoring it + diff --git a/test/prism/fixtures/rescue_modifier_assignment.txt b/test/prism/fixtures/rescue_modifier_assignment.txt new file mode 100644 index 0000000000..4882835d77 --- /dev/null +++ b/test/prism/fixtures/rescue_modifier_assignment.txt @@ -0,0 +1,3 @@ +x, y = 1 rescue a, b = 1 +x, y = a rescue b, c = 1 +x, y = a rescue b = c d diff --git a/test/prism/fixtures/rescue_modifier_block_command_value.txt b/test/prism/fixtures/rescue_modifier_block_command_value.txt new file mode 100644 index 0000000000..94091f072a --- /dev/null +++ b/test/prism/fixtures/rescue_modifier_block_command_value.txt @@ -0,0 +1,5 @@ +x = foo a do end rescue b +x = foo a do end rescue b, c = 1 +x = a.foo b do end rescue c, d = 1 +x, y = foo a do end rescue b +x += foo a do end rescue b, c = 1 diff --git a/test/prism/fixtures/rescue_modifier_command_value.txt b/test/prism/fixtures/rescue_modifier_command_value.txt new file mode 100644 index 0000000000..7b31adcd2d --- /dev/null +++ b/test/prism/fixtures/rescue_modifier_command_value.txt @@ -0,0 +1,6 @@ +a rescue b = c d +x = foo bar rescue b, c = 1 +x = foo bar rescue b = c d +x += foo bar rescue b, c = 1 +x, y = foo bar rescue b +x, y = foo bar rescue b, c = 1 diff --git a/test/prism/fixtures/rescue_modifier_multiple_assignment.txt b/test/prism/fixtures/rescue_modifier_multiple_assignment.txt new file mode 100644 index 0000000000..79ca21f577 --- /dev/null +++ b/test/prism/fixtures/rescue_modifier_multiple_assignment.txt @@ -0,0 +1,9 @@ +a rescue b, c = 1 +a rescue b, c, d = 1 +a rescue *b, c = 1 +a rescue b, *c = 1 +a rescue b, *c, d = 1 +a rescue *b = 1 +a rescue (b, c), d = 1 +a rescue b.c, d = 1 +a rescue b[0], c = 1 diff --git a/test/prism/fixtures/rescue_modifier_multiple_value.txt b/test/prism/fixtures/rescue_modifier_multiple_value.txt new file mode 100644 index 0000000000..bac7b039a0 --- /dev/null +++ b/test/prism/fixtures/rescue_modifier_multiple_value.txt @@ -0,0 +1,5 @@ +a rescue b = c, d +a rescue b = c, d, e +a rescue b = *c +a rescue b = c, *d +a rescue b = *c, d diff --git a/test/prism/fixtures/rescue_modifier_statement_value.txt b/test/prism/fixtures/rescue_modifier_statement_value.txt new file mode 100644 index 0000000000..d3951a9608 --- /dev/null +++ b/test/prism/fixtures/rescue_modifier_statement_value.txt @@ -0,0 +1,8 @@ +a rescue alias x y +a rescue undef m +a rescue END { b } +a rescue retry +alias x y rescue b +undef m rescue b +END { b } rescue b +x = foo bar rescue retry diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index ad9fa0c92c..856ecedc1d 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -132,7 +132,6 @@ class ParserTest < TestCase "whitequark/lbrace_arg_after_command_args.txt", "whitequark/multiple_pattern_matches.txt", "whitequark/newline_in_hash_argument.txt", - "whitequark/pattern_matching_expr_in_paren.txt", "whitequark/pattern_matching_hash.txt", "whitequark/ruby_bug_14690.txt", "whitequark/ruby_bug_9669.txt",