зеркало из https://github.com/github/ruby.git
Add Module#ruby2_keywords for passing keywords through regular argument splats
This approach uses a flag bit on the final hash object in the regular splat, as opposed to a previous approach that used a VM frame flag. The hash flag approach is less invasive, and handles some cases that the VM frame flag approach does not, such as saving the argument splat array and splatting it later: ruby2_keywords def foo(*args) @args = args bar end def bar baz(*@args) end def baz(*args, **kw) [args, kw] end foo(a:1) #=> [[], {a: 1}] foo({a: 1}, **{}) #=> [[{a: 1}], {}] foo({a: 1}) #=> 2.7: [[], {a: 1}] # and warning foo({a: 1}) #=> 3.0: [[{a: 1}], {}] It doesn't handle some cases that the VM frame flag handles, such as when the final hash object is replaced using Hash#merge, but those cases are probably less common and are unlikely to properly support keyword argument separation. Use ruby2_keywords to handle argument delegation in the delegate library.
This commit is contained in:
Родитель
80b5a0ff2a
Коммит
3b302ea8c9
|
@ -815,6 +815,7 @@ struct RComplex {
|
|||
#define RCOMPLEX_SET_IMAG(cmp, i) RB_OBJ_WRITE((cmp), &((struct RComplex *)(cmp))->imag,(i))
|
||||
|
||||
enum ruby_rhash_flags {
|
||||
RHASH_PASS_AS_KEYWORDS = FL_USER1, /* FL 1 */
|
||||
RHASH_PROC_DEFAULT = FL_USER2, /* FL 2 */
|
||||
RHASH_ST_TABLE_FLAG = FL_USER3, /* FL 3 */
|
||||
#define RHASH_AR_TABLE_MAX_SIZE SIZEOF_VALUE
|
||||
|
|
|
@ -75,7 +75,7 @@ class Delegator < BasicObject
|
|||
#
|
||||
# Handles the magic of delegation through \_\_getobj\_\_.
|
||||
#
|
||||
def method_missing(m, *args, &block)
|
||||
ruby2_keywords def method_missing(m, *args, &block)
|
||||
r = true
|
||||
target = self.__getobj__ {r = false}
|
||||
|
||||
|
|
|
@ -2306,6 +2306,278 @@ class TestKeywordArguments < Test::Unit::TestCase
|
|||
assert_raise(ArgumentError) { m.call(42, a: 1, **h2) }
|
||||
end
|
||||
|
||||
def test_ruby2_keywords
|
||||
c = Class.new do
|
||||
ruby2_keywords def foo(meth, *args)
|
||||
send(meth, *args)
|
||||
end
|
||||
|
||||
ruby2_keywords def foo_bar(*args)
|
||||
bar(*args)
|
||||
end
|
||||
|
||||
ruby2_keywords def foo_baz(*args)
|
||||
baz(*args)
|
||||
end
|
||||
|
||||
ruby2_keywords def foo_mod(meth, *args)
|
||||
args << 1
|
||||
send(meth, *args)
|
||||
end
|
||||
|
||||
ruby2_keywords def foo_bar_mod(*args)
|
||||
args << 1
|
||||
bar(*args)
|
||||
end
|
||||
|
||||
ruby2_keywords def foo_baz_mod(*args)
|
||||
args << 1
|
||||
baz(*args)
|
||||
end
|
||||
|
||||
def bar(*args, **kw)
|
||||
[args, kw]
|
||||
end
|
||||
|
||||
def baz(*args)
|
||||
args
|
||||
end
|
||||
|
||||
ruby2_keywords def foo_dbar(*args)
|
||||
dbar(*args)
|
||||
end
|
||||
|
||||
ruby2_keywords def foo_dbaz(*args)
|
||||
dbaz(*args)
|
||||
end
|
||||
|
||||
define_method(:dbar) do |*args, **kw|
|
||||
[args, kw]
|
||||
end
|
||||
|
||||
define_method(:dbaz) do |*args|
|
||||
args
|
||||
end
|
||||
|
||||
ruby2_keywords def block(*args)
|
||||
->(*args, **kw){[args, kw]}.(*args)
|
||||
end
|
||||
|
||||
ruby2_keywords def cfunc(*args)
|
||||
self.class.new(*args).init_args
|
||||
end
|
||||
|
||||
ruby2_keywords def store_foo(meth, *args)
|
||||
@stored_args = args
|
||||
use(meth)
|
||||
end
|
||||
def use(meth)
|
||||
send(meth, *@stored_args)
|
||||
end
|
||||
|
||||
attr_reader :init_args
|
||||
def initialize(*args, **kw)
|
||||
@init_args = [args, kw]
|
||||
end
|
||||
end
|
||||
|
||||
mmkw = Class.new do
|
||||
def method_missing(*args, **kw)
|
||||
[args, kw]
|
||||
end
|
||||
end
|
||||
|
||||
mmnokw = Class.new do
|
||||
def method_missing(*args)
|
||||
args
|
||||
end
|
||||
end
|
||||
|
||||
implicit_super = Class.new(c) do
|
||||
ruby2_keywords def bar(*args)
|
||||
super
|
||||
end
|
||||
|
||||
ruby2_keywords def baz(*args)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
explicit_super = Class.new(c) do
|
||||
ruby2_keywords def bar(*args)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
ruby2_keywords def baz(*args)
|
||||
super(*args)
|
||||
end
|
||||
end
|
||||
|
||||
h1 = {a: 1}
|
||||
o = c.new
|
||||
|
||||
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.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))
|
||||
assert_equal([1, h1], o.foo_baz(1, :a=>1))
|
||||
|
||||
assert_equal([[1], h1], o.foo(:bar, 1, **h1))
|
||||
assert_equal([1, h1], o.foo(: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))
|
||||
assert_equal([1, h1], o.foo_baz(1, **h1))
|
||||
|
||||
assert_equal([[h1], {}], o.foo(:bar, h1, **{}))
|
||||
assert_equal([h1], o.foo(: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, **{}))
|
||||
assert_equal([h1], o.foo_baz(h1, **{}))
|
||||
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
|
||||
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.store_foo(:bar, 1, h1))
|
||||
end
|
||||
assert_equal([1, h1], o.store_foo(:baz, 1, h1))
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
|
||||
assert_equal([[1], h1], o.foo_bar(1, h1))
|
||||
end
|
||||
assert_equal([1, h1], o.foo_baz(1, h1))
|
||||
|
||||
assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, :a=>1))
|
||||
assert_equal([1, h1, 1], o.foo_mod(:baz, 1, :a=>1))
|
||||
assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, :a=>1))
|
||||
assert_equal([1, h1, 1], o.foo_baz_mod(1, :a=>1))
|
||||
|
||||
assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, **h1))
|
||||
assert_equal([1, h1, 1], o.foo_mod(:baz, 1, **h1))
|
||||
assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, **h1))
|
||||
assert_equal([1, h1, 1], o.foo_baz_mod(1, **h1))
|
||||
|
||||
assert_equal([[h1, {}, 1], {}], o.foo_mod(:bar, h1, **{}))
|
||||
assert_equal([h1, {}, 1], o.foo_mod(:baz, h1, **{}))
|
||||
assert_equal([[h1, {}, 1], {}], o.foo_bar_mod(h1, **{}))
|
||||
assert_equal([h1, {}, 1], o.foo_baz_mod(h1, **{}))
|
||||
|
||||
assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, h1))
|
||||
assert_equal([1, h1, 1], o.foo_mod(:baz, 1, h1))
|
||||
assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, h1))
|
||||
assert_equal([1, h1, 1], o.foo_baz_mod(1, h1))
|
||||
|
||||
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.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))
|
||||
assert_equal([1, h1], o.foo_dbaz(1, :a=>1))
|
||||
|
||||
assert_equal([[1], h1], o.foo(:dbar, 1, **h1))
|
||||
assert_equal([1, h1], o.foo(: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))
|
||||
assert_equal([1, h1], o.foo_dbaz(1, **h1))
|
||||
|
||||
assert_equal([[h1], {}], o.foo(:dbar, h1, **{}))
|
||||
assert_equal([h1], o.foo(: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, **{}))
|
||||
assert_equal([h1], o.foo_dbaz(h1, **{}))
|
||||
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for method/m) do
|
||||
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.store_foo(:dbar, 1, h1))
|
||||
end
|
||||
assert_equal([1, h1], o.store_foo(:dbaz, 1, h1))
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for method/m) do
|
||||
assert_equal([[1], h1], o.foo_dbar(1, h1))
|
||||
end
|
||||
assert_equal([1, h1], o.foo_dbaz(1, h1))
|
||||
|
||||
assert_equal([[1], h1], o.block(1, :a=>1))
|
||||
assert_equal([[1], h1], o.block(1, **h1))
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for `call'/m) do
|
||||
assert_equal([[1], h1], o.block(1, h1))
|
||||
end
|
||||
assert_equal([[h1], {}], o.block(h1, **{}))
|
||||
|
||||
assert_equal([[1], h1], o.cfunc(1, :a=>1))
|
||||
assert_equal([[1], h1], o.cfunc(1, **h1))
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for `initialize'/m) do
|
||||
assert_equal([[1], h1], o.cfunc(1, h1))
|
||||
end
|
||||
assert_equal([[h1], {}], o.cfunc(h1, **{}))
|
||||
|
||||
o = mmkw.new
|
||||
assert_equal([[:b, 1], h1], o.b(1, :a=>1))
|
||||
assert_equal([[:b, 1], h1], o.b(1, **h1))
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for `method_missing'/m) do
|
||||
assert_equal([[:b, 1], h1], o.b(1, h1))
|
||||
end
|
||||
assert_equal([[:b, h1], {}], o.b(h1, **{}))
|
||||
|
||||
o = mmnokw.new
|
||||
assert_equal([:b, 1, h1], o.b(1, :a=>1))
|
||||
assert_equal([:b, 1, h1], o.b(1, **h1))
|
||||
assert_equal([:b, 1, h1], o.b(1, h1))
|
||||
assert_equal([:b, h1], o.b(h1, **{}))
|
||||
|
||||
o = implicit_super.new
|
||||
assert_equal([[1], h1], o.bar(1, :a=>1))
|
||||
assert_equal([[1], h1], o.bar(1, **h1))
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
|
||||
assert_equal([[1], h1], o.bar(1, h1))
|
||||
end
|
||||
assert_equal([[h1], {}], o.bar(h1, **{}))
|
||||
|
||||
assert_equal([1, h1], o.baz(1, :a=>1))
|
||||
assert_equal([1, h1], o.baz(1, **h1))
|
||||
assert_equal([1, h1], o.baz(1, h1))
|
||||
assert_equal([h1], o.baz(h1, **{}))
|
||||
|
||||
o = explicit_super.new
|
||||
assert_equal([[1], h1], o.bar(1, :a=>1))
|
||||
assert_equal([[1], h1], o.bar(1, **h1))
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
|
||||
assert_equal([[1], h1], o.bar(1, h1))
|
||||
end
|
||||
assert_equal([[h1], {}], o.bar(h1, **{}))
|
||||
|
||||
assert_equal([1, h1], o.baz(1, :a=>1))
|
||||
assert_equal([1, h1], o.baz(1, **h1))
|
||||
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_nil(c.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)
|
||||
end
|
||||
m = Module.new
|
||||
assert_warn(/Skipping set of ruby2_keywords flag for system \(can only set in method defining module\)/) do
|
||||
m.send(:ruby2_keywords, :system)
|
||||
end
|
||||
|
||||
assert_raise(NameError) { c.send(:ruby2_keywords, "a5e36ccec4f5080a1d5e63f8") }
|
||||
assert_raise(NameError) { c.send(:ruby2_keywords, :quux) }
|
||||
|
||||
c.freeze
|
||||
assert_raise(FrozenError) { c.send(:ruby2_keywords, :baz) }
|
||||
end
|
||||
|
||||
def test_dig_kwsplat
|
||||
kw = {}
|
||||
h = {:a=>1}
|
||||
|
|
|
@ -177,6 +177,25 @@ class TestDelegateClass < Test::Unit::TestCase
|
|||
assert_not_operator(s0, :eql?, "bar")
|
||||
end
|
||||
|
||||
def test_keyword_and_hash
|
||||
foo = Object.new
|
||||
def foo.bar(*args)
|
||||
args
|
||||
end
|
||||
def foo.foo(*args, **kw)
|
||||
[args, kw]
|
||||
end
|
||||
d = SimpleDelegator.new(foo)
|
||||
assert_equal([[], {}], d.foo)
|
||||
assert_equal([], d.bar)
|
||||
assert_equal([[], {:a=>1}], d.foo(:a=>1))
|
||||
assert_equal([{:a=>1}], d.bar(:a=>1))
|
||||
assert_warn(/The last argument is used as the keyword parameter.* for `foo'/m) do
|
||||
assert_equal([[], {:a=>1}], d.foo({:a=>1}))
|
||||
end
|
||||
assert_equal([{:a=>1}], d.bar({:a=>1}))
|
||||
end
|
||||
|
||||
class Foo
|
||||
private
|
||||
def delegate_test_private
|
||||
|
|
60
vm_args.c
60
vm_args.c
|
@ -671,6 +671,8 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
|
|||
VALUE keyword_hash = Qnil;
|
||||
VALUE * const orig_sp = ec->cfp->sp;
|
||||
unsigned int i;
|
||||
int remove_empty_keyword_hash = 1;
|
||||
VALUE flag_keyword_hash = 0;
|
||||
|
||||
vm_check_canary(ec, orig_sp);
|
||||
/*
|
||||
|
@ -720,41 +722,79 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
|
|||
args->kw_argv = NULL;
|
||||
}
|
||||
|
||||
if (kw_flag && iseq->body->param.flags.ruby2_keywords) {
|
||||
remove_empty_keyword_hash = 0;
|
||||
}
|
||||
|
||||
if (ci->flag & VM_CALL_ARGS_SPLAT) {
|
||||
VALUE rest_last = 0;
|
||||
int len;
|
||||
args->rest = locals[--args->argc];
|
||||
args->rest_index = 0;
|
||||
given_argc += RARRAY_LENINT(args->rest) - 1;
|
||||
len = RARRAY_LENINT(args->rest);
|
||||
given_argc += len - 1;
|
||||
rest_last = RARRAY_AREF(args->rest, len - 1);
|
||||
|
||||
if (!kw_flag && len > 0) {
|
||||
if (RB_TYPE_P(rest_last, T_HASH) &&
|
||||
(((struct RHash *)rest_last)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
|
||||
kw_flag |= VM_CALL_KW_SPLAT;
|
||||
} else {
|
||||
rest_last = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (kw_flag & VM_CALL_KW_SPLAT) {
|
||||
int len = RARRAY_LENINT(args->rest);
|
||||
if (len > 0 && ignore_keyword_hash_p(RARRAY_AREF(args->rest, len - 1), iseq)) {
|
||||
if (given_argc != min_argc) {
|
||||
arg_rest_dup(args);
|
||||
rb_ary_pop(args->rest);
|
||||
given_argc--;
|
||||
kw_flag &= ~VM_CALL_KW_SPLAT;
|
||||
if (remove_empty_keyword_hash) {
|
||||
arg_rest_dup(args);
|
||||
rb_ary_pop(args->rest);
|
||||
given_argc--;
|
||||
kw_flag &= ~VM_CALL_KW_SPLAT;
|
||||
}
|
||||
else {
|
||||
flag_keyword_hash = rest_last;
|
||||
}
|
||||
}
|
||||
else {
|
||||
rb_warn_keyword_to_last_hash(calling, ci, iseq);
|
||||
}
|
||||
}
|
||||
else if (!remove_empty_keyword_hash && rest_last) {
|
||||
flag_keyword_hash = rest_last;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (kw_flag & VM_CALL_KW_SPLAT) {
|
||||
if (ignore_keyword_hash_p(args->argv[args->argc-1], iseq)) {
|
||||
VALUE last_arg = args->argv[args->argc-1];
|
||||
if (ignore_keyword_hash_p(last_arg, iseq)) {
|
||||
if (given_argc != min_argc) {
|
||||
args->argc--;
|
||||
given_argc--;
|
||||
kw_flag &= ~VM_CALL_KW_SPLAT;
|
||||
if (remove_empty_keyword_hash) {
|
||||
args->argc--;
|
||||
given_argc--;
|
||||
kw_flag &= ~VM_CALL_KW_SPLAT;
|
||||
}
|
||||
else {
|
||||
flag_keyword_hash = last_arg;
|
||||
}
|
||||
}
|
||||
else {
|
||||
rb_warn_keyword_to_last_hash(calling, ci, iseq);
|
||||
}
|
||||
}
|
||||
else if (!remove_empty_keyword_hash) {
|
||||
flag_keyword_hash = args->argv[args->argc-1];
|
||||
}
|
||||
}
|
||||
args->rest = Qfalse;
|
||||
}
|
||||
|
||||
if (flag_keyword_hash && RB_TYPE_P(flag_keyword_hash, T_HASH)) {
|
||||
((struct RHash *)flag_keyword_hash)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
|
||||
}
|
||||
|
||||
if (kw_flag && iseq->body->param.flags.accepts_no_kwarg) {
|
||||
rb_raise(rb_eArgError, "no keywords accepted");
|
||||
}
|
||||
|
|
|
@ -358,6 +358,7 @@ struct rb_iseq_constant_body {
|
|||
|
||||
unsigned int ambiguous_param0 : 1; /* {|a|} */
|
||||
unsigned int accepts_no_kwarg : 1;
|
||||
unsigned int ruby2_keywords: 1;
|
||||
} flags;
|
||||
|
||||
unsigned int size;
|
||||
|
|
|
@ -1770,15 +1770,21 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq)
|
|||
|
||||
|
||||
static inline void
|
||||
CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(struct rb_control_frame_struct *restrict cfp,
|
||||
struct rb_calling_info *restrict calling,
|
||||
const struct rb_call_info *restrict ci)
|
||||
CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
|
||||
struct rb_calling_info *restrict calling,
|
||||
const struct rb_call_info *restrict ci)
|
||||
{
|
||||
if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
|
||||
/* This expands the rest argument to the stack.
|
||||
* So, ci->flag & VM_CALL_ARGS_SPLAT is now inconsistent.
|
||||
*/
|
||||
vm_caller_setup_arg_splat(cfp, calling);
|
||||
if (!IS_ARGS_KW_OR_KW_SPLAT(ci) &&
|
||||
calling->argc > 0 &&
|
||||
RB_TYPE_P(*(cfp->sp - 1), T_HASH) &&
|
||||
(((struct RHash *)*(cfp->sp - 1))->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
|
||||
calling->kw_splat = 1;
|
||||
}
|
||||
}
|
||||
if (UNLIKELY(IS_ARGS_KEYWORD(ci))) {
|
||||
/* This converts VM_CALL_KWARG style to VM_CALL_KW_SPLAT style
|
||||
|
@ -1790,12 +1796,10 @@ CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(struct rb_control_frame_struct *restrict cfp,
|
|||
}
|
||||
|
||||
static inline void
|
||||
CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
|
||||
struct rb_calling_info *restrict calling,
|
||||
const struct rb_call_info *restrict ci)
|
||||
CALLER_REMOVE_EMPTY_KW_SPLAT(struct rb_control_frame_struct *restrict cfp,
|
||||
struct rb_calling_info *restrict calling,
|
||||
const struct rb_call_info *restrict ci)
|
||||
{
|
||||
CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(cfp, calling, ci);
|
||||
|
||||
if (UNLIKELY(calling->kw_splat)) {
|
||||
/* This removes the last Hash object if it is empty.
|
||||
* So, ci->flag & VM_CALL_KW_SPLAT is now inconsistent.
|
||||
|
@ -1920,6 +1924,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
|
|||
if (LIKELY(rb_simple_iseq_p(iseq))) {
|
||||
rb_control_frame_t *cfp = ec->cfp;
|
||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
||||
CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
|
||||
|
||||
if (calling->argc != iseq->body->param.lead_num) {
|
||||
argument_arity_error(ec, iseq, calling->argc, iseq->body->param.lead_num, iseq->body->param.lead_num);
|
||||
|
@ -1931,6 +1936,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
|
|||
else if (rb_iseq_only_optparam_p(iseq)) {
|
||||
rb_control_frame_t *cfp = ec->cfp;
|
||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
||||
CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
|
||||
|
||||
const int lead_num = iseq->body->param.lead_num;
|
||||
const int opt_num = iseq->body->param.opt_num;
|
||||
|
@ -2285,10 +2291,12 @@ vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp
|
|||
static VALUE
|
||||
vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
|
||||
{
|
||||
int empty_kw_splat = calling->kw_splat;
|
||||
int empty_kw_splat;
|
||||
RB_DEBUG_COUNTER_INC(ccf_cfunc);
|
||||
|
||||
CALLER_SETUP_ARG(reg_cfp, calling, ci);
|
||||
empty_kw_splat = calling->kw_splat;
|
||||
CALLER_REMOVE_EMPTY_KW_SPLAT(reg_cfp, calling, ci);
|
||||
if (empty_kw_splat && calling->kw_splat) {
|
||||
empty_kw_splat = 0;
|
||||
}
|
||||
|
@ -2333,7 +2341,7 @@ vm_call_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_c
|
|||
VALUE *argv;
|
||||
int argc;
|
||||
|
||||
CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(cfp, calling, ci);
|
||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
||||
argc = calling->argc;
|
||||
argv = ALLOCA_N(VALUE, argc);
|
||||
MEMCPY(argv, cfp->sp - argc, VALUE, argc);
|
||||
|
@ -2363,7 +2371,7 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct
|
|||
struct rb_call_info_with_kwarg ci_entry;
|
||||
struct rb_call_cache cc_entry, *cc;
|
||||
|
||||
CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(reg_cfp, calling, orig_ci);
|
||||
CALLER_SETUP_ARG(reg_cfp, calling, orig_ci);
|
||||
|
||||
i = calling->argc - 1;
|
||||
|
||||
|
@ -2468,7 +2476,7 @@ vm_call_method_missing(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
|
|||
struct rb_call_cache cc_entry, *cc;
|
||||
unsigned int argc;
|
||||
|
||||
CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(reg_cfp, calling, orig_ci);
|
||||
CALLER_SETUP_ARG(reg_cfp, calling, orig_ci);
|
||||
argc = calling->argc+1;
|
||||
|
||||
ci_entry.flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
|
||||
|
@ -2673,12 +2681,12 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
|
|||
return vm_call_cfunc(ec, cfp, calling, ci, cc);
|
||||
|
||||
case VM_METHOD_TYPE_ATTRSET:
|
||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
||||
if (calling->argc == 1 && calling->kw_splat && RHASH_EMPTY_P(cfp->sp[-1])) {
|
||||
CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(cfp, calling, ci);
|
||||
rb_warn_keyword_to_last_hash(calling, ci, NULL);
|
||||
}
|
||||
else {
|
||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
||||
CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
|
||||
}
|
||||
|
||||
rb_check_arity(calling->argc, 1, 1);
|
||||
|
@ -2688,6 +2696,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
|
|||
|
||||
case VM_METHOD_TYPE_IVAR:
|
||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
||||
CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
|
||||
rb_check_arity(calling->argc, 0, 0);
|
||||
cc->aux.index = 0;
|
||||
CC_SET_FASTPATH(cc, vm_call_ivar, !(ci->flag & VM_CALL_ARGS_SPLAT));
|
||||
|
@ -2998,12 +3007,12 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
|
|||
rb_control_frame_t *cfp = ec->cfp;
|
||||
VALUE arg0;
|
||||
|
||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
||||
if (calling->kw_splat && calling->argc == iseq->body->param.lead_num + iseq->body->param.post_num && RHASH_EMPTY_P(cfp->sp[-1])) {
|
||||
CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(cfp, calling, ci);
|
||||
rb_warn_keyword_to_last_hash(calling, ci, iseq);
|
||||
}
|
||||
else {
|
||||
CALLER_SETUP_ARG(cfp, calling, ci);
|
||||
CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
|
||||
}
|
||||
|
||||
if (arg_setup_type == arg_setup_block &&
|
||||
|
@ -3088,7 +3097,7 @@ vm_invoke_symbol_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
|
|||
{
|
||||
VALUE val;
|
||||
int argc;
|
||||
CALLER_SETUP_ARG_WITHOUT_KW_SPLAT(ec->cfp, calling, ci);
|
||||
CALLER_SETUP_ARG(ec->cfp, calling, ci);
|
||||
argc = calling->argc;
|
||||
val = vm_yield_with_symbol(ec, symbol, argc, STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler);
|
||||
POPN(argc);
|
||||
|
@ -3104,6 +3113,7 @@ vm_invoke_ifunc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
|
|||
int argc;
|
||||
int kw_splat = calling->kw_splat;
|
||||
CALLER_SETUP_ARG(ec->cfp, calling, ci);
|
||||
CALLER_REMOVE_EMPTY_KW_SPLAT(ec->cfp, calling, ci);
|
||||
if (kw_splat && !calling->kw_splat) {
|
||||
kw_splat = 2;
|
||||
}
|
||||
|
|
79
vm_method.c
79
vm_method.c
|
@ -1745,6 +1745,84 @@ rb_mod_private(int argc, VALUE *argv, VALUE module)
|
|||
return set_visibility(argc, argv, module, METHOD_VISI_PRIVATE);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* ruby2_keywords(method_name, ...) -> self
|
||||
*
|
||||
* For the given method names, marks the method as passing keywords through
|
||||
* a normal argument splat. This should only be called on methods that
|
||||
* accept an argument splat (<tt>*args</tt>) but not explicit keywords or
|
||||
* a keyword splat. It marks the method such that if the method is called
|
||||
* with keyword arguments, the final hash argument is marked with a special
|
||||
* flag such that if it is the final element of a normal argument splat to
|
||||
* another method call, and that method calls does not include explicit
|
||||
* keywords or a keyword splat, the final element is interpreted as keywords.
|
||||
* In other words, keywords will be passed through the method to other
|
||||
* methods.
|
||||
*
|
||||
* This should only be used for methods that delegate keywords to another
|
||||
* method, and only for backwards compatibility with Ruby versions before
|
||||
* 2.7.
|
||||
*
|
||||
* This method will probably be removed at some point, as it exists only
|
||||
* for backwards compatibility, so always check that the module responds
|
||||
* to this method before calling it.
|
||||
*
|
||||
* module Mod
|
||||
* def foo(meth, *args, &block)
|
||||
* send(:"do_#{meth}", *args, &block)
|
||||
* end
|
||||
* ruby2_keywords(:foo) if respond_to?(:ruby2_keywords, true)
|
||||
* end
|
||||
*/
|
||||
|
||||
static VALUE
|
||||
rb_mod_ruby2_keywords(int argc, VALUE *argv, VALUE module)
|
||||
{
|
||||
int i;
|
||||
VALUE origin_class = RCLASS_ORIGIN(module);
|
||||
|
||||
rb_check_frozen(module);
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
VALUE v = argv[i];
|
||||
ID name = rb_check_id(&v);
|
||||
rb_method_entry_t *me;
|
||||
VALUE defined_class;
|
||||
|
||||
if (!name) {
|
||||
rb_print_undef_str(module, v);
|
||||
}
|
||||
|
||||
me = search_method(origin_class, name, &defined_class);
|
||||
if (!me && RB_TYPE_P(module, T_MODULE)) {
|
||||
me = search_method(rb_cObject, name, &defined_class);
|
||||
}
|
||||
|
||||
if (UNDEFINED_METHOD_ENTRY_P(me) ||
|
||||
UNDEFINED_REFINED_METHOD_P(me->def)) {
|
||||
rb_print_undef(module, name, METHOD_VISI_UNDEF);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
else {
|
||||
rb_warn("Skipping set of ruby2_keywords flag for %s (can only set in method defining module)", rb_id2name(name));
|
||||
}
|
||||
}
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* mod.public_class_method(symbol, ...) -> mod
|
||||
|
@ -2127,6 +2205,7 @@ Init_eval_method(void)
|
|||
rb_define_private_method(rb_cModule, "protected", rb_mod_protected, -1);
|
||||
rb_define_private_method(rb_cModule, "private", rb_mod_private, -1);
|
||||
rb_define_private_method(rb_cModule, "module_function", rb_mod_modfunc, -1);
|
||||
rb_define_private_method(rb_cModule, "ruby2_keywords", rb_mod_ruby2_keywords, -1);
|
||||
|
||||
rb_define_method(rb_cModule, "method_defined?", rb_mod_method_defined, -1);
|
||||
rb_define_method(rb_cModule, "public_method_defined?", rb_mod_public_method_defined, -1);
|
||||
|
|
Загрузка…
Ссылка в новой задаче