From bf17086940ed64a8249ef464e34018608dcaca8f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 2 Mar 2026 11:49:29 -0500 Subject: [PATCH 1/4] Fix in handling in is a unique keyword because it can be the start of a clause or an infix keyword. We need to be explicitly sure that even though in _could_ close an expression context (the body of another in clause) that we are not also parsing an inline in. The exception is the case of a command call, which can never be the LHS of an expression, and so we must immediately exit. [Bug #21925] [Bug #21674] --- snapshots/case_in_in.txt | 81 ++++++++++++++++++++++++++++++ src/prism.c | 17 ++++--- test/prism/fixtures/case_in_in.txt | 4 ++ 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 snapshots/case_in_in.txt create mode 100644 test/prism/fixtures/case_in_in.txt diff --git a/snapshots/case_in_in.txt b/snapshots/case_in_in.txt new file mode 100644 index 0000000000..0fcedb0e89 --- /dev/null +++ b/snapshots/case_in_in.txt @@ -0,0 +1,81 @@ +@ ProgramNode (location: (1,0)-(4,3)) +├── flags: ∅ +├── locals: [:event] +└── statements: + @ StatementsNode (location: (1,0)-(4,3)) + ├── flags: ∅ + └── body: (length: 1) + └── @ CaseMatchNode (location: (1,0)-(4,3)) + ├── flags: newline + ├── predicate: + │ @ CallNode (location: (1,5)-(1,9)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :args + │ ├── message_loc: (1,5)-(1,9) = "args" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── conditions: (length: 1) + │ └── @ InNode (location: (2,0)-(3,25)) + │ ├── flags: ∅ + │ ├── pattern: + │ │ @ ArrayPatternNode (location: (2,3)-(2,10)) + │ │ ├── flags: ∅ + │ │ ├── constant: ∅ + │ │ ├── requireds: (length: 1) + │ │ │ └── @ LocalVariableTargetNode (location: (2,4)-(2,9)) + │ │ │ ├── flags: ∅ + │ │ │ ├── name: :event + │ │ │ └── depth: 0 + │ │ ├── rest: ∅ + │ │ ├── posts: (length: 0) + │ │ ├── opening_loc: (2,3)-(2,4) = "[" + │ │ └── closing_loc: (2,9)-(2,10) = "]" + │ ├── statements: + │ │ @ StatementsNode (location: (3,2)-(3,25)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ MatchPredicateNode (location: (3,2)-(3,25)) + │ │ ├── flags: newline + │ │ ├── value: + │ │ │ @ CallNode (location: (3,2)-(3,15)) + │ │ │ ├── flags: ∅ + │ │ │ ├── receiver: + │ │ │ │ @ CallNode (location: (3,2)-(3,9)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :context + │ │ │ │ ├── message_loc: (3,2)-(3,9) = "context" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── call_operator_loc: (3,9)-(3,10) = "." + │ │ │ ├── name: :event + │ │ │ ├── message_loc: (3,10)-(3,15) = "event" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ PinnedVariableNode (location: (3,19)-(3,25)) + │ │ │ ├── flags: ∅ + │ │ │ ├── variable: + │ │ │ │ @ LocalVariableReadNode (location: (3,20)-(3,25)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :event + │ │ │ │ └── depth: 0 + │ │ │ └── operator_loc: (3,19)-(3,20) = "^" + │ │ └── operator_loc: (3,16)-(3,18) = "in" + │ ├── in_loc: (2,0)-(2,2) = "in" + │ └── then_loc: ∅ + ├── else_clause: ∅ + ├── case_keyword_loc: (1,0)-(1,4) = "case" + └── end_keyword_loc: (4,0)-(4,3) = "end" diff --git a/src/prism.c b/src/prism.c index b36a6da204..b8fb4545c5 100644 --- a/src/prism.c +++ b/src/prism.c @@ -21639,12 +21639,6 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc ) { node = parse_expression_infix(parser, node, binding_power, current_binding_powers.right, accepts_command_call, (uint16_t) (depth + 1)); - if (context_terminator(parser->current_context->context, &parser->current)) { - // If this token terminates the current context, then we need to - // stop parsing the expression, as it has become a statement. - return node; - } - switch (PM_NODE_TYPE(node)) { case PM_MULTI_WRITE_NODE: // Multi-write nodes are statements, and cannot be followed by @@ -21757,6 +21751,17 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc break; } } + + if (context_terminator(parser->current_context->context, &parser->current)) { + pm_binding_powers_t next_binding_powers = pm_binding_powers[parser->current.type]; + if ( + !next_binding_powers.binary || + binding_power > next_binding_powers.left || + (PM_NODE_TYPE_P(node, PM_CALL_NODE) && pm_call_node_command_p((pm_call_node_t *) node)) + ) { + return node; + } + } } return node; diff --git a/test/prism/fixtures/case_in_in.txt b/test/prism/fixtures/case_in_in.txt new file mode 100644 index 0000000000..a5f9e4ec41 --- /dev/null +++ b/test/prism/fixtures/case_in_in.txt @@ -0,0 +1,4 @@ +case args +in [event] + context.event in ^event +end From 2c15e473ad011101d5ec5d30aa21965bd80ed398 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:51:22 +0100 Subject: [PATCH 2/4] Fix error message for block/lambda with `...` argument They currently complain that the parent method is not forwarding. But the actual problem is that these types of arguments simply don't accept `...` Fixes [Bug #21927] --- config.yml | 2 ++ src/prism.c | 28 +++++++++++++++++-- templates/src/diagnostic.c.erb | 2 ++ ..._not_allow_forward_arguments_in_blocks.txt | 12 +++++++- ...w_forward_arguments_in_lambda_literals.txt | 12 +++++++- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/config.yml b/config.yml index 5e29d6fa18..2d51ca0496 100644 --- a/config.yml +++ b/config.yml @@ -17,6 +17,8 @@ errors: - ARGUMENT_FORWARDING_UNBOUND - ARGUMENT_NO_FORWARDING_AMPERSAND - ARGUMENT_NO_FORWARDING_ELLIPSES + - ARGUMENT_NO_FORWARDING_ELLIPSES_LAMBDA + - ARGUMENT_NO_FORWARDING_ELLIPSES_BLOCK - ARGUMENT_NO_FORWARDING_STAR - ARGUMENT_NO_FORWARDING_STAR_STAR - ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT diff --git a/src/prism.c b/src/prism.c index b8fb4545c5..721d58474e 100644 --- a/src/prism.c +++ b/src/prism.c @@ -13859,6 +13859,7 @@ parse_parameters( bool allows_forwarding_parameters, bool accepts_blocks_in_defaults, bool in_block, + pm_diagnostic_id_t diag_id_forwarding, uint16_t depth ) { pm_do_loop_stack_push(parser, false); @@ -13914,7 +13915,7 @@ parse_parameters( } case PM_TOKEN_UDOT_DOT_DOT: { if (!allows_forwarding_parameters) { - pm_parser_err_current(parser, PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES); + pm_parser_err_current(parser, diag_id_forwarding); } bool succeeded = update_parameter_state(parser, &parser->current, &order); @@ -14594,6 +14595,7 @@ parse_block_parameters( false, accepts_blocks_in_defaults, true, + is_lambda_literal ? PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES_LAMBDA : PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES_BLOCK, (uint16_t) (depth + 1) ); if (!is_lambda_literal) { @@ -18836,7 +18838,17 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { params = NULL; } else { - params = parse_parameters(parser, PM_BINDING_POWER_DEFINED, true, false, true, true, false, (uint16_t) (depth + 1)); + params = parse_parameters( + parser, + PM_BINDING_POWER_DEFINED, + true, + false, + true, + true, + false, + PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES, + (uint16_t) (depth + 1) + ); } lex_state_set(parser, PM_LEX_STATE_BEG); @@ -18861,7 +18873,17 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b lparen = not_provided(parser); rparen = not_provided(parser); - params = parse_parameters(parser, PM_BINDING_POWER_DEFINED, false, false, true, true, false, (uint16_t) (depth + 1)); + params = parse_parameters( + parser, + PM_BINDING_POWER_DEFINED, + false, + false, + true, + true, + false, + PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES, + (uint16_t) (depth + 1) + ); // Reject `def * = 1` and similar. We have to specifically check // for them because they create ambiguity with optional arguments. diff --git a/templates/src/diagnostic.c.erb b/templates/src/diagnostic.c.erb index 121dd4b2b6..98f9b922ed 100644 --- a/templates/src/diagnostic.c.erb +++ b/templates/src/diagnostic.c.erb @@ -102,6 +102,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_AMPERSAND] = { "unexpected `&`; no anonymous block parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected ... when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES_LAMBDA] = { "unexpected ... in lambda argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES_BLOCK] = { "unexpected ... in block argument", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*`; no anonymous rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**`; no anonymous keyword rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT] = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_LEVEL_SYNTAX }, diff --git a/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt b/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt index df49557617..639dec3af2 100644 --- a/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt +++ b/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt @@ -1,3 +1,13 @@ a {|...|} - ^~~ unexpected ... when the parent method is not forwarding + ^~~ unexpected ... in block argument + +def foo(...) + a {|...|} + ^~~ unexpected ... in block argument +end + +def foo + a {|...|} + ^~~ unexpected ... in block argument +end diff --git a/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt b/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt index c2405a5c66..03e17683e4 100644 --- a/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt +++ b/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt @@ -1,3 +1,13 @@ ->(...) {} - ^~~ unexpected ... when the parent method is not forwarding + ^~~ unexpected ... in lambda argument + +def foo(...) + ->(...) {} + ^~~ unexpected ... in lambda argument +end + +def foo + ->(...) {} + ^~~ unexpected ... in lambda argument +end From 655916c6683515acad2155a11021f2553ffe7892 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:41:21 +0100 Subject: [PATCH 3/4] Correctly handle `and?` and similar on ruby 4.0 It gets confused for syntax introduced in https://bugs.ruby-lang.org/issues/20925 But it actually should be a plain method call. `!`/`?` are not valid as part of an identifier, methods however allow them as the last character. Fixes [Bug #21946] --- snapshots/4.0/leading_logical.txt | 72 ++++------ snapshots/and_or_with_suffix.txt | 139 ++++++++++++++++++++ src/prism.c | 21 ++- test/prism/fixtures/4.0/leading_logical.txt | 5 - test/prism/fixtures/and_or_with_suffix.txt | 17 +++ test/prism/lex_test.rb | 7 + test/prism/ruby/ripper_test.rb | 7 + 7 files changed, 211 insertions(+), 57 deletions(-) create mode 100644 snapshots/and_or_with_suffix.txt create mode 100644 test/prism/fixtures/and_or_with_suffix.txt diff --git a/snapshots/4.0/leading_logical.txt b/snapshots/4.0/leading_logical.txt index 9e043a88ce..c8ecaa40ba 100644 --- a/snapshots/4.0/leading_logical.txt +++ b/snapshots/4.0/leading_logical.txt @@ -1,10 +1,10 @@ -@ ProgramNode (location: (1,0)-(21,5)) +@ ProgramNode (location: (1,0)-(15,4)) ├── flags: ∅ ├── locals: [] └── statements: - @ StatementsNode (location: (1,0)-(21,5)) + @ StatementsNode (location: (1,0)-(15,4)) ├── flags: ∅ - └── body: (length: 8) + └── body: (length: 4) ├── @ AndNode (location: (1,0)-(3,4)) │ ├── flags: newline │ ├── left: @@ -62,50 +62,22 @@ │ │ ├── flags: static_literal, decimal │ │ └── value: 3 │ └── operator_loc: (11,0)-(11,3) = "and" - ├── @ OrNode (location: (13,0)-(15,4)) - │ ├── flags: newline - │ ├── left: - │ │ @ OrNode (location: (13,0)-(14,4)) - │ │ ├── flags: ∅ - │ │ ├── left: - │ │ │ @ IntegerNode (location: (13,0)-(13,1)) - │ │ │ ├── flags: static_literal, decimal - │ │ │ └── value: 1 - │ │ ├── right: - │ │ │ @ IntegerNode (location: (14,3)-(14,4)) - │ │ │ ├── flags: static_literal, decimal - │ │ │ └── value: 2 - │ │ └── operator_loc: (14,0)-(14,2) = "or" - │ ├── right: - │ │ @ IntegerNode (location: (15,3)-(15,4)) - │ │ ├── flags: static_literal, decimal - │ │ └── value: 3 - │ └── operator_loc: (15,0)-(15,2) = "or" - ├── @ IntegerNode (location: (17,0)-(17,1)) - │ ├── flags: newline, static_literal, decimal - │ └── value: 1 - ├── @ CallNode (location: (18,0)-(18,6)) - │ ├── flags: newline, variable_call, ignore_visibility - │ ├── receiver: ∅ - │ ├── call_operator_loc: ∅ - │ ├── name: :andfoo - │ ├── message_loc: (18,0)-(18,6) = "andfoo" - │ ├── opening_loc: ∅ - │ ├── arguments: ∅ - │ ├── closing_loc: ∅ - │ ├── equal_loc: ∅ - │ └── block: ∅ - ├── @ IntegerNode (location: (20,0)-(20,1)) - │ ├── flags: newline, static_literal, decimal - │ └── value: 2 - └── @ CallNode (location: (21,0)-(21,5)) - ├── flags: newline, variable_call, ignore_visibility - ├── receiver: ∅ - ├── call_operator_loc: ∅ - ├── name: :orfoo - ├── message_loc: (21,0)-(21,5) = "orfoo" - ├── opening_loc: ∅ - ├── arguments: ∅ - ├── closing_loc: ∅ - ├── equal_loc: ∅ - └── block: ∅ + └── @ OrNode (location: (13,0)-(15,4)) + ├── flags: newline + ├── left: + │ @ OrNode (location: (13,0)-(14,4)) + │ ├── flags: ∅ + │ ├── left: + │ │ @ IntegerNode (location: (13,0)-(13,1)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 1 + │ ├── right: + │ │ @ IntegerNode (location: (14,3)-(14,4)) + │ │ ├── flags: static_literal, decimal + │ │ └── value: 2 + │ └── operator_loc: (14,0)-(14,2) = "or" + ├── right: + │ @ IntegerNode (location: (15,3)-(15,4)) + │ ├── flags: static_literal, decimal + │ └── value: 3 + └── operator_loc: (15,0)-(15,2) = "or" diff --git a/snapshots/and_or_with_suffix.txt b/snapshots/and_or_with_suffix.txt new file mode 100644 index 0000000000..593dee1766 --- /dev/null +++ b/snapshots/and_or_with_suffix.txt @@ -0,0 +1,139 @@ +@ ProgramNode (location: (1,0)-(17,5)) +├── flags: ∅ +├── locals: [] +└── statements: + @ StatementsNode (location: (1,0)-(17,5)) + ├── flags: ∅ + └── body: (length: 12) + ├── @ CallNode (location: (1,0)-(1,3)) + │ ├── flags: newline, variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (1,0)-(1,3) = "foo" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (2,0)-(2,4)) + │ ├── flags: newline, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :and? + │ ├── message_loc: (2,0)-(2,4) = "and?" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (4,0)-(4,3)) + │ ├── flags: newline, variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (4,0)-(4,3) = "foo" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (5,0)-(5,3)) + │ ├── flags: newline, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :or? + │ ├── message_loc: (5,0)-(5,3) = "or?" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (7,0)-(7,3)) + │ ├── flags: newline, variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (7,0)-(7,3) = "foo" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (8,0)-(8,4)) + │ ├── flags: newline, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :and! + │ ├── message_loc: (8,0)-(8,4) = "and!" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (10,0)-(10,3)) + │ ├── flags: newline, variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (10,0)-(10,3) = "foo" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (11,0)-(11,3)) + │ ├── flags: newline, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :or! + │ ├── message_loc: (11,0)-(11,3) = "or!" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (13,0)-(13,3)) + │ ├── flags: newline, variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (13,0)-(13,3) = "foo" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (14,0)-(14,6)) + │ ├── flags: newline, variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :andbar + │ ├── message_loc: (14,0)-(14,6) = "andbar" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (16,0)-(16,3)) + │ ├── flags: newline, variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :foo + │ ├── message_loc: (16,0)-(16,3) = "foo" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: ∅ + └── @ CallNode (location: (17,0)-(17,5)) + ├── flags: newline, variable_call, ignore_visibility + ├── receiver: ∅ + ├── call_operator_loc: ∅ + ├── name: :orbar + ├── message_loc: (17,0)-(17,5) = "orbar" + ├── opening_loc: ∅ + ├── arguments: ∅ + ├── closing_loc: ∅ + ├── equal_loc: ∅ + └── block: ∅ diff --git a/src/prism.c b/src/prism.c index 721d58474e..222bb2dac2 100644 --- a/src/prism.c +++ b/src/prism.c @@ -9979,8 +9979,21 @@ parser_lex(pm_parser_t *parser) { following && ( (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') || (peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') || - (peek_at(parser, following) == 'a' && peek_at(parser, following + 1) == 'n' && peek_at(parser, following + 2) == 'd' && !char_is_identifier(parser, following + 3, parser->end - (following + 3))) || - (peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r' && !char_is_identifier(parser, following + 2, parser->end - (following + 2))) + ( + peek_at(parser, following) == 'a' && + peek_at(parser, following + 1) == 'n' && + peek_at(parser, following + 2) == 'd' && + peek_at(parser, next_content + 3) != '!' && + peek_at(parser, next_content + 3) != '?' && + !char_is_identifier(parser, following + 3, parser->end - (following + 3)) + ) || + ( + peek_at(parser, following) == 'o' && + peek_at(parser, following + 1) == 'r' && + peek_at(parser, next_content + 2) != '!' && + peek_at(parser, next_content + 2) != '?' && + !char_is_identifier(parser, following + 2, parser->end - (following + 2)) + ) ) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); @@ -10051,6 +10064,8 @@ parser_lex(pm_parser_t *parser) { peek_at(parser, next_content) == 'a' && peek_at(parser, next_content + 1) == 'n' && peek_at(parser, next_content + 2) == 'd' && + peek_at(parser, next_content + 3) != '!' && + peek_at(parser, next_content + 3) != '?' && !char_is_identifier(parser, next_content + 3, parser->end - (next_content + 3)) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); @@ -10067,6 +10082,8 @@ parser_lex(pm_parser_t *parser) { if ( peek_at(parser, next_content) == 'o' && peek_at(parser, next_content + 1) == 'r' && + peek_at(parser, next_content + 2) != '!' && + peek_at(parser, next_content + 2) != '?' && !char_is_identifier(parser, next_content + 2, parser->end - (next_content + 2)) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); diff --git a/test/prism/fixtures/4.0/leading_logical.txt b/test/prism/fixtures/4.0/leading_logical.txt index feb5ee245c..ee87e00d4f 100644 --- a/test/prism/fixtures/4.0/leading_logical.txt +++ b/test/prism/fixtures/4.0/leading_logical.txt @@ -14,8 +14,3 @@ and 3 or 2 or 3 -1 -andfoo - -2 -orfoo diff --git a/test/prism/fixtures/and_or_with_suffix.txt b/test/prism/fixtures/and_or_with_suffix.txt new file mode 100644 index 0000000000..59ee4d0b88 --- /dev/null +++ b/test/prism/fixtures/and_or_with_suffix.txt @@ -0,0 +1,17 @@ +foo +and? + +foo +or? + +foo +and! + +foo +or! + +foo +andbar + +foo +orbar diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 68e47a0964..d3fdb4277e 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -36,6 +36,13 @@ class LexTest < TestCase except << "whitequark/ruby_bug_19281.txt" end + if RUBY_VERSION.start_with?("4.") + except += [ + # https://bugs.ruby-lang.org/issues/21945 + "and_or_with_suffix.txt", + ] + end + # https://bugs.ruby-lang.org/issues/21168#note-5 except << "command_method_call_2.txt" diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index bbd85585a9..46b0970e68 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -37,6 +37,13 @@ class RipperTest < TestCase ] end + if RUBY_VERSION.start_with?("4.") + incorrect += [ + # https://bugs.ruby-lang.org/issues/21945 + "and_or_with_suffix.txt", + ] + end + # Skip these tests that we haven't implemented yet. omitted = [ "dos_endings.txt", From 0d87c5961caae38ac2a971fc84e50a5d980bef69 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:41:46 +0100 Subject: [PATCH 4/4] Run cruby tests against the stable branch --- .github/workflows/cruby-bindings.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cruby-bindings.yml b/.github/workflows/cruby-bindings.yml index d635e550f0..918bee4098 100644 --- a/.github/workflows/cruby-bindings.yml +++ b/.github/workflows/cruby-bindings.yml @@ -32,6 +32,7 @@ jobs: with: repository: ruby/ruby path: ruby/ruby + ref: ruby_4_0 fetch-depth: 1 - name: Install libraries run: |