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.
This commit is contained in:
Jeremy Evans 2019-10-06 21:18:20 -07:00
Родитель 3374e1450c
Коммит 468184a996
2 изменённых файлов: 69 добавлений и 10 удалений

Просмотреть файл

@ -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, **{}))
@ -2766,6 +2776,10 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal([[1], h1], o.foo(:bar, 1, h1))
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
@ -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, **{}))
@ -2820,6 +2840,10 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal([[1], h1], o.foo(:dbar, 1, h1))
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
@ -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)

Просмотреть файл

@ -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 {