aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNobuyoshi Nakada <nobu@ruby-lang.org>2019-10-19 03:05:03 +0900
committerNobuyoshi Nakada <nobu@ruby-lang.org>2019-10-22 02:35:43 +0900
commit62d43828770211470bcacb9e943876f981b5a1b4 (patch)
tree97c1cdebd90cff8bf28320d5ad053b80cc53df53
parent35f90bf1b9b06f93e919af3c4095b7aff903d799 (diff)
downloadruby-62d43828770211470bcacb9e943876f981b5a1b4.tar.gz
Arguments forwarding [Feature #16253]
-rw-r--r--NEWS9
-rw-r--r--parse.y47
-rw-r--r--test/ripper/test_parser_events.rb6
-rw-r--r--test/ruby/test_syntax.rb40
4 files changed, 99 insertions, 3 deletions
diff --git a/NEWS b/NEWS
index 5f499b8765..3c9687f831 100644
--- a/NEWS
+++ b/NEWS
@@ -186,6 +186,15 @@ sufficient information, see the ChangeLog file or Redmine
* +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 4cd6b2195e..657e521982 100644
--- a/parse.y
+++ b/parse.y
@@ -597,6 +597,10 @@ static void numparam_pop(struct parser_params *p, NODE *prev_inner);
# 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
%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
/*% %*/
/*% 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 '|'
/*%%%*/
$$ = 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 '|'
/*%%%*/
$$ = 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
{
$$ = 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
}
;
+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 4cb56f66f0..1be2833a4b 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
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 4b9be497d0..5b933d9682 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -1471,6 +1471,46 @@ eom
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