summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2019-10-06 21:18:20 -0700
committerJeremy Evans <code@jeremyevans.net>2019-10-07 07:37:12 -0700
commit468184a996c99d1f94f94d7468f65e386cf13564 (patch)
tree09864e79f6a21686bb2548048118d76b5bb163ba
parent3374e1450c18c33420f616f41ad7f884fdb4df21 (diff)
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.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/2532
-rw-r--r--test/ruby/test_keyword.rb33
-rw-r--r--vm_method.c46
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 {