diff --git a/ext/-test-/funcall/funcall.c b/ext/-test-/funcall/funcall.c index 4e13c952e5..43521bf2e9 100644 --- a/ext/-test-/funcall/funcall.c +++ b/ext/-test-/funcall/funcall.c @@ -1,7 +1,5 @@ #include "ruby.h" -VALUE rb_funcall_passing_block(VALUE, ID, int, const VALUE*); - static VALUE with_funcall2(int argc, VALUE *argv, VALUE self) { @@ -14,6 +12,24 @@ with_funcall_passing_block(int argc, VALUE *argv, VALUE self) return rb_funcall_passing_block(self, rb_intern("target"), argc, argv); } +static VALUE +with_funcall_passing_block_kw(int argc, VALUE *argv, VALUE self) +{ + return rb_funcall_passing_block_kw(self, rb_intern("target"), argc-1, argv+1, FIX2INT(argv[0])); +} + +static VALUE +with_funcallv_public_kw(int argc, VALUE *argv, VALUE self) +{ + return rb_funcallv_public_kw(argv[0], SYM2ID(argv[1]), argc-3, argv+3, FIX2INT(argv[2])); +} + +static VALUE +with_yield_splat_kw(int argc, VALUE *argv, VALUE self) +{ + return rb_yield_splat_kw(argv[1], FIX2INT(argv[0])); +} + static VALUE extra_args_name(VALUE self) { @@ -34,10 +50,22 @@ Init_funcall(void) "with_funcall2", with_funcall2, -1); + rb_define_singleton_method(cRelay, + "with_funcall_passing_block_kw", + with_funcall_passing_block_kw, + -1); rb_define_singleton_method(cRelay, "with_funcall_passing_block", with_funcall_passing_block, -1); + rb_define_singleton_method(cRelay, + "with_funcallv_public_kw", + with_funcallv_public_kw, + -1); + rb_define_singleton_method(cRelay, + "with_yield_splat_kw", + with_yield_splat_kw, + -1); rb_define_singleton_method(cTestFuncall, "extra_args_name", extra_args_name, 0); diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index ca238dda64..2ec0cc84ae 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -1893,9 +1893,11 @@ VALUE rb_funcall(VALUE, ID, int, ...); VALUE rb_funcallv(VALUE, ID, int, const VALUE*); VALUE rb_funcallv_kw(VALUE, ID, int, const VALUE*, int); VALUE rb_funcallv_public(VALUE, ID, int, const VALUE*); +VALUE rb_funcallv_public_kw(VALUE, ID, int, const VALUE*, int); #define rb_funcall2 rb_funcallv #define rb_funcall3 rb_funcallv_public VALUE rb_funcall_passing_block(VALUE, ID, int, const VALUE*); +VALUE rb_funcall_passing_block_kw(VALUE, ID, int, const VALUE*, int); VALUE rb_funcall_with_block(VALUE, ID, int, const VALUE*, VALUE); VALUE rb_funcall_with_block_kw(VALUE, ID, int, const VALUE*, VALUE, int); int rb_scan_args(int, const VALUE*, const char*, ...); @@ -1972,6 +1974,7 @@ VALUE rb_yield_values(int n, ...); VALUE rb_yield_values2(int n, const VALUE *argv); VALUE rb_yield_values_kw(int n, const VALUE *argv, int kw_splat); VALUE rb_yield_splat(VALUE); +VALUE rb_yield_splat_kw(VALUE, int); VALUE rb_yield_block(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, callback_arg)); /* rb_block_call_func */ #define RB_NO_KEYWORDS 0 #define RB_PASS_KEYWORDS 1 diff --git a/test/-ext-/funcall/test_passing_block.rb b/test/-ext-/funcall/test_passing_block.rb index 5112bc0925..c2d7639b52 100644 --- a/test/-ext-/funcall/test_passing_block.rb +++ b/test/-ext-/funcall/test_passing_block.rb @@ -3,8 +3,8 @@ require 'test/unit' class TestFuncall < Test::Unit::TestCase module Relay - def self.target(*args, &block) - yield(*args) if block + def self.target(*args, **kw, &block) + yield(*args, **kw) if block end end require '-test-/funcall' @@ -20,4 +20,56 @@ class TestFuncall < Test::Unit::TestCase Relay.with_funcall_passing_block("feature#4504") {|arg| ok = arg || true} assert_equal("feature#4504", ok) end + + def test_with_funcall_passing_block_kw + block = ->(*a, **kw) { [a, kw] } + assert_equal([[1], {}], Relay.with_funcall_passing_block_kw(0, 1, &block)) + assert_equal([[], {a: 1}], Relay.with_funcall_passing_block_kw(1, a: 1, &block)) + assert_equal([[1], {a: 1}], Relay.with_funcall_passing_block_kw(1, 1, a: 1, &block)) + assert_equal([[{}], {}], Relay.with_funcall_passing_block_kw(2, {}, **{}, &block)) + assert_equal([[], {a: 1}], Relay.with_funcall_passing_block_kw(3, a: 1, &block)) + assert_equal([[{a: 1}], {}], Relay.with_funcall_passing_block_kw(3, {a: 1}, **{}, &block)) + assert_warn(/warning: The keyword argument is passed as the last hash parameter.*for method/m) do + assert_equal({}, Relay.with_funcall_passing_block_kw(3, **{}, &->(a){a})) + end + end + + def test_with_funcallv_public_kw + o = Object.new + def o.foo(*args, **kw) + [args, kw] + end + def o.bar(*args, **kw) + [args, kw] + end + o.singleton_class.send(:private, :bar) + def o.baz(arg) + arg + end + assert_equal([[1], {}], Relay.with_funcallv_public_kw(o, :foo, 0, 1)) + assert_equal([[], {a: 1}], Relay.with_funcallv_public_kw(o, :foo, 1, a: 1)) + assert_equal([[1], {a: 1}], Relay.with_funcallv_public_kw(o, :foo, 1, 1, a: 1)) + assert_equal([[{}], {}], Relay.with_funcallv_public_kw(o, :foo, 2, {}, **{})) + assert_equal([[], {a: 1}], Relay.with_funcallv_public_kw(o, :foo, 3, a: 1)) + assert_equal([[{a: 1}], {}], Relay.with_funcallv_public_kw(o, :foo, 3, {a: 1}, **{})) + assert_raise(NoMethodError) { Relay.with_funcallv_public_kw(o, :bar, 3, {a: 1}, **{}) } + assert_warn(/warning: The keyword argument is passed as the last hash parameter.*for `baz'/m) do + assert_equal({}, Relay.with_funcallv_public_kw(o, :baz, 3, **{})) + end + end + + def test_with_yield_splat_kw + block = ->(*a, **kw) { [a, kw] } + assert_equal([[1], {}], Relay.with_yield_splat_kw(0, [1], &block)) + assert_equal([[], {a: 1}], Relay.with_yield_splat_kw(1, [{a: 1}], &block)) + assert_equal([[1], {a: 1}], Relay.with_yield_splat_kw(1, [1, {a: 1}], &block)) + assert_equal([[{}], {}], Relay.with_yield_splat_kw(2, [{}], **{}, &block)) + assert_warn(/warning: The last argument is used as the keyword parameter.*for method/m) do + assert_equal([[], {a: 1}], Relay.with_yield_splat_kw(3, [{a: 1}], &block)) + end + assert_equal([[{a: 1}], {}], Relay.with_yield_splat_kw(3, [{a: 1}], **{}, &block)) + assert_warn(/warning: The keyword argument is passed as the last hash parameter/) do + assert_equal({}, Relay.with_yield_splat_kw(3, [], **{}, &->(a){a})) + end + end end diff --git a/vm_eval.c b/vm_eval.c index f6a1cc035e..c741773657 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -989,6 +989,15 @@ rb_funcallv_public(VALUE recv, ID mid, int argc, const VALUE *argv) return rb_call(recv, mid, argc, argv, CALL_PUBLIC); } +VALUE +rb_funcallv_public_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat) +{ + VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); + VALUE ret = rb_call(recv, mid, argc, argv, kw_splat ? CALL_PUBLIC_KW : CALL_PUBLIC); + rb_free_tmp_buffer(&v); + return ret; +} + /*! * Calls a method * \private @@ -1032,6 +1041,17 @@ rb_funcall_passing_block(VALUE recv, ID mid, int argc, const VALUE *argv) return rb_call(recv, mid, argc, argv, CALL_PUBLIC); } +VALUE +rb_funcall_passing_block_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat) +{ + VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); + VALUE ret; + PASS_PASSED_BLOCK_HANDLER(); + ret = rb_call(recv, mid, argc, argv, kw_splat ? CALL_PUBLIC_KW : CALL_PUBLIC); + rb_free_tmp_buffer(&v); + return ret; +} + VALUE rb_funcall_with_block(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE passed_procval) { @@ -1278,6 +1298,19 @@ rb_yield_splat(VALUE values) return v; } +VALUE +rb_yield_splat_kw(VALUE values, int kw_splat) +{ + VALUE tmp = rb_check_array_type(values); + VALUE v; + if (NIL_P(tmp)) { + rb_raise(rb_eArgError, "not an array"); + } + v = rb_yield_0_kw(RARRAY_LENINT(tmp), RARRAY_CONST_PTR(tmp), kw_splat); + RB_GC_GUARD(tmp); + return v; +} + VALUE rb_yield_force_blockarg(VALUE values) {