[前][次][番号順一覧][スレッド一覧]

ruby-changes:71563

From: Jeremy <ko1@a...>
Date: Thu, 31 Mar 2022 03:04:15 +0900 (JST)
Subject: [ruby-changes:71563] fbaadd1cfe (master): Do not autosplat array in block call just because keywords accepted

https://git.ruby-lang.org/ruby.git/commit/?id=fbaadd1cfe

From fbaadd1cfe7fbfd1b904f193f99d7c845a6ed804 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
Date: Wed, 30 Mar 2022 11:03:56 -0700
Subject: Do not autosplat array in block call just because keywords accepted

If the block only accepts a single positional argument plus keywords,
then do not autosplat.  Still autosplat if the block accepts more
than one positional argument in addition to keywords.

Autosplatting a single positional argument plus keywords made sense
in Ruby 2, since a final positional hash could be used as keywords,
but it does not make sense in Ruby 3.

Fixes [Bug #18633]
---
 spec/ruby/language/block_spec.rb | 13 ++++++-
 test/ruby/test_keyword.rb        |  2 +-
 test/ruby/test_proc.rb           | 82 ++++++++++++++++++++++++++++++++++++++++
 vm_args.c                        |  4 +-
 4 files changed, 97 insertions(+), 4 deletions(-)

diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb
index b2a3cc84c4..f213d68ba1 100644
--- a/spec/ruby/language/block_spec.rb
+++ b/spec/ruby/language/block_spec.rb
@@ -40,8 +40,17 @@ describe "A block yielded a single" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/language/block_spec.rb#L40
       m([1, 2]) { |a=5, b, c, d| [a, b, c, d] }.should == [5, 1, 2, nil]
     end
 
-    it "assigns elements to required arguments when a keyword rest argument is present" do
-      m([1, 2]) { |a, **k| [a, k] }.should == [1, {}]
+    ruby_version_is "3.2" do
+      it "does not autosplat single argument to required arguments when a keyword rest argument is present" do
+        m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}]
+      end
+    end
+
+    ruby_version_is ''..."3.2" do
+      # https://bugs.ruby-lang.org/issues/18633
+      it "autosplats single argument to required arguments when a keyword rest argument is present" do
+        m([1, 2]) { |a, **k| [a, k] }.should == [1, {}]
+      end
     end
 
     ruby_version_is ''..."3.0" do
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 9094259bc2..0d766905bd 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -3538,7 +3538,7 @@ class TestKeywordArguments < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L3538
     assert_equal(splat_expect, pr.call(a), bug8463)
 
     pr = proc {|a, **opt| next a, opt}
-    assert_equal(splat_expect.values_at(0, -1), pr.call(splat_expect), bug8463)
+    assert_equal([splat_expect, {}], pr.call(splat_expect), bug8463)
   end
 
   def req_plus_keyword(x, **h)
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index bcba3a127b..05c6e03aac 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -857,6 +857,88 @@ class TestProc < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_proc.rb#L857
     assert_equal [[1, 2], Proc, :x], (pr.call(1, 2){|x| x})
   end
 
