diff --git a/enumerator.c b/enumerator.c index 63322197d6..9eb530c91b 100644 --- a/enumerator.c +++ b/enumerator.c @@ -1320,7 +1320,7 @@ yielder_yield(VALUE obj, VALUE args) { struct yielder *ptr = yielder_ptr(obj); - return rb_proc_call(ptr->proc, args); + return rb_proc_call_kw(ptr->proc, args, RB_PASS_CALLED_KEYWORDS); } /* :nodoc: */ @@ -1357,7 +1357,7 @@ yielder_to_proc(VALUE obj) static VALUE yielder_yield_i(RB_BLOCK_CALL_FUNC_ARGLIST(obj, memo)) { - return rb_yield_values2(argc, argv); + return rb_yield_values_kw(argc, argv, RB_PASS_CALLED_KEYWORDS); } static VALUE diff --git a/ext/-test-/iter/yield.c b/ext/-test-/iter/yield.c index 3cd408a928..0f6f3e87eb 100644 --- a/ext/-test-/iter/yield.c +++ b/ext/-test-/iter/yield.c @@ -4,7 +4,7 @@ static VALUE yield_block(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); - return rb_block_call(self, rb_to_id(argv[0]), argc-1, argv+1, rb_yield_block, 0); + return rb_block_call_kw(self, rb_to_id(argv[0]), argc-1, argv+1, rb_yield_block, 0, RB_PASS_CALLED_KEYWORDS); } void diff --git a/hash.c b/hash.c index 8b84a14484..d00a09db5b 100644 --- a/hash.c +++ b/hash.c @@ -4471,7 +4471,7 @@ rb_hash_gt(VALUE hash, VALUE other) } static VALUE -hash_proc_call(VALUE key, VALUE hash, int argc, const VALUE *argv, VALUE passed_proc) +hash_proc_call(RB_BLOCK_CALL_FUNC_ARGLIST(key, hash)) { rb_check_arity(argc, 1, 1); return rb_hash_aref(hash, *argv); diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index 18901bbda4..ca238dda64 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -1970,8 +1970,9 @@ VALUE rb_each(VALUE); VALUE rb_yield(VALUE); 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_block(VALUE, VALUE, int, const VALUE *, VALUE); /* rb_block_call_func */ +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 #define RB_PASS_EMPTY_KEYWORDS 2 diff --git a/proc.c b/proc.c index 7c82cc63c8..91e9f98ddc 100644 --- a/proc.c +++ b/proc.c @@ -2895,7 +2895,7 @@ mlambda(VALUE method) static VALUE bmcall(RB_BLOCK_CALL_FUNC_ARGLIST(args, method)) { - return rb_method_call_with_block(argc, argv, method, blockarg); + return rb_method_call_with_block_kw(argc, argv, method, blockarg, RB_PASS_CALLED_KEYWORDS); } VALUE diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index da502ac10f..5992434c97 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -1,6 +1,7 @@ # frozen_string_literal: false require 'test/unit' require '-test-/rb_call_super_kw' +require '-test-/iter' class TestKeywordArguments < Test::Unit::TestCase def f1(str: "foo", num: 424242) @@ -918,6 +919,83 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h3], g.new(&f).each(a: 1, **h2)) end + def test_Enumerator_Yielder_yield_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + g = Enumerator::Generator + f = -> { true } + assert_equal(true, g.new{|y| y.yield(**{})}.each(&f)) + assert_equal(true, g.new{|y| y.yield(**kw)}.each(&f)) + assert_raise(ArgumentError) { g.new{|y| y.yield(**h)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h2)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h3)}.each(&f) } + + f = ->(a) { a } + assert_warn(/The keyword argument is passed as the last hash parameter/m) do + assert_equal(kw, g.new{|y| y.yield(**{})}.each(&f)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/m) do + assert_equal(kw, g.new{|y| y.yield(**kw)}.each(&f)) + end + assert_equal(h, g.new{|y| y.yield(**h)}.each(&f)) + assert_equal(h, g.new{|y| y.yield(a: 1)}.each(&f)) + assert_equal(h2, g.new{|y| y.yield(**h2)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(**h3)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + + f = ->(**x) { x } + assert_equal(kw, g.new{|y| y.yield(**{})}.each(&f)) + assert_equal(kw, g.new{|y| y.yield(**kw)}.each(&f)) + assert_equal(h, g.new{|y| y.yield(**h)}.each(&f)) + assert_equal(h, g.new{|y| y.yield(a: 1)}.each(&f)) + assert_equal(h2, g.new{|y| y.yield(**h2)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(**h3)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + assert_warn(/The last argument is used as the keyword parameter.*for method/m) do + assert_equal(h, g.new{|y| y.yield(h)}.each(&f)) + end + assert_raise(ArgumentError) { g.new{|y| y.yield(h2)}.each(&f) } + assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do + assert_raise(ArgumentError) { g.new{|y| y.yield(h3)}.each(&f) } + end + + f = ->(a, **x) { [a,x] } + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([{}, {}], g.new{|y| y.yield(**{})}.each(&f)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([{}, {}], g.new{|y| y.yield(**kw)}.each(&f)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, {}], g.new{|y| y.yield(**h)}.each(&f)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, {}], g.new{|y| y.yield(a: 1)}.each(&f)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h2, {}], g.new{|y| y.yield(**h2)}.each(&f)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, {}], g.new{|y| y.yield(**h3)}.each(&f)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, {}], g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + end + + f = ->(a=1, **x) { [a, x] } + assert_equal([1, kw], g.new{|y| y.yield(**{})}.each(&f)) + assert_equal([1, kw], g.new{|y| y.yield(**kw)}.each(&f)) + assert_equal([1, h], g.new{|y| y.yield(**h)}.each(&f)) + assert_equal([1, h], g.new{|y| y.yield(a: 1)}.each(&f)) + assert_equal([1, h2], g.new{|y| y.yield(**h2)}.each(&f)) + assert_equal([1, h3], g.new{|y| y.yield(**h3)}.each(&f)) + assert_equal([1, h3], g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + end + def test_Class_new_kwsplat_call kw = {} h = {:a=>1} @@ -3131,6 +3209,576 @@ class TestKeywordArguments < Test::Unit::TestCase end end + def test_instance_exec_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + m = ->(*args) { args } + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + assert_equal([h2], c.instance_exec(**h2, &m)) + assert_equal([h3], c.instance_exec(**h3, &m)) + assert_equal([h3], c.instance_exec(a: 1, **h2, &m)) + + m = ->() { nil } + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(c.instance_exec(**kw, &m)) + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + + m = ->(args) { args } + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal(kw, c.instance_exec(**{}, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal(kw, c.instance_exec(**kw, &m)) + end + assert_equal(kw, c.instance_exec(kw, **kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + + m = ->(**args) { args } + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + assert_warn(/The last argument is used as the keyword parameter/) do + assert_equal(h, c.instance_exec(h, &m)) + end + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } + end + + m = ->(arg, **args) { [arg, args] } + assert_warn(/The keyword argument is passed as the last hash parameter/) do + c.instance_exec(**{}, &m) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + c.instance_exec(**kw, &m) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, kw], c.instance_exec(**h, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, kw], c.instance_exec(a: 1, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h2, kw], c.instance_exec(**h2, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, kw], c.instance_exec(**h3, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, kw], c.instance_exec(a: 1, **h2, &m)) + end + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + + m = ->(arg=1, **args) { [arg, args] } + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + assert_equal([1, h2], c.instance_exec(**h2, &m)) + assert_equal([1, h3], c.instance_exec(**h3, &m)) + assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) + assert_warn(/The last argument is used as the keyword parameter/m) do + assert_equal([1, h], c.instance_exec(h, &m)) + end + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_warn(/The last argument is split into positional and keyword parameters/m) do + assert_equal([h2, h], c.instance_exec(h3, &m)) + end + end + + def test_instance_exec_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + m = c.method(:m) + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + assert_equal([h2], c.instance_exec(**h2, &m)) + assert_equal([h3], c.instance_exec(**h3, &m)) + assert_equal([h3], c.instance_exec(a: 1, **h2, &m)) + + c.singleton_class.remove_method(:m) + def c.m + end + m = c.method(:m) + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(c.instance_exec(**kw, &m)) + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + m = c.method(:m) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal(kw, c.instance_exec(**{}, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal(kw, c.instance_exec(**kw, &m)) + end + assert_equal(kw, c.instance_exec(kw, **kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + m = c.method(:m) + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + assert_warn(/The last argument is used as the keyword parameter/) do + assert_equal(h, c.instance_exec(h, &m)) + end + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } + end + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + m = c.method(:m) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + c.instance_exec(**{}, &m) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + c.instance_exec(**kw, &m) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, kw], c.instance_exec(**h, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, kw], c.instance_exec(a: 1, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h2, kw], c.instance_exec(**h2, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, kw], c.instance_exec(**h3, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, kw], c.instance_exec(a: 1, **h2, &m)) + end + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + m = c.method(:m) + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + assert_equal([1, h2], c.instance_exec(**h2, &m)) + assert_equal([1, h3], c.instance_exec(**h3, &m)) + assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) + assert_warn(/The last argument is used as the keyword parameter/m) do + assert_equal([1, h], c.instance_exec(h, &m)) + end + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([h2, h], c.instance_exec(h3, &m)) + end + end + + def test_instance_exec_define_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.define_singleton_method(:m) do |*args| + args + end + m = c.method(:m) + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + assert_equal([h2], c.instance_exec(**h2, &m)) + assert_equal([h3], c.instance_exec(**h3, &m)) + assert_equal([h3], c.instance_exec(a: 1, **h2, &m)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do + end + m = c.method(:m) + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(c.instance_exec(**kw, &m)) + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |args| + args + end + m = c.method(:m) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal(kw, c.instance_exec(**{}, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal(kw, c.instance_exec(**kw, &m)) + end + assert_equal(kw, c.instance_exec(kw, **kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |**args| + args + end + m = c.method(:m) + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + assert_warn(/The last argument is used as the keyword parameter/) do + assert_equal(h, c.instance_exec(h, &m)) + end + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } + end + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |arg, **args| + [arg, args] + end + m = c.method(:m) + assert_warn(/The keyword argument is passed as the last hash parameter/) do + c.instance_exec(**{}, &m) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + c.instance_exec(**kw, &m) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, kw], c.instance_exec(**h, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, kw], c.instance_exec(a: 1, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h2, kw], c.instance_exec(**h2, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, kw], c.instance_exec(**h3, &m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, kw], c.instance_exec(a: 1, **h2, &m)) + end + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |arg=1, **args| + [arg, args] + end + m = c.method(:m) + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + assert_equal([1, h2], c.instance_exec(**h2, &m)) + assert_equal([1, h3], c.instance_exec(**h3, &m)) + assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) + assert_warn(/The last argument is used as the keyword parameter/m) do + assert_equal([1, h], c.instance_exec(h, &m)) + end + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([h2, h], c.instance_exec(h3, &m)) + end + end + + def test_instance_exec_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([], c.instance_exec(c, **{}, &:m)) + assert_equal([], c.instance_exec(c, **kw, &:m)) + assert_equal([h], c.instance_exec(c, **h, &:m)) + assert_equal([h], c.instance_exec(c, a: 1, &:m)) + assert_equal([h2], c.instance_exec(c, **h2, &:m)) + assert_equal([h3], c.instance_exec(c, **h3, &:m)) + assert_equal([h3], c.instance_exec(c, a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:m) + def c.m + end + assert_nil(c.instance_exec(c, **{}, &:m)) + assert_nil(c.instance_exec(c, **kw, &:m)) + assert_raise(ArgumentError) { c.instance_exec(c, **h, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, a: 1, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h2, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h3, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, a: 1, **h2, &:m) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal(kw, c.instance_exec(c, **{}, &:m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal(kw, c.instance_exec(c, **kw, &:m)) + end + assert_equal(kw, c.instance_exec(c, kw, **kw, &:m)) + assert_equal(h, c.instance_exec(c, **h, &:m)) + assert_equal(h, c.instance_exec(c, a: 1, &:m)) + assert_equal(h2, c.instance_exec(c, **h2, &:m)) + assert_equal(h3, c.instance_exec(c, **h3, &:m)) + assert_equal(h3, c.instance_exec(c, a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.instance_exec(c, **{}, &:m)) + assert_equal(kw, c.instance_exec(c, **kw, &:m)) + assert_equal(h, c.instance_exec(c, **h, &:m)) + assert_equal(h, c.instance_exec(c, a: 1, &:m)) + assert_equal(h2, c.instance_exec(c, **h2, &:m)) + assert_equal(h3, c.instance_exec(c, **h3, &:m)) + assert_equal(h3, c.instance_exec(c, a: 1, **h2, &:m)) + assert_warn(/The last argument is used as the keyword parameter/) do + assert_equal(h, c.instance_exec(c, h, &:m)) + end + assert_raise(ArgumentError) { c.instance_exec(c, h2, &:m) } + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_raise(ArgumentError) { c.instance_exec(c, h3, &:m) } + 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/) do + c.instance_exec(c, **{}, &:m) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + c.instance_exec(c, **kw, &:m) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, kw], c.instance_exec(c, **h, &:m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h, kw], c.instance_exec(c, a: 1, &:m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h2, kw], c.instance_exec(c, **h2, &:m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, kw], c.instance_exec(c, **h3, &:m)) + end + assert_warn(/The keyword argument is passed as the last hash parameter/) do + assert_equal([h3, kw], c.instance_exec(c, a: 1, **h2, &:m)) + end + assert_equal([h, kw], c.instance_exec(c, h, &:m)) + assert_equal([h2, kw], c.instance_exec(c, h2, &:m)) + assert_equal([h3, kw], c.instance_exec(c, h3, &:m)) + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.instance_exec(c, **{}, &:m)) + assert_equal([1, kw], c.instance_exec(c, **kw, &:m)) + assert_equal([1, h], c.instance_exec(c, **h, &:m)) + assert_equal([1, h], c.instance_exec(c, a: 1, &:m)) + assert_equal([1, h2], c.instance_exec(c, **h2, &:m)) + assert_equal([1, h3], c.instance_exec(c, **h3, &:m)) + assert_equal([1, h3], c.instance_exec(c, a: 1, **h2, &:m)) + assert_warn(/The last argument is used as the keyword parameter/m) do + assert_equal([1, h], c.instance_exec(c, h, &:m)) + end + assert_equal([h2, kw], c.instance_exec(c, h2, &:m)) + assert_warn(/The last argument is split into positional and keyword parameters/) do + assert_equal([h2, h], c.instance_exec(c, h3, &:m)) + end + end + + def test_rb_yield_block_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.extend Bug::Iter::Yield + class << c + alias m yield_block + end + def c.c(*args) + args + end + assert_equal([], c.m(:c, **{})) + assert_equal([], c.m(:c, **kw)) + assert_equal([h], c.m(:c, **h)) + assert_equal([h], c.m(:c, a: 1)) + assert_equal([h2], c.m(:c, **h2)) + assert_equal([h3], c.m(:c, **h3)) + assert_equal([h3], c.m(:c, a: 1, **h2)) + + c.singleton_class.remove_method(:c) + def c.c; end + assert_nil(c.m(:c, **{})) + assert_nil(c.m(:c, **kw)) + assert_raise(ArgumentError) { c.m(:c, **h) } + assert_raise(ArgumentError) { c.m(:c, a: 1) } + assert_raise(ArgumentError) { c.m(:c, **h2) } + assert_raise(ArgumentError) { c.m(:c, **h3) } + assert_raise(ArgumentError) { c.m(:c, a: 1, **h2) } + + c.singleton_class.remove_method(:c) + def c.c(args) + args + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal(kw, c.m(:c, **{})) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal(kw, c.m(:c, **kw)) + end + assert_equal(kw, c.m(:c, kw, **kw)) + assert_equal(h, c.m(:c, **h)) + assert_equal(h, c.m(:c, a: 1)) + assert_equal(h2, c.m(:c, **h2)) + assert_equal(h3, c.m(:c, **h3)) + assert_equal(h3, c.m(:c, a: 1, **h2)) + + c.singleton_class.remove_method(:c) + def c.c(**args) + [args, yield(**args)] + end + m = ->(**args){ args } + assert_equal([kw, kw], c.m(:c, **{}, &m)) + assert_equal([kw, kw], c.m(:c, **kw, &m)) + assert_equal([h, h], c.m(:c, **h, &m)) + assert_equal([h, h], c.m(:c, a: 1, &m)) + assert_equal([h2, h2], c.m(:c, **h2, &m)) + assert_equal([h3, h3], c.m(:c, **h3, &m)) + assert_equal([h3, h3], c.m(:c, a: 1, **h2, &m)) + assert_warn(/The last argument is used as the keyword parameter.*for `c'/m) do + assert_equal([h, h], c.m(:c, h, &m)) + end + assert_raise(ArgumentError) { c.m(:c, h2, &m) } + assert_warn(/The last argument is split into positional and keyword parameters.*for `c'/m) do + assert_raise(ArgumentError) { c.m(:c, h3, &m) } + end + + c.singleton_class.remove_method(:c) + def c.c(arg, **args) + [arg, args] + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal([kw, kw], c.m(:c, **{})) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal([kw, kw], c.m(:c, **kw)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal([h, kw], c.m(:c, **h)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal([h, kw], c.m(:c, a: 1)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal([h2, kw], c.m(:c, **h2)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal([h3, kw], c.m(:c, **h3)) + end + assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do + assert_equal([h3, kw], c.m(:c, a: 1, **h2)) + end + assert_equal([h, kw], c.m(:c, h)) + assert_equal([h2, kw], c.m(:c, h2)) + assert_equal([h3, kw], c.m(:c, h3)) + + c.singleton_class.remove_method(:c) + def c.c(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.m(:c, **{})) + assert_equal([1, kw], c.m(:c, **kw)) + assert_equal([1, h], c.m(:c, **h)) + assert_equal([1, h], c.m(:c, a: 1)) + assert_equal([1, h2], c.m(:c, **h2)) + assert_equal([1, h3], c.m(:c, **h3)) + assert_equal([1, h3], c.m(:c, a: 1, **h2)) + assert_warn(/The last argument is used as the keyword parameter.*for `c'/m) do + assert_equal([1, h], c.m(:c, h)) + end + assert_equal([h2, kw], c.m(:c, h2)) + assert_warn(/The last argument is split into positional and keyword parameters.*for `c'/m) do + assert_equal([h2, h], c.m(:c, h3)) + end + end + def p1 Proc.new do |str: "foo", num: 424242| [str, num] diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index b7d644345d..467bbf5925 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1552,9 +1552,7 @@ class TestProcKeywords < Test::Unit::TestCase assert_warn(/The last argument is used as the keyword parameter.*for method/m) do assert_equal(1, (f << g).call(a: 3)[:a]) end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(2, (f >> g).call(a: 3)[:a]) - end + assert_equal(2, (f >> g).call(a: 3)[:a]) assert_warn(/The last argument is used as the keyword parameter.*for method/m) do assert_equal(1, (f << g).call({a: 3})[:a]) end @@ -1574,9 +1572,7 @@ class TestProcKeywords < Test::Unit::TestCase assert_warn(/The keyword argument is passed as the last hash parameter.*The last argument is used as the keyword parameter.*for method/m) do assert_equal(1, (f << g).call(**{})[:a]) end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(2, (f >> g).call(**{})[:a]) - end + assert_equal(2, (f >> g).call(**{})[:a]) end def test_compose_keywords_non_proc @@ -1632,9 +1628,7 @@ class TestProcKeywords < Test::Unit::TestCase assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do assert_equal(2, (g << f).call(a: 3)[:a]) end - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(1, (g >> f).call(a: 3)[:a]) - end + assert_equal(1, (g >> f).call(a: 3)[:a]) assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do assert_equal(2, (g << f).call({a: 3})[:a]) end diff --git a/vm.c b/vm.c index f26ab73dfe..a442e9f553 100644 --- a/vm.c +++ b/vm.c @@ -1164,26 +1164,26 @@ check_block_handler(rb_execution_context_t *ec) } static VALUE -vm_yield_with_cref(rb_execution_context_t *ec, int argc, const VALUE *argv, const rb_cref_t *cref, int is_lambda) +vm_yield_with_cref(rb_execution_context_t *ec, int argc, const VALUE *argv, int kw_splat, const rb_cref_t *cref, int is_lambda) { return invoke_block_from_c_bh(ec, check_block_handler(ec), - argc, argv, VM_NO_KEYWORDS, VM_BLOCK_HANDLER_NONE, + argc, argv, kw_splat, VM_BLOCK_HANDLER_NONE, cref, is_lambda, FALSE); } static VALUE -vm_yield(rb_execution_context_t *ec, int argc, const VALUE *argv) +vm_yield(rb_execution_context_t *ec, int argc, const VALUE *argv, int kw_splat) { return invoke_block_from_c_bh(ec, check_block_handler(ec), - argc, argv, VM_NO_KEYWORDS, VM_BLOCK_HANDLER_NONE, + argc, argv, kw_splat, VM_BLOCK_HANDLER_NONE, NULL, FALSE, FALSE); } static VALUE -vm_yield_with_block(rb_execution_context_t *ec, int argc, const VALUE *argv, VALUE block_handler) +vm_yield_with_block(rb_execution_context_t *ec, int argc, const VALUE *argv, VALUE block_handler, int kw_splat) { return invoke_block_from_c_bh(ec, check_block_handler(ec), - argc, argv, VM_NO_KEYWORDS, block_handler, + argc, argv, kw_splat, block_handler, NULL, FALSE, FALSE); } @@ -1213,6 +1213,10 @@ invoke_block_from_c_proc(rb_execution_context_t *ec, const rb_proc_t *proc, case block_type_iseq: return invoke_iseq_block_from_c(ec, &block->as.captured, self, argc, argv, kw_splat, passed_block_handler, NULL, is_lambda, me); case block_type_ifunc: + if (kw_splat == 1 && RHASH_EMPTY_P(argv[argc-1])) { + argc--; + kw_splat = 2; + } return vm_yield_with_cfunc(ec, &block->as.captured, self, argc, argv, kw_splat, passed_block_handler, me); case block_type_symbol: return vm_yield_with_symbol(ec, block->as.symbol, argc, argv, kw_splat, passed_block_handler); diff --git a/vm_eval.c b/vm_eval.c index 0fd4f573ef..b97e3527b6 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -16,9 +16,9 @@ struct local_var_list { }; static inline VALUE method_missing(VALUE obj, ID id, int argc, const VALUE *argv, enum method_missing_reason call_status, int kw_splat); -static inline VALUE vm_yield_with_cref(rb_execution_context_t *ec, int argc, const VALUE *argv, const rb_cref_t *cref, int is_lambda); -static inline VALUE vm_yield(rb_execution_context_t *ec, int argc, const VALUE *argv); -static inline VALUE vm_yield_with_block(rb_execution_context_t *ec, int argc, const VALUE *argv, VALUE block_handler); +static inline VALUE vm_yield_with_cref(rb_execution_context_t *ec, int argc, const VALUE *argv, int kw_splat, const rb_cref_t *cref, int is_lambda); +static inline VALUE vm_yield(rb_execution_context_t *ec, int argc, const VALUE *argv, int kw_splat); +static inline VALUE vm_yield_with_block(rb_execution_context_t *ec, int argc, const VALUE *argv, VALUE block_handler, int kw_splat); static inline VALUE vm_yield_force_blockarg(rb_execution_context_t *ec, VALUE args); VALUE vm_exec(rb_execution_context_t *ec, int mjit_enable_p); static void vm_set_eval_stack(rb_execution_context_t * th, const rb_iseq_t *iseq, const rb_cref_t *cref, const struct rb_block *base_block); @@ -1198,10 +1198,19 @@ rb_f_public_send(int argc, VALUE *argv, VALUE recv) /* yield */ +static inline VALUE +rb_yield_0_kw(int argc, const VALUE * argv, int kw_splat) +{ + VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); + VALUE ret = vm_yield(GET_EC(), argc, argv, kw_splat); + rb_free_tmp_buffer(&v); + return ret; +} + static inline VALUE rb_yield_0(int argc, const VALUE * argv) { - return vm_yield(GET_EC(), argc, argv); + return vm_yield(GET_EC(), argc, argv, RB_NO_KEYWORDS); } VALUE @@ -1250,6 +1259,12 @@ rb_yield_values2(int argc, const VALUE *argv) return rb_yield_0(argc, argv); } +VALUE +rb_yield_values_kw(int argc, const VALUE *argv, int kw_splat) +{ + return rb_yield_0_kw(argc, argv, kw_splat); +} + VALUE rb_yield_splat(VALUE values) { @@ -1270,10 +1285,15 @@ rb_yield_force_blockarg(VALUE values) } VALUE -rb_yield_block(VALUE val, VALUE arg, int argc, const VALUE *argv, VALUE blockarg) +rb_yield_block(RB_BLOCK_CALL_FUNC_ARGLIST(val, arg)) { - return vm_yield_with_block(GET_EC(), argc, argv, - NIL_P(blockarg) ? VM_BLOCK_HANDLER_NONE : blockarg); + int kw_splat = RB_PASS_CALLED_KEYWORDS; + VALUE v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); + VALUE ret = vm_yield_with_block(GET_EC(), argc, argv, + NIL_P(blockarg) ? VM_BLOCK_HANDLER_NONE : blockarg, + kw_splat); + rb_free_tmp_buffer(&v); + return ret; } static VALUE @@ -1772,6 +1792,10 @@ yield_under(VALUE under, VALUE self, int argc, const VALUE *argv) const VALUE *ep = NULL; rb_cref_t *cref; int is_lambda = FALSE; + VALUE v = 0, ret; + int kw_splat = RB_PASS_CALLED_KEYWORDS; + + v = rb_adjust_argv_kw_splat(&argc, &argv, &kw_splat); if (block_handler != VM_BLOCK_HANDLER_NONE) { again: @@ -1791,8 +1815,10 @@ yield_under(VALUE under, VALUE self, int argc, const VALUE *argv) block_handler = vm_proc_to_block_handler(VM_BH_TO_PROC(block_handler)); goto again; case block_handler_type_symbol: - return rb_sym_proc_call(SYM2ID(VM_BH_TO_SYMBOL(block_handler)), - argc, argv, VM_NO_KEYWORDS, VM_BLOCK_HANDLER_NONE); + ret = rb_sym_proc_call(SYM2ID(VM_BH_TO_SYMBOL(block_handler)), + argc, argv, kw_splat, VM_BLOCK_HANDLER_NONE); + rb_free_tmp_buffer(&v); + return ret; } new_captured.self = self; @@ -1802,7 +1828,9 @@ yield_under(VALUE under, VALUE self, int argc, const VALUE *argv) } cref = vm_cref_push(ec, under, ep, TRUE); - return vm_yield_with_cref(ec, argc, argv, cref, is_lambda); + ret = vm_yield_with_cref(ec, argc, argv, kw_splat, cref, is_lambda); + rb_free_tmp_buffer(&v); + return ret; } VALUE @@ -1823,7 +1851,7 @@ rb_yield_refine_block(VALUE refinement, VALUE refinements) CREF_REFINEMENTS_SET(cref, refinements); VM_FORCE_WRITE_SPECIAL_CONST(&VM_CF_LEP(ec->cfp)[VM_ENV_DATA_INDEX_SPECVAL], new_block_handler); new_captured.self = refinement; - return vm_yield_with_cref(ec, 0, NULL, cref, FALSE); + return vm_yield_with_cref(ec, 0, NULL, RB_NO_KEYWORDS, cref, FALSE); } }