Feature #12023 ยป ruby-method-argument-ivars.diff
| compile.c | ||
|---|---|---|
| 		} | ||
| 	    } | ||
| 	} | ||
| 	if (args->has_ivars) { | ||
| 	    iseq->body->param.flags.has_ivars = TRUE; | ||
| 	} | ||
|     } | ||
|     return COMPILE_OK; | ||
| node.h | ||
|---|---|---|
|     NODE *kw_rest_arg; | ||
|     NODE *opt_args; | ||
|     int has_ivars; | ||
| }; | ||
| struct parser_params; | ||
| parse.y | ||
|---|---|---|
|     unsigned int past_scope_enabled: 1; | ||
| # endif | ||
|     unsigned int error_p: 1; | ||
|     unsigned int ivar_arg_seen: 1; | ||
|     unsigned int in_def_args: 1; | ||
| #ifndef RIPPER | ||
|     /* Ruby core only */ | ||
| ... | ... | |
| %type <node> command_args aref_args opt_block_arg block_arg var_ref var_lhs | ||
| %type <node> command_asgn mrhs mrhs_arg superclass block_call block_command | ||
| %type <node> f_block_optarg f_block_opt | ||
| %type <node> f_arglist f_args f_arg f_arg_item f_optarg f_marg f_marg_list f_margs | ||
| %type <node> f_arglist f_def_args f_args f_arg f_arg_item f_optarg f_marg f_marg_list f_margs | ||
| %type <node> assoc_list assocs assoc undef_list backref string_dvar for_var | ||
| %type <node> block_param opt_block_param block_param_def f_opt | ||
| %type <node> f_kwarg f_kw f_block_kwarg f_block_kw | ||
| ... | ... | |
| 		    } | ||
| 		; | ||
| f_arglist	: '(' f_args rparen | ||
| f_arglist	: '(' f_def_args rparen | ||
| 		    { | ||
| 		    /*%%%*/ | ||
| 			$$ = $2; | ||
| ... | ... | |
| 			parser->in_kwarg = 1; | ||
| 			lex_state |= EXPR_LABEL; /* force for args */ | ||
| 		    } | ||
| 		    f_args term | ||
| 		    f_def_args term | ||
| 		    { | ||
| 			parser->in_kwarg = !!$<num>1; | ||
| 			$$ = $2; | ||
| ... | ... | |
| 		    } | ||
| 		; | ||
| f_bad_arg	: tCONSTANT | ||
| f_def_args	:   { | ||
| 			parser->in_def_args = 1; | ||
| 			parser->ivar_arg_seen = 0; | ||
| 		    } | ||
| 		f_args | ||
| 		    { | ||
| 		    /*%%%*/ | ||
| 			yyerror("formal argument cannot be a constant"); | ||
| 			$$ = 0; | ||
| 		    /*% | ||
| 			$$ = dispatch1(param_error, $1); | ||
| 			ripper_error(); | ||
| 		    %*/ | ||
| 			parser->in_def_args = 0; | ||
| 			$$ = $2; | ||
| 		    } | ||
| 		| tIVAR | ||
| f_bad_arg	: tCONSTANT | ||
| 		    { | ||
| 		    /*%%%*/ | ||
| 			yyerror("formal argument cannot be an instance variable"); | ||
| 			yyerror("formal argument cannot be a constant"); | ||
| 			$$ = 0; | ||
| 		    /*% | ||
| 			$$ = dispatch1(param_error, $1); | ||
| ... | ... | |
| 			formal_argument(get_id($1)); | ||
| 			$$ = $1; | ||
| 		    } | ||
| 		| tIVAR | ||
| 		    { | ||
| 		    /*%%%*/ | ||
| 			if (!parser->in_def_args) { | ||
| 			    yyerror("formal argument cannot be an instance variable in a block"); | ||
| 			    $$ = 0; | ||
| 			} | ||
| 			else { | ||
| 			    parser->ivar_arg_seen = 1; | ||
| 			    formal_argument(get_id($1)); | ||
| 			    $$ = $1; | ||
| 			} | ||
| 		    /*% | ||
| 			if (!parser->in_def_args) { | ||
| 			    $$ = dispatch1(param_error, $1); | ||
| 			    ripper_error(); | ||
| 			} | ||
| 			else { | ||
| 			    parser->ivar_arg_seen = 1; | ||
| 			    formal_argument(get_id($1)); | ||
| 			    $$ = $1; | ||
| 			} | ||
| 		    %*/ | ||
| 		    } | ||
| 		; | ||
| f_arg_asgn	: f_norm_arg | ||
| ... | ... | |
| { | ||
|     switch (id_type(lhs)) { | ||
|       case ID_LOCAL: | ||
| 	shadowing_lvar(lhs); | ||
| 	break; | ||
|       case ID_INSTANCE: | ||
| 	break; | ||
| #ifndef RIPPER | ||
|       case ID_CONST: | ||
| 	yyerror("formal argument cannot be a constant"); | ||
| 	return 0; | ||
|       case ID_INSTANCE: | ||
| 	yyerror("formal argument cannot be an instance variable"); | ||
| 	return 0; | ||
|       case ID_GLOBAL: | ||
| 	yyerror("formal argument cannot be a global variable"); | ||
| 	return 0; | ||
| ... | ... | |
| 	return 0; | ||
| #endif | ||
|     } | ||
|     shadowing_lvar(lhs); | ||
|     return lhs; | ||
| } | ||
| ... | ... | |
|     if (tokadd_ident(parser, c)) return 0; | ||
|     SET_LEX_STATE(EXPR_END); | ||
|     tokenize_ident(parser, last_state); | ||
|     if (parser->in_def_args && result == tIVAR && peek(':')) { | ||
| 	nextc(); | ||
| 	SET_LEX_STATE(EXPR_ARG|EXPR_LABELED); | ||
| 	return tLABEL; | ||
|     } | ||
|     return result; | ||
| } | ||
| ... | ... | |
|     args->opt_args       = o; | ||
|     args->has_ivars      = parser->ivar_arg_seen; | ||
|     ruby_sourceline = saved_line; | ||
|     return tail; | ||
| test/ruby/test_method.rb | ||
|---|---|---|
|     assert_equal('1', obj.foo(1)) | ||
|     assert_equal('1', obj.bar(1)) | ||
|   end | ||
|   class IvarArgumentExample | ||
|     def initialize(local, @ivar, @def = 9, kwarg:, @ikwarg:, @idkwarg: 10) | ||
|       @stored_local = local | ||
|       @stored_kwarg = kwarg | ||
|     end | ||
|     def ivar_values | ||
|       [@ivar, @def, @ikwarg, @idkwarg] | ||
|     end | ||
|     def local_values | ||
|       [@local, @kwarg] | ||
|     end | ||
|     def stored_local_values | ||
|       [@stored_local, @stored_kwarg] | ||
|     end | ||
|   end | ||
|   def ivar_argument_example | ||
|     IvarArgumentExample.new(1, 2, kwarg: 4, ikwarg: 5) | ||
|   end | ||
|   def test_ivar_arguments_basic | ||
|     assert_equal [2, 9, 5, 10], ivar_argument_example.ivar_values | ||
|   end | ||
|   def test_ivar_arguments_supplying_optionals | ||
|     values = IvarArgumentExample.new(1, 2, 3, kwarg: 4, ikwarg: 5, idkwarg: 6).ivar_values | ||
|     assert_equal [2, 3, 5, 6], values | ||
|   end | ||
|   def test_local_arguments_are_not_set_as_ivars | ||
|     assert_equal [nil, nil], ivar_argument_example.local_values | ||
|   end | ||
|   def test_local_arguments_were_stored | ||
|     assert_equal [1, 4], ivar_argument_example.stored_local_values | ||
|   end | ||
|   def test_ivar_arguments_are_not_accepted_in_a_block | ||
|     assert_raise(SyntaxError) do | ||
|       eval "[].each { |@x| true }" | ||
|     end | ||
|   end | ||
|   def test_ivar_arguments_are_not_accepted_in_a_lambda | ||
|     assert_raise(SyntaxError) do | ||
|       eval "-> (@x) { true }" | ||
|     end | ||
|   end | ||
|   def accessing_ivar_as_local(@ivar) | ||
|     ivar # this should raise | ||
|   end | ||
|   def test_ivar_arguments_are_not_exposed_as_local_variables | ||
|     assert_raise(NameError) { accessing_ivar_as_local(10) } | ||
|   end | ||
| end | ||
| vm_args.c | ||
|---|---|---|
| args_setup_kw_parameters(VALUE* const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords, | ||
| 			 const rb_iseq_t * const iseq, VALUE * const locals) | ||
| { | ||
|     const ID *acceptable_keywords = iseq->body->param.keyword->table; | ||
|     const ID *acceptable_keywords; | ||
|     ID modified_acceptable_keywords[iseq->body->param.keyword->num]; | ||
|     const int req_key_num = iseq->body->param.keyword->required_num; | ||
|     const int key_num = iseq->body->param.keyword->num; | ||
|     const VALUE * const default_values = iseq->body->param.keyword->default_values; | ||
| ... | ... | |
|     int unspecified_bits = 0; | ||
|     VALUE unspecified_bits_value = Qnil; | ||
|     if (iseq->body->param.flags.has_ivars) { | ||
| 	const ID *pid = iseq->body->param.keyword->table; | ||
| 	for (i=0; i<iseq->body->param.keyword->num; i++, pid++) { | ||
| 	    modified_acceptable_keywords[i] = rb_is_instance_id(*pid) ? rb_intern(rb_id2name(*pid) + 1) : *pid; | ||
| 	} | ||
| 	acceptable_keywords = (const ID *)&modified_acceptable_keywords; | ||
|     } | ||
|     else { | ||
| 	acceptable_keywords = iseq->body->param.keyword->table; | ||
|     } | ||
|     for (i=0; i<req_key_num; i++) { | ||
| 	ID key = acceptable_keywords[i]; | ||
| 	if (args_setup_kw_parameters_lookup(key, &locals[i], passed_keywords, passed_values, passed_keyword_len)) { | ||
| ... | ... | |
|     *locals = blockval; | ||
| } | ||
| static inline void | ||
| args_setup_ivar_parameters(const rb_iseq_t * const iseq, struct rb_calling_info *calling, VALUE *locals) | ||
| { | ||
|     unsigned int i; | ||
|     ID id; | ||
|     for (i=0; i<iseq->body->local_table_size; i++) { | ||
| 	id = iseq->body->local_table[i]; | ||
| 	if (rb_is_instance_id(id)) { | ||
| 	    rb_ivar_set(calling->recv, id, locals[i]); | ||
| 	    locals[i] = Qnil; | ||
| 	} | ||
|     } | ||
| } | ||
| struct fill_values_arg { | ||
|     VALUE *keys; | ||
|     VALUE *vals; | ||
| ... | ... | |
| 	args_setup_block_parameter(th, calling, locals + iseq->body->param.block_start); | ||
|     } | ||
|     if (iseq->body->param.flags.has_ivars) { | ||
| 	args_setup_ivar_parameters(iseq, calling, locals); | ||
|     } | ||
| #if 0 | ||
|     { | ||
| 	int i; | ||
| vm_core.h | ||
|---|---|---|
| 	    unsigned int has_kw     : 1; | ||
| 	    unsigned int has_kwrest : 1; | ||
| 	    unsigned int has_block  : 1; | ||
| 	    unsigned int has_ivars  : 1; | ||
| 	    unsigned int ambiguous_param0 : 1; /* {|a|} */ | ||
| 	} flags; | ||
| vm_insnhelper.c | ||
|---|---|---|
| 	   iseq->body->param.flags.has_post == FALSE && | ||
| 	   iseq->body->param.flags.has_kw == FALSE && | ||
| 	   iseq->body->param.flags.has_kwrest == FALSE && | ||
| 	   iseq->body->param.flags.has_block == FALSE; | ||
| 	   iseq->body->param.flags.has_block == FALSE && | ||
| 	   iseq->body->param.flags.has_ivars == FALSE; | ||
| } | ||
| static inline int | ||