Skip to content

Commit 3e6830c

Browse files
committed
Reduce locals variables per CRuby
1 parent ffc4d21 commit 3e6830c

File tree

3 files changed

+128
-25
lines changed

3 files changed

+128
-25
lines changed

bin/prism

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ module Prism
399399
case argv.first
400400
when "-e"
401401
argv.shift
402-
Prism.parse(argv.shift)
402+
Prism.parse(argv.shift, command_line: "e")
403403
when nil
404404
Prism.parse_file("test.rb")
405405
else

src/prism.c

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,14 +1005,18 @@ pm_locals_reads(pm_locals_t *locals, pm_constant_id_t name) {
10051005
* written but not read in certain contexts.
10061006
*/
10071007
static void
1008-
pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, pm_constant_id_list_t *list, bool warn_unused) {
1008+
pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, pm_constant_id_list_t *list, bool toplevel) {
10091009
pm_constant_id_list_init_capacity(list, locals->size);
10101010

10111011
// If we're still below the threshold for switching to a hash, then we only
10121012
// need to loop over the locals until we hit the size because the locals are
10131013
// stored in a list.
10141014
uint32_t capacity = locals->capacity < PM_LOCALS_HASH_THRESHOLD ? locals->size : locals->capacity;
10151015

1016+
// We will only warn for unused variables if we're not at the top level, or
1017+
// if we're parsing a file outside of eval or -e.
1018+
bool warn_unused = !toplevel || (!parser->parsing_eval && !PM_PARSER_COMMAND_LINE_OPTION_E(parser));
1019+
10161020
for (uint32_t index = 0; index < capacity; index++) {
10171021
pm_local_t *local = &locals->locals[index];
10181022

@@ -12329,7 +12333,8 @@ static pm_node_t *
1232912333
parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id);
1233012334

1233112335
/**
12332-
* This is a wrapper of parse_expression, which also checks whether the resulting node is value expression.
12336+
* This is a wrapper of parse_expression, which also checks whether the
12337+
* resulting node is a value expression.
1233312338
*/
1233412339
static pm_node_t *
1233512340
parse_value_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) {
@@ -13217,7 +13222,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
1321713222
pm_static_literals_t literals = { 0 };
1321813223
pm_hash_key_static_literals_add(parser, &literals, argument);
1321913224

13220-
// Finish parsing the one we are part way through
13225+
// Finish parsing the one we are part way through.
1322113226
pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_HASH_VALUE);
1322213227
argument = (pm_node_t *) pm_assoc_node_create(parser, argument, &operator, value);
1322313228

@@ -14014,9 +14019,8 @@ parse_block_parameters(
1401414019
pm_parser_local_add_token(parser, &parser->previous, 1);
1401514020

1401614021
pm_block_local_variable_node_t *local = pm_block_local_variable_node_create(parser, &parser->previous);
14017-
if (repeated) {
14018-
pm_node_flag_set_repeated_parameter((pm_node_t *)local);
14019-
}
14022+
if (repeated) pm_node_flag_set_repeated_parameter((pm_node_t *) local);
14023+
1402014024
pm_block_parameters_node_append_local(block_parameters, local);
1402114025
} while (accept1(parser, PM_TOKEN_COMMA));
1402214026
}
@@ -14118,7 +14122,7 @@ parse_block(pm_parser_t *parser) {
1411814122
}
1411914123

1412014124
pm_constant_id_list_t locals;
14121-
pm_locals_order(parser, &parser->current_scope->locals, &locals, !pm_parser_scope_toplevel_p(parser));
14125+
pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser));
1412214126
pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &opening, &parser->previous);
1412314127

1412414128
pm_parser_scope_pop(parser);
@@ -17442,7 +17446,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1744217446
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM);
1744317447

1744417448
pm_constant_id_list_t locals;
17445-
pm_locals_order(parser, &parser->current_scope->locals, &locals, true);
17449+
pm_locals_order(parser, &parser->current_scope->locals, &locals, false);
1744617450

1744717451
pm_parser_scope_pop(parser);
1744817452
pm_do_loop_stack_pop(parser);
@@ -17502,7 +17506,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1750217506
}
1750317507

1750417508
pm_constant_id_list_t locals;
17505-
pm_locals_order(parser, &parser->current_scope->locals, &locals, true);
17509+
pm_locals_order(parser, &parser->current_scope->locals, &locals, false);
1750617510

1750717511
pm_parser_scope_pop(parser);
1750817512
pm_do_loop_stack_pop(parser);
@@ -17767,7 +17771,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1776717771
}
1776817772

1776917773
pm_constant_id_list_t locals;
17770-
pm_locals_order(parser, &parser->current_scope->locals, &locals, true);
17774+
pm_locals_order(parser, &parser->current_scope->locals, &locals, false);
1777117775
pm_parser_scope_pop(parser);
1777217776

