ruby-changes:57874
From: Jeremy <ko1@a...>
Date: Tue, 24 Sep 2019 01:28:46 +0900 (JST)
Subject: [ruby-changes:57874] 74e33662fe (master): Make public_send and rb_f_send handle keyword argument separation
https://git.ruby-lang.org/ruby.git/commit/?id=74e33662fe From 74e33662fe987e5418fc277c8a7ba1f9805f8673 Mon Sep 17 00:00:00 2001 From: Jeremy Evans <code@j...> 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. diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 62ba0bd..1dbde80 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -1218,6 +1218,108 @@ class TestKeywordArguments < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L1218 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 4b3cb47..0fd4f57 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) https://github.com/ruby/ruby/blob/trunk/vm_eval.c#L1123 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) https://github.com/ruby/ruby/blob/trunk/vm_eval.c#L1173 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) https://github.com/ruby/ruby/blob/trunk/vm_eval.c#L1193 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 v0.10.2 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/