[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]