1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Do not autosplat when calling procs that accept rest and keywords

When providing a single array to a block that takes a splat, pass the
array as one argument of the splat instead of as the splat itself,
even if the block also accepts keyword arguments.  Previously, this
behavior was only used for blocks that did not accept keywords.

Implements [Feature#16166]
This commit is contained in:
Jeremy Evans 2019-09-27 13:35:29 -07:00
parent 040cfc89b9
commit f4394bbca3
Notes: git 2020-03-09 12:49:36 +09:00
4 changed files with 61 additions and 20 deletions

View file

@ -76,18 +76,18 @@ describe "A block yielded a single" do
result.should == [1, 2, [3], {x: 9}, 2, {}]
end
it "does not treat final Hash as keyword arguments" do
it "does not treat final Hash as keyword arguments and does not autosplat" do
result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] }
result.should == [{"a" => 1, a: 10}, {}]
result.should == [[{"a" => 1, a: 10}], {}]
end
it "does not call #to_hash on final argument to get keyword arguments" do
it "does not call #to_hash on final argument to get keyword arguments and does not autosplat" do
suppress_keyword_warning do
obj = mock("coerce block keyword arguments")
obj.should_not_receive(:to_hash)
result = m([obj]) { |a=nil, **b| [a, b] }
result.should == [obj, {}]
result.should == [[obj], {}]
end
end
end
@ -113,12 +113,12 @@ describe "A block yielded a single" do
end
ruby_version_is "2.8" do
it "does not call #to_hash on the argument when optional argument and keyword argument accepted" do
it "does not call #to_hash on the argument when optional argument and keyword argument accepted and does not autosplat" do
obj = mock("coerce block keyword arguments")
obj.should_not_receive(:to_hash)
result = m([obj]) { |a=nil, **b| [a, b] }
result.should == [obj, {}]
result.should == [[obj], {}]
end
end
@ -132,18 +132,27 @@ describe "A block yielded a single" do
end
end
ruby_version_is "2.8" do
it "does not separates non-symbol keys and symbol keys" do
it "does not separate non-symbol keys and symbol keys and does not autosplat" do
suppress_keyword_warning do
result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] }
result.should == [{"a" => 10, b: 2}, {}]
result.should == [[{"a" => 10, b: 2}], {}]
end
end
end
end
it "does not treat hashes with string keys as keyword arguments" do
result = m(["a" => 10]) { |a = nil, **b| [a, b] }
result.should == [{"a" => 10}, {}]
ruby_version_is ""..."2.8" do
it "does not treat hashes with string keys as keyword arguments" do
result = m(["a" => 10]) { |a = nil, **b| [a, b] }
result.should == [{"a" => 10}, {}]
end
end
ruby_version_is "2.8" do
it "does not treat hashes with string keys as keyword arguments and does not autosplat" do
result = m(["a" => 10]) { |a = nil, **b| [a, b] }
result.should == [[{"a" => 10}], {}]
end
end
ruby_version_is ''...'2.8' do

View file

@ -3284,15 +3284,20 @@ class TestKeywordArguments < Test::Unit::TestCase
bug7665 = '[ruby-core:51278]'
bug8463 = '[ruby-core:55203] [Bug #8463]'
a = [*%w[foo bar], {zzz: 42}]
expect = a + [{}]
assert_equal(expect, rest_keyrest(*a), bug7665)
splat_expect = a + [{}]
nonsplat_expect = [a, {}]
assert_equal(splat_expect, rest_keyrest(*a), bug7665)
assert_equal(nonsplat_expect, rest_keyrest(a), bug7665)
pr = proc {|*args, **opt| next *args, opt}
assert_equal(expect, pr.call(*a), bug7665)
assert_equal(expect, pr.call(a), bug8463)
assert_equal(splat_expect, pr.call(*a), bug7665)
assert_equal(nonsplat_expect, pr.call(a), bug8463)
pr = proc {|a, *b, **opt| next a, *b, opt}
assert_equal(expect, pr.call(a), bug8463)
assert_equal(splat_expect, pr.call(a), bug8463)
pr = proc {|a, **opt| next a, opt}
assert_equal(expect.values_at(0, -1), pr.call(expect), bug8463)
assert_equal(splat_expect.values_at(0, -1), pr.call(splat_expect), bug8463)
end
def req_plus_keyword(x, **h)
@ -3662,7 +3667,7 @@ class TestKeywordArguments < Test::Unit::TestCase
def test_nonsymbol_key
result = m(["a" => 10]) { |a = nil, **b| [a, b] }
assert_equal([{"a" => 10}, {}], result)
assert_equal([[{"a" => 10}], {}], result)
end
def method_for_test_to_hash_call_during_setup_complex_parameters k1:, k2:, **rest_kw

View file

@ -784,6 +784,33 @@ class TestProc < Test::Unit::TestCase
assert_equal [[1, 2], Proc, :x], (pr.call(1, 2){|x| x})
end
def test_proc_args_only_rest
pr = proc {|*c| c }
assert_equal [], pr.call()
assert_equal [1], pr.call(1)
assert_equal [[1]], pr.call([1])
assert_equal [1, 2], pr.call(1,2)
assert_equal [[1, 2]], pr.call([1,2])
end
def test_proc_args_rest_kw
pr = proc {|*c, a: 1| [c, a] }
assert_equal [[], 1], pr.call()
assert_equal [[1], 1], pr.call(1)
assert_equal [[[1]], 1], pr.call([1])
assert_equal [[1, 2], 1], pr.call(1,2)
assert_equal [[[1, 2]], 1], pr.call([1,2])
end
def test_proc_args_rest_kwsplat
pr = proc {|*c, **kw| [c, kw] }
assert_equal [[], {}], pr.call()
assert_equal [[1], {}], pr.call(1)
assert_equal [[[1]], {}], pr.call([1])
assert_equal [[1, 2], {}], pr.call(1,2)
assert_equal [[[1, 2]], {}], pr.call([1,2])
end
def test_proc_args_pos_rest_post_block
pr = proc {|a,b,*c,d,e,&f|
[a, b, c, d, e, f.class, f&&f.call(:x)]

View file

@ -568,14 +568,14 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
rb_raise(rb_eArgError, "no keywords accepted");
}
switch (arg_setup_type) {
case arg_setup_method:
break; /* do nothing special */
case arg_setup_block:
if (given_argc == (keyword_hash == Qnil ? 1 : 2) &&
allow_autosplat &&
(min_argc > 0 || iseq->body->param.opt_num > 1 ||
iseq->body->param.flags.has_kw || iseq->body->param.flags.has_kwrest) &&
(min_argc > 0 || iseq->body->param.opt_num > 1) &&
!iseq->body->param.flags.ambiguous_param0 &&
args_check_block_arg0(args)) {
given_argc = RARRAY_LENINT(args->rest);