From a22ac6b59496f8229a04470c485baa6b3bb23238 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:36:48 +0100 Subject: [PATCH 1/3] Remove dead code See https://bugs.ruby-lang.org/issues/21669#note-4 > At line 1067, void_node = vn is executed (and vn is guaranteed to be non-NULL on the line above), so it's impossible for void_node to be NULL at line 1075. Thus, it is indeed dead code. --- src/prism.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/prism.c b/src/prism.c index 20037b5b9f..89d01b34c9 100644 --- a/src/prism.c +++ b/src/prism.c @@ -1090,9 +1090,6 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { void_node = NULL; break; } - if (void_node == NULL) { - void_node = vn; - } } if (cast->else_clause != NULL) { From 0de72f6e80c5ee2bbe21129a7347a9b586c8c7eb Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:34:18 +0100 Subject: [PATCH 2/3] [Bug #21669] Thoroughly implement void value expression check --- snapshots/3.3-3.4/void_value.txt | 151 ++++++++++++ snapshots/4.0/void_value.txt | 59 +++++ snapshots/non_void_value.txt | 232 ++++++++++++++++++ src/prism.c | 94 ++++++- test/prism/errors/3.4-3.4/void_value.txt | 18 ++ test/prism/errors/4.0/void_value.txt | 30 +++ ...id_value_expression_in_begin_statement.txt | 2 - test/prism/fixtures/3.3-3.4/void_value.txt | 17 ++ test/prism/fixtures/4.0/void_value.txt | 7 + test/prism/fixtures/non_void_value.txt | 26 ++ test/prism/fixtures_test.rb | 3 + test/prism/lex_test.rb | 2 + test/prism/locals_test.rb | 3 + test/prism/ruby/ripper_test.rb | 3 + test/prism/ruby/ruby_parser_test.rb | 2 + 15 files changed, 637 insertions(+), 12 deletions(-) create mode 100644 snapshots/3.3-3.4/void_value.txt create mode 100644 snapshots/4.0/void_value.txt create mode 100644 snapshots/non_void_value.txt create mode 100644 test/prism/errors/3.4-3.4/void_value.txt create mode 100644 test/prism/errors/4.0/void_value.txt create mode 100644 test/prism/fixtures/3.3-3.4/void_value.txt create mode 100644 test/prism/fixtures/4.0/void_value.txt create mode 100644 test/prism/fixtures/non_void_value.txt diff --git a/snapshots/3.3-3.4/void_value.txt b/snapshots/3.3-3.4/void_value.txt new file mode 100644 index 0000000000..dac043ad8c --- /dev/null +++ b/snapshots/3.3-3.4/void_value.txt @@ -0,0 +1,151 @@ +@ ProgramNode (location: (1,0)-(17,3)) +├── flags: ∅ +├── locals: [:x] +└── statements: + @ StatementsNode (location: (1,0)-(17,3)) + ├── flags: ∅ + └── body: (length: 3) + ├── @ LocalVariableWriteNode (location: (1,0)-(7,3)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (1,0)-(1,1) = "x" + │ ├── value: + │ │ @ BeginNode (location: (1,4)-(7,3)) + │ │ ├── flags: ∅ + │ │ ├── begin_keyword_loc: (1,4)-(1,9) = "begin" + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (2,2)-(2,5)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ CallNode (location: (2,2)-(2,5)) + │ │ │ ├── flags: newline, variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :foo + │ │ │ ├── message_loc: (2,2)-(2,5) = "foo" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── rescue_clause: + │ │ │ @ RescueNode (location: (3,0)-(4,8)) + │ │ │ ├── flags: ∅ + │ │ │ ├── keyword_loc: (3,0)-(3,6) = "rescue" + │ │ │ ├── exceptions: (length: 0) + │ │ │ ├── operator_loc: ∅ + │ │ │ ├── reference: ∅ + │ │ │ ├── then_keyword_loc: ∅ + │ │ │ ├── statements: + │ │ │ │ @ StatementsNode (location: (4,2)-(4,8)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── body: (length: 1) + │ │ │ │ └── @ ReturnNode (location: (4,2)-(4,8)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── keyword_loc: (4,2)-(4,8) = "return" + │ │ │ │ └── arguments: ∅ + │ │ │ └── subsequent: ∅ + │ │ ├── else_clause: + │ │ │ @ ElseNode (location: (5,0)-(7,3)) + │ │ │ ├── flags: ∅ + │ │ │ ├── else_keyword_loc: (5,0)-(5,4) = "else" + │ │ │ ├── statements: + │ │ │ │ @ StatementsNode (location: (6,2)-(6,8)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── body: (length: 1) + │ │ │ │ └── @ ReturnNode (location: (6,2)-(6,8)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── keyword_loc: (6,2)-(6,8) = "return" + │ │ │ │ └── arguments: ∅ + │ │ │ └── end_keyword_loc: (7,0)-(7,3) = "end" + │ │ ├── ensure_clause: ∅ + │ │ └── end_keyword_loc: (7,0)-(7,3) = "end" + │ └── operator_loc: (1,2)-(1,3) = "=" + ├── @ LocalVariableWriteNode (location: (9,0)-(12,3)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (9,0)-(9,1) = "x" + │ ├── value: + │ │ @ CaseNode (location: (9,4)-(12,3)) + │ │ ├── flags: ∅ + │ │ ├── predicate: ∅ + │ │ ├── conditions: (length: 1) + │ │ │ └── @ WhenNode (location: (10,2)-(10,20)) + │ │ │ ├── flags: ∅ + │ │ │ ├── keyword_loc: (10,2)-(10,6) = "when" + │ │ │ ├── conditions: (length: 1) + │ │ │ │ └── @ IntegerNode (location: (10,7)-(10,8)) + │ │ │ │ ├── flags: static_literal, decimal + │ │ │ │ └── value: 1 + │ │ │ ├── then_keyword_loc: (10,9)-(10,13) = "then" + │ │ │ └── statements: + │ │ │ @ StatementsNode (location: (10,14)-(10,20)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ ReturnNode (location: (10,14)-(10,20)) + │ │ │ ├── flags: newline + │ │ │ ├── keyword_loc: (10,14)-(10,20) = "return" + │ │ │ └── arguments: ∅ + │ │ ├── else_clause: + │ │ │ @ ElseNode (location: (11,2)-(12,3)) + │ │ │ ├── flags: ∅ + │ │ │ ├── else_keyword_loc: (11,2)-(11,6) = "else" + │ │ │ ├── statements: + │ │ │ │ @ StatementsNode (location: (11,14)-(11,20)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── body: (length: 1) + │ │ │ │ └── @ ReturnNode (location: (11,14)-(11,20)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── keyword_loc: (11,14)-(11,20) = "return" + │ │ │ │ └── arguments: ∅ + │ │ │ └── end_keyword_loc: (12,0)-(12,3) = "end" + │ │ ├── case_keyword_loc: (9,4)-(9,8) = "case" + │ │ └── end_keyword_loc: (12,0)-(12,3) = "end" + │ └── operator_loc: (9,2)-(9,3) = "=" + └── @ LocalVariableWriteNode (location: (14,0)-(17,3)) + ├── flags: newline + ├── name: :x + ├── depth: 0 + ├── name_loc: (14,0)-(14,1) = "x" + ├── value: + │ @ CaseMatchNode (location: (14,4)-(17,3)) + │ ├── flags: ∅ + │ ├── predicate: + │ │ @ IntegerNode (location: (14,9)-(14,10)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 1 + │ ├── conditions: (length: 1) + │ │ └── @ InNode (location: (15,2)-(15,18)) + │ │ ├── flags: ∅ + │ │ ├── pattern: + │ │ │ @ IntegerNode (location: (15,5)-(15,6)) + │ │ │ ├── flags: static_literal, decimal + │ │ │ └── value: 2 + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (15,12)-(15,18)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ ReturnNode (location: (15,12)-(15,18)) + │ │ │ ├── flags: newline + │ │ │ ├── keyword_loc: (15,12)-(15,18) = "return" + │ │ │ └── arguments: ∅ + │ │ ├── in_loc: (15,2)-(15,4) = "in" + │ │ └── then_loc: (15,7)-(15,11) = "then" + │ ├── else_clause: + │ │ @ ElseNode (location: (16,2)-(17,3)) + │ │ ├── flags: ∅ + │ │ ├── else_keyword_loc: (16,2)-(16,6) = "else" + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (16,12)-(16,18)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ ReturnNode (location: (16,12)-(16,18)) + │ │ │ ├── flags: newline + │ │ │ ├── keyword_loc: (16,12)-(16,18) = "return" + │ │ │ └── arguments: ∅ + │ │ └── end_keyword_loc: (17,0)-(17,3) = "end" + │ ├── case_keyword_loc: (14,4)-(14,8) = "case" + │ └── end_keyword_loc: (17,0)-(17,3) = "end" + └── operator_loc: (14,2)-(14,3) = "=" diff --git a/snapshots/4.0/void_value.txt b/snapshots/4.0/void_value.txt new file mode 100644 index 0000000000..b6ca0253aa --- /dev/null +++ b/snapshots/4.0/void_value.txt @@ -0,0 +1,59 @@ +@ ProgramNode (location: (1,0)-(7,3)) +├── flags: ∅ +├── locals: [:x] +└── statements: + @ StatementsNode (location: (1,0)-(7,3)) + ├── flags: ∅ + └── body: (length: 1) + └── @ LocalVariableWriteNode (location: (1,0)-(7,3)) + ├── flags: newline + ├── name: :x + ├── depth: 0 + ├── name_loc: (1,0)-(1,1) = "x" + ├── value: + │ @ BeginNode (location: (1,4)-(7,3)) + │ ├── flags: ∅ + │ ├── begin_keyword_loc: (1,4)-(1,9) = "begin" + │ ├── statements: + │ │ @ StatementsNode (location: (2,2)-(2,8)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ ReturnNode (location: (2,2)-(2,8)) + │ │ ├── flags: newline + │ │ ├── keyword_loc: (2,2)-(2,8) = "return" + │ │ └── arguments: ∅ + │ ├── rescue_clause: + │ │ @ RescueNode (location: (3,0)-(4,6)) + │ │ ├── flags: ∅ + │ │ ├── keyword_loc: (3,0)-(3,6) = "rescue" + │ │ ├── exceptions: (length: 0) + │ │ ├── operator_loc: ∅ + │ │ ├── reference: ∅ + │ │ ├── then_keyword_loc: ∅ + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (4,2)-(4,6)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ StringNode (location: (4,2)-(4,6)) + │ │ │ ├── flags: newline + │ │ │ ├── opening_loc: (4,2)-(4,3) = "\"" + │ │ │ ├── content_loc: (4,3)-(4,5) = "OK" + │ │ │ ├── closing_loc: (4,5)-(4,6) = "\"" + │ │ │ └── unescaped: "OK" + │ │ └── subsequent: ∅ + │ ├── else_clause: + │ │ @ ElseNode (location: (5,0)-(7,3)) + │ │ ├── flags: ∅ + │ │ ├── else_keyword_loc: (5,0)-(5,4) = "else" + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (6,2)-(6,8)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ ReturnNode (location: (6,2)-(6,8)) + │ │ │ ├── flags: newline + │ │ │ ├── keyword_loc: (6,2)-(6,8) = "return" + │ │ │ └── arguments: ∅ + │ │ └── end_keyword_loc: (7,0)-(7,3) = "end" + │ ├── ensure_clause: ∅ + │ └── end_keyword_loc: (7,0)-(7,3) = "end" + └── operator_loc: (1,2)-(1,3) = "=" diff --git a/snapshots/non_void_value.txt b/snapshots/non_void_value.txt new file mode 100644 index 0000000000..6ddcecbdf5 --- /dev/null +++ b/snapshots/non_void_value.txt @@ -0,0 +1,232 @@ +@ ProgramNode (location: (1,0)-(26,3)) +├── flags: ∅ +├── locals: [:x] +└── statements: + @ StatementsNode (location: (1,0)-(26,3)) + ├── flags: ∅ + └── body: (length: 4) + ├── @ LocalVariableWriteNode (location: (1,0)-(4,3)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (1,0)-(1,1) = "x" + │ ├── value: + │ │ @ BeginNode (location: (1,4)-(4,3)) + │ │ ├── flags: ∅ + │ │ ├── begin_keyword_loc: (1,4)-(1,9) = "begin" + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (2,2)-(3,15)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 2) + │ │ │ ├── @ IfNode (location: (2,2)-(2,16)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── if_keyword_loc: (2,9)-(2,11) = "if" + │ │ │ │ ├── predicate: + │ │ │ │ │ @ TrueNode (location: (2,12)-(2,16)) + │ │ │ │ │ └── flags: static_literal + │ │ │ │ ├── then_keyword_loc: ∅ + │ │ │ │ ├── statements: + │ │ │ │ │ @ StatementsNode (location: (2,2)-(2,8)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ └── body: (length: 1) + │ │ │ │ │ └── @ ReturnNode (location: (2,2)-(2,8)) + │ │ │ │ │ ├── flags: newline + │ │ │ │ │ ├── keyword_loc: (2,2)-(2,8) = "return" + │ │ │ │ │ └── arguments: ∅ + │ │ │ │ ├── subsequent: ∅ + │ │ │ │ └── end_keyword_loc: ∅ + │ │ │ └── @ StringNode (location: (3,2)-(3,15)) + │ │ │ ├── flags: newline + │ │ │ ├── opening_loc: (3,2)-(3,3) = "\"" + │ │ │ ├── content_loc: (3,3)-(3,14) = "conditional" + │ │ │ ├── closing_loc: (3,14)-(3,15) = "\"" + │ │ │ └── unescaped: "conditional" + │ │ ├── rescue_clause: ∅ + │ │ ├── else_clause: ∅ + │ │ ├── ensure_clause: ∅ + │ │ └── end_keyword_loc: (4,0)-(4,3) = "end" + │ └── operator_loc: (1,2)-(1,3) = "=" + ├── @ LocalVariableWriteNode (location: (6,0)-(11,3)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (6,0)-(6,1) = "x" + │ ├── value: + │ │ @ IfNode (location: (6,4)-(11,3)) + │ │ ├── flags: newline + │ │ ├── if_keyword_loc: (6,4)-(6,6) = "if" + │ │ ├── predicate: + │ │ │ @ TrueNode (location: (6,7)-(6,11)) + │ │ │ └── flags: static_literal + │ │ ├── then_keyword_loc: ∅ + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (7,2)-(8,15)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 2) + │ │ │ ├── @ IfNode (location: (7,2)-(7,16)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── if_keyword_loc: (7,9)-(7,11) = "if" + │ │ │ │ ├── predicate: + │ │ │ │ │ @ TrueNode (location: (7,12)-(7,16)) + │ │ │ │ │ └── flags: static_literal + │ │ │ │ ├── then_keyword_loc: ∅ + │ │ │ │ ├── statements: + │ │ │ │ │ @ StatementsNode (location: (7,2)-(7,8)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ └── body: (length: 1) + │ │ │ │ │ └── @ ReturnNode (location: (7,2)-(7,8)) + │ │ │ │ │ ├── flags: newline + │ │ │ │ │ ├── keyword_loc: (7,2)-(7,8) = "return" + │ │ │ │ │ └── arguments: ∅ + │ │ │ │ ├── subsequent: ∅ + │ │ │ │ └── end_keyword_loc: ∅ + │ │ │ └── @ StringNode (location: (8,2)-(8,15)) + │ │ │ ├── flags: newline + │ │ │ ├── opening_loc: (8,2)-(8,3) = "\"" + │ │ │ ├── content_loc: (8,3)-(8,14) = "conditional" + │ │ │ ├── closing_loc: (8,14)-(8,15) = "\"" + │ │ │ └── unescaped: "conditional" + │ │ ├── subsequent: + │ │ │ @ ElseNode (location: (9,0)-(11,3)) + │ │ │ ├── flags: ∅ + │ │ │ ├── else_keyword_loc: (9,0)-(9,4) = "else" + │ │ │ ├── statements: + │ │ │ │ @ StatementsNode (location: (10,2)-(10,8)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── body: (length: 1) + │ │ │ │ └── @ ReturnNode (location: (10,2)-(10,8)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── keyword_loc: (10,2)-(10,8) = "return" + │ │ │ │ └── arguments: ∅ + │ │ │ └── end_keyword_loc: (11,0)-(11,3) = "end" + │ │ └── end_keyword_loc: (11,0)-(11,3) = "end" + │ └── operator_loc: (6,2)-(6,3) = "=" + ├── @ LocalVariableWriteNode (location: (13,0)-(18,3)) + │ ├── flags: newline + │ ├── name: :x + │ ├── depth: 0 + │ ├── name_loc: (13,0)-(13,1) = "x" + │ ├── value: + │ │ @ IfNode (location: (13,4)-(18,3)) + │ │ ├── flags: newline + │ │ ├── if_keyword_loc: (13,4)-(13,6) = "if" + │ │ ├── predicate: + │ │ │ @ TrueNode (location: (13,7)-(13,11)) + │ │ │ └── flags: static_literal + │ │ ├── then_keyword_loc: ∅ + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (14,2)-(14,16)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ IfNode (location: (14,2)-(14,16)) + │ │ │ ├── flags: newline + │ │ │ ├── if_keyword_loc: (14,9)-(14,11) = "if" + │ │ │ ├── predicate: + │ │ │ │ @ TrueNode (location: (14,12)-(14,16)) + │ │ │ │ └── flags: static_literal + │ │ │ ├── then_keyword_loc: ∅ + │ │ │ ├── statements: + │ │ │ │ @ StatementsNode (location: (14,2)-(14,8)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── body: (length: 1) + │ │ │ │ └── @ ReturnNode (location: (14,2)-(14,8)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── keyword_loc: (14,2)-(14,8) = "return" + │ │ │ │ └── arguments: ∅ + │ │ │ ├── subsequent: ∅ + │ │ │ └── end_keyword_loc: ∅ + │ │ ├── subsequent: + │ │ │ @ ElseNode (location: (15,0)-(18,3)) + │ │ │ ├── flags: ∅ + │ │ │ ├── else_keyword_loc: (15,0)-(15,4) = "else" + │ │ │ ├── statements: + │ │ │ │ @ StatementsNode (location: (16,2)-(17,15)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── body: (length: 2) + │ │ │ │ ├── @ IfNode (location: (16,2)-(16,16)) + │ │ │ │ │ ├── flags: newline + │ │ │ │ │ ├── if_keyword_loc: (16,9)-(16,11) = "if" + │ │ │ │ │ ├── predicate: + │ │ │ │ │ │ @ TrueNode (location: (16,12)-(16,16)) + │ │ │ │ │ │ └── flags: static_literal + │ │ │ │ │ ├── then_keyword_loc: ∅ + │ │ │ │ │ ├── statements: + │ │ │ │ │ │ @ StatementsNode (location: (16,2)-(16,8)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── body: (length: 1) + │ │ │ │ │ │ └── @ ReturnNode (location: (16,2)-(16,8)) + │ │ │ │ │ │ ├── flags: newline + │ │ │ │ │ │ ├── keyword_loc: (16,2)-(16,8) = "return" + │ │ │ │ │ │ └── arguments: ∅ + │ │ │ │ │ ├── subsequent: ∅ + │ │ │ │ │ └── end_keyword_loc: ∅ + │ │ │ │ └── @ StringNode (location: (17,2)-(17,15)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── opening_loc: (17,2)-(17,3) = "\"" + │ │ │ │ ├── content_loc: (17,3)-(17,14) = "conditional" + │ │ │ │ ├── closing_loc: (17,14)-(17,15) = "\"" + │ │ │ │ └── unescaped: "conditional" + │ │ │ └── end_keyword_loc: (18,0)-(18,3) = "end" + │ │ └── end_keyword_loc: (18,0)-(18,3) = "end" + │ └── operator_loc: (13,2)-(13,3) = "=" + └── @ LocalVariableWriteNode (location: (20,0)-(26,3)) + ├── flags: newline + ├── name: :x + ├── depth: 0 + ├── name_loc: (20,0)-(20,1) = "x" + ├── value: + │ @ CaseNode (location: (20,4)-(26,3)) + │ ├── flags: ∅ + │ ├── predicate: ∅ + │ ├── conditions: (length: 1) + │ │ └── @ WhenNode (location: (21,2)-(23,17)) + │ │ ├── flags: ∅ + │ │ ├── keyword_loc: (21,2)-(21,6) = "when" + │ │ ├── conditions: (length: 1) + │ │ │ └── @ IntegerNode (location: (21,7)-(21,8)) + │ │ │ ├── flags: static_literal, decimal + │ │ │ └── value: 1 + │ │ ├── then_keyword_loc: ∅ + │ │ └── statements: + │ │ @ StatementsNode (location: (22,4)-(23,17)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 2) + │ │ ├── @ IfNode (location: (22,4)-(22,18)) + │ │ │ ├── flags: newline + │ │ │ ├── if_keyword_loc: (22,11)-(22,13) = "if" + │ │ │ ├── predicate: + │ │ │ │ @ TrueNode (location: (22,14)-(22,18)) + │ │ │ │ └── flags: static_literal + │ │ │ ├── then_keyword_loc: ∅ + │ │ │ ├── statements: + │ │ │ │ @ StatementsNode (location: (22,4)-(22,10)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── body: (length: 1) + │ │ │ │ └── @ ReturnNode (location: (22,4)-(22,10)) + │ │ │ │ ├── flags: newline + │ │ │ │ ├── keyword_loc: (22,4)-(22,10) = "return" + │ │ │ │ └── arguments: ∅ + │ │ │ ├── subsequent: ∅ + │ │ │ └── end_keyword_loc: ∅ + │ │ └── @ StringNode (location: (23,4)-(23,17)) + │ │ ├── flags: newline + │ │ ├── opening_loc: (23,4)-(23,5) = "\"" + │ │ ├── content_loc: (23,5)-(23,16) = "conditional" + │ │ ├── closing_loc: (23,16)-(23,17) = "\"" + │ │ └── unescaped: "conditional" + │ ├── else_clause: + │ │ @ ElseNode (location: (24,2)-(26,3)) + │ │ ├── flags: ∅ + │ │ ├── else_keyword_loc: (24,2)-(24,6) = "else" + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (25,4)-(25,10)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ ReturnNode (location: (25,4)-(25,10)) + │ │ │ ├── flags: newline + │ │ │ ├── keyword_loc: (25,4)-(25,10) = "return" + │ │ │ └── arguments: ∅ + │ │ └── end_keyword_loc: (26,0)-(26,3) = "end" + │ ├── case_keyword_loc: (20,4)-(20,8) = "case" + │ └── end_keyword_loc: (26,0)-(26,3) = "end" + └── operator_loc: (20,2)-(20,3) = "=" diff --git a/src/prism.c b/src/prism.c index 89d01b34c9..fc243341e2 100644 --- a/src/prism.c +++ b/src/prism.c @@ -1042,6 +1042,13 @@ pm_parser_optional_constant_id_token(pm_parser_t *parser, const pm_token_t *toke return token->type == PM_TOKEN_NOT_PROVIDED ? 0 : pm_parser_constant_id_token(parser, token); } +/** + * This macro allows you to define a case statement for all of the nodes that + * may result in a void value. + */ +#define PM_CASE_VOID_VALUE PM_RETURN_NODE: case PM_BREAK_NODE: case PM_NEXT_NODE: \ + case PM_REDO_NODE: case PM_RETRY_NODE: case PM_MATCH_REQUIRED_NODE + /** * Check whether or not the given node is value expression. * If the node is value node, it returns NULL. @@ -1053,12 +1060,7 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { while (node != NULL) { switch (PM_NODE_TYPE(node)) { - case PM_RETURN_NODE: - case PM_BREAK_NODE: - case PM_NEXT_NODE: - case PM_REDO_NODE: - case PM_RETRY_NODE: - case PM_MATCH_REQUIRED_NODE: + case PM_CASE_VOID_VALUE: return void_node != NULL ? void_node : node; case PM_MATCH_PREDICATE_NODE: return NULL; @@ -1078,15 +1080,23 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { node = UP(cast->ensure_clause); } else if (cast->rescue_clause != NULL) { - if (cast->statements == NULL) return NULL; + // https://bugs.ruby-lang.org/issues/21669 + if (cast->else_clause == NULL || parser->version < PM_OPTIONS_VERSION_CRUBY_4_0) { + if (cast->statements == NULL) return NULL; - pm_node_t *vn = pm_check_value_expression(parser, UP(cast->statements)); - if (vn == NULL) return NULL; - if (void_node == NULL) void_node = vn; + pm_node_t *vn = pm_check_value_expression(parser, UP(cast->statements)); + if (vn == NULL) return NULL; + if (void_node == NULL) void_node = vn; + } for (pm_rescue_node_t *rescue_clause = cast->rescue_clause; rescue_clause != NULL; rescue_clause = rescue_clause->subsequent) { pm_node_t *vn = pm_check_value_expression(parser, UP(rescue_clause->statements)); + if (vn == NULL) { + // https://bugs.ruby-lang.org/issues/21669 + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { + return NULL; + } void_node = NULL; break; } @@ -1094,6 +1104,12 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { if (cast->else_clause != NULL) { node = UP(cast->else_clause); + + // https://bugs.ruby-lang.org/issues/21669 + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { + pm_node_t *vn = pm_check_value_expression(parser, node); + if (vn != NULL) return vn; + } } else { return void_node; } @@ -1103,6 +1119,50 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { break; } + case PM_CASE_NODE: { + // https://bugs.ruby-lang.org/issues/21669 + if (parser->version < PM_OPTIONS_VERSION_CRUBY_4_0) { + return NULL; + } + + pm_case_node_t *cast = (pm_case_node_t *) node; + if (cast->else_clause == NULL) return NULL; + + pm_node_t *condition; + PM_NODE_LIST_FOREACH(&cast->conditions, index, condition) { + assert(PM_NODE_TYPE_P(condition, PM_WHEN_NODE)); + + pm_when_node_t *cast = (pm_when_node_t *) condition; + pm_node_t *vn = pm_check_value_expression(parser, UP(cast->statements)); + if (vn == NULL) return NULL; + if (void_node == NULL) void_node = vn; + } + + node = UP(cast->else_clause); + break; + } + case PM_CASE_MATCH_NODE: { + // https://bugs.ruby-lang.org/issues/21669 + if (parser->version < PM_OPTIONS_VERSION_CRUBY_4_0) { + return NULL; + } + + pm_case_match_node_t *cast = (pm_case_match_node_t *) node; + if (cast->else_clause == NULL) return NULL; + + pm_node_t *condition; + PM_NODE_LIST_FOREACH(&cast->conditions, index, condition) { + assert(PM_NODE_TYPE_P(condition, PM_IN_NODE)); + + pm_in_node_t *cast = (pm_in_node_t *) condition; + pm_node_t *vn = pm_check_value_expression(parser, UP(cast->statements)); + if (vn == NULL) return NULL; + if (void_node == NULL) void_node = vn; + } + + node = UP(cast->else_clause); + break; + } case PM_ENSURE_NODE: { pm_ensure_node_t *cast = (pm_ensure_node_t *) node; node = UP(cast->statements); @@ -1115,6 +1175,20 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { } case PM_STATEMENTS_NODE: { pm_statements_node_t *cast = (pm_statements_node_t *) node; + + // https://bugs.ruby-lang.org/issues/21669 + if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { + pm_node_t *body_part; + PM_NODE_LIST_FOREACH(&cast->body, index, body_part) { + switch (PM_NODE_TYPE(body_part)) { + case PM_CASE_VOID_VALUE: + if (void_node == NULL) void_node = body_part; + break; + default: break; + } + } + } + node = cast->body.nodes[cast->body.size - 1]; break; } diff --git a/test/prism/errors/3.4-3.4/void_value.txt b/test/prism/errors/3.4-3.4/void_value.txt new file mode 100644 index 0000000000..c03139bb05 --- /dev/null +++ b/test/prism/errors/3.4-3.4/void_value.txt @@ -0,0 +1,18 @@ +x = begin + return + ^~~~~~ unexpected void value expression +rescue + return +else + return +end + +x = begin + return +rescue + "OK" +else + return + ^~~~~~ unexpected void value expression +end + diff --git a/test/prism/errors/4.0/void_value.txt b/test/prism/errors/4.0/void_value.txt new file mode 100644 index 0000000000..6f34b0c3b0 --- /dev/null +++ b/test/prism/errors/4.0/void_value.txt @@ -0,0 +1,30 @@ +x = begin + return +rescue + return +else + return + ^~~~~~ unexpected void value expression +end + +x = begin + ignored_because_else_branch +rescue + return +else + return + ^~~~~~ unexpected void value expression +end + +x = case + when 1 then return + ^~~~~~ unexpected void value expression + else return +end + +x = case 1 + in 2 then return + ^~~~~~ unexpected void value expression + else return +end + diff --git a/test/prism/errors/void_value_expression_in_begin_statement.txt b/test/prism/errors/void_value_expression_in_begin_statement.txt index aa8f1ded96..fb968a12e1 100644 --- a/test/prism/errors/void_value_expression_in_begin_statement.txt +++ b/test/prism/errors/void_value_expression_in_begin_statement.txt @@ -14,8 +14,6 @@ x = begin return ensure return end ^~~~~~ unexpected void value expression x = begin return; rescue; return end ^~~~~~ unexpected void value expression -x = begin return; rescue; return; else return end - ^~~~~~ unexpected void value expression x = begin; return; rescue; retry; end ^~~~~~ unexpected void value expression diff --git a/test/prism/fixtures/3.3-3.4/void_value.txt b/test/prism/fixtures/3.3-3.4/void_value.txt new file mode 100644 index 0000000000..b723d6a0fd --- /dev/null +++ b/test/prism/fixtures/3.3-3.4/void_value.txt @@ -0,0 +1,17 @@ +x = begin + foo +rescue + return +else + return +end + +x = case + when 1 then return + else return +end + +x = case 1 + in 2 then return + else return +end diff --git a/test/prism/fixtures/4.0/void_value.txt b/test/prism/fixtures/4.0/void_value.txt new file mode 100644 index 0000000000..915112d623 --- /dev/null +++ b/test/prism/fixtures/4.0/void_value.txt @@ -0,0 +1,7 @@ +x = begin + return +rescue + "OK" +else + return +end diff --git a/test/prism/fixtures/non_void_value.txt b/test/prism/fixtures/non_void_value.txt new file mode 100644 index 0000000000..9a4522af8b --- /dev/null +++ b/test/prism/fixtures/non_void_value.txt @@ -0,0 +1,26 @@ +x = begin + return if true + "conditional" +end + +x = if true + return if true + "conditional" +else + return +end + +x = if true + return if true +else + return if true + "conditional" +end + +x = case + when 1 + return if true + "conditional" + else + return +end diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 7df97029d3..475306c5e8 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -24,7 +24,10 @@ class FixturesTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" + # https://bugs.ruby-lang.org/issues/21669 + except << "4.0/void_value.txt" Fixture.each_for_current_ruby(except: except) do |fixture| define_method(fixture.test_name) { assert_valid_syntax(fixture.read) } diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 68e47a0964..f49963bb78 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -38,6 +38,8 @@ class LexTest < TestCase # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" + # https://bugs.ruby-lang.org/issues/21669 + except << "4.0/void_value.txt" Fixture.each_for_current_ruby(except: except) do |fixture| define_method(fixture.test_name) { assert_lex(fixture) } diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index 4f8d6080e8..e9852487aa 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -28,6 +28,9 @@ class LocalsTest < TestCase # https://bugs.ruby-lang.org/issues/21168#note-5 "command_method_call_2.txt", + + # https://bugs.ruby-lang.org/issues/21669 + "4.0/void_value.txt" ] Fixture.each_for_current_ruby(except: except) do |fixture| diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index bd63302efc..ba9f7da5f7 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -36,6 +36,9 @@ class RipperTest < TestCase ] end + # https://bugs.ruby-lang.org/issues/21669 + incorrect << "4.0/void_value.txt" + # Skip these tests that we haven't implemented yet. omitted = [ "dos_endings.txt", diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 4b7e9c93ed..59feaf867c 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -83,6 +83,8 @@ class RubyParserTest < TestCase "3.3-3.3/keyword_args_in_array_assignment.txt", "3.3-3.3/return_in_sclass.txt", + "3.3-3.4/void_value.txt", + "3.4/circular_parameters.txt", "4.0/endless_methods_command_call.txt", From 779470a7eec15d1e98ac13d58ad11f209c4183cd Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:27:29 +0100 Subject: [PATCH 3/3] https://github.com/ruby/ruby/pull/15498/commits/74ea62b99ebbaf68343224ba56e3e21892a4cd0c --- src/prism.c | 9 ++--- .../singleton_method_for_literals.txt | 0 .../4.0/singleton_method_for_literals.txt | 40 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) rename test/prism/errors/{ => 3.3-3.4}/singleton_method_for_literals.txt (100%) create mode 100644 test/prism/errors/4.0/singleton_method_for_literals.txt diff --git a/src/prism.c b/src/prism.c index fc243341e2..528ace87f9 100644 --- a/src/prism.c +++ b/src/prism.c @@ -1180,12 +1180,9 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) { pm_node_t *body_part; PM_NODE_LIST_FOREACH(&cast->body, index, body_part) { - switch (PM_NODE_TYPE(body_part)) { - case PM_CASE_VOID_VALUE: - if (void_node == NULL) void_node = body_part; - break; - default: break; - } + if (index == cast->body.size - 1) break; + pm_node_t *vn = pm_check_value_expression(parser, body_part); + if (vn) return vn; } } diff --git a/test/prism/errors/singleton_method_for_literals.txt b/test/prism/errors/3.3-3.4/singleton_method_for_literals.txt similarity index 100% rename from test/prism/errors/singleton_method_for_literals.txt rename to test/prism/errors/3.3-3.4/singleton_method_for_literals.txt diff --git a/test/prism/errors/4.0/singleton_method_for_literals.txt b/test/prism/errors/4.0/singleton_method_for_literals.txt new file mode 100644 index 0000000000..12dd368980 --- /dev/null +++ b/test/prism/errors/4.0/singleton_method_for_literals.txt @@ -0,0 +1,40 @@ +def (1).g; end + ^ cannot define singleton method for literals +def ((a; 1)).foo; end + ^ cannot define singleton method for literals +def ((return; 1)).bar; end + ^~~~~~ unexpected void value expression + ^ cannot define singleton method for literals +def (((1))).foo; end + ^ cannot define singleton method for literals +def (__FILE__).foo; end + ^~~~~~~~ cannot define singleton method for literals +def (__ENCODING__).foo; end + ^~~~~~~~~~~~ cannot define singleton method for literals +def (__LINE__).foo; end + ^~~~~~~~ cannot define singleton method for literals +def ("foo").foo; end + ^~~~~ cannot define singleton method for literals +def (3.14).foo; end + ^~~~ cannot define singleton method for literals +def (3.14i).foo; end + ^~~~~ cannot define singleton method for literals +def (:foo).foo; end + ^~~~ cannot define singleton method for literals +def (:'foo').foo; end + ^~~~~~ cannot define singleton method for literals +def (:'f{o}').foo; end + ^~~~~~~ cannot define singleton method for literals +def ('foo').foo; end + ^~~~~ cannot define singleton method for literals +def ("foo").foo; end + ^~~~~ cannot define singleton method for literals +def ("#{fo}o").foo; end + ^~~~~~~~ cannot define singleton method for literals +def (/foo/).foo; end + ^~~~~ cannot define singleton method for literals +def (/f#{oo}/).foo; end + ^~~~~~~~ cannot define singleton method for literals +def ([1]).foo; end + ^~~ cannot define singleton method for literals +