+  def test_proc_args_single_kw_no_autosplat
+    pr = proc {|c, a: 1| [c, a] }
+    assert_equal [nil, 1], pr.call()
+    assert_equal [1, 1], pr.call(1)
+    assert_equal [[1], 1], pr.call([1])
+    assert_equal [1, 1], pr.call(1,2)
+    assert_equal [[1, 2], 1], pr.call([1,2])
+
+    assert_equal [nil, 3], pr.call(a: 3)
+    assert_equal [1, 3], pr.call(1, a: 3)
+    assert_equal [[1], 3], pr.call([1], a: 3)
+    assert_equal [1, 3], pr.call(1,2, a: 3)
+    assert_equal [[1, 2], 3], pr.call([1,2], a: 3)
+  end
+
+  def test_proc_args_single_kwsplat_no_autosplat
+    pr = proc {|c, **kw| [c, kw] }
+    assert_equal [nil, {}], pr.call()
+    assert_equal [1, {}], pr.call(1)
+    assert_equal [[1], {}], pr.call([1])
+    assert_equal [1, {}], pr.call(1,2)
+    assert_equal [[1, 2], {}], pr.call([1,2])
+
+    assert_equal [nil, {a: 3}], pr.call(a: 3)
+    assert_equal [1, {a: 3}], pr.call(1, a: 3)
+    assert_equal [[1], {a: 3}], pr.call([1], a: 3)
+    assert_equal [1, {a: 3}], pr.call(1,2, a: 3)
+    assert_equal [[1, 2], {a: 3}], pr.call([1,2], a: 3)
+  end
+
+  def test_proc_args_multiple_kw_autosplat
+    pr = proc {|c, b, a: 1| [c, b, a] }
+    assert_equal [1, 2, 1], pr.call([1,2])
+
+    pr = proc {|c=nil, b=nil, a: 1| [c, b, a] }
+    assert_equal [nil, nil, 1], pr.call([])
+    assert_equal [1, nil, 1], pr.call([1])
+    assert_equal [1, 2, 1], pr.call([1,2])
+
+    pr = proc {|c, b=nil, a: 1| [c, b, a] }
+    assert_equal [1, nil, 1], pr.call([1])
+    assert_equal [1, 2, 1], pr.call([1,2])
+
+    pr = proc {|c=nil, b, a: 1| [c, b, a] }
+    assert_equal [nil, 1, 1], pr.call([1])
+    assert_equal [1, 2, 1], pr.call([1,2])
+
+    pr = proc {|c, *b, a: 1| [c, b, a] }
+    assert_equal [1, [], 1], pr.call([1])
+    assert_equal [1, [2], 1], pr.call([1,2])
+
+    pr = proc {|*c, b, a: 1| [c, b, a] }
+    assert_equal [[], 1, 1], pr.call([1])
+    assert_equal [[1], 2, 1], pr.call([1,2])
+  end
+
+  def test_proc_args_multiple_kwsplat_autosplat
+    pr = proc {|c, b, **kw| [c, b, kw] }
+    assert_equal [1, 2, {}], pr.call([1,2])
+
+    pr = proc {|c=nil, b=nil, **kw| [c, b, kw] }
+    assert_equal [nil, nil, {}], pr.call([])
+    assert_equal [1, nil, {}], pr.call([1])
+    assert_equal [1, 2, {}], pr.call([1,2])
+
+    pr = proc {|c, b=nil, **kw| [c, b, kw] }
+    assert_equal [1, nil, {}], pr.call([1])
+    assert_equal [1, 2, {}], pr.call([1,2])
+
+    pr = proc {|c=nil, b, **kw| [c, b, kw] }
+    assert_equal [nil, 1, {}], pr.call([1])
+    assert_equal [1, 2, {}], pr.call([1,2])
+
+    pr = proc {|c, *b, **kw| [c, b, kw] }
+    assert_equal [1, [], {}], pr.call([1])
+    assert_equal [1, [2], {}], pr.call([1,2])
+
+    pr = proc {|*c, b, **kw| [c, b, kw] }
+    assert_equal [[], 1, {}], pr.call([1])
+    assert_equal [[1], 2, {}], pr.call([1,2])
+  end
+
   def test_proc_args_only_rest
     pr = proc {|*c| c }
     assert_equal [], pr.call()
diff --git a/vm_args.c b/vm_args.c
index 7439579f43..a3ddf9a633 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -599,7 +599,6 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co https://github.com/ruby/ruby/blob/trunk/vm_args.c#L599
 	rb_raise(rb_eArgError, "no keywords accepted");
     }
 
-
     switch (arg_setup_type) {
       case arg_setup_method:
 	break; /* do nothing special */
@@ -608,6 +607,9 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co https://github.com/ruby/ruby/blob/trunk/vm_args.c#L607
             allow_autosplat &&
             (min_argc > 0 || ISEQ_BODY(iseq)->param.opt_num > 1) &&
             !ISEQ_BODY(iseq)->param.flags.ambiguous_param0 &&
+            !((ISEQ_BODY(iseq)->param.flags.has_kw ||
+               ISEQ_BODY(iseq)->param.flags.has_kwrest)
+               && max_argc == 1) &&
 	    args_check_block_arg0(args)) {
 	    given_argc = RARRAY_LENINT(args->rest);
 	}
-- 
cgit v1.2.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]