From 74e33662fe987e5418fc277c8a7ba1f9805f8673 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Mon, 23 Sep 2019 08:44:38 -0700 Subject: Make public_send and rb_f_send handle keyword argument separation Kernel#send takes a different optimized code path that was already handled. --- test/ruby/test_keyword.rb | 102 ++++++++++++++++++++++++++++++++++++++++++++++ vm_eval.c | 27 +++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 62ba0bd0de..1dbde80cd5 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -1218,6 +1218,108 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h3], c.send(:m, a: 1, **h2)) end + def test_public_send_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.public_send(:m, **{})) + assert_equal([], c.public_send(:m, **kw)) + assert_equal([h], c.public_send(:m, **h)) + assert_equal([h], c.public_send(:m, a: 1)) + assert_equal([h2], c.public_send(:m, **h2)) + assert_equal([h3], c.public_send(:m, **h3)) + assert_equal([h3], c.public_send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.public_send(:m, **{})) + assert_nil(c.public_send(:m, **kw)) + assert_raise(ArgumentError) { c.public_send(:m, **h) } + assert_raise(ArgumentError) { c.public_send(:m, a: 1) } + assert_raise(ArgumentError) { c.public_send(:m, **h2) } + assert_raise(ArgumentError) { c.public_send(:m, **h3) } + assert_raise(ArgumentError) { c.public_send(:m, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal(kw, c.public_send(:m, **{})) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal(kw, c.public_send(:m, **kw)) + end + assert_equal(h, c.public_send(:m, **h)) + assert_equal(h, c.public_send(:m, a: 1)) + assert_equal(h2, c.public_send(:m, **h2)) + assert_equal(h3, c.public_send(:m, **h3)) + assert_equal(h3, c.public_send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.public_send(:m, **{})) + assert_equal(kw, c.public_send(:m, **kw)) + assert_equal(h, c.public_send(:m, **h)) + assert_equal(h, c.public_send(:m, a: 1)) + assert_equal(h2, c.public_send(:m, **h2)) + assert_equal(h3, c.public_send(:m, **h3)) + assert_equal(h3, c.public_send(:m, a: 1, **h2)) + assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do + assert_equal(h, c.public_send(:m, h)) + end + assert_raise(ArgumentError) { c.public_send(:m, h2) } + assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do + assert_raise(ArgumentError) { c.public_send(:m, h3) } + end + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + c.public_send(:m, **{}) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + c.public_send(:m, **kw) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([h, kw], c.public_send(:m, **h)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([h, kw], c.public_send(:m, a: 1)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([h2, kw], c.public_send(:m, **h2)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([h3, kw], c.public_send(:m, **h3)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([h3, kw], c.public_send(:m, a: 1, **h2)) + end + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.public_send(:m, **{})) + assert_equal([1, kw], c.public_send(:m, **kw)) + assert_equal([1, h], c.public_send(:m, **h)) + assert_equal([1, h], c.public_send(:m, a: 1)) + assert_equal([1, h2], c.public_send(:m, **h2)) + assert_equal([1, h3], c.public_send(:m, **h3)) + assert_equal([1, h3], c.public_send(:m, a: 1, **h2)) + end + def test_send_method_kwsplat kw = {} h = {:a=>1} diff --git a/vm_eval.c b/vm_eval.c index 4b3cb47072..0fd4f573ef 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1123,6 +1123,29 @@ send_internal(int argc, const VALUE *argv, VALUE recv, call_type scope) return ret; } +static VALUE +send_internal_kw(int argc, const VALUE *argv, VALUE recv, call_type scope) +{ + VALUE v=0, ret; + int kw_splat = RB_PASS_CALLED_KEYWORDS; + v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); + if (kw_splat) { + switch (scope) { + case CALL_PUBLIC: + scope = CALL_PUBLIC_KW; + break; + case CALL_FCALL: + scope = CALL_FCALL_KW; + break; + default: + break; + } + } + ret = send_internal(argc, argv, recv, scope); + rb_free_tmp_buffer(&v); + return ret; +} + /* * call-seq: * foo.send(symbol [, args...]) -> obj @@ -1150,7 +1173,7 @@ send_internal(int argc, const VALUE *argv, VALUE recv, call_type scope) VALUE rb_f_send(int argc, VALUE *argv, VALUE recv) { - return send_internal(argc, argv, recv, CALL_FCALL); + return send_internal_kw(argc, argv, recv, CALL_FCALL); } /* @@ -1170,7 +1193,7 @@ rb_f_send(int argc, VALUE *argv, VALUE recv) static VALUE rb_f_public_send(int argc, VALUE *argv, VALUE recv) { - return send_internal(argc, argv, recv, CALL_PUBLIC); + return send_internal_kw(argc, argv, recv, CALL_PUBLIC); } /* yield */ -- cgit v1.2.3