From 44592c4e20a17946b27c50081aee96802db981e6 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 25 Dec 2023 01:15:41 -0800 Subject: [PATCH] Implement `it` (#9199) [[Feature #18980]](https://bugs.ruby-lang.org/issues/18980) Co-authored-by: Yusuke Endoh --- parse.y | 89 ++++++++++++++++++++++++++++++++-------- test/ruby/test_syntax.rb | 56 ++++++++++++++++++++----- 2 files changed, 118 insertions(+), 27 deletions(-) diff --git a/parse.y b/parse.y index f8e21dc9a3..cd6f655a14 100644 --- a/parse.y +++ b/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 p_lparen p_lbracket p_pktbl p_pvtbl /* ripper */ %type max_numparam /* ripper */ %type numparam +/* ripper */ %type it_id %token END_OF_INPUT 0 "end-of-input" %token '.' @@ -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] $$ = 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 = $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 : {$$ = 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 : { $$ = 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 = $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 } diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 33fcc6a1e0..e53e2a2e61 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -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