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
|