Skip to content

Commit

Permalink
Merge pull request #1720 from ruby/hash-pattern-splat
Browse files Browse the repository at this point in the history
Fix parsing ** within hash patterns
  • Loading branch information
kddnewton authored Oct 27, 2023
2 parents 1274464 + 1da5e05 commit f310ec6
Show file tree
Hide file tree
Showing 20 changed files with 171 additions and 137 deletions.
4 changes: 2 additions & 2 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1356,9 +1356,9 @@ nodes:
fields:
- name: constant
type: node?
- name: assocs
- name: elements
type: node[]
- name: kwrest
- name: rest
type: node?
- name: opening_loc
type: location?
Expand Down
6 changes: 3 additions & 3 deletions lib/prism/pattern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,12 @@ def compile_constant_read_node(node)
# in InstanceVariableReadNode[name: Symbol]
# in { name: Symbol }
def compile_hash_pattern_node(node)
compile_error(node) unless node.kwrest.nil?
compile_error(node) if node.rest
compiled_constant = compile_node(node.constant) if node.constant

preprocessed =
node.assocs.to_h do |assoc|
[assoc.key.unescaped.to_sym, compile_node(assoc.value)]
node.elements.to_h do |element|
[element.key.unescaped.to_sym, compile_node(element.value)]
end

compiled_keywords = ->(other) do
Expand Down
118 changes: 80 additions & 38 deletions src/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -2588,38 +2588,55 @@ pm_hash_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *opening
},
},
.constant = NULL,
.kwrest = NULL,
.opening_loc = PM_LOCATION_TOKEN_VALUE(opening),
.closing_loc = PM_LOCATION_TOKEN_VALUE(closing),
.assocs = PM_EMPTY_NODE_LIST
.elements = PM_EMPTY_NODE_LIST,
.rest = NULL
};

return node;
}

