From f7bcf35807e3f7a9665124e2adfc6f76770b4abf Mon Sep 17 00:00:00 2001 From: Ilya Bylich Date: Fri, 5 Jan 2024 14:40:45 +0100 Subject: [PATCH] + ruby33.y: reject ambiguous anonymous arguments (#983) This commit tracks upstream commit ruby/ruby@a9f0961. --- lib/parser/messages.rb | 85 +++++++++++++++++--------------- lib/parser/ruby33.y | 15 ++++++ lib/parser/static_environment.rb | 12 +++++ test/test_parser.rb | 48 ++++++++++++++++++ 4 files changed, 119 insertions(+), 41 deletions(-) diff --git a/lib/parser/messages.rb b/lib/parser/messages.rb index 618a66890..c82c6343a 100644 --- a/lib/parser/messages.rb +++ b/lib/parser/messages.rb @@ -41,47 +41,50 @@ module Parser :triple_dot_at_eol => '... at EOL, should be parenthesized', # Parser errors - :nth_ref_alias => 'cannot define an alias for a back-reference variable', - :begin_in_method => 'BEGIN in method', - :backref_assignment => 'cannot assign to a back-reference variable', - :invalid_assignment => 'cannot assign to a keyword', - :module_name_const => 'class or module name must be a constant literal', - :unexpected_token => 'unexpected token %{token}', - :argument_const => 'formal argument cannot be a constant', - :argument_ivar => 'formal argument cannot be an instance variable', - :argument_gvar => 'formal argument cannot be a global variable', - :argument_cvar => 'formal argument cannot be a class variable', - :duplicate_argument => 'duplicate argument name', - :empty_symbol => 'empty symbol literal', - :odd_hash => 'odd number of entries for a hash', - :singleton_literal => 'cannot define a singleton method for a literal', - :dynamic_const => 'dynamic constant assignment', - :const_reassignment => 'constant re-assignment', - :module_in_def => 'module definition in method body', - :class_in_def => 'class definition in method body', - :unexpected_percent_str => '%{type}: unknown type of percent-literal', - :block_and_blockarg => 'both block argument and literal block are passed', - :masgn_as_condition => 'multiple assignment in conditional context', - :block_given_to_yield => 'block given to yield', - :invalid_regexp => '%{message}', - :invalid_return => 'Invalid return in class/module body', - :csend_in_lhs_of_masgn => '&. inside multiple assignment destination', - :cant_assign_to_numparam => 'cannot assign to numbered parameter %{name}', - :reserved_for_numparam => '%{name} is reserved for numbered parameter', - :ordinary_param_defined => 'ordinary parameter is defined', - :numparam_used_in_outer_scope => 'numbered parameter is already used in an outer scope', - :circular_argument_reference => 'circular argument reference %{var_name}', - :pm_interp_in_var_name => 'symbol literal with interpolation is not allowed', - :lvar_name => "`%{name}' is not allowed as a local variable name", - :undefined_lvar => "no such local variable: `%{name}'", - :duplicate_variable_name => 'duplicate variable name %{name}', - :duplicate_pattern_key => 'duplicate hash pattern key %{name}', - :endless_setter => 'setter method cannot be defined in an endless method definition', - :invalid_id_to_get => 'identifier %{identifier} is not valid to get', - :forward_arg_after_restarg => '... after rest argument', - :no_anonymous_blockarg => 'no anonymous block parameter', - :no_anonymous_restarg => 'no anonymous rest parameter', - :no_anonymous_kwrestarg => 'no anonymous keyword rest parameter', + :nth_ref_alias => 'cannot define an alias for a back-reference variable', + :begin_in_method => 'BEGIN in method', + :backref_assignment => 'cannot assign to a back-reference variable', + :invalid_assignment => 'cannot assign to a keyword', + :module_name_const => 'class or module name must be a constant literal', + :unexpected_token => 'unexpected token %{token}', + :argument_const => 'formal argument cannot be a constant', + :argument_ivar => 'formal argument cannot be an instance variable', + :argument_gvar => 'formal argument cannot be a global variable', + :argument_cvar => 'formal argument cannot be a class variable', + :duplicate_argument => 'duplicate argument name', + :empty_symbol => 'empty symbol literal', + :odd_hash => 'odd number of entries for a hash', + :singleton_literal => 'cannot define a singleton method for a literal', + :dynamic_const => 'dynamic constant assignment', + :const_reassignment => 'constant re-assignment', + :module_in_def => 'module definition in method body', + :class_in_def => 'class definition in method body', + :unexpected_percent_str => '%{type}: unknown type of percent-literal', + :block_and_blockarg => 'both block argument and literal block are passed', + :masgn_as_condition => 'multiple assignment in conditional context', + :block_given_to_yield => 'block given to yield', + :invalid_regexp => '%{message}', + :invalid_return => 'Invalid return in class/module body', + :csend_in_lhs_of_masgn => '&. inside multiple assignment destination', + :cant_assign_to_numparam => 'cannot assign to numbered parameter %{name}', + :reserved_for_numparam => '%{name} is reserved for numbered parameter', + :ordinary_param_defined => 'ordinary parameter is defined', + :numparam_used_in_outer_scope => 'numbered parameter is already used in an outer scope', + :circular_argument_reference => 'circular argument reference %{var_name}', + :pm_interp_in_var_name => 'symbol literal with interpolation is not allowed', + :lvar_name => "`%{name}' is not allowed as a local variable name", + :undefined_lvar => "no such local variable: `%{name}'", + :duplicate_variable_name => 'duplicate variable name %{name}', + :duplicate_pattern_key => 'duplicate hash pattern key %{name}', + :endless_setter => 'setter method cannot be defined in an endless method definition', + :invalid_id_to_get => 'identifier %{identifier} is not valid to get', + :forward_arg_after_restarg => '... after rest argument', + :no_anonymous_blockarg => 'no anonymous block parameter', + :no_anonymous_restarg => 'no anonymous rest parameter', + :no_anonymous_kwrestarg => 'no anonymous keyword rest parameter', + :ambiguous_anonymous_restarg => 'anonymous rest parameter is also used within block', + :ambiguous_anonymous_kwrestarg => 'anonymous keyword rest parameter is also used within block', + :ambiguous_anonymous_blockarg => 'anonymous block parameter is also used within block', # Parser warnings :useless_else => 'else without rescue is useless', diff --git a/lib/parser/ruby33.y b/lib/parser/ruby33.y index e2803110f..25f097975 100644 --- a/lib/parser/ruby33.y +++ b/lib/parser/ruby33.y @@ -1100,6 +1100,11 @@ rule diagnostic :error, :no_anonymous_blockarg, nil, val[0] end + if @context.in_dynamic_block? && context.in_def && + @static_env.declared_anonymous_blockarg? && @static_env.parent_has_anonymous_blockarg? + diagnostic :error, :ambiguous_anonymous_blockarg, nil, val[0] + end + result = @builder.block_pass(val[0], nil) } @@ -1136,6 +1141,11 @@ rule diagnostic :error, :no_anonymous_restarg, nil, val[0] end + if @context.in_dynamic_block? && context.in_def && + @static_env.declared_anonymous_restarg? && @static_env.parent_has_anonymous_restarg? + diagnostic :error, :ambiguous_anonymous_restarg, nil, val[0] + end + result = [ @builder.forwarded_restarg(val[0]) ] } @@ -3048,6 +3058,11 @@ f_opt_paren_args: f_paren_args diagnostic :error, :no_anonymous_kwrestarg, nil, val[0] end + if @context.in_dynamic_block? && context.in_def && + @static_env.declared_anonymous_kwrestarg? && @static_env.parent_has_anonymous_kwrestarg? + diagnostic :error, :ambiguous_anonymous_kwrestarg, nil, val[0] + end + result = @builder.forwarded_kwrestarg(val[0]) } diff --git a/lib/parser/static_environment.rb b/lib/parser/static_environment.rb index 99eadfd8d..44e2f1a7c 100644 --- a/lib/parser/static_environment.rb +++ b/lib/parser/static_environment.rb @@ -63,6 +63,10 @@ def declared_anonymous_blockarg? declared?(ANONYMOUS_BLOCKARG) end + def parent_has_anonymous_blockarg? + @stack.any? { |variables| variables.include?(ANONYMOUS_BLOCKARG) } + end + def declare_anonymous_restarg declare(ANONYMOUS_RESTARG) end @@ -71,6 +75,10 @@ def declared_anonymous_restarg? declared?(ANONYMOUS_RESTARG) end + def parent_has_anonymous_restarg? + @stack.any? { |variables| variables.include?(ANONYMOUS_RESTARG) } + end + def declare_anonymous_kwrestarg declare(ANONYMOUS_KWRESTARG) end @@ -79,6 +87,10 @@ def declared_anonymous_kwrestarg? declared?(ANONYMOUS_KWRESTARG) end + def parent_has_anonymous_kwrestarg? + @stack.any? { |variables| variables.include?(ANONYMOUS_KWRESTARG) } + end + def empty? @stack.empty? end diff --git a/test/test_parser.rb b/test/test_parser.rb index 3ec758d6c..79245fa79 100644 --- a/test/test_parser.rb +++ b/test/test_parser.rb @@ -11440,4 +11440,52 @@ def test_it_warning_in_33 'it = 1; 0.times { it }', ALL_VERSIONS) end + + def test_anonymous_params_in_nested_scopes + assert_diagnoses( + [:error, :ambiguous_anonymous_blockarg, {}], + 'def b(&) ->(&) {c(&)} end', + ' ^ location', + SINCE_3_3) + assert_diagnoses( + [:error, :ambiguous_anonymous_restarg, {}], + 'def b(*) ->(*) {c(*)} end', + ' ^ location', + SINCE_3_3) + assert_diagnoses( + [:error, :ambiguous_anonymous_restarg, {}], + 'def b(a, *) ->(*) {c(1, *)} end', + ' ^ location', + SINCE_3_3) + assert_diagnoses( + [:error, :ambiguous_anonymous_restarg, {}], + 'def b(*) ->(a, *) {c(*)} end', + ' ^ location', + SINCE_3_3) + assert_diagnoses( + [:error, :ambiguous_anonymous_kwrestarg, {}], + 'def b(**) ->(**) {c(**)} end', + ' ^^ location', + SINCE_3_3) + assert_diagnoses( + [:error, :ambiguous_anonymous_kwrestarg, {}], + 'def b(k:, **) ->(**) {c(k: 1, **)} end', + ' ^^ location', + SINCE_3_3) + assert_diagnoses( + [:error, :ambiguous_anonymous_kwrestarg, {}], + 'def b(**) ->(k:, **) {c(**)} end', + ' ^^ location', + SINCE_3_3) + + refute_diagnoses( + 'def b(&) ->(&) {c()} end', + SINCE_3_3) + refute_diagnoses( + 'def b(*) ->(*) {c()} end', + SINCE_3_3) + refute_diagnoses( + 'def b(**) ->(**) {c()} end', + SINCE_3_3) + end end