From 7bcca7177d6c42891004899d63e13ef47d78a337 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 5 Mar 2024 08:51:10 -0500 Subject: [PATCH] [ruby/prism] Implement hash patterns for ripper translation https://github.com/ruby/prism/commit/6bd7ae2ed2 --- lib/prism/translation/ripper.rb | 20 ++- test/prism/ripper_test.rb | 278 +------------------------------- 2 files changed, 20 insertions(+), 278 deletions(-) diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index adc82406fa77c0..89f793cb70de18 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -1117,7 +1117,25 @@ def visit_hash_node(node) # foo => {} # ^^ def visit_hash_pattern_node(node) - raise NoMethodError, __method__ + constant = visit(node.constant) + elements = + if node.elements.any? || !node.rest.nil? + node.elements.map do |element| + bounds(element.key.location) + key = on_label(element.key.slice) + value = visit(element.value) + + [key, value] + end + end + + rest = + if !node.rest.nil? + visit(node.rest.value) + end + + bounds(node.location) + on_hshptn(constant, elements, rest) end # if foo then bar end diff --git a/test/prism/ripper_test.rb b/test/prism/ripper_test.rb index 228240e0052fa3..5b91ea905d9bda 100644 --- a/test/prism/ripper_test.rb +++ b/test/prism/ripper_test.rb @@ -6,273 +6,7 @@ File.delete("failing.txt") if File.exist?("failing.txt") module Prism - class RipperTestCase < TestCase - private - - def truffleruby? - RUBY_ENGINE == "truffleruby" - end - - # Ripper produces certain ambiguous structures. For instance, it often - # adds an :args_add_block with "false" as the block meaning there is - # no block call. It can be hard to tell which of multiple equivalent - # structures it will produce. This method attempts to return a normalized - # comparable structure. - def normalized_sexp(parsed) - if parsed.is_a?(Array) - # For args_add_block, if the third entry is nil or false, remove it. - # Note that CRuby Ripper uses false for no block, while older JRuby - # uses nil. We need to do this for both. - return normalized_sexp(parsed[1]) if parsed[0] == :args_add_block && !parsed[2] - - parsed.each.with_index do |item, idx| - if item.is_a?(Array) - parsed[idx] = normalized_sexp(parsed[idx]) - end - end - end - - parsed - end - - def assert_ripper_equivalent(source, path: "inline source code") - expected = Ripper.sexp_raw(source) - - refute_nil expected, "Could not parse #{path} with Ripper!" - expected = normalized_sexp(expected) - actual = Prism::Translation::Ripper.sexp_raw(source) - refute_nil actual, "Could not parse #{path} with Prism!" - actual = normalized_sexp(actual) - assert_equal expected, actual, "Expected Ripper and Prism to give equivalent output for #{path}!" - end - end - - class RipperShortSourceTest < RipperTestCase - def test_binary - assert_equivalent("1 + 2") - assert_equivalent("3 - 4 * 5") - assert_equivalent("6 / 7; 8 % 9") - end - - def test_unary - assert_equivalent("-7") - end - - def test_unary_parens - assert_equivalent("-(7)") - assert_equivalent("(-7)") - assert_equivalent("(-\n7)") - end - - def test_binary_parens - assert_equivalent("(3 + 7) * 4") - end - - def test_method_calls_with_variable_names - assert_equivalent("foo") - assert_equivalent("foo()") - assert_equivalent("foo -7") - assert_equivalent("foo(-7)") - assert_equivalent("foo(1, 2, 3)") - assert_equivalent("foo 1") - assert_equivalent("foo bar") - assert_equivalent("foo 1, 2") - assert_equivalent("foo.bar") - - # TruffleRuby prints emoji symbols differently in a way that breaks here. - unless truffleruby? - assert_equivalent("🗻") - assert_equivalent("🗻.location") - assert_equivalent("foo.🗻") - assert_equivalent("🗻.😮!") - assert_equivalent("🗻 🗻,🗻,🗻") - end - - assert_equivalent("foo&.bar") - assert_equivalent("foo { bar }") - assert_equivalent("foo.bar { 7 }") - assert_equivalent("foo(1) { bar }") - assert_equivalent("foo(bar)") - assert_equivalent("foo(bar(1))") - assert_equivalent("foo(bar(1)) { 7 }") - assert_equivalent("foo bar(1)") - end - - def test_method_call_blocks - assert_equivalent("foo { |a| a }") - - assert_equivalent("foo(bar 1)") - assert_equivalent("foo bar 1") - assert_equivalent("foo(bar 1) { 7 }") - assert_equivalent("foo(bar 1) {; 7 }") - assert_equivalent("foo(bar 1) {;}") - - assert_equivalent("foo do\n bar\nend") - assert_equivalent("foo do\nend") - assert_equivalent("foo do; end") - assert_equivalent("foo do bar; end") - assert_equivalent("foo do bar end") - assert_equivalent("foo do; bar; end") - end - - def test_method_calls_on_immediate_values - assert_equivalent("7.even?") - assert_equivalent("!1") - assert_equivalent("7 && 7") - assert_equivalent("7 and 7") - assert_equivalent("7 || 7") - assert_equivalent("7 or 7") - assert_equivalent("'racecar'.reverse") - end - - def test_range - assert_equivalent("(...2)") - assert_equivalent("(..2)") - assert_equivalent("(1...2)") - assert_equivalent("(1..2)") - assert_equivalent("(foo..-7)") - end - - def test_parentheses - assert_equivalent("()") - assert_equivalent("(1)") - assert_equivalent("(1; 2)") - end - - def test_numbers - assert_equivalent("[1, -1, +1, 1.0, -1.0, +1.0]") - assert_equivalent("[1r, -1r, +1r, 1.5r, -1.5r, +1.5r]") - assert_equivalent("[1i, -1i, +1i, 1.5i, -1.5i, +1.5i]") - assert_equivalent("[1ri, -1ri, +1ri, 1.5ri, -1.5ri, +1.5ri]") - end - - def test_begin_end - # Empty begin - assert_equivalent("begin; end") - assert_equivalent("begin end") - assert_equivalent("begin; rescue; end") - - assert_equivalent("begin:s.l end") - end - - def test_begin_rescue - # Rescue with exception(s) - assert_equivalent("begin a; rescue Exception => ex; c; end") - assert_equivalent("begin a; rescue RuntimeError => ex; c; rescue Exception => ex; d; end") - assert_equivalent("begin a; rescue RuntimeError => ex; c; rescue Exception => ex; end") - assert_equivalent("begin a; rescue RuntimeError,FakeError,Exception => ex; c; end") - assert_equivalent("begin a; rescue RuntimeError,FakeError,Exception; c; end") - - # Empty rescue - assert_equivalent("begin a; rescue; ensure b; end") - assert_equivalent("begin a; rescue; end") - - assert_equivalent("begin; a; ensure; b; end") - end - - def test_begin_ensure - # Empty ensure - assert_equivalent("begin a; rescue; c; ensure; end") - assert_equivalent("begin a; ensure; end") - assert_equivalent("begin; ensure; end") - - # Ripper treats statements differently, depending whether there's - # a semicolon after the keyword. - assert_equivalent("begin a; rescue; c; ensure b; end") - assert_equivalent("begin a; rescue c; ensure b; end") - assert_equivalent("begin a; rescue; c; ensure; b; end") - - # Need to make sure we're handling multibyte characters correctly for source offsets - assert_equivalent("begin 🗻; rescue; c; ensure;🗻🗻🗻🗻🗻; end") - assert_equivalent("begin 🗻; rescue; c; ensure 🗻🗻🗻🗻🗻; end") - end - - def test_break - assert_equivalent("foo { break }") - assert_equivalent("foo { break 7 }") - assert_equivalent("foo { break [1, 2, 3] }") - end - - def test_constants - assert_equivalent("Foo") - assert_equivalent("Foo + F🗻") - assert_equivalent("Foo = 'soda'") - end - - def test_op_assign - assert_equivalent("a += b") - assert_equivalent("a -= b") - assert_equivalent("a *= b") - assert_equivalent("a /= b") - end - - def test_arrays - assert_equivalent("[1, 2, 7]") - assert_equivalent("[1, [2, 7]]") - end - - def test_array_refs - assert_equivalent("a[1]") - assert_equivalent("a[1] = 7") - end - - def test_strings - assert_equivalent("'a'") - assert_equivalent("'a\01'") - assert_equivalent("`a`") - assert_equivalent("`a\07`") - assert_equivalent('"a#{1}c"') - assert_equivalent('"a#{1}b#{2}c"') - assert_equivalent("`f\oo`") - end - - def test_symbols - assert_equivalent(":a") - assert_equivalent(":'a'") - assert_equivalent(':"a"') - assert_equivalent("%s(foo)") - end - - def test_assign - assert_equivalent("a = b") - assert_equivalent("a = 1") - end - - def test_alias - assert_equivalent("alias :foo :bar") - assert_equivalent("alias $a $b") - assert_equivalent("alias $a $'") - assert_equivalent("alias foo bar") - assert_equivalent("alias foo if") - assert_equivalent("alias :'def' :\"abc\#{1}\"") - assert_equivalent("alias :\"abc\#{1}\" :'def'") - - unless truffleruby? - assert_equivalent("alias :foo :Ę") # Uppercase Unicode character is a constant - assert_equivalent("alias :Ę :foo") - end - - assert_equivalent("alias foo +") - assert_equivalent("alias foo :+") - assert_equivalent("alias :foo :''") - assert_equivalent("alias :'' :foo") - end - - def test_keyword_aliases - assert_equivalent("alias :foo :if") - assert_equivalent("alias :foo :self") - assert_equivalent("alias :foo :__FILE__") - assert_equivalent("alias foo __ENCODING__") - end - - private - - def assert_equivalent(source) - assert_ripper_equivalent(source) - end - end - - class RipperFixturesTest < RipperTestCase + class RipperTest < RipperTestCase base = File.join(__dir__, "fixtures") relatives = ENV["FOCUS"] ? [ENV["FOCUS"]] : Dir["**/*.txt", base: base] @@ -362,14 +96,7 @@ class RipperFixturesTest < RipperTestCase seattlerb/call_stabby_with_braces_block.txt seattlerb/call_trailing_comma.txt seattlerb/case_in.txt - seattlerb/case_in_37.txt seattlerb/case_in_else.txt - seattlerb/case_in_hash_pat.txt - seattlerb/case_in_hash_pat_assign.txt - seattlerb/case_in_hash_pat_paren_assign.txt - seattlerb/case_in_hash_pat_paren_true.txt - seattlerb/case_in_hash_pat_rest.txt - seattlerb/case_in_hash_pat_rest_solo.txt seattlerb/class_comments.txt seattlerb/defn_arg_forward_args.txt seattlerb/defn_args_forward_args.txt @@ -478,7 +205,6 @@ class RipperFixturesTest < RipperTestCase seattlerb/parse_pattern_051.txt seattlerb/parse_pattern_058.txt seattlerb/parse_pattern_058_2.txt - seattlerb/parse_pattern_069.txt seattlerb/parse_pattern_076.txt seattlerb/parse_until_not_canonical.txt seattlerb/parse_until_not_noncanonical.txt @@ -655,7 +381,6 @@ class RipperFixturesTest < RipperTestCase whitequark/masgn_nested.txt whitequark/masgn_splat.txt whitequark/method_definition_in_while_cond.txt - whitequark/multiple_pattern_matches.txt whitequark/newline_in_hash_argument.txt whitequark/next_block.txt whitequark/not.txt @@ -676,7 +401,6 @@ class RipperFixturesTest < RipperTestCase whitequark/parser_slash_slash_n_escaping_in_literals.txt whitequark/pattern_matching_blank_else.txt whitequark/pattern_matching_else.txt - whitequark/pattern_matching_single_line_allowed_omission_of_parentheses.txt whitequark/resbody_var.txt whitequark/rescue_else.txt whitequark/rescue_else_ensure.txt