// Allocate and initialize a new hash pattern node.
static pm_hash_pattern_node_t *
pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *assocs) {
pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *elements, pm_node_t *rest) {
pm_hash_pattern_node_t *node = PM_ALLOC_NODE(parser, pm_hash_pattern_node_t);

const uint8_t *start;
const uint8_t *end;

if (elements->size > 0) {
if (rest) {
start = elements->nodes[0]->location.start;
end = rest->location.end;
} else {
start = elements->nodes[0]->location.start;
end = elements->nodes[elements->size - 1]->location.end;
}
} else {
assert(rest != NULL);
start = rest->location.start;
end = rest->location.end;
}

*node = (pm_hash_pattern_node_t) {
{
.type = PM_HASH_PATTERN_NODE,
.location = {
.start = assocs->nodes[0]->location.start,
.end = assocs->nodes[assocs->size - 1]->location.end
.start = start,
.end = end
},
},
.constant = NULL,
.kwrest = NULL,
.assocs = PM_EMPTY_NODE_LIST,
.elements = PM_EMPTY_NODE_LIST,
.rest = rest,
.opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE,
.closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE
};

for (size_t index = 0; index < assocs->size; index++) {
pm_node_t *assoc = assocs->nodes[index];
pm_node_list_append(&node->assocs, assoc);
for (size_t index = 0; index < elements->size; index++) {
pm_node_t *element = elements->nodes[index];
pm_node_list_append(&node->elements, element);
}

return node;
Expand Down Expand Up @@ -11712,28 +11729,40 @@ parse_pattern_keyword_rest(pm_parser_t *parser) {
// Parse a hash pattern.
static pm_hash_pattern_node_t *
parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_assoc) {
if (PM_NODE_TYPE_P(first_assoc, PM_ASSOC_NODE)) {
if (!match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) {
// Here we have a value for the first assoc in the list, so we will parse it
// now and update the first assoc.
pm_node_t *value = parse_pattern(parser, false, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY);

pm_assoc_node_t *assoc = (pm_assoc_node_t *) first_assoc;
assoc->base.location.end = value->location.end;
assoc->value = value;
} else {
pm_node_t *key = ((pm_assoc_node_t *) first_assoc)->key;
pm_node_list_t assocs = PM_EMPTY_NODE_LIST;
pm_node_t *rest = NULL;

if (PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)) {
const pm_location_t *value_loc = &((pm_symbol_node_t *) key)->value_loc;
pm_parser_local_add_location(parser, value_loc->start, value_loc->end);
switch (PM_NODE_TYPE(first_assoc)) {
case PM_ASSOC_NODE: {
if (!match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) {
// Here we have a value for the first assoc in the list, so we will
// parse it now and update the first assoc.
pm_node_t *value = parse_pattern(parser, false, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY);

pm_assoc_node_t *assoc = (pm_assoc_node_t *) first_assoc;
assoc->base.location.end = value->location.end;
assoc->value = value;
} else {
pm_node_t *key = ((pm_assoc_node_t *) first_assoc)->key;

if (PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)) {
const pm_location_t *value_loc = &((pm_symbol_node_t *) key)->value_loc;
pm_parser_local_add_location(parser, value_loc->start, value_loc->end);
}
}

pm_node_list_append(&assocs, first_assoc);
break;
}
case PM_ASSOC_SPLAT_NODE:
case PM_NO_KEYWORDS_PARAMETER_NODE:
rest = first_assoc;
break;
default:
assert(false);
break;
}

pm_node_list_t assocs = PM_EMPTY_NODE_LIST;
pm_node_list_append(&assocs, first_assoc);

// If there are any other assocs, then we'll parse them now.
while (accept1(parser, PM_TOKEN_COMMA)) {
// Here we need to break to support trailing commas.
Expand Down Expand Up @@ -11764,7 +11793,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_assoc) {
pm_node_list_append(&assocs, assoc);
}

pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs);
pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs, rest);
free(assocs.nodes);

return node;
Expand Down Expand Up @@ -11849,32 +11878,45 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) {
// pattern node.
node = pm_hash_pattern_node_empty_create(parser, &opening, &parser->previous);
} else {
pm_node_t *key;
pm_node_t *first_assoc;

switch (parser->current.type) {
case PM_TOKEN_LABEL:
case PM_TOKEN_LABEL: {
parser_lex(parser);
key = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous);

pm_symbol_node_t *key = pm_symbol_node_label_create(parser, &parser->previous);
pm_token_t operator = not_provided(parser);

first_assoc = (pm_node_t *) pm_assoc_node_create(parser, (pm_node_t *) key, &operator, NULL);
break;
}
case PM_TOKEN_USTAR_STAR:
key = parse_pattern_keyword_rest(parser);
first_assoc = parse_pattern_keyword_rest(parser);
break;
case PM_TOKEN_STRING_BEGIN:
key = parse_expression(parser, PM_BINDING_POWER_MAX, PM_ERR_PATTERN_HASH_KEY);
case PM_TOKEN_STRING_BEGIN: {
pm_node_t *key = parse_expression(parser, PM_BINDING_POWER_MAX, PM_ERR_PATTERN_HASH_KEY);
pm_token_t operator = not_provided(parser);

if (!pm_symbol_node_label_p(key)) {
pm_parser_err_node(parser, key, PM_ERR_PATTERN_HASH_KEY_LABEL);
}

first_assoc = (pm_node_t *) pm_assoc_node_create(parser, key, &operator, NULL);
break;
default:
}
default: {
parser_lex(parser);
pm_parser_err_previous(parser, PM_ERR_PATTERN_HASH_KEY);
key = (pm_node_t *) pm_missing_node_create(parser, parser->previous.start, parser->previous.end);

pm_missing_node_t *key = pm_missing_node_create(parser, parser->previous.start, parser->previous.end);
pm_token_t operator = not_provided(parser);

first_assoc = (pm_node_t *) pm_assoc_node_create(parser, (pm_node_t *) key, &operator, NULL);
break;
}
}

pm_token_t operator = not_provided(parser);
node = parse_pattern_hash(parser, (pm_node_t *) pm_assoc_node_create(parser, key, &operator, NULL));
node = parse_pattern_hash(parser, first_assoc);

accept1(parser, PM_TOKEN_NEWLINE);
expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_PATTERN_TERM_BRACE);
Expand Down
8 changes: 4 additions & 4 deletions test/prism/snapshots/patterns.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 11 additions & 11 deletions test/prism/snapshots/seattlerb/case_in.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions test/prism/snapshots/seattlerb/case_in_37.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions test/prism/snapshots/seattlerb/case_in_hash_pat.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions test/prism/snapshots/seattlerb/case_in_hash_pat_assign.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f310ec6

Please sign in to comment.