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:
Takashi Kokubun 2023-12-25 01:15:41 -08:00 committed by GitHub
parent 98eeadc932
commit 44592c4e20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 118 additions and 27 deletions

89
parse.y
View File

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

View File

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