diff --git a/prism/prism.c b/prism/prism.c index 4465b4982e13b5..4ab1ca4594db98 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13907,6 +13907,43 @@ update_parameter_state(pm_parser_t *parser, pm_token_t *token, pm_parameters_ord return true; } +static inline void +parse_parameters_handle_trailing_comma( + pm_parser_t *parser, + pm_parameters_node_t *params, + pm_parameters_order_t order, + bool in_block, + bool allows_trailing_comma +) { + if (!allows_trailing_comma) { + pm_parser_err_previous(parser, PM_ERR_PARAMETER_WILD_LOOSE_COMMA); + return; + } + + if (in_block) { + if (order >= PM_PARAMETERS_ORDER_NAMED) { + // foo do |bar,|; end + pm_node_t *param = UP(pm_implicit_rest_node_create(parser, &parser->previous)); + + if (params->rest == NULL) { + pm_parameters_node_rest_set(params, param); + } else { + pm_parser_err_node(parser, UP(param), PM_ERR_PARAMETER_SPLAT_MULTI); + pm_parameters_node_posts_append(params, UP(param)); + } + } else { + // foo do |*bar,|; end + pm_parser_err_previous(parser, PM_ERR_PARAMETER_WILD_LOOSE_COMMA); + } + } else { + // https://bugs.ruby-lang.org/issues/19107 + // Allow `def foo(bar,); end`, `def foo(*bar,); end`, etc. but not `def foo(...,); end` + if (parser->version < PM_OPTIONS_VERSION_CRUBY_4_1 || order == PM_PARAMETERS_ORDER_NOTHING_AFTER) { + pm_parser_err_previous(parser, PM_ERR_PARAMETER_WILD_LOOSE_COMMA); + } + } +} + /** * Parse a list of parameters on a method definition. */ @@ -14255,20 +14292,7 @@ parse_parameters( } default: if (parser->previous.type == PM_TOKEN_COMMA) { - if (allows_trailing_comma && order >= PM_PARAMETERS_ORDER_NAMED) { - // If we get here, then we have a trailing comma in a - // block parameter list. - pm_node_t *param = UP(pm_implicit_rest_node_create(parser, &parser->previous)); - - if (params->rest == NULL) { - pm_parameters_node_rest_set(params, param); - } else { - pm_parser_err_node(parser, UP(param), PM_ERR_PARAMETER_SPLAT_MULTI); - pm_parameters_node_posts_append(params, UP(param)); - } - } else { - pm_parser_err_previous(parser, PM_ERR_PARAMETER_WILD_LOOSE_COMMA); - } + parse_parameters_handle_trailing_comma(parser, params, order, in_block, allows_trailing_comma); } parsing = false; @@ -15933,6 +15957,19 @@ parse_heredoc_dedent_string(pm_string_t *string, size_t common_whitespace) { string->length = dest_length; } +/** + * If we end up trimming all of the whitespace from a node and it isn't + * part of a line continuation, then we'll drop it from the list entirely. + */ +static inline bool +heredoc_dedent_discard_string_node(pm_parser_t *parser, pm_string_node_t *string_node) { + if (string_node->unescaped.length == 0) { + const uint8_t *cursor = parser->start + PM_LOCATION_START(&string_node->content_loc); + return pm_memchr(cursor, '\\', string_node->content_loc.length, parser->encoding_changed, parser->encoding) == NULL; + } + return false; +} + /** * Take a heredoc node that is indented by a ~ and trim the leading whitespace. */ @@ -15943,8 +15980,7 @@ parse_heredoc_dedent(pm_parser_t *parser, pm_node_list_t *nodes, size_t common_w bool dedent_next = true; // Iterate over all nodes, and trim whitespace accordingly. We're going to - // keep around two indices: a read and a write. If we end up trimming all of - // the whitespace from a node, then we'll drop it from the list entirely. + // keep around two indices: a read and a write. size_t write_index = 0; pm_node_t *node; @@ -15963,7 +15999,7 @@ parse_heredoc_dedent(pm_parser_t *parser, pm_node_list_t *nodes, size_t common_w parse_heredoc_dedent_string(&string_node->unescaped, common_whitespace); } - if (string_node->unescaped.length == 0) { + if (heredoc_dedent_discard_string_node(parser, string_node)) { pm_node_destroy(parser, node); } else { nodes->nodes[write_index++] = node; @@ -18865,7 +18901,9 @@ 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)); + // https://bugs.ruby-lang.org/issues/19107 + bool allow_trailing_comma = parser->version >= PM_OPTIONS_VERSION_CRUBY_4_1; + params = parse_parameters(parser, PM_BINDING_POWER_DEFINED, true, allow_trailing_comma, true, true, false, (uint16_t) (depth + 1)); } lex_state_set(parser, PM_LEX_STATE_BEG); diff --git a/prism/templates/template.rb b/prism/templates/template.rb index 65e6ed038168d9..18da0647a007bd 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -724,9 +724,9 @@ def locals "javascript/src/deserialize.js", "javascript/src/nodes.js", "javascript/src/visitor.js", - "java/org/prism/Loader.java", - "java/org/prism/Nodes.java", - "java/org/prism/AbstractNodeVisitor.java", + "java/org/ruby_lang/prism/Loader.java", + "java/org/ruby_lang/prism/Nodes.java", + "java/org/ruby_lang/prism/AbstractNodeVisitor.java", "lib/prism/compiler.rb", "lib/prism/dispatcher.rb", "lib/prism/dot_visitor.rb", diff --git a/test/prism/errors/do_not_allow_trailing_commas_in_method_parameters.txt b/test/prism/errors/3.3-4.0/do_not_allow_trailing_commas_in_method_parameters.txt similarity index 100% rename from test/prism/errors/do_not_allow_trailing_commas_in_method_parameters.txt rename to test/prism/errors/3.3-4.0/do_not_allow_trailing_commas_in_method_parameters.txt diff --git a/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt b/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt new file mode 100644 index 00000000000000..b3e06f4154ee58 --- /dev/null +++ b/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt @@ -0,0 +1,6 @@ +def foo(a,b,...,);end + ^ unexpected `,` in parameters + +def foo(a,b,&block,);end + ^ unexpected `,` in parameters + diff --git a/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt b/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt new file mode 100644 index 00000000000000..ef1385d973f2ef --- /dev/null +++ b/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt @@ -0,0 +1,15 @@ +def foo(a,b,c,);end + +def foo(a,b,*c,);end + +def foo(a,b,*,);end + +def foo(a,b,**c,);end + +def foo(a,b,**,);end + +def foo( + a, + b, + c, +);end diff --git a/test/prism/fixtures/heredoc_dedent_line_continuation.txt b/test/prism/fixtures/heredoc_dedent_line_continuation.txt new file mode 100644 index 00000000000000..661db490c766e1 --- /dev/null +++ b/test/prism/fixtures/heredoc_dedent_line_continuation.txt @@ -0,0 +1,5 @@ +<<~FOO + foo\ + \ + bar +FOO diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb index 3a704b83895054..dcbcb7c117c725 100644 --- a/test/prism/fixtures_test.rb +++ b/test/prism/fixtures_test.rb @@ -28,6 +28,8 @@ class FixturesTest < TestCase except << "command_method_call_2.txt" # https://bugs.ruby-lang.org/issues/21669 except << "4.1/void_value.txt" + # https://bugs.ruby-lang.org/issues/19107 + except << "4.1/trailing_comma_after_method_arguments.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/locals_test.rb b/test/prism/locals_test.rb index bcb964aff67568..417730a8a73ba6 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -31,6 +31,9 @@ class LocalsTest < TestCase # https://bugs.ruby-lang.org/issues/21669 "4.1/void_value.txt", + + # https://bugs.ruby-lang.org/issues/19107 + "4.1/trailing_comma_after_method_arguments.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 39cb9395ab680e..ba01732bcb2823 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -39,6 +39,8 @@ class RipperTest < TestCase # https://bugs.ruby-lang.org/issues/21669 incorrect << "4.1/void_value.txt" + # https://bugs.ruby-lang.org/issues/19107 + incorrect << "4.1/trailing_comma_after_method_arguments.txt" # Skip these tests that we haven't implemented yet. omitted_sexp_raw = [ diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 25107978f3828c..bc89bdae72b9bc 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -37,6 +37,7 @@ class RubyParserTest < TestCase "alias.txt", "dsym_str.txt", "dos_endings.txt", + "heredoc_dedent_line_continuation.txt", "heredoc_percent_q_newline_delimiter.txt", "heredocs_with_fake_newlines.txt", "heredocs_with_ignored_newlines.txt",