Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 56 additions & 18 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions prism/templates/template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def foo(a,b,...,);end
^ unexpected `,` in parameters

def foo(a,b,&block,);end
^ unexpected `,` in parameters

15 changes: 15 additions & 0 deletions test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions test/prism/fixtures/heredoc_dedent_line_continuation.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<<~FOO
foo\
\
bar
FOO
2 changes: 2 additions & 0 deletions test/prism/fixtures_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down
3 changes: 3 additions & 0 deletions test/prism/locals_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
2 changes: 2 additions & 0 deletions test/prism/ruby/ripper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
1 change: 1 addition & 0 deletions test/prism/ruby/ruby_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down