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
|
||