Skip to content

Commit 4c43174

Browse files
kddnewtonmatzbot
authored andcommitted
[ruby/prism] Warn for nested hashes as well
ruby/prism@76e802f59e
1 parent 7d64fbd commit 4c43174

3 files changed

Lines changed: 53 additions & 21 deletions

File tree

prism/parser.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "prism/ast.h"
1111
#include "prism/encoding.h"
1212
#include "prism/options.h"
13+
#include "prism/static_literals.h"
1314
#include "prism/util/pm_constant_pool.h"
1415
#include "prism/util/pm_list.h"
1516
#include "prism/util/pm_newline_list.h"
@@ -717,6 +718,15 @@ struct pm_parser {
717718
/** The current parsing context. */
718719
pm_context_node_t *current_context;
719720

721+
/**
722+
* The hash keys for the hash that is currently being parsed. This is not
723+
* usually necessary because it can pass it down the various call chains,
724+
* but in the event that you're parsing a hash that is being directly
725+
* pushed into another hash with **, we need to share the hash keys so that
726+
* we can warn for the nested hash as well.
727+
*/
728+
pm_static_literals_t *current_hash_keys;
729+
720730
/**
721731
* The encoding functions for the current file is attached to the parser as
722732
* it's parsing so that it can change with a magic comment.

prism/prism.c

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13206,10 +13206,16 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod
1320613206
pm_token_t operator = parser->previous;
1320713207
pm_node_t *value = NULL;
1320813208

13209-
if (token_begins_expression_p(parser->current.type)) {
13209+
if (match1(parser, PM_TOKEN_BRACE_LEFT)) {
13210+
// If we're about to parse a nested hash that is being
13211+
// pushed into this hash directly with **, then we want the
13212+
// inner hash to share the static literals with the outer
13213+
// hash.
13214+
parser->current_hash_keys = literals;
1321013215
value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH);
13211-
}
13212-
else {
13216+
} else if (token_begins_expression_p(parser->current.type)) {
13217+
value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH);
13218+
} else {
1321313219
pm_parser_scope_forwarding_keywords_check(parser, &operator);
1321413220
}
1321513221

@@ -13360,15 +13366,15 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
1336013366
pm_keyword_hash_node_t *hash = pm_keyword_hash_node_create(parser);
1336113367
argument = (pm_node_t *) hash;
1336213368

13363-
pm_static_literals_t literals = { 0 };
13364-
bool contains_keyword_splat = parse_assocs(parser, &literals, (pm_node_t *) hash);
13369+
pm_static_literals_t hash_keys = { 0 };
13370+
bool contains_keyword_splat = parse_assocs(parser, &hash_keys, (pm_node_t *) hash);
1336513371

1336613372
parse_arguments_append(parser, arguments, argument);
1336713373
if (contains_keyword_splat) {
13368-
pm_node_flag_set((pm_node_t *)arguments->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT);
13374+
pm_node_flag_set((pm_node_t *) arguments->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT);
1336913375
}
1337013376

13371-
pm_static_literals_free(&literals);
13377+
pm_static_literals_free(&hash_keys);
1337213378
parsed_bare_hash = true;
1337313379

1337413380
break;
@@ -13460,8 +13466,8 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
1346013466
pm_keyword_hash_node_t *bare_hash = pm_keyword_hash_node_create(parser);
1346113467

1346213468
// Create the set of static literals for this hash.
13463-
pm_static_literals_t literals = { 0 };
13464-
pm_hash_key_static_literals_add(parser, &literals, argument);
13469+
pm_static_literals_t hash_keys = { 0 };
13470+
pm_hash_key_static_literals_add(parser, &hash_keys, argument);
1346513471

1346613472
// Finish parsing the one we are part way through.
1346713473
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
1347513481
token_begins_expression_p(parser->current.type) ||
1347613482
match2(parser, PM_TOKEN_USTAR_STAR, PM_TOKEN_LABEL)
1347713483
)) {
13478-
contains_keyword_splat = parse_assocs(parser, &literals, (pm_node_t *) bare_hash);
13484+
contains_keyword_splat = parse_assocs(parser, &hash_keys, (pm_node_t *) bare_hash);
1347913485
}
1348013486

13481-
pm_static_literals_free(&literals);
13487+
pm_static_literals_free(&hash_keys);
1348213488
parsed_bare_hash = true;
1348313489
} else if (accept1(parser, PM_TOKEN_KEYWORD_IN)) {
1348413490
// 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
1672416730
}
1672516731

1672616732
element = (pm_node_t *) pm_keyword_hash_node_create(parser);
16727-
pm_static_literals_t literals = { 0 };
16733+
pm_static_literals_t hash_keys = { 0 };
1672816734

1672916735
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)) {
16730-
parse_assocs(parser, &literals, element);
16736+
parse_assocs(parser, &hash_keys, element);
1673116737
}
1673216738

16733-
pm_static_literals_free(&literals);
16739+
pm_static_literals_free(&hash_keys);
1673416740
parsed_bare_hash = true;
1673516741
} else {
1673616742
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
1674116747
}
1674216748

1674316749
pm_keyword_hash_node_t *hash = pm_keyword_hash_node_create(parser);
16744-
pm_static_literals_t literals = { 0 };
16745-
pm_hash_key_static_literals_add(parser, &literals, element);
16750+
pm_static_literals_t hash_keys = { 0 };
16751+
pm_hash_key_static_literals_add(parser, &hash_keys, element);
1674616752

1674716753
pm_token_t operator;
1674816754
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
1675716763

1675816764
element = (pm_node_t *) hash;
1675916765
if (accept1(parser, PM_TOKEN_COMMA) && !match1(parser, PM_TOKEN_BRACKET_RIGHT)) {
16760-
parse_assocs(parser, &literals, element);
16766+
parse_assocs(parser, &hash_keys, element);
1676116767
}
1676216768

16763-
pm_static_literals_free(&literals);
16769+
pm_static_literals_free(&hash_keys);
1676416770
parsed_bare_hash = true;
1676516771
}
1676616772
}
@@ -16920,22 +16926,37 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1692016926
return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous);
1692116927
}
1692216928
case PM_TOKEN_BRACE_LEFT: {
16929+
// If we were passed a current_hash_keys via the parser, then that
16930+
// means we're already parsing a hash and we want to share the set
16931+
// of hash keys with this inner hash we're about to parse for the
16932+
// sake of warnings. We'll set it to NULL after we grab it to make
16933+
// sure subsequent expressions don't use it. Effectively this is a
16934+
// way of getting around passing it to every call to
16935+
// parse_expression.
16936+
pm_static_literals_t *current_hash_keys = parser->current_hash_keys;
16937+
parser->current_hash_keys = NULL;
16938+
1692316939
pm_accepts_block_stack_push(parser, true);
1692416940
parser_lex(parser);
1692516941

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

1692916944
if (!match2(parser, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_EOF)) {
16930-
parse_assocs(parser, &literals, (pm_node_t *) node);
16945+
if (current_hash_keys != NULL) {
16946+
parse_assocs(parser, current_hash_keys, (pm_node_t *) node);
16947+
} else {
16948+
pm_static_literals_t hash_keys = { 0 };
16949+
parse_assocs(parser, &hash_keys, (pm_node_t *) node);
16950+
pm_static_literals_free(&hash_keys);
16951+
}
16952+
1693116953
accept1(parser, PM_TOKEN_NEWLINE);
1693216954
}
1693316955

1693416956
pm_accepts_block_stack_pop(parser);
1693516957
expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_HASH_TERM);
1693616958
pm_hash_node_closing_loc_set(node, &parser->previous);
1693716959

16938-
pm_static_literals_free(&literals);
1693916960
return (pm_node_t *) node;
1694016961
}
1694116962
case PM_TOKEN_CHARACTER_LITERAL: {

test/prism/warnings_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def test_END_in_method
4444

4545
def test_duplicated_hash_key
4646
assert_warning("{ a: 1, a: 2 }", "duplicated and overwritten")
47+
assert_warning("{ a: 1, **{ a: 2 } }", "duplicated and overwritten")
4748
end
4849

4950
def test_duplicated_when_clause

0 commit comments

Comments
 (0)