1777317777
/**
@@ -18029,7 +18033,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1802918033
}
1803018034

1803118035
pm_constant_id_list_t locals;
18032-
pm_locals_order(parser, &parser->current_scope->locals, &locals, true);
18036+
pm_locals_order(parser, &parser->current_scope->locals, &locals, false);
1803318037

1803418038
pm_parser_scope_pop(parser);
1803518039
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_MODULE_TERM);
@@ -18795,7 +18799,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1879518799
}
1879618800

1879718801
pm_constant_id_list_t locals;
18798-
pm_locals_order(parser, &parser->current_scope->locals, &locals, !pm_parser_scope_toplevel_p(parser));
18802+
pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser));
1879918803
pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &operator, &parser->previous);
1880018804

1880118805
pm_parser_scope_pop(parser);
@@ -18851,11 +18855,21 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1885118855
}
1885218856
}
1885318857

18854-
static inline pm_node_t *
18858+
/**
18859+
* Parse a value that is going to be written to some kind of variable or method
18860+
* call. We need to handle this separately because the rescue modifier is
18861+
* permitted on the end of the these expressions, which is a deviation from its
18862+
* normal binding power.
18863+
*
18864+
* Note that this will only be called after an operator write, as in &&=, ||=,
18865+
* or any of the binary operators that can be written to a variable.
18866+
*/
18867+
static pm_node_t *
1885518868
parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) {
1885618869
pm_node_t *value = parse_value_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id);
1885718870

18858-
// Contradicting binding powers, the right-hand-side value of rthe assignment allows the `rescue` modifier.
18871+
// Contradicting binding powers, the right-hand-side value of the assignment
18872+
// allows the `rescue` modifier.
1885918873
if (match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) {
1886018874
context_push(parser, PM_CONTEXT_RESCUE_MODIFIER);
1886118875

@@ -18871,14 +18885,63 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_
1887118885
return value;
1887218886
}
1887318887

18888+
/**
18889+
* When a local variable write node is the value being written in a different
18890+
* write, the local variable is considered "used".
18891+
*/
18892+
static void
18893+
parse_assignment_value_local(pm_parser_t *parser, const pm_node_t *node) {
18894+
switch (PM_NODE_TYPE(node)) {
18895+
case PM_BEGIN_NODE: {
18896+
const pm_begin_node_t *cast = (const pm_begin_node_t *) node;
18897+
if (cast->statements != NULL) parse_assignment_value_local(parser, (const pm_node_t *) cast->statements);
18898+
break;
18899+
}
18900+
case PM_LOCAL_VARIABLE_WRITE_NODE: {
18901+
const pm_local_variable_write_node_t *cast = (const pm_local_variable_write_node_t *) node;
18902+
pm_locals_read(&pm_parser_scope_find(parser, cast->depth)->locals, cast->name);
18903+
break;
18904+
}
18905+
case PM_PARENTHESES_NODE: {
18906+
const pm_parentheses_node_t *cast = (const pm_parentheses_node_t *) node;
18907+
if (cast->body != NULL) parse_assignment_value_local(parser, cast->body);
18908+
break;
18909+
}
18910+
case PM_STATEMENTS_NODE: {
18911+
const pm_statements_node_t *cast = (const pm_statements_node_t *) node;
18912+
const pm_node_t *statement;
1887418913

18875-
static inline pm_node_t *
18914+
PM_NODE_LIST_FOREACH(&cast->body, index, statement) {
18915+
parse_assignment_value_local(parser, statement);
18916+
}
18917+
break;
18918+
}
18919+
default:
18920+
break;
18921+
}
18922+
}
18923+
18924+
/**
18925+
* Parse the value (or values, through an implicit array) that is going to be
18926+
* written to some kind of variable or method call. We need to handle this
18927+
* separately because the rescue modifier is permitted on the end of the these
18928+
* expressions, which is a deviation from its normal binding power.
18929+
*
18930+
* Additionally, if the value is a local variable write node (e.g., a = a = 1),
18931+
* the "a" is marked as being used so the parser should not warn on it.
18932+
*
18933+
* Note that this will only be called after an = operator, as that is the only
18934+
* operator that allows multiple values after it.
18935+
*/
18936+
static pm_node_t *
1887618937
parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) {
1887718938
pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id);
18878-
bool single_value = true;
18939+
parse_assignment_value_local(parser, value);
1887918940

