diff options
author | Jeremy Evans <[email protected]> | 2020-05-28 11:09:44 -0700 |
---|---|---|
committer | Jeremy Evans <[email protected]> | 2020-06-06 17:07:11 -0700 |
commit | f8b4340fa2c254cd093ebc3bc70d2d0c46ea9997 (patch) | |
tree | ad5406a80ddad1a4aa3aaae929724b8b196995c6 | |
parent | 4178cbd2974fc0022823392c2460e09df2517a6e (diff) |
Add leading arguments support to arguments forwarding
The idFWD_KWREST sections may be wrong. However, the existing
idFWD_KWREST sections for ... without leading arguments are already
broken.
Implements [Feature #16378]
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/3190
-rw-r--r-- | parse.y | 41 | ||||
-rw-r--r-- | test/ripper/test_parser_events.rb | 14 | ||||
-rw-r--r-- | test/ruby/test_syntax.rb | 113 |
3 files changed, 165 insertions, 3 deletions
@@ -2568,6 +2568,32 @@ paren_args : '(' opt_call_args rparen /*% %*/ /*% ripper: arg_paren!(escape_Qundef($2)) %*/ } + | '(' args ',' args_forward rparen + { + if (!local_id(p, idFWD_REST) || +#if idFWD_KWREST + !local_id(p, idFWD_KWREST) || +#endif + !local_id(p, idFWD_BLOCK)) { + compile_error(p, "unexpected ..."); + $$ = Qnone; + } + else { + /*%%%*/ + NODE *splat = NEW_SPLAT(NEW_LVAR(idFWD_REST, &@4), &@4); +#if idFWD_KWREST + NODE *kwrest = list_append(p, NEW_LIST(0, &@4), NEW_LVAR(idFWD_KWREST, &@4)); +#endif + NODE *block = NEW_BLOCK_PASS(NEW_LVAR(idFWD_BLOCK, &@4), &@4); + $$ = rest_arg_append(p, $2, splat, &@$); +#if idFWD_KWREST + $$ = arg_append(p, $$, new_hash(p, kwrest, &@4), &@4); +#endif + $$ = arg_blk_pass($$, block); + /*% %*/ + /*% ripper: arg_paren!(args_add!($2, $4)) %*/ + } + } | '(' args_forward rparen { if (!local_id(p, idFWD_REST) || @@ -4914,6 +4940,21 @@ f_paren_args : '(' f_args rparen SET_LEX_STATE(EXPR_BEG); p->command_start = TRUE; } + | '(' f_arg ',' args_forward rparen + { + arg_var(p, idFWD_REST); +#if idFWD_KWREST + arg_var(p, idFWD_KWREST); +#endif + arg_var(p, idFWD_BLOCK); + /*%%%*/ + $$ = new_args_tail(p, Qnone, idFWD_KWREST, idFWD_BLOCK, &@4); + $$ = new_args(p, $2, Qnone, idFWD_REST, Qnone, $$, &@4); + /*% %*/ + /*% ripper: paren!(params_new($2, Qnone, $4, Qnone, Qnone, Qnone, Qnone)) %*/ + SET_LEX_STATE(EXPR_BEG); + p->command_start = TRUE; + } | '(' args_forward rparen { arg_var(p, idFWD_REST); diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index b6eeb77cda..544896b15f 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -132,9 +132,17 @@ class TestRipper::ParserEvents < Test::Unit::TestCase 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 + [ + 'def m(...) n(...) end', + 'def m(...) end', + 'def m(a, ...) n(1, ...) end', + 'def m(...) n(1, ...) end', + 'def m(a, ...) n(...) end' + ].each do |code| + thru_args_forward = false + parse(code, :on_args_forward) {thru_args_forward = true} + assert_equal true, thru_args_forward, "no args_forward for: #{code}" + end end def test_arg_paren diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index d419fc87a0..cf0a93e185 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1581,6 +1581,119 @@ eom end end + def test_argument_forwarding_with_leading_arguments + obj = Object.new + def obj.bar(*args, **kws, &block) + if block + block.call(args, kws) + else + [args, kws] + end + end + obj.instance_eval('def foo(_a, ...) bar(...) end', __FILE__, __LINE__) + assert_equal [[], {}], obj.foo(1) + assert_equal [[2], {}], obj.foo(1, 2) + assert_equal [[2, 3], {}], obj.foo(1, 2, 3) + assert_equal [[], {a: 1}], obj.foo(1, a: 1) + assert_equal [[2], {a: 1}], obj.foo(1, 2, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo + assert_equal [[1, 1], {}], obj.foo(1) + assert_equal [[1, 1, 2], {}], obj.foo(1, 2) + assert_equal [[1, 1, 2, 3], {}], obj.foo(1, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(a: 1) + assert_equal [[1, 1], {a: 1}], obj.foo(1, a: 1) + assert_equal [[1, 1, 2], {a: 1}], obj.foo(1, 2, a: 1) + assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1) + assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, ...) bar(a, ...) end', __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4) + assert_equal [[4, 2], {}], obj.foo(4, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, ...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo(4) + assert_equal [[1, 2], {}], obj.foo(4, 2) + assert_equal [[1, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(4, a: 1) + assert_equal [[1, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, _b, ...) bar(...) end', __FILE__, __LINE__) + assert_equal [[], {}], obj.foo(4, 5) + assert_equal [[2], {}], obj.foo(4, 5, 2) + assert_equal [[2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, _b, ...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo(4, 5) + assert_equal [[1, 2], {}], obj.foo(4, 5, 2) + assert_equal [[1, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[1, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, ...) bar(1, 2, ...) end', __FILE__, __LINE__) + assert_equal [[1, 2], {}], obj.foo(5) + assert_equal [[1, 2, 5], {}], obj.foo(4, 5) + assert_equal [[1, 2, 5, 2], {}], obj.foo(4, 5, 2) + assert_equal [[1, 2, 5, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[1, 2, 5], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[1, 2, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, b, ...) bar(b, a, ...) end', __FILE__, __LINE__) + assert_equal [[5, 4], {}], obj.foo(4, 5) + assert_equal [[5, 4, 2], {}], obj.foo(4, 5, 2) + assert_equal [[5, 4, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[5, 4], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[5, 4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, _b, ...) bar(a, ...) end', __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4, 5) + assert_equal [[4, 2], {}], obj.foo(4, 5, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, ...) bar(a, 1, ...) end', __FILE__, __LINE__) + assert_equal [[4, 1], {}], obj.foo(4) + assert_equal [[4, 1, 5], {}], obj.foo(4, 5) + assert_equal [[4, 1, 5, 2], {}], obj.foo(4, 5, 2) + assert_equal [[4, 1, 5, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[4, 1, 5], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[4, 1, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + end + def test_rightward_assign assert_equal(1, eval("1 => a")) assert_equal([2,3], eval("13.divmod(5) => a,b; [a, b]")) |