summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakashi Kokubun <takashikkbn@gmail.com>2023-12-25 01:15:41 -0800
committerGitHub <noreply@github.com>2023-12-25 01:15:41 -0800
commit44592c4e20a17946b27c50081aee96802db981e6 (patch)
tree7d2d4c33b27df8053968c81fe602eae40120a763
parent98eeadc93298b2f178d400d99f3921e3663369b6 (diff)
Implement `it` (#9199)
[[Feature #18980]](https://bugs.ruby-lang.org/issues/18980) Co-authored-by: Yusuke Endoh <mame@ruby-lang.org>
-rw-r--r--parse.y89
-rw-r--r--test/ruby/test_syntax.rb56
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 <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
}
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