ruby-changes:58349
From: Nobuyoshi <ko1@a...>
Date: Tue, 22 Oct 2019 02:36:06 +0900 (JST)
Subject: [ruby-changes:58349] 62d4382877 (master): Arguments forwarding [Feature #16253]
https://git.ruby-lang.org/ruby.git/commit/?id=62d4382877 From 62d43828770211470bcacb9e943876f981b5a1b4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada <nobu@r...> Date: Sat, 19 Oct 2019 03:05:03 +0900 Subject: Arguments forwarding [Feature #16253] diff --git a/NEWS b/NEWS index 5f499b8..3c9687f 100644 --- a/NEWS +++ b/NEWS @@ -186,6 +186,15 @@ sufficient information, see the ChangeLog file or Redmine https://github.com/ruby/ruby/blob/trunk/NEWS#L186 * +yield+ in singleton class syntax is warned and will be deprecated later [Feature #15575]. +* Argument forwarding by <code>...</code> is introduced. [Feature #16253] + + def foo(...) + bar(...) + end + + All arguments to +foo+ are forwarded to +bar+, including keyword and + block arguments. + === Core classes updates (outstanding ones only) Array:: diff --git a/parse.y b/parse.y index 4cd6b21..657e521 100644 --- a/parse.y +++ b/parse.y @@ -597,6 +597,10 @@ static void numparam_pop(struct parser_params *p, NODE *prev_inner); https://github.com/ruby/ruby/blob/trunk/parse.y#L597 # define METHOD_NOT '!' #endif +#define idFWD_REST '*' +#define idFWD_KWREST idPow /* Use simple "**", as tDSTAR is "**arg" */ +#define idFWD_BLOCK '&' + #define RE_OPTION_ONCE (1<<16) #define RE_OPTION_ENCODING_SHIFT 8 #define RE_OPTION_ENCODING(e) (((e)&0xff)<<RE_OPTION_ENCODING_SHIFT) @@ -1063,7 +1067,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in https://github.com/ruby/ruby/blob/trunk/parse.y#L1067 %type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg %type <id> f_kwrest f_label f_arg_asgn call_op call_op2 reswords relop dot_or_colon %type <id> p_kwrest p_kwnorest -%type <id> f_no_kwarg +%type <id> f_no_kwarg args_forward %token END_OF_INPUT 0 "end-of-input" %token <id> '.' /* escaped chars, should be ignored otherwise */ @@ -2406,6 +2410,23 @@ paren_args : '(' opt_call_args rparen https://github.com/ruby/ruby/blob/trunk/parse.y#L2410 /*% %*/ /*% ripper: arg_paren!(escape_Qundef($2)) %*/ } + | '(' args_forward rparen + { + if (!local_id(p, idFWD_REST) || !local_id(p, idFWD_KWREST) || !local_id(p, idFWD_BLOCK)) { + compile_error(p, "unexpected ..."); + $$ = Qnone; + } + else { + /*%%%*/ + NODE *splat = NEW_SPLAT(NEW_LVAR(idFWD_REST, &@2), &@2); + NODE *kwrest = list_append(p, NEW_LIST(0, &@2), NEW_LVAR(idFWD_KWREST, &@2)); + NODE *block = NEW_BLOCK_PASS(NEW_LVAR(idFWD_BLOCK, &@2), &@2); + $$ = arg_append(p, splat, new_hash(p, kwrest, &@2), &@2); + $$ = arg_blk_pass($$, block); + /*% %*/ + /*% ripper: arg_paren!($2) %*/ + } + } ; opt_paren_args : none @@ -3396,7 +3417,7 @@ block_param_def : '|' opt_bv_decl '|' https://github.com/ruby/ruby/blob/trunk/parse.y#L3417 /*%%%*/ $$ = 0; /*% %*/ - /*% ripper: block_var!(params_new(Qnil,Qnil,Qnil,Qnil,Qnil,Qnil,Qnil), escape_Qundef($2)) %*/ + /*% ripper: block_var!(params!(Qnil,Qnil,Qnil,Qnil,Qnil,Qnil,Qnil), escape_Qundef($2)) %*/ } | tOROP { @@ -3404,7 +3425,7 @@ block_param_def : '|' opt_bv_decl '|' https://github.com/ruby/ruby/blob/trunk/parse.y#L3425 /*%%%*/ $$ = 0; /*% %*/ - /*% ripper: block_var!(params_new(Qnil,Qnil,Qnil,Qnil,Qnil,Qnil,Qnil), Qnil) %*/ + /*% ripper: block_var!(params!(Qnil,Qnil,Qnil,Qnil,Qnil,Qnil,Qnil), Qnil) %*/ } | '|' block_param opt_bv_decl '|' { @@ -4862,6 +4883,17 @@ f_args : f_arg ',' f_optarg ',' f_rest_arg opt_args_tail https://github.com/ruby/ruby/blob/trunk/parse.y#L4883 { $$ = new_args(p, Qnone, Qnone, Qnone, Qnone, $1, &@$); } + | args_forward + { + arg_var(p, idFWD_REST); + arg_var(p, idFWD_KWREST); + arg_var(p, idFWD_BLOCK); + /*%%%*/ + $$ = new_args_tail(p, Qnone, idFWD_KWREST, idFWD_BLOCK, &@1); + $$ = new_args(p, Qnone, Qnone, idFWD_REST, Qnone, $$, &@$); + /*% %*/ + /*% ripper: params_new(Qnone, Qnone, $1, Qnone, Qnone, Qnone, Qnone) %*/ + } | /* none */ { $$ = new_args_tail(p, Qnone, Qnone, Qnone, &@0); @@ -4869,6 +4901,15 @@ f_args : f_arg ',' f_optarg ',' f_rest_arg opt_args_tail https://github.com/ruby/ruby/blob/trunk/parse.y#L4901 } ; +args_forward : tBDOT3 + { + /*%%%*/ + $$ = idDot3; + /*% %*/ + /*% ripper: args_forward! %*/ + } + ; + f_bad_arg : tCONSTANT { /*%%%*/ diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index 4cb56f6..1be2833 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -131,6 +131,12 @@ class TestRipper::ParserEvents < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ripper/test_parser_events.rb#L131 assert_equal true, thru_args_new end + def test_args_forward + thru_args_forward = false + parse('def m(...) n(...) end', :on_args_forward) {thru_args_forward = true} + assert_equal true, thru_args_forward + end + def test_arg_paren # FIXME end diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 4b9be49..5b933d9 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1471,6 +1471,46 @@ eom https://github.com/ruby/ruby/blob/trunk/test/ruby/test_syntax.rb#L1471 assert_valid_syntax("tap {a = (break unless true)}") end + def test_argument_forwarding + assert_valid_syntax('def foo(...) bar(...) end') + assert_valid_syntax('def foo(...) end') + assert_syntax_error('iter do |...| end', /unexpected/) + assert_syntax_error('iter {|...|}', /unexpected/) + assert_syntax_error('def foo(x, y, z) bar(...); end', /unexpected/) + assert_syntax_error('def foo(x, y, z) super(...); end', /unexpected/) + assert_syntax_error('def foo(...) yield(...); end', /unexpected/) + assert_syntax_error('def foo(...) return(...); end', /unexpected/) + assert_syntax_error('def foo(...) a = (...); end', /unexpected/) + assert_syntax_error('def foo(...) [...]; end', /unexpected/) + assert_syntax_error('def foo(...) foo[...]; end', /unexpected/) + assert_syntax_error('def foo(...) foo[...] = x; end', /unexpected/) + assert_syntax_error('def foo(...) foo(...) { }; end', /both block arg and actual block given/) + assert_syntax_error('def foo(...) defined?(...); end', /unexpected/) + + obj1 = Object.new + def obj1.bar(*args, **kws, &block) + block.call(args, kws) + end + obj1.instance_eval('def foo(...) bar(...) end') + + klass = Class.new { + def foo(*args, **kws, &block) + block.call(args, kws) + end + } + obj2 = klass.new + obj2.instance_eval('def foo(...) super(...) end') + + [obj1, obj2].each do |obj| + assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x}) + assert_equal(-1, obj.:foo.arity) + parameters = obj.:foo.parameters + assert_equal(:rest, parameters.dig(0, 0)) + assert_equal(:keyrest, parameters.dig(1, 0)) + assert_equal(:block, parameters.dig(2, 0)) + end + end + private def not_label(x) @result = x; @not_label ||= nil end -- cgit v0.10.2 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/