From 6f9b86616a8ad60cfed2979e2a0f8398a12e7c85 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 3 Sep 2019 14:54:37 -0700 Subject: [PATCH] Make Symbol#to_proc calls handle keyword arguments Make rb_sym_proc_call take a flag for whether a keyword argument is used, and use the new rb_funcall_with_block_kw function to pass that information. --- internal.h | 2 +- string.c | 4 +-- test/ruby/test_keyword.rb | 73 +++++++++++++++++++++++++++++++++++++++ vm_insnhelper.c | 3 +- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/internal.h b/internal.h index 14f78301fb..21491c317f 100644 --- a/internal.h +++ b/internal.h @@ -2140,7 +2140,7 @@ VALUE rb_str_initialize(VALUE str, const char *ptr, long len, rb_encoding *enc); #define is_ascii_string(str) (rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT) #define is_broken_string(str) (rb_enc_str_coderange(str) == ENC_CODERANGE_BROKEN) size_t rb_str_memsize(VALUE); -VALUE rb_sym_proc_call(ID mid, int argc, const VALUE *argv, VALUE passed_proc); +VALUE rb_sym_proc_call(ID mid, int argc, const VALUE *argv, int kw_splat, VALUE passed_proc); VALUE rb_sym_to_proc(VALUE sym); char *rb_str_to_cstr(VALUE str); VALUE rb_str_eql(VALUE str1, VALUE str2); diff --git a/string.c b/string.c index d6ea7d6f91..05ce0ed8d6 100644 --- a/string.c +++ b/string.c @@ -10887,7 +10887,7 @@ sym_to_sym(VALUE sym) } MJIT_FUNC_EXPORTED VALUE -rb_sym_proc_call(ID mid, int argc, const VALUE *argv, VALUE passed_proc) +rb_sym_proc_call(ID mid, int argc, const VALUE *argv, int kw_splat, VALUE passed_proc) { VALUE obj; @@ -10895,7 +10895,7 @@ rb_sym_proc_call(ID mid, int argc, const VALUE *argv, VALUE passed_proc) rb_raise(rb_eArgError, "no receiver given"); } obj = argv[0]; - return rb_funcall_with_block(obj, mid, argc - 1, argv + 1, passed_proc); + return rb_funcall_with_block_kw(obj, mid, argc - 1, argv + 1, passed_proc, kw_splat); } #if 0 diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index b8d8736c5f..91e53e1dee 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -447,6 +447,79 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h3], c.send(:m, **h3)) end + def test_sym_proc_kwsplat + kw = {} + h = {'a'=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], :m.to_proc.call(c, **{})) + assert_equal([], :m.to_proc.call(c, **kw)) + assert_equal([h], :m.to_proc.call(c, **h)) + assert_equal([h2], :m.to_proc.call(c, **h2)) + assert_equal([h3], :m.to_proc.call(c, **h3)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(:m.to_proc.call(c, **{})) + assert_nil(:m.to_proc.call(c, **kw)) + assert_raise(ArgumentError) { :m.to_proc.call(c, **h) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h2) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h3) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { :m.to_proc.call(c, **{}) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **kw) } + assert_equal(h, :m.to_proc.call(c, **h)) + assert_equal(h2, :m.to_proc.call(c, **h2)) + assert_equal(h3, :m.to_proc.call(c, **h3)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, :m.to_proc.call(c, **{})) + assert_equal(kw, :m.to_proc.call(c, **kw)) + assert_equal(h, :m.to_proc.call(c, **h)) + assert_equal(h2, :m.to_proc.call(c, **h2)) + assert_equal(h3, :m.to_proc.call(c, **h3)) + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { :m.to_proc.call(c, **{}) } + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([kw, kw], :m.to_proc.call(c, **kw)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([h, kw], :m.to_proc.call(c, **h)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([h2, kw], :m.to_proc.call(c, **h2)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do + assert_equal([h3, kw], :m.to_proc.call(c, **h3)) + end + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg=1, args] + end + assert_equal([1, kw], :m.to_proc.call(c, **{})) + assert_equal([1, kw], :m.to_proc.call(c, **kw)) + assert_equal([1, h], :m.to_proc.call(c, **h)) + assert_equal([1, h2], :m.to_proc.call(c, **h2)) + assert_equal([1, h3], :m.to_proc.call(c, **h3)) + end + def test_method_missing_kwsplat kw = {} h = {'a'=>1} diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 7895c8a0eb..57300073d2 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2884,8 +2884,7 @@ vm_yield_with_cfunc(rb_execution_context_t *ec, static VALUE vm_yield_with_symbol(rb_execution_context_t *ec, VALUE symbol, int argc, const VALUE *argv, int kw_splat, VALUE block_handler) { - /* XXX: need to pass kw_splat? */ - return rb_sym_proc_call(SYM2ID(symbol), argc, argv, rb_vm_bh_to_procval(ec, block_handler)); + return rb_sym_proc_call(SYM2ID(symbol), argc, argv, kw_splat, rb_vm_bh_to_procval(ec, block_handler)); } static inline int