[ruby/prism] Warn for nested hashes as well

https://github.com/ruby/prism/commit/76e802f59e
This commit is contained in:
Kevin Newton 2024-04-24 15:28:40 -04:00 committed by git
parent 7d64fbda53
commit 4c431744b7
3 changed files with 53 additions and 21 deletions

View File

@ -10,6 +10,7 @@
#include "prism/ast.h"
#include "prism/encoding.h"
#include "prism/options.h"
#include "prism/static_literals.h"
#include "prism/util/pm_constant_pool.h"
#include "prism/util/pm_list.h"
#include "prism/util/pm_newline_list.h"
@ -717,6 +718,15 @@ struct pm_parser {
/** The current parsing context. */
pm_context_node_t *current_context;
/**
* The hash keys for the hash that is currently being parsed. This is not
* usually necessary because it can pass it down the various call chains,
* but in the event that you're parsing a hash that is being directly
* pushed into another hash with **, we need to share the hash keys so that
* we can warn for the nested hash as well.
*/
pm_static_literals_t *current_hash_keys;
/**
* The encoding functions for the current file is attached to the parser as
* it's parsing so that it can change with a magic comment.

View File

@ -13206,10 +13206,16 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod
pm_token_t operator = parser->previous;
pm_node_t *value = NULL;
if (token_begins_expression_p(parser->current.type)) {
if (match1(parser, PM_TOKEN_BRACE_LEFT)) {
// If we're about to parse a nested hash that is being
// pushed into this hash directly with **, then we want the
// inner hash to share the static literals with the outer
// hash.
parser->current_hash_keys = literals;
value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH);
}
else {
} else if (token_begins_expression_p(parser->current.type)) {
value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH);
} else {
pm_parser_scope_forwarding_keywords_check(parser, &operator);
}
@ -13360,15 +13366,15 @@ 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;
pm_static_literals_t literals = { 0 };
bool contains_keyword_splat = parse_assocs(parser, &literals, (pm_node_t *) hash);
pm_static_literals_t hash_keys = { 0 };
bool contains_keyword_splat = parse_assocs(parser, &hash_keys, (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_node_flag_set((pm_node_t *) arguments->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT);
}
pm_static_literals_free(&literals);
pm_static_literals_free(&hash_keys);
parsed_bare_hash = true;
break;
@ -13460,8 +13466,8 @@ 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);
pm_static_literals_t hash_keys = { 0 };
pm_hash_key_static_literals_add(parser, &hash_keys, 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);
@ -13475,10 +13481,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, &literals, (pm_node_t *) bare_hash);
contains_keyword_splat = parse_assocs(parser, &hash_keys, (pm_node_t *) bare_hash);
}
pm_static_literals_free(&literals);
pm_static_literals_free(&hash_keys);
parsed_bare_hash = true;
} else if (accept1(parser, PM_TOKEN_KEYWORD_IN)) {
// TODO: Could we solve this with binding powers instead?
@ -16724,13 +16730,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
}
element = (pm_node_t *) pm_keyword_hash_node_create(parser);
pm_static_literals_t literals = { 0 };
pm_static_literals_t hash_keys = { 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, &literals, element);
parse_assocs(parser, &hash_keys, element);
}
pm_static_literals_free(&literals);
pm_static_literals_free(&hash_keys);
parsed_bare_hash = true;
} else {
element = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_ARRAY_EXPRESSION);
@ -16741,8 +16747,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_static_literals_t hash_keys = { 0 };
pm_hash_key_static_literals_add(parser, &hash_keys, element);
pm_token_t operator;
if (parser->previous.type == PM_TOKEN_EQUAL_GREATER) {
@ -16757,10 +16763,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
element = (pm_node_t *) hash;
if (accept1(parser, PM_TOKEN_COMMA) && !match1(parser, PM_TOKEN_BRACKET_RIGHT)) {
parse_assocs(parser, &literals, element);
parse_assocs(parser, &hash_keys, element);
}
pm_static_literals_free(&literals);
pm_static_literals_free(&hash_keys);
parsed_bare_hash = true;
}
}
@ -16920,14 +16926,30 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous);
}
case PM_TOKEN_BRACE_LEFT: {
// If we were passed a current_hash_keys via the parser, then that
// means we're already parsing a hash and we want to share the set
// of hash keys with this inner hash we're about to parse for the
// sake of warnings. We'll set it to NULL after we grab it to make
// sure subsequent expressions don't use it. Effectively this is a
// way of getting around passing it to every call to
// parse_expression.
pm_static_literals_t *current_hash_keys = parser->current_hash_keys;
parser->current_hash_keys = NULL;
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, &literals, (pm_node_t *) node);
if (current_hash_keys != NULL) {
parse_assocs(parser, current_hash_keys, (pm_node_t *) node);
} else {
pm_static_literals_t hash_keys = { 0 };
parse_assocs(parser, &hash_keys, (pm_node_t *) node);
pm_static_literals_free(&hash_keys);
}
accept1(parser, PM_TOKEN_NEWLINE);
}
@ -16935,7 +16957,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
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: {

View File

@ -44,6 +44,7 @@ module Prism
def test_duplicated_hash_key
assert_warning("{ a: 1, a: 2 }", "duplicated and overwritten")
assert_warning("{ a: 1, **{ a: 2 } }", "duplicated and overwritten")
end
def test_duplicated_when_clause