1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Arguments forwarding [Feature #16253]

This commit is contained in:
Nobuyoshi Nakada 2019-10-19 03:05:03 +09:00
parent 35f90bf1b9
commit 62d4382877
Notes: git 2019-10-22 02:36:12 +09:00
4 changed files with 99 additions and 3 deletions

9
NEWS
View file

@ -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::

47
parse.y
View file

@ -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
{
/*%%%*/

View file

@ -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

View file

@ -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