diff --git a/compile.c b/compile.c index 85372ae..de41318 100644 --- a/compile.c +++ b/compile.c @@ -1435,6 +1435,10 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args) } } } + + if (args->has_ivars) { + iseq->body->param.flags.has_ivars = TRUE; + } } return COMPILE_OK; diff --git a/node.h b/node.h index 8d2ea53..f112359 100644 --- a/node.h +++ b/node.h @@ -499,6 +499,8 @@ struct rb_args_info { NODE *kw_rest_arg; NODE *opt_args; + + int has_ivars; }; struct parser_params; diff --git a/parse.y b/parse.y index 4475277..af7d951 100644 --- a/parse.y +++ b/parse.y @@ -305,6 +305,8 @@ struct parser_params { 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 */ @@ -857,7 +859,7 @@ static void token_info_pop(struct parser_params*, const char *token, size_t len) %type command_args aref_args opt_block_arg block_arg var_ref var_lhs %type command_asgn mrhs mrhs_arg superclass block_call block_command %type f_block_optarg f_block_opt -%type f_arglist f_args f_arg f_arg_item f_optarg f_marg f_marg_list f_margs +%type f_arglist f_def_args f_args f_arg f_arg_item f_optarg f_marg f_marg_list f_margs %type assoc_list assocs assoc undef_list backref string_dvar for_var %type block_param opt_block_param block_param_def f_opt %type f_kwarg f_kw f_block_kwarg f_block_kw @@ -4526,7 +4528,7 @@ superclass : '<' } ; -f_arglist : '(' f_args rparen +f_arglist : '(' f_def_args rparen { /*%%%*/ $$ = $2; @@ -4541,7 +4543,7 @@ f_arglist : '(' f_args rparen parser->in_kwarg = 1; lex_state |= EXPR_LABEL; /* force for args */ } - f_args term + f_def_args term { parser->in_kwarg = !!$1; $$ = $2; @@ -4641,20 +4643,20 @@ f_args : f_arg ',' f_optarg ',' f_rest_arg opt_args_tail } ; -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); @@ -4689,6 +4691,30 @@ f_norm_arg : f_bad_arg 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 @@ -6942,14 +6968,14 @@ formal_argument_gen(struct parser_params *parser, ID lhs) { 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; @@ -6966,7 +6992,6 @@ formal_argument_gen(struct parser_params *parser, ID lhs) return 0; #endif } - shadowing_lvar(lhs); return lhs; } @@ -7963,6 +7988,13 @@ parse_atmark(struct parser_params *parser, const enum lex_state_e last_state) 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; } @@ -10044,6 +10076,8 @@ new_args_gen(struct parser_params *parser, NODE *m, NODE *o, ID r, NODE *p, NODE args->opt_args = o; + args->has_ivars = parser->ivar_arg_seen; + ruby_sourceline = saved_line; return tail; diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index 1e5f054..69bdd2c 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -953,4 +953,64 @@ def test_define_method_with_symbol 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 diff --git a/vm_args.c b/vm_args.c index 2546b86..8781330 100644 --- a/vm_args.c +++ b/vm_args.c @@ -386,7 +386,8 @@ static void 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; @@ -395,6 +396,17 @@ args_setup_kw_parameters(VALUE* const passed_values, const int passed_keyword_le 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; ibody->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; ibody->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; @@ -678,6 +706,10 @@ setup_parameters_complex(rb_thread_t * const th, const rb_iseq_t * const iseq, 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; diff --git a/vm_core.h b/vm_core.h index 38fcc0c..13506a8 100644 --- a/vm_core.h +++ b/vm_core.h @@ -316,6 +316,7 @@ struct rb_iseq_constant_body { 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; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 86f6c4b..938f7fe 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1344,7 +1344,8 @@ simple_iseq_p(const rb_iseq_t *iseq) 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