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/