ruby-changes:58151
From: Jeremy <ko1@a...>
Date: Mon, 7 Oct 2019 23:37:34 +0900 (JST)
Subject: [ruby-changes:58151] 468184a996 (master): Allow ruby2_keywords to be used with bmethods
https://git.ruby-lang.org/ruby.git/commit/?id=468184a996 From 468184a996c99d1f94f94d7468f65e386cf13564 Mon Sep 17 00:00:00 2001 From: Jeremy Evans <code@j...> Date: Sun, 6 Oct 2019 21:18:20 -0700 Subject: Allow ruby2_keywords to be used with bmethods There are libraries that use define_method with argument splats where they would like to pass keywords through the method. To more easily allow such libraries to use ruby2_keywords to handle backwards compatibility, it is necessary for ruby2_keywords to support bmethods. diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 1a445cd..e5c6da4 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -2637,6 +2637,10 @@ class TestKeywordArguments < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2637 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2747 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2756 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2765 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2777 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2811 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2820 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2829 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2841 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2907 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 554d209..6465798 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1805,15 +1805,43 @@ rb_mod_ruby2_keywords(int argc, VALUE *argv, VALUE module) https://github.com/ruby/ruby/blob/trunk/vm_method.c#L1805 } 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 { -- cgit v0.10.2 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/