summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.md15
-rw-r--r--doc/syntax/methods.rdoc14
-rw-r--r--parse.y39
-rw-r--r--proc.c16
-rw-r--r--spec/ruby/core/method/parameters_spec.rb15
-rw-r--r--spec/ruby/core/proc/parameters_spec.rb12
-rw-r--r--test/ruby/test_iseq.rb14
-rw-r--r--test/ruby/test_method.rb12
-rw-r--r--test/ruby/test_proc.rb2
-rw-r--r--test/ruby/test_syntax.rb28
10 files changed, 146 insertions, 21 deletions
diff --git a/NEWS.md b/NEWS.md
index 69f57d5997..06696129a5 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -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>:
diff --git a/parse.y b/parse.y
index 7555d0db16..794c818e34 100644
--- a/parse.y
+++ b/parse.y
@@ -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
diff --git a/proc.c b/proc.c
index d075b7382e..6cdd7bd836 100644
--- a/proc.c
+++ b/proc.c
@@ -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|