From fc5f985e22b6a37053df9dea4eecc508854645b4 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:47:36 +0100 Subject: [PATCH] Reject `END { break }` for Ruby 4.0 For [Bug #20409] --- snapshots/3.3-3.4/end_block_exit.txt | 33 +++++++++++++++++++ snapshots/end_block_exit.txt | 20 +++++++++++ src/prism.c | 12 ++++++- test/prism/errors/4.0/end_block_exit.txt | 5 +++ test/prism/errors_test.rb | 9 ++++- .../prism/fixtures/3.3-3.4/end_block_exit.txt | 7 ++++ test/prism/fixtures/end_block_exit.txt | 3 ++ 7 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 snapshots/3.3-3.4/end_block_exit.txt create mode 100644 snapshots/end_block_exit.txt create mode 100644 test/prism/errors/4.0/end_block_exit.txt create mode 100644 test/prism/fixtures/3.3-3.4/end_block_exit.txt create mode 100644 test/prism/fixtures/end_block_exit.txt diff --git a/snapshots/3.3-3.4/end_block_exit.txt b/snapshots/3.3-3.4/end_block_exit.txt new file mode 100644 index 0000000000..04e2c3b668 --- /dev/null +++ b/snapshots/3.3-3.4/end_block_exit.txt @@ -0,0 +1,33 @@ +@ ProgramNode (location: (1,0)-(7,1)) +├── flags: ∅ +├── locals: [] +└── statements: + @ StatementsNode (location: (1,0)-(7,1)) + ├── flags: ∅ + └── body: (length: 2) + ├── @ PostExecutionNode (location: (1,0)-(3,1)) + │ ├── flags: newline + │ ├── 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: ∅ + │ ├── keyword_loc: (1,0)-(1,3) = "END" + │ ├── opening_loc: (1,4)-(1,5) = "{" + │ └── closing_loc: (3,0)-(3,1) = "}" + └── @ PostExecutionNode (location: (5,0)-(7,1)) + ├── flags: newline + ├── statements: + │ @ StatementsNode (location: (6,2)-(6,7)) + │ ├── flags: ∅ + │ └── body: (length: 1) + │ └── @ BreakNode (location: (6,2)-(6,7)) + │ ├── flags: newline + │ ├── arguments: ∅ + │ └── keyword_loc: (6,2)-(6,7) = "break" + ├── keyword_loc: (5,0)-(5,3) = "END" + ├── opening_loc: (5,4)-(5,5) = "{" + └── closing_loc: (7,0)-(7,1) = "}" diff --git a/snapshots/end_block_exit.txt b/snapshots/end_block_exit.txt new file mode 100644 index 0000000000..4acc5f4218 --- /dev/null +++ b/snapshots/end_block_exit.txt @@ -0,0 +1,20 @@ +@ ProgramNode (location: (1,0)-(3,1)) +├── flags: ∅ +├── locals: [] +└── statements: + @ StatementsNode (location: (1,0)-(3,1)) + ├── flags: ∅ + └── body: (length: 1) + └── @ PostExecutionNode (location: (1,0)-(3,1)) + ├── flags: newline + ├── statements: + │ @ StatementsNode (location: (2,2)-(2,6)) + │ ├── flags: ∅ + │ └── body: (length: 1) + │ └── @ NextNode (location: (2,2)-(2,6)) + │ ├── flags: newline + │ ├── arguments: ∅ + │ └── keyword_loc: (2,2)-(2,6) = "next" + ├── keyword_loc: (1,0)-(1,3) = "END" + ├── opening_loc: (1,4)-(1,5) = "{" + └── closing_loc: (3,0)-(3,1) = "}" diff --git a/src/prism.c b/src/prism.c index 7059db8f1d..1c4d0dfca8 100644 --- a/src/prism.c +++ b/src/prism.c @@ -14950,12 +14950,22 @@ parse_block_exit(pm_parser_t *parser, pm_node_t *node) { case PM_CONTEXT_LAMBDA_ENSURE: case PM_CONTEXT_LAMBDA_RESCUE: case PM_CONTEXT_LOOP_PREDICATE: - case PM_CONTEXT_POSTEXE: case PM_CONTEXT_UNTIL: case PM_CONTEXT_WHILE: // These are the good cases. We're allowed to have a block exit // in these contexts. return; + case PM_CONTEXT_POSTEXE: + // https://bugs.ruby-lang.org/issues/20409 + if (context_node->context == PM_CONTEXT_POSTEXE) { + if (parser->version < PM_OPTIONS_VERSION_CRUBY_4_0) { + return; + } + if (PM_NODE_TYPE_P(node, PM_NEXT_NODE)) { + return; + } + } + PRISM_FALLTHROUGH case PM_CONTEXT_DEF: case PM_CONTEXT_DEF_PARAMS: case PM_CONTEXT_DEF_ELSE: diff --git a/test/prism/errors/4.0/end_block_exit.txt b/test/prism/errors/4.0/end_block_exit.txt new file mode 100644 index 0000000000..f8fc59d1a3 --- /dev/null +++ b/test/prism/errors/4.0/end_block_exit.txt @@ -0,0 +1,5 @@ +END { + break + ^~~~~ Invalid break +} + diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 706b739557..919335417a 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -9,6 +9,11 @@ class ErrorsTest < TestCase base = File.expand_path("errors", __dir__) filepaths = Dir["**/*.txt", base: base] + PARSE_Y_EXCLUDES = [ + # https://bugs.ruby-lang.org/issues/20409 + "#{base}/4.0/end_block_exit.txt" + ] + filepaths.each do |filepath| ruby_versions_for(filepath).each do |version| define_method(:"test_#{version}_#{File.basename(filepath, ".txt")}") do @@ -88,7 +93,9 @@ def assert_errors(filepath, version) expected = File.read(filepath, binmode: true, external_encoding: Encoding::UTF_8) source = expected.lines.grep_v(/^\s*\^/).join.gsub(/\n*\z/, "") - refute_valid_syntax(source) if CURRENT_MAJOR_MINOR == version + if CURRENT_MAJOR_MINOR == version && !PARSE_Y_EXCLUDES.include?(filepath) + refute_valid_syntax(source) + end result = Prism.parse(source, version: version) errors = result.errors diff --git a/test/prism/fixtures/3.3-3.4/end_block_exit.txt b/test/prism/fixtures/3.3-3.4/end_block_exit.txt new file mode 100644 index 0000000000..53afa1e2f4 --- /dev/null +++ b/test/prism/fixtures/3.3-3.4/end_block_exit.txt @@ -0,0 +1,7 @@ +END { + return +} + +END { + break +} diff --git a/test/prism/fixtures/end_block_exit.txt b/test/prism/fixtures/end_block_exit.txt new file mode 100644 index 0000000000..1f64dcb6ed --- /dev/null +++ b/test/prism/fixtures/end_block_exit.txt @@ -0,0 +1,3 @@ +END { + next +}