diff options
-rw-r--r-- | NEWS.md | 15 | ||||
-rw-r--r-- | doc/syntax/methods.rdoc | 14 | ||||
-rw-r--r-- | parse.y | 39 | ||||
-rw-r--r-- | proc.c | 16 | ||||
-rw-r--r-- | spec/ruby/core/method/parameters_spec.rb | 15 | ||||
-rw-r--r-- | spec/ruby/core/proc/parameters_spec.rb | 12 | ||||
-rw-r--r-- | test/ruby/test_iseq.rb | 14 | ||||
-rw-r--r-- | test/ruby/test_method.rb | 12 | ||||
-rw-r--r-- | test/ruby/test_proc.rb | 2 | ||||
-rw-r--r-- | test/ruby/test_syntax.rb | 28 |
10 files changed, 146 insertions, 21 deletions
@@ -7,6 +7,19 @@ Note that each entry is kept to a minimum, see links for details. ## Language changes +* Anonymous rest and keyword rest arguments can now be passed as + arguments, instead of just used in method parameters. + [[Feature #18351]] + + ```ruby + def foo(*) + bar(*) + end + def baz(**) + quux(**) + end + ``` + ## Command line options ## Core classes updates @@ -52,3 +65,5 @@ Note: Excluding feature bug fixes. ## IRB Autocomplete and Document Display ## Miscellaneous changes + +[Feature #18351]: https://bugs.ruby-lang.org/issues/18351 diff --git a/doc/syntax/methods.rdoc b/doc/syntax/methods.rdoc index 2bb350def1..8dafa6bb0c 100644 --- a/doc/syntax/methods.rdoc +++ b/doc/syntax/methods.rdoc @@ -441,6 +441,13 @@ Also, note that a bare <code>*</code> can be used to ignore arguments: def ignore_arguments(*) end +You can also use a bare <code>*</code> when calling a method to pass the +arguments directly to another method: + + def delegate_arguments(*) + other_method(*) + end + === Keyword Arguments Keyword arguments are similar to positional arguments with default values: @@ -481,6 +488,13 @@ Also, note that <code>**</code> can be used to ignore keyword arguments: def ignore_keywords(**) end +You can also use <code>**</code> when calling a method to delegate +keyword arguments to another method: + + def delegate_keywords(**) + other_method(**) + end + To mark a method as accepting keywords, but not actually accepting keywords, you can use the <code>**nil</code>: @@ -427,6 +427,8 @@ static void token_info_drop(struct parser_params *p, const char *token, rb_code_ #define lambda_beginning_p() (p->lex.lpar_beg == p->lex.paren_nest) #define ANON_BLOCK_ID '&' +#define ANON_REST_ID '*' +#define ANON_KEYWORD_REST_ID idPow static enum yytokentype yylex(YYSTYPE*, YYLTYPE*, struct parser_params*); @@ -2890,6 +2892,16 @@ args : arg_value /*% %*/ /*% ripper: args_add_star!(args_new!, $2) %*/ } + | tSTAR + { + /*%%%*/ + if (!local_id(p, ANON_REST_ID)) { + compile_error(p, "no anonymous rest parameter"); + } + $$ = NEW_SPLAT(NEW_LVAR(ANON_REST_ID, &@1), &@$); + /*% %*/ + /*% ripper: args_add_star!(args_new!, Qnil) %*/ + } | args ',' arg_value { /*%%%*/ @@ -2904,6 +2916,16 @@ args : arg_value /*% %*/ /*% ripper: args_add_star!($1, $4) %*/ } + | args ',' tSTAR + { + /*%%%*/ + if (!local_id(p, ANON_REST_ID)) { + compile_error(p, "no anonymous rest parameter"); + } + $$ = rest_arg_append(p, $1, NEW_LVAR(ANON_REST_ID, &@3), &@$); + /*% %*/ + /*% ripper: args_add_star!($1, Qnil) %*/ + } ; /* value */ @@ -5479,8 +5501,7 @@ f_kwrest : kwrest_mark tIDENTIFIER | kwrest_mark { /*%%%*/ - $$ = internal_id(p); - arg_var(p, $$); + arg_var(p, shadowing_lvar(p, get_id(ANON_KEYWORD_REST_ID))); /*% %*/ /*% ripper: kwrest_param!(Qnil) %*/ } @@ -5555,8 +5576,7 @@ f_rest_arg : restarg_mark tIDENTIFIER | restarg_mark { /*%%%*/ - $$ = internal_id(p); - arg_var(p, $$); + arg_var(p, shadowing_lvar(p, get_id(ANON_REST_ID))); /*% %*/ /*% ripper: rest_param!(Qnil) %*/ } @@ -5710,6 +5730,17 @@ assoc : arg_value tASSOC arg_value /*% %*/ /*% ripper: assoc_splat!($2) %*/ } + | tDSTAR + { + /*%%%*/ + if (!local_id(p, ANON_KEYWORD_REST_ID)) { + compile_error(p, "no anonymous keyword rest parameter"); + } + $$ = list_append(p, NEW_LIST(0, &@$), + NEW_LVAR(ANON_KEYWORD_REST_ID, &@$)); + /*% %*/ + /*% ripper: assoc_splat!(Qnil) %*/ + } ; operation : tIDENTIFIER @@ -3124,6 +3124,16 @@ method_inspect(VALUE method) rb_str_buf_cat2(str, "("); + if (RARRAY_LEN(params) == 3 && + RARRAY_AREF(RARRAY_AREF(params, 0), 0) == rest && + RARRAY_AREF(RARRAY_AREF(params, 0), 1) == ID2SYM('*') && + RARRAY_AREF(RARRAY_AREF(params, 1), 0) == keyrest && + RARRAY_AREF(RARRAY_AREF(params, 1), 1) == ID2SYM(idPow) && + RARRAY_AREF(RARRAY_AREF(params, 2), 0) == block && + RARRAY_AREF(RARRAY_AREF(params, 2), 1) == ID2SYM('&')) { + forwarding = 1; + } + for (int i = 0; i < RARRAY_LEN(params); i++) { pair = RARRAY_AREF(params, i); kind = RARRAY_AREF(pair, 0); @@ -3159,8 +3169,7 @@ method_inspect(VALUE method) } else if (kind == rest) { if (name == ID2SYM('*')) { - forwarding = 1; - rb_str_cat_cstr(str, "..."); + rb_str_cat_cstr(str, forwarding ? "..." : "*"); } else { rb_str_catf(str, "*%"PRIsVALUE, name); @@ -3173,6 +3182,9 @@ method_inspect(VALUE method) else if (i > 0) { rb_str_set_len(str, RSTRING_LEN(str) - 2); } + else { + rb_str_cat_cstr(str, "**"); + } } else if (kind == block) { if (name == ID2SYM('&')) { diff --git a/spec/ruby/core/method/parameters_spec.rb b/spec/ruby/core/method/parameters_spec.rb index 3fdaf9ce6f..67b42afafa 100644 --- a/spec/ruby/core/method/parameters_spec.rb +++ b/spec/ruby/core/method/parameters_spec.rb @@ -222,9 +222,18 @@ describe "Method#parameters" do m.method(:handled_via_method_missing).parameters.should == [[:rest]] end - it "adds nameless rest arg for \"star\" argument" do - m = MethodSpecs::Methods.new - m.method(:one_unnamed_splat).parameters.should == [[:rest]] + ruby_version_is '3.1' do + it "adds * rest arg for \"star\" argument" do + m = MethodSpecs::Methods.new + m.method(:one_unnamed_splat).parameters.should == [[:rest, :*]] + end + end + + ruby_version_is ''...'3.1' do + it "adds nameless rest arg for \"star\" argument" do + m = MethodSpecs::Methods.new + m.method(:one_unnamed_splat).parameters.should == [[:rest]] + end end it "returns the args and block for a splat and block argument" do diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index 5fb5cf418d..60620722f8 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -80,8 +80,16 @@ describe "Proc#parameters" do -> x {}.parameters.should == [[:req, :x]] end - it "adds nameless rest arg for \"star\" argument" do - -> x, * {}.parameters.should == [[:req, :x], [:rest]] + ruby_version_is '3.1' do + it "adds * rest arg for \"star\" argument" do + -> x, * {}.parameters.should == [[:req, :x], [:rest, :*]] + end + end + + ruby_version_is ''...'3.1' do + it "adds nameless rest arg for \"star\" argument" do + -> x, * {}.parameters.should == [[:req, :x], [:rest]] + end end it "does not add locals as block options with a block and splat" do diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index f01d36cc5a..1ee41e6e81 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -162,7 +162,7 @@ class TestISeq < Test::Unit::TestCase end obj = Object.new def obj.foo(*) nil.instance_eval{ ->{super} } end - assert_raise_with_message(Ractor::IsolationError, /hidden variable/) do + assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable `\*'/) do Ractor.make_shareable(obj.foo) end end @@ -392,10 +392,18 @@ class TestISeq < Test::Unit::TestCase def anon_star(*); end - def test_anon_param_in_disasm + def test_anon_rest_param_in_disasm iseq = RubyVM::InstructionSequence.of(method(:anon_star)) param_names = iseq.to_a[iseq.to_a.index(:method) + 1] - assert_equal [2], param_names + assert_equal [:*], param_names + end + + def anon_keyrest(**); end + + def test_anon_keyrest_param_in_disasm + iseq = RubyVM::InstructionSequence.of(method(:anon_keyrest)) + param_names = iseq.to_a[iseq.to_a.index(:method) + 1] + assert_equal [:**], param_names end def anon_block(&); end diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index da68787933..d6de330dff 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -566,9 +566,9 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:mo5).parameters) assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:mo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:mo7).parameters) - assert_equal([[:req, :a], [:opt, :b], [:rest], [:req, :d], [:block, :e]], method(:mo8).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], method(:mo8).parameters) assert_equal([[:req], [:block, :b]], method(:ma1).parameters) - assert_equal([[:keyrest]], method(:mk1).parameters) + assert_equal([[:keyrest, :**]], method(:mk1).parameters) assert_equal([[:keyrest, :o]], method(:mk2).parameters) assert_equal([[:req, :a], [:keyrest, :o]], method(:mk3).parameters) assert_equal([[:opt, :a], [:keyrest, :o]], method(:mk4).parameters) @@ -592,9 +592,9 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c]], self.class.instance_method(:mo5).parameters) assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:mo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:mo7).parameters) - assert_equal([[:req, :a], [:opt, :b], [:rest], [:req, :d], [:block, :e]], self.class.instance_method(:mo8).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], self.class.instance_method(:mo8).parameters) assert_equal([[:req], [:block, :b]], self.class.instance_method(:ma1).parameters) - assert_equal([[:keyrest]], self.class.instance_method(:mk1).parameters) + assert_equal([[:keyrest, :**]], self.class.instance_method(:mk1).parameters) assert_equal([[:keyrest, :o]], self.class.instance_method(:mk2).parameters) assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:mk3).parameters) assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:mk4).parameters) @@ -619,7 +619,7 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).parameters) assert_equal([[:req], [:block, :b]], method(:pma1).parameters) - assert_equal([[:keyrest]], method(:pmk1).parameters) + assert_equal([[:keyrest, :**]], method(:pmk1).parameters) assert_equal([[:keyrest, :o]], method(:pmk2).parameters) assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).parameters) assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).parameters) @@ -643,7 +643,7 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:pmo7).parameters) assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters) assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters) - assert_equal([[:keyrest]], self.class.instance_method(:pmk1).parameters) + assert_equal([[:keyrest, :**]], self.class.instance_method(:pmk1).parameters) assert_equal([[:keyrest, :o]], self.class.instance_method(:pmk2).parameters) assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:pmk3).parameters) assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:pmk4).parameters) diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 51872e49be..05b2493b24 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1261,7 +1261,7 @@ class TestProc < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).to_proc.parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).to_proc.parameters) assert_equal([[:req], [:block, :b]], method(:pma1).to_proc.parameters) - assert_equal([[:keyrest]], method(:pmk1).to_proc.parameters) + assert_equal([[:keyrest, :**]], method(:pmk1).to_proc.parameters) assert_equal([[:keyrest, :o]], method(:pmk2).to_proc.parameters) assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).to_proc.parameters) assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).to_proc.parameters) diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index b71f492f9c..622527dafe 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -78,6 +78,34 @@ class TestSyntax < Test::Unit::TestCase end; end + def test_anonymous_rest_forwarding + assert_syntax_error("def b; c(*); end", /no anonymous rest parameter/) + assert_syntax_error("def b; c(1, *); end", /no anonymous rest parameter/) + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(*); c(*) end + def c(*a); a end + def d(*); b(*, *) end + assert_equal([1, 2], b(1, 2)) + assert_equal([1, 2, 1, 2], d(1, 2)) + end; + end + + def test_anonymous_keyword_rest_forwarding + assert_syntax_error("def b; c(**); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def b; c(k: 1, **); end", /no anonymous keyword rest parameter/) + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(**); c(**) end + def c(**kw); kw end + def d(**); b(k: 1, **) end + def e(**); b(**, k: 1) end + assert_equal({a: 1, k: 3}, b(a: 1, k: 3)) + assert_equal({a: 1, k: 3}, d(a: 1, k: 3)) + assert_equal({a: 1, k: 1}, e(a: 1, k: 3)) + end; + end + def test_newline_in_block_parameters bug = '[ruby-dev:45292]' ["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params| |