diff options
-rw-r--r-- | test/ruby/test_keyword.rb | 33 | ||||
-rw-r--r-- | vm_method.c | 46 |
2 files changed, 69 insertions, 10 deletions
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 1a445cdf50..e5c6da44c2 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -2637,6 +2637,10 @@ class TestKeywordArguments < Test::Unit::TestCase send(meth, *args) end + ruby2_keywords(define_method(:bfoo) do |meth, *args| + send(meth, *args) + end) + ruby2_keywords def foo_bar(*args) bar(*args) end @@ -2743,6 +2747,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[1], h1], o.foo(:bar, 1, :a=>1)) assert_equal([1, h1], o.foo(:baz, 1, :a=>1)) + assert_equal([[1], h1], o.bfoo(:bar, 1, :a=>1)) + assert_equal([1, h1], o.bfoo(:baz, 1, :a=>1)) assert_equal([[1], h1], o.store_foo(:bar, 1, :a=>1)) assert_equal([1, h1], o.store_foo(:baz, 1, :a=>1)) assert_equal([[1], h1], o.foo_bar(1, :a=>1)) @@ -2750,6 +2756,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[1], h1], o.foo(:bar, 1, **h1)) assert_equal([1, h1], o.foo(:baz, 1, **h1)) + assert_equal([[1], h1], o.bfoo(:bar, 1, **h1)) + assert_equal([1, h1], o.bfoo(:baz, 1, **h1)) assert_equal([[1], h1], o.store_foo(:bar, 1, **h1)) assert_equal([1, h1], o.store_foo(:baz, 1, **h1)) assert_equal([[1], h1], o.foo_bar(1, **h1)) @@ -2757,6 +2765,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[h1], {}], o.foo(:bar, h1, **{})) assert_equal([h1], o.foo(:baz, h1, **{})) + assert_equal([[h1], {}], o.bfoo(:bar, h1, **{})) + assert_equal([h1], o.bfoo(:baz, h1, **{})) assert_equal([[h1], {}], o.store_foo(:bar, h1, **{})) assert_equal([h1], o.store_foo(:baz, h1, **{})) assert_equal([[h1], {}], o.foo_bar(h1, **{})) @@ -2767,6 +2777,10 @@ class TestKeywordArguments < Test::Unit::TestCase end assert_equal([1, h1], o.foo(:baz, 1, h1)) assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do + assert_equal([[1], h1], o.bfoo(:bar, 1, h1)) + end + assert_equal([1, h1], o.bfoo(:baz, 1, h1)) + assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do assert_equal([[1], h1], o.store_foo(:bar, 1, h1)) end assert_equal([1, h1], o.store_foo(:baz, 1, h1)) @@ -2797,6 +2811,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[1], h1], o.foo(:dbar, 1, :a=>1)) assert_equal([1, h1], o.foo(:dbaz, 1, :a=>1)) + assert_equal([[1], h1], o.bfoo(:dbar, 1, :a=>1)) + assert_equal([1, h1], o.bfoo(:dbaz, 1, :a=>1)) assert_equal([[1], h1], o.store_foo(:dbar, 1, :a=>1)) assert_equal([1, h1], o.store_foo(:dbaz, 1, :a=>1)) assert_equal([[1], h1], o.foo_dbar(1, :a=>1)) @@ -2804,6 +2820,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[1], h1], o.foo(:dbar, 1, **h1)) assert_equal([1, h1], o.foo(:dbaz, 1, **h1)) + assert_equal([[1], h1], o.bfoo(:dbar, 1, **h1)) + assert_equal([1, h1], o.bfoo(:dbaz, 1, **h1)) assert_equal([[1], h1], o.store_foo(:dbar, 1, **h1)) assert_equal([1, h1], o.store_foo(:dbaz, 1, **h1)) assert_equal([[1], h1], o.foo_dbar(1, **h1)) @@ -2811,6 +2829,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[h1], {}], o.foo(:dbar, h1, **{})) assert_equal([h1], o.foo(:dbaz, h1, **{})) + assert_equal([[h1], {}], o.bfoo(:dbar, h1, **{})) + assert_equal([h1], o.bfoo(:dbaz, h1, **{})) assert_equal([[h1], {}], o.store_foo(:dbar, h1, **{})) assert_equal([h1], o.store_foo(:dbaz, h1, **{})) assert_equal([[h1], {}], o.foo_dbar(h1, **{})) @@ -2821,6 +2841,10 @@ class TestKeywordArguments < Test::Unit::TestCase end assert_equal([1, h1], o.foo(:dbaz, 1, h1)) assert_warn(/The last argument is used as the keyword parameter.* for method/m) do + assert_equal([[1], h1], o.bfoo(:dbar, 1, h1)) + end + assert_equal([1, h1], o.bfoo(:dbaz, 1, h1)) + assert_warn(/The last argument is used as the keyword parameter.* for method/m) do assert_equal([[1], h1], o.store_foo(:dbar, 1, h1)) end assert_equal([1, h1], o.store_foo(:dbaz, 1, h1)) @@ -2883,10 +2907,17 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h1], o.baz(1, h1)) assert_equal([h1], o.baz(h1, **{})) - assert_warn(/Skipping set of ruby2_keywords flag for bar \(method not defined in Ruby, method accepts keywords, or method does not accept argument splat\)/) do + assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or method does not accept argument splat\)/) do assert_nil(c.send(:ruby2_keywords, :bar)) end + o = Object.new + class << o + alias bar p + end + assert_warn(/Skipping set of ruby2_keywords flag for bar \(method not defined in Ruby\)/) do + assert_nil(o.singleton_class.send(:ruby2_keywords, :bar)) + end sc = Class.new(c) assert_warn(/Skipping set of ruby2_keywords flag for bar \(can only set in method defining module\)/) do sc.send(:ruby2_keywords, :bar) diff --git a/vm_method.c b/vm_method.c index 554d209110..6465798d30 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1805,15 +1805,43 @@ rb_mod_ruby2_keywords(int argc, VALUE *argv, VALUE module) } if (module == defined_class || origin_class == defined_class) { - if (me->def->type == VM_METHOD_TYPE_ISEQ && - me->def->body.iseq.iseqptr->body->param.flags.has_rest && - !me->def->body.iseq.iseqptr->body->param.flags.has_kw && - !me->def->body.iseq.iseqptr->body->param.flags.has_kwrest) { - me->def->body.iseq.iseqptr->body->param.flags.ruby2_keywords = 1; - rb_clear_method_cache_by_class(module); - } - else { - rb_warn("Skipping set of ruby2_keywords flag for %s (method not defined in Ruby, method accepts keywords, or method does not accept argument splat)", rb_id2name(name)); + switch (me->def->type) { + case VM_METHOD_TYPE_ISEQ: + if (me->def->body.iseq.iseqptr->body->param.flags.has_rest && + !me->def->body.iseq.iseqptr->body->param.flags.has_kw && + !me->def->body.iseq.iseqptr->body->param.flags.has_kwrest) { + me->def->body.iseq.iseqptr->body->param.flags.ruby2_keywords = 1; + rb_clear_method_cache_by_class(module); + } + else { + rb_warn("Skipping set of ruby2_keywords flag for %s (method accepts keywords or method does not accept argument splat)", rb_id2name(name)); + } + break; + case VM_METHOD_TYPE_BMETHOD: { + VALUE procval = me->def->body.bmethod.proc; + if (vm_block_handler_type(procval) == block_handler_type_proc) { + procval = vm_proc_to_block_handler(VM_BH_TO_PROC(procval)); + } + + if (vm_block_handler_type(procval) == block_handler_type_iseq) { + const struct rb_captured_block *captured = VM_BH_TO_ISEQ_BLOCK(procval); + const rb_iseq_t *iseq = rb_iseq_check(captured->code.iseq); + if (iseq->body->param.flags.has_rest && + !iseq->body->param.flags.has_kw && + !iseq->body->param.flags.has_kwrest) { + iseq->body->param.flags.ruby2_keywords = 1; + rb_clear_method_cache_by_class(module); + } + else { + rb_warn("Skipping set of ruby2_keywords flag for %s (method accepts keywords or method does not accept argument splat)", rb_id2name(name)); + } + return Qnil; + } + } + /* fallthrough */ + default: + rb_warn("Skipping set of ruby2_keywords flag for %s (method not defined in Ruby)", rb_id2name(name)); + break; } } else { |