Skip to content

Commit

Permalink
[ruby/prism] Duplicated hash keys
Browse files Browse the repository at this point in the history
  • Loading branch information
kddnewton committed Feb 23, 2024
1 parent 73dd3ce commit d1ce989
Show file tree
Hide file tree
Showing 12 changed files with 545 additions and 26 deletions.
1 change: 1 addition & 0 deletions prism/diagnostic.c
Expand Up @@ -306,6 +306,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
[PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS] = { "ambiguous first argument; put parentheses or a space even after `+` operator", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_AMBIGUOUS_PREFIX_STAR] = { "ambiguous `*` has been interpreted as an argument prefix", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_AMBIGUOUS_SLASH] = { "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_DUPLICATED_HASH_KEY] = { "key %.*s is duplicated and overwritten on line %" PRIi32, PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_EQUAL_IN_CONDITIONAL] = { "found `= literal' in conditional, should be ==", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_END_IN_METHOD] = { "END in method; use at_exit", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_FLOAT_OUT_OF_RANGE] = { "Float %.*s%s out of range", PM_WARNING_LEVEL_VERBOSE }
Expand Down
1 change: 1 addition & 0 deletions prism/diagnostic.h
Expand Up @@ -306,6 +306,7 @@ typedef enum {
PM_WARN_AMBIGUOUS_SLASH,
PM_WARN_EQUAL_IN_CONDITIONAL,
PM_WARN_END_IN_METHOD,
PM_WARN_DUPLICATED_HASH_KEY,
PM_WARN_FLOAT_OUT_OF_RANGE,

// This is the number of diagnostic codes.
Expand Down
18 changes: 18 additions & 0 deletions prism/node.h
Expand Up @@ -10,6 +10,17 @@
#include "prism/parser.h"
#include "prism/util/pm_buffer.h"

/**
* Attempts to grow the node list to the next size. If there is already
* capacity in the list, this function does nothing. Otherwise it reallocates
* the list to be twice as large as it was before. If the reallocation fails,
* this function returns false, otherwise it returns true.
*
* @param list The list to grow.
* @return True if the list was successfully grown, false otherwise.
*/
bool pm_node_list_grow(pm_node_list_t *list);

/**
* Append a new node onto the end of the node list.
*
Expand All @@ -18,6 +29,13 @@
*/
void pm_node_list_append(pm_node_list_t *list, pm_node_t *node);

/**
* Free the internal memory associated with the given node list.
*
* @param list The list to free.
*/
void pm_node_list_free(pm_node_list_t *list);

/**
* Deallocate a node and all of its children.
*
Expand Down
73 changes: 61 additions & 12 deletions prism/prism.c
Expand Up @@ -11676,11 +11676,32 @@ parse_statements(pm_parser_t *parser, pm_context_t context) {
return statements;
}

/**
* Add a node to a set of static literals that holds a set of hash keys. If the
* node is a duplicate, then add an appropriate warning.
*/
static void
pm_hash_key_static_literals_add(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) {
const pm_node_t *duplicated = pm_static_literals_add(parser, literals, node);

if (duplicated != NULL) {
pm_diagnostic_list_append_format(
&parser->warning_list,
duplicated->location.start,
duplicated->location.end,
PM_WARN_DUPLICATED_HASH_KEY,
(int) (duplicated->location.end - duplicated->location.start),
duplicated->location.start,
pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line).line
);
}
}

/**
* Parse all of the elements of a hash. returns true if a double splat was found.
*/
static bool
parse_assocs(pm_parser_t *parser, pm_node_t *node) {
parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) {
assert(PM_NODE_TYPE_P(node, PM_HASH_NODE) || PM_NODE_TYPE_P(node, PM_KEYWORD_HASH_NODE));
bool contains_keyword_splat = false;

Expand Down Expand Up @@ -11709,6 +11730,8 @@ parse_assocs(pm_parser_t *parser, pm_node_t *node) {
parser_lex(parser);

pm_node_t *key = (pm_node_t *) pm_symbol_node_label_create(parser, &label);
pm_hash_key_static_literals_add(parser, literals, key);

pm_token_t operator = not_provided(parser);
pm_node_t *value = NULL;

Expand Down Expand Up @@ -11738,8 +11761,16 @@ parse_assocs(pm_parser_t *parser, pm_node_t *node) {
}
default: {
pm_node_t *key = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_HASH_KEY);
pm_token_t operator;

// Hash keys that are strings are automatically frozen. We will
// mark that here.
if (PM_NODE_TYPE_P(key, PM_STRING_NODE)) {
pm_node_flag_set(key, PM_STRING_FLAGS_FROZEN | PM_NODE_FLAG_STATIC_LITERAL);
}

pm_hash_key_static_literals_add(parser, literals, key);

pm_token_t operator;
if (pm_symbol_node_label_p(key)) {
operator = not_provided(parser);
} else {
Expand Down Expand Up @@ -11773,6 +11804,7 @@ parse_assocs(pm_parser_t *parser, pm_node_t *node) {
// Otherwise by default we will exit out of this loop.
break;
}

return contains_keyword_splat;
}

Expand Down Expand Up @@ -11830,12 +11862,17 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
pm_keyword_hash_node_t *hash = pm_keyword_hash_node_create(parser);
argument = (pm_node_t *) hash;

bool contains_keyword_splat = parse_assocs(parser, (pm_node_t *) hash);
parsed_bare_hash = true;
pm_static_literals_t literals = { 0 };
bool contains_keyword_splat = parse_assocs(parser, &literals, (pm_node_t *) hash);

parse_arguments_append(parser, arguments, argument);
if (contains_keyword_splat) {
pm_node_flag_set((pm_node_t *)arguments->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT);
}

pm_static_literals_free(&literals);
parsed_bare_hash = true;

break;
}
case PM_TOKEN_UAMPERSAND: {
Expand Down Expand Up @@ -11925,10 +11962,14 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for

pm_keyword_hash_node_t *bare_hash = pm_keyword_hash_node_create(parser);

// Create the set of static literals for this hash.
pm_static_literals_t literals = { 0 };
pm_hash_key_static_literals_add(parser, &literals, argument);

// Finish parsing the one we are part way through
pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_HASH_VALUE);

argument = (pm_node_t *) pm_assoc_node_create(parser, argument, &operator, value);

pm_keyword_hash_node_elements_append(bare_hash, argument);
argument = (pm_node_t *) bare_hash;

Expand All @@ -11937,9 +11978,10 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
token_begins_expression_p(parser->current.type) ||
match2(parser, PM_TOKEN_USTAR_STAR, PM_TOKEN_LABEL)
)) {
contains_keyword_splat = parse_assocs(parser, (pm_node_t *) bare_hash);
contains_keyword_splat = parse_assocs(parser, &literals, (pm_node_t *) bare_hash);
}

pm_static_literals_free(&literals);
parsed_bare_hash = true;
} else if (accept1(parser, PM_TOKEN_KEYWORD_IN)) {
// TODO: Could we solve this with binding powers instead?
Expand Down Expand Up @@ -14661,13 +14703,14 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
pm_parser_err_current(parser, PM_ERR_EXPRESSION_BARE_HASH);
}

pm_keyword_hash_node_t *hash = pm_keyword_hash_node_create(parser);
element = (pm_node_t *)hash;
element = (pm_node_t *) pm_keyword_hash_node_create(parser);
pm_static_literals_t literals = { 0 };

if (!match8(parser, PM_TOKEN_EOF, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_KEYWORD_DO, PM_TOKEN_PARENTHESIS_RIGHT)) {
parse_assocs(parser, (pm_node_t *) hash);
parse_assocs(parser, &literals, element);
}

pm_static_literals_free(&literals);
parsed_bare_hash = true;
} else {
element = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_ARRAY_EXPRESSION);
Expand All @@ -14678,6 +14721,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
}

pm_keyword_hash_node_t *hash = pm_keyword_hash_node_create(parser);
pm_static_literals_t literals = { 0 };
pm_hash_key_static_literals_add(parser, &literals, element);

pm_token_t operator;
if (parser->previous.type == PM_TOKEN_EQUAL_GREATER) {
Expand All @@ -14690,11 +14735,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
pm_node_t *assoc = (pm_node_t *) pm_assoc_node_create(parser, element, &operator, value);
pm_keyword_hash_node_elements_append(hash, assoc);

element = (pm_node_t *)hash;
element = (pm_node_t *) hash;
if (accept1(parser, PM_TOKEN_COMMA) && !match1(parser, PM_TOKEN_BRACKET_RIGHT)) {
parse_assocs(parser, (pm_node_t *) hash);
parse_assocs(parser, &literals, element);
}

pm_static_literals_free(&literals);
parsed_bare_hash = true;
}
}
Expand Down Expand Up @@ -14840,17 +14886,20 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
case PM_TOKEN_BRACE_LEFT: {
pm_accepts_block_stack_push(parser, true);
parser_lex(parser);

pm_hash_node_t *node = pm_hash_node_create(parser, &parser->previous);
pm_static_literals_t literals = { 0 };

if (!match2(parser, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_EOF)) {
parse_assocs(parser, (pm_node_t *) node);
parse_assocs(parser, &literals, (pm_node_t *) node);
accept1(parser, PM_TOKEN_NEWLINE);
}

pm_accepts_block_stack_pop(parser);
expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_HASH_TERM);
pm_hash_node_closing_loc_set(node, &parser->previous);

pm_static_literals_free(&literals);
return (pm_node_t *) node;
}
case PM_TOKEN_CHARACTER_LITERAL: {
Expand Down
1 change: 1 addition & 0 deletions prism/prism.h
Expand Up @@ -21,6 +21,7 @@
#include "prism/parser.h"
#include "prism/prettyprint.h"
#include "prism/regexp.h"
#include "prism/static_literals.h"
#include "prism/version.h"

#include <assert.h>
Expand Down

0 comments on commit d1ce989

Please sign in to comment.