18941+
bool single_value = true;
1888018942
if (previous_binding_power == PM_BINDING_POWER_STATEMENT && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) {
1888118943
single_value = false;
18944+
1888218945
pm_token_t opening = not_provided(parser);
1888318946
pm_array_node_t *array = pm_array_node_create(parser, &opening);
1888418947

@@ -18887,8 +18950,11 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding
1888718950

1888818951
while (accept1(parser, PM_TOKEN_COMMA)) {
1888918952
pm_node_t *element = parse_starred_expression(parser, binding_power, false, PM_ERR_ARRAY_ELEMENT);
18953+
1889018954
pm_array_node_elements_append(array, element);
1889118955
if (PM_NODE_TYPE_P(element, PM_MISSING_NODE)) break;
18956+
18957+
parse_assignment_value_local(parser, element);
1889218958
}
1889318959
}
1889418960

@@ -19173,7 +19239,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
1917319239
pm_location_t *message_loc = &cast->message_loc;
1917419240
pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end);
1917519241

19176-
pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0);
19242+
pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1);
1917719243
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
1917819244
pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
1917919245

@@ -19286,7 +19352,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
1928619352
pm_location_t *message_loc = &cast->message_loc;
1928719353
pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end);
1928819354

19289-
pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0);
19355+
pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1);
1929019356
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
1929119357
pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
1929219358

@@ -19409,7 +19475,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
1940919475
pm_location_t *message_loc = &cast->message_loc;
1941019476
pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end);
1941119477

19412-
pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0);
19478+
pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1);
1941319479
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
1941419480
pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
1941519481

@@ -20070,7 +20136,7 @@ parse_program(pm_parser_t *parser) {
2007020136
}
2007120137

2007220138
pm_constant_id_list_t locals;
20073-
pm_locals_order(parser, &parser->current_scope->locals, &locals, false);
20139+
pm_locals_order(parser, &parser->current_scope->locals, &locals, true);
2007420140
pm_parser_scope_pop(parser);
2007520141

2007620142
// If this is an empty file, then we're still going to parse all of the

test/prism/warnings_test.rb

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_ambiguous_regexp
2424
end
2525

2626
def test_equal_in_conditional
27-
assert_warning("if a = 1; end", "should be ==")
27+
assert_warning("if a = 1; end; a", "should be ==")
2828
end
2929

3030
def test_dot_dot_dot_eol
@@ -88,6 +88,43 @@ def test_regexp_in_predicate
8888
assert_warning("if /foo\#{bar}/; end", "regex")
8989
end
9090

91+
def test_unused_local_variables
92+
assert_warning("foo = 1", "unused")
93+
94+
refute_warning("foo = 1", compare: false, command_line: "e")
95+
refute_warning("foo = 1", compare: false, scopes: [[]])
96+
97+
assert_warning("def foo; bar = 1; end", "unused")
98+
assert_warning("def foo; bar, = 1; end", "unused")
99+
100+
refute_warning("def foo; bar &&= 1; end")
101+
refute_warning("def foo; bar ||= 1; end")
102+
refute_warning("def foo; bar += 1; end")
103+
104+
refute_warning("def foo; bar = bar; end")
105+
refute_warning("def foo; bar = bar = 1; end")
106+
refute_warning("def foo; bar = (bar = 1); end")
107+
refute_warning("def foo; bar = begin; bar = 1; end; end")
108+
refute_warning("def foo; bar = (qux; bar = 1); end")
109+
refute_warning("def foo; bar, = bar = 1; end")
110+
refute_warning("def foo; bar, = 1, bar = 1; end")
111+
112+
refute_warning("def foo(bar); end")
113+
refute_warning("def foo(bar = 1); end")
114+
refute_warning("def foo((bar)); end")
115+
refute_warning("def foo(*bar); end")
116+
refute_warning("def foo(*, bar); end")
117+
refute_warning("def foo(*, (bar)); end")
118+
refute_warning("def foo(bar:); end")
119+
refute_warning("def foo(**bar); end")
120+
refute_warning("def foo(&bar); end")
121+
refute_warning("->(bar) {}")
122+
refute_warning("->(; bar) {}", compare: false)
123+
124+
refute_warning("def foo; bar = 1; tap { bar }; end")
125+
refute_warning("def foo; bar = 1; tap { baz = bar; baz }; end")
126+
end
127+
91128
private
92129

93130
def assert_warning(source, message)
@@ -101,10 +138,10 @@ def assert_warning(source, message)
101138
end
102139
end
103140

104-
def refute_warning(source)
105-
assert_empty Prism.parse(source).warnings
141+
def refute_warning(source, compare: true, **options)
142+
assert_empty Prism.parse(source, **options).warnings
106143

107-
if defined?(RubyVM::AbstractSyntaxTree)
144+
if compare && defined?(RubyVM::AbstractSyntaxTree)
108145
assert_empty capture_warning { RubyVM::AbstractSyntaxTree.parse(source) }
109146
end
110147
end

0 commit comments

Comments
 (0)