Implement it
(#9199)
[[Feature #18980]](https://bugs.ruby-lang.org/issues/18980) Co-authored-by: Yusuke Endoh <mame@ruby-lang.org>
This commit is contained in:
parent
98eeadc932
commit
44592c4e20
89
parse.y
89
parse.y
@ -393,6 +393,7 @@ struct local_vars {
|
||||
struct {
|
||||
NODE *outer, *inner, *current;
|
||||
} numparam;
|
||||
NODE *it;
|
||||
# endif
|
||||
};
|
||||
|
||||
@ -488,6 +489,7 @@ struct parser_params {
|
||||
int node_id;
|
||||
|
||||
int max_numparam;
|
||||
ID it_id;
|
||||
|
||||
struct lex_context ctxt;
|
||||
|
||||
@ -1223,7 +1225,7 @@ static NODE *new_hash_pattern(struct parser_params *p, NODE *constant, NODE *hsh
|
||||
static NODE *new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, const YYLTYPE *loc);
|
||||
|
||||
static rb_node_kw_arg_t *new_kw_arg(struct parser_params *p, NODE *k, const YYLTYPE *loc);
|
||||
static rb_node_args_t *args_with_numbered(struct parser_params*,rb_node_args_t*,int);
|
||||
static rb_node_args_t *args_with_numbered(struct parser_params*,rb_node_args_t*,int,ID);
|
||||
|
||||
static VALUE negate_lit(struct parser_params*, VALUE);
|
||||
static NODE *ret_args(struct parser_params*,NODE*);
|
||||
@ -1480,7 +1482,7 @@ new_args_tail(struct parser_params *p, VALUE kw_args, VALUE kw_rest_arg, VALUE b
|
||||
}
|
||||
|
||||
static inline VALUE
|
||||
args_with_numbered(struct parser_params *p, VALUE args, int max_numparam)
|
||||
args_with_numbered(struct parser_params *p, VALUE args, int max_numparam, ID it_id)
|
||||
{
|
||||
return args;
|
||||
}
|
||||
@ -2099,6 +2101,7 @@ get_nd_args(struct parser_params *p, NODE *node)
|
||||
%type <tbl> p_lparen p_lbracket p_pktbl p_pvtbl
|
||||
/* ripper */ %type <num> max_numparam
|
||||
/* ripper */ %type <node> numparam
|
||||
/* ripper */ %type <id> it_id
|
||||
%token END_OF_INPUT 0 "end-of-input"
|
||||
%token <id> '.'
|
||||
|
||||
@ -4743,6 +4746,12 @@ numparam : {
|
||||
}
|
||||
;
|
||||
|
||||
it_id : {
|
||||
$$ = p->it_id;
|
||||
p->it_id = 0;
|
||||
}
|
||||
;
|
||||
|
||||
lambda : tLAMBDA[dyna]
|
||||
{
|
||||
token_info_push(p, "->", &@1);
|
||||
@ -4750,7 +4759,7 @@ lambda : tLAMBDA[dyna]
|
||||
$<num>$ = p->lex.lpar_beg;
|
||||
p->lex.lpar_beg = p->lex.paren_nest;
|
||||
}[lpar]
|
||||
max_numparam numparam allow_exits
|
||||
max_numparam numparam it_id allow_exits
|
||||
f_larglist[args]
|
||||
{
|
||||
CMDARG_PUSH(0);
|
||||
@ -4758,11 +4767,13 @@ lambda : tLAMBDA[dyna]
|
||||
lambda_body[body]
|
||||
{
|
||||
int max_numparam = p->max_numparam;
|
||||
ID it_id = p->it_id;
|
||||
p->lex.lpar_beg = $<num>lpar;
|
||||
p->max_numparam = $max_numparam;
|
||||
p->it_id = $it_id;
|
||||
restore_block_exit(p, $allow_exits);
|
||||
CMDARG_POP();
|
||||
$args = args_with_numbered(p, $args, max_numparam);
|
||||
$args = args_with_numbered(p, $args, max_numparam, it_id);
|
||||
/*%%%*/
|
||||
{
|
||||
YYLTYPE loc = code_loc_gen(&@args, &@body);
|
||||
@ -4950,12 +4961,14 @@ brace_block : '{' brace_body '}'
|
||||
;
|
||||
|
||||
brace_body : {$<vars>$ = dyna_push(p);}[dyna]
|
||||
max_numparam numparam allow_exits
|
||||
max_numparam numparam it_id allow_exits
|
||||
opt_block_param[args] compstmt
|
||||
{
|
||||
int max_numparam = p->max_numparam;
|
||||
ID it_id = p->it_id;
|
||||
p->max_numparam = $max_numparam;
|
||||
$args = args_with_numbered(p, $args, max_numparam);
|
||||
p->it_id = $it_id;
|
||||
$args = args_with_numbered(p, $args, max_numparam, it_id);
|
||||
/*%%%*/
|
||||
$$ = NEW_ITER($args, $compstmt, &@$);
|
||||
/*% %*/
|
||||
@ -4970,12 +4983,14 @@ do_body : {
|
||||
$<vars>$ = dyna_push(p);
|
||||
CMDARG_PUSH(0);
|
||||
}[dyna]
|
||||
max_numparam numparam allow_exits
|
||||
max_numparam numparam it_id allow_exits
|
||||
opt_block_param[args] bodystmt
|
||||
{
|
||||
int max_numparam = p->max_numparam;
|
||||
ID it_id = p->it_id;
|
||||
p->max_numparam = $max_numparam;
|
||||
$args = args_with_numbered(p, $args, max_numparam);
|
||||
p->it_id = $<id>it_id;
|
||||
$args = args_with_numbered(p, $args, max_numparam, it_id);
|
||||
/*%%%*/
|
||||
$$ = NEW_ITER($args, $bodystmt, &@$);
|
||||
/*% %*/
|
||||
@ -12715,6 +12730,34 @@ numparam_nested_p(struct parser_params *p)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
numparam_used_p(struct parser_params *p)
|
||||
{
|
||||
NODE *numparam = p->lvtbl->numparam.current;
|
||||
if (numparam) {
|
||||
compile_error(p, "numbered parameter is already used in\n"
|
||||
"%s:%d: current block here",
|
||||
p->ruby_sourcefile, nd_line(numparam));
|
||||
parser_show_error_line(p, &numparam->nd_loc);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
it_used_p(struct parser_params *p)
|
||||
{
|
||||
NODE *it = p->lvtbl->it;
|
||||
if (it) {
|
||||
compile_error(p, "`it` is already used in\n"
|
||||
"%s:%d: current block here",
|
||||
p->ruby_sourcefile, nd_line(it));
|
||||
parser_show_error_line(p, &it->nd_loc);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NODE*
|
||||
gettable(struct parser_params *p, ID id, const YYLTYPE *loc)
|
||||
{
|
||||
@ -12751,7 +12794,7 @@ gettable(struct parser_params *p, ID id, const YYLTYPE *loc)
|
||||
switch (id_type(id)) {
|
||||
case ID_LOCAL:
|
||||
if (dyna_in_block(p) && dvar_defined_ref(p, id, &vidp)) {
|
||||
if (NUMPARAM_ID_P(id) && numparam_nested_p(p)) return 0;
|
||||
if (NUMPARAM_ID_P(id) && (numparam_nested_p(p) || it_used_p(p))) return 0;
|
||||
if (id == p->cur_arg) {
|
||||
compile_error(p, "circular argument reference - %"PRIsWARN, rb_id2str(id));
|
||||
return 0;
|
||||
@ -12771,7 +12814,7 @@ gettable(struct parser_params *p, ID id, const YYLTYPE *loc)
|
||||
}
|
||||
if (dyna_in_block(p) && NUMPARAM_ID_P(id) &&
|
||||
parser_numbered_param(p, NUMPARAM_ID_TO_IDX(id))) {
|
||||
if (numparam_nested_p(p)) return 0;
|
||||
if (numparam_nested_p(p) || it_used_p(p)) return 0;
|
||||
node = NEW_DVAR(id, loc);
|
||||
struct local_vars *local = p->lvtbl;
|
||||
if (!local->numparam.current) local->numparam.current = node;
|
||||
@ -12783,10 +12826,19 @@ gettable(struct parser_params *p, ID id, const YYLTYPE *loc)
|
||||
}
|
||||
# endif
|
||||
/* method call without arguments */
|
||||
if (dyna_in_block(p) && id == rb_intern("it")
|
||||
&& !(DVARS_TERMINAL_P(p->lvtbl->args) || DVARS_TERMINAL_P(p->lvtbl->args->prev))
|
||||
&& p->max_numparam != ORDINAL_PARAM) {
|
||||
rb_warn0("`it` calls without arguments will refer to the first block param in Ruby 3.4; use it() or self.it");
|
||||
if (dyna_in_block(p) && id == rb_intern("it") && !(DVARS_TERMINAL_P(p->lvtbl->args) || DVARS_TERMINAL_P(p->lvtbl->args->prev))) {
|
||||
if (numparam_used_p(p)) return 0;
|
||||
if (p->max_numparam == ORDINAL_PARAM) {
|
||||
compile_error(p, "ordinary parameter is defined");
|
||||
return 0;
|
||||
}
|
||||
if (!p->it_id) {
|
||||
p->it_id = internal_id(p);
|
||||
vtable_add(p->lvtbl->args, p->it_id);
|
||||
}
|
||||
NODE *node = NEW_DVAR(p->it_id, loc);
|
||||
if (!p->lvtbl->it) p->lvtbl->it = node;
|
||||
return node;
|
||||
}
|
||||
return NEW_VCALL(id, loc);
|
||||
case ID_GLOBAL:
|
||||
@ -14441,15 +14493,15 @@ new_args_tail(struct parser_params *p, rb_node_kw_arg_t *kw_args, ID kw_rest_arg
|
||||
}
|
||||
|
||||
static rb_node_args_t *
|
||||
args_with_numbered(struct parser_params *p, rb_node_args_t *args, int max_numparam)
|
||||
args_with_numbered(struct parser_params *p, rb_node_args_t *args, int max_numparam, ID it_id)
|
||||
{
|
||||
if (max_numparam > NO_PARAM) {
|
||||
if (max_numparam > NO_PARAM || it_id) {
|
||||
if (!args) {
|
||||
YYLTYPE loc = RUBY_INIT_YYLLOC();
|
||||
args = new_args_tail(p, 0, 0, 0, 0);
|
||||
nd_set_loc(RNODE(args), &loc);
|
||||
}
|
||||
args->nd_ainfo.pre_args_num = max_numparam;
|
||||
args->nd_ainfo.pre_args_num = it_id ? 1 : max_numparam;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
@ -14848,6 +14900,7 @@ local_push(struct parser_params *p, int toplevel_scope)
|
||||
local->numparam.outer = 0;
|
||||
local->numparam.inner = 0;
|
||||
local->numparam.current = 0;
|
||||
local->it = 0;
|
||||
#endif
|
||||
local->used = warn_unused_vars ? vtable_alloc(0) : 0;
|
||||
|
||||
@ -15068,6 +15121,7 @@ numparam_push(struct parser_params *p)
|
||||
}
|
||||
local->numparam.inner = 0;
|
||||
local->numparam.current = 0;
|
||||
local->it = 0;
|
||||
return inner;
|
||||
#else
|
||||
return 0;
|
||||
@ -15096,6 +15150,7 @@ numparam_pop(struct parser_params *p, NODE *prev_inner)
|
||||
/* no numbered parameter */
|
||||
local->numparam.current = 0;
|
||||
}
|
||||
local->it = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -1778,16 +1778,52 @@ eom
|
||||
end
|
||||
|
||||
def test_it
|
||||
assert_no_warning(/`it`/) {eval('if false; it; end')}
|
||||
assert_no_warning(/`it`/) {eval('def foo; it; end')}
|
||||
assert_warn(/`it`/) {eval('0.times { it }')}
|
||||
assert_no_warning(/`it`/) {eval('0.times { || it }')}
|
||||
assert_no_warning(/`it`/) {eval('0.times { |_n| it }')}
|
||||
assert_warn(/`it`/) {eval('0.times { it; it = 1; it }')}
|
||||
assert_no_warning(/`it`/) {eval('0.times { it = 1; it }')}
|
||||
assert_no_warning(/`it`/) {eval('it = 1; 0.times { it }')}
|
||||
ensure
|
||||
self.class.remove_method(:foo)
|
||||
assert_valid_syntax('proc {it}')
|
||||
assert_syntax_error('[1,2].then {it+_2}', /`it` is already used/)
|
||||
assert_syntax_error('[1,2].then {_2+it}', /numbered parameter is already used/)
|
||||
assert_equal([1, 2], eval('[1,2].then {it}'))
|
||||
assert_syntax_error('[1,2].then {"#{it}#{_2}"}', /`it` is already used/)
|
||||
assert_syntax_error('[1,2].then {"#{_2}#{it}"}', /numbered parameter is already used/)
|
||||
assert_syntax_error('->{it+_2}.call(1,2)', /`it` is already used/)
|
||||
assert_syntax_error('->{_2+it}.call(1,2)', /numbered parameter is already used/)
|
||||
assert_equal(4, eval('->(a=->{it}){a}.call.call(4)'))
|
||||
assert_equal(5, eval('-> a: ->{it} {a}.call.call(5)'))
|
||||
assert_syntax_error('proc {|| it}', /ordinary parameter is defined/)
|
||||
assert_syntax_error('proc {|;a| it}', /ordinary parameter is defined/)
|
||||
assert_syntax_error("proc {|\n| it}", /ordinary parameter is defined/)
|
||||
assert_syntax_error('proc {|x| it}', /ordinary parameter is defined/)
|
||||
assert_equal([1, 2], eval('1.then {[it, 2.then {_1}]}'))
|
||||
assert_equal([2, 1], eval('1.then {[2.then {_1}, it]}'))
|
||||
assert_syntax_error('->(){it}', /ordinary parameter is defined/)
|
||||
assert_syntax_error('->(x){it}', /ordinary parameter is defined/)
|
||||
assert_syntax_error('->x{it}', /ordinary parameter is defined/)
|
||||
assert_syntax_error('->x:_1{}', /ordinary parameter is defined/)
|
||||
assert_syntax_error('->x=it{}', /ordinary parameter is defined/)
|
||||
assert_valid_syntax('-> {it; -> {_2}}')
|
||||
assert_valid_syntax('-> {-> {it}; _2}')
|
||||
assert_equal([1, nil], eval('proc {that=it; it=nil; [that, it]}.call(1)'))
|
||||
assert_equal(1, eval('proc {it = 1}.call'))
|
||||
assert_equal(2, eval('a=Object.new; def a.foo; it = 2; end; a.foo'))
|
||||
assert_equal(3, eval('proc {|it| it}.call(3)'))
|
||||
assert_equal(4, eval('a=Object.new; def a.foo(it); it; end; a.foo(4)'))
|
||||
assert_equal(5, eval('a=Object.new; def a.it; 5; end; a.it'))
|
||||
assert_equal(6, eval('a=Class.new; a.class_eval{ def it; 6; end }; a.new.it'))
|
||||
assert_raise_with_message(NameError, /undefined local variable or method `it'/) do
|
||||
eval('it')
|
||||
end
|
||||
['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c|
|
||||
assert_valid_syntax("->{#{c};->{it};end;it}\n")
|
||||
assert_valid_syntax("->{it;#{c};->{it};end}\n")
|
||||
end
|
||||
1.times do
|
||||
[
|
||||
assert_equal(0, it),
|
||||
assert_equal([:a], eval('[:a].map{it}')),
|
||||
assert_raise(NameError) {eval('it')},
|
||||
]
|
||||
end
|
||||
assert_valid_syntax('proc {def foo(_);end;it}')
|
||||
assert_syntax_error('p { [it **2] }', /unexpected \*\*arg/)
|
||||
end
|
||||
|
||||
def test_value_expr_in_condition
|
||||
|
Loading…
x
Reference in New Issue
Block a user