Bug #16154 ยป delegate-keyword-argument-separation.patch
lib/delegate.rb | ||
---|---|---|
#
|
||
# Handles the magic of delegation through \_\_getobj\_\_.
|
||
#
|
||
def method_missing(m, *args, &block)
|
||
pass_positional_hash def method_missing(m, *args, **kw, &block)
|
||
r = true
|
||
target = self.__getobj__ {r = false}
|
||
if r && target.respond_to?(m)
|
||
target.__send__(m, *args, &block)
|
||
target.__send__(m, *args, **kw, &block)
|
||
elsif ::Kernel.method_defined?(m) || ::Kernel.private_method_defined?(m)
|
||
::Kernel.instance_method(m).bind(self).(*args, &block)
|
||
else
|
||
super(m, *args, &block)
|
||
super
|
||
end
|
||
end
|
||
mjit_compile.c | ||
---|---|---|
fprintf(f, " calling.argc = %d;\n", inline_context->orig_argc);
|
||
fprintf(f, " calling.recv = reg_cfp->self;\n");
|
||
fprintf(f, " reg_cfp->self = orig_self;\n");
|
||
fprintf(f, " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d);\n\n",
|
||
fprintf(f, " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d, 0);\n\n",
|
||
inline_context->me, inline_context->param_size, inline_context->local_size); // fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
|
||
// Start usual cancel from here.
|
test/ruby/test_keyword.rb | ||
---|---|---|
assert_equal(["bar", 111111], f[str: "bar", num: 111111])
|
||
end
|
||
def test_pass_positional_hash
|
||
c = Class.new do
|
||
pass_positional_hash def foo(x, *args, **kw)
|
||
send(x ? :bar : :baz, *args, **kw)
|
||
end
|
||
def bar(*args, **kw)
|
||
[args, kw]
|
||
end
|
||
def baz(*args)
|
||
args
|
||
end
|
||
end
|
||
o = c.new
|
||
assert_equal([[1], {:a=>1}], o.foo(true, 1, :a=>1))
|
||
assert_equal([1, {:a=>1}], o.foo(false, 1, :a=>1))
|
||
assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
|
||
assert_equal([[1], {:a=>1}], o.foo(true, 1, {:a=>1}))
|
||
end
|
||
assert_equal([1, {:a=>1}], o.foo(false, 1, {:a=>1}))
|
||
assert_warn(/Skipping set of pass positional hash flag for baz \(method not defined in Ruby or method does not accept keyword arguments\)/) do
|
||
assert_nil(c.send(:pass_positional_hash, :baz))
|
||
end
|
||
sc = Class.new(c)
|
||
assert_warn(/Skipping set of pass positional hash flag for bar \(can only set in method defining module\)/) do
|
||
sc.send(:pass_positional_hash, :bar)
|
||
end
|
||
m = Module.new
|
||
assert_warn(/Skipping set of pass positional hash flag for system \(can only set in method defining module\)/) do
|
||
m.send(:pass_positional_hash, :system)
|
||
end
|
||
assert_raise(NameError) { c.send(:pass_positional_hash, "a5e36ccec4f5080a1d5e63f8") }
|
||
assert_raise(NameError) { c.send(:pass_positional_hash, :quux) }
|
||
c.freeze
|
||
assert_raise(FrozenError) { c.send(:pass_positional_hash, :baz) }
|
||
end
|
||
def test_regular_kwsplat
|
||
kw = {}
|
||
h = {:a=>1}
|
test/test_delegate.rb | ||
---|---|---|
end
|
||
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
|
||
def test_private_method
|
||
foo = Foo.new
|
||
d = SimpleDelegator.new(foo)
|
tool/mk_call_iseq_optimized.rb | ||
---|---|---|
#{fname(param, local)}(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
|
||
{
|
||
RB_DEBUG_COUNTER_INC(ccf_iseq_fix);
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, #{param}, #{local});
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, #{param}, #{local}, 0);
|
||
}
|
||
EOS
|
tool/ruby_vm/views/_mjit_compile_send.erb | ||
---|---|---|
% # JIT: Special CALL_METHOD. Bypass cc_copy->call and inline vm_call_iseq_setup_normal for vm_call_iseq_setup_func FASTPATH.
|
||
fprintf(f, " {\n");
|
||
fprintf(f, " VALUE v;\n");
|
||
fprintf(f, " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d);\n",
|
||
fprintf(f, " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d, 0);\n",
|
||
(VALUE)cc_copy->me, param_size, iseq->body->local_table_size); // fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
|
||
if (iseq->body->catch_except_p) {
|
||
fprintf(f, " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n");
|
vm_args.c | ||
---|---|---|
setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * const iseq,
|
||
struct rb_calling_info *const calling,
|
||
const struct rb_call_info *ci,
|
||
VALUE * const locals, const enum arg_setup_type arg_setup_type)
|
||
VALUE * const locals, const enum arg_setup_type arg_setup_type,
|
||
int *frame_flag)
|
||
{
|
||
const int min_argc = iseq->body->param.lead_num + iseq->body->param.post_num;
|
||
const int max_argc = (iseq->body->param.flags.has_rest == FALSE) ? min_argc + iseq->body->param.opt_num : UNLIMITED_ARGUMENTS;
|
||
... | ... | |
args->argv = locals;
|
||
args->rest_dupped = FALSE;
|
||
if (VM_FRAME_PASS_POSITIONAL_HASH_P(ec->cfp)) {
|
||
kw_flag &= ~VM_CALL_KW_SPLAT;
|
||
}
|
||
if (kw_flag & VM_CALL_KWARG) {
|
||
args->kw_arg = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
|
||
... | ... | |
* def foo(k:1) p [k]; end
|
||
* foo({k:42}) #=> 42
|
||
*/
|
||
rb_warn_last_hash_to_keyword(calling, ci, iseq);
|
||
if (iseq->body->param.flags.pass_positional_hash) {
|
||
if (frame_flag) {
|
||
*frame_flag = VM_FRAME_FLAG_PASS_POSITIONAL_HASH;
|
||
}
|
||
}
|
||
else {
|
||
rb_warn_last_hash_to_keyword(calling, ci, iseq);
|
||
}
|
||
given_argc--;
|
||
}
|
||
else if (keyword_hash != Qnil) {
|
vm_core.h | ||
---|---|---|
unsigned int ambiguous_param0 : 1; /* {|a|} */
|
||
unsigned int accepts_no_kwarg : 1;
|
||
unsigned int pass_positional_hash: 1; /* -- Remove In 3.0 -- */
|
||
} flags;
|
||
unsigned int size;
|
||
... | ... | |
enum {
|
||
/* Frame/Environment flag bits:
|
||
* MMMM MMMM MMMM MMMM ____ FFFF FFFF EEEX (LSB)
|
||
* MMMM MMMM MMMM MMMM ___F FFFF FFFF EEEX (LSB)
|
||
*
|
||
* X : tag for GC marking (It seems as Fixnum)
|
||
* EEE : 3 bits Env flags
|
||
* FF..: 8 bits Frame flags
|
||
* FF..: 9 bits Frame flags
|
||
* MM..: 15 bits frame magic (to check frame corruption)
|
||
*/
|
||
... | ... | |
VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200,
|
||
VM_FRAME_FLAG_CFRAME_KW = 0x0400,
|
||
VM_FRAME_FLAG_CFRAME_EMPTY_KW = 0x0800, /* -- Remove In 3.0 -- */
|
||
VM_FRAME_FLAG_PASS_POSITIONAL_HASH = 0x1000, /* -- Remove In 3.0 -- */
|
||
/* env flag */
|
||
VM_ENV_FLAG_LOCAL = 0x0002,
|
||
... | ... | |
{
|
||
return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_CFRAME_EMPTY_KW) != 0;
|
||
}
|
||
static inline int
|
||
VM_FRAME_PASS_POSITIONAL_HASH_P(const rb_control_frame_t *cfp)
|
||
{
|
||
return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_PASS_POSITIONAL_HASH) != 0;
|
||
}
|
||
static inline int
|
||
VM_FRAME_FINISHED_P(const rb_control_frame_t *cfp)
|
vm_insnhelper.c | ||
---|---|---|
#include "vm_args.c"
|
||
static inline VALUE vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc, int param_size, int local_size);
|
||
ALWAYS_INLINE(static VALUE vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me, int opt_pc, int param_size, int local_size));
|
||
static inline VALUE vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc, int param_size, int local_size, int frame_flag);
|
||
ALWAYS_INLINE(static VALUE vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me, int opt_pc, int param_size, int local_size, int frame_flag));
|
||
static inline VALUE vm_call_iseq_setup_tailcall(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc);
|
||
static VALUE vm_call_super_method(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);
|
||
static VALUE vm_call_method_nome(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc);
|
||
... | ... | |
const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
|
||
int param = iseq->body->param.size;
|
||
int local = iseq->body->local_table_size;
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
|
||
}
|
||
MJIT_STATIC bool
|
||
... | ... | |
}
|
||
#endif
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param - delta, local);
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param - delta, local, 0);
|
||
}
|
||
static void
|
||
... | ... | |
int param = iseq->body->param.size;
|
||
int local = iseq->body->local_table_size;
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
|
||
}
|
||
static VALUE
|
||
... | ... | |
int param = iseq->body->param.size;
|
||
int local = iseq->body->local_table_size;
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
|
||
}
|
||
static inline int
|
||
vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc,
|
||
const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size)
|
||
const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size, int *frame_flag)
|
||
{
|
||
if (LIKELY(!(ci->flag & VM_CALL_KW_SPLAT))) {
|
||
if (LIKELY(rb_simple_iseq_p(iseq))) {
|
||
... | ... | |
}
|
||
}
|
||
return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method);
|
||
return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method, frame_flag);
|
||
}
|
||
static VALUE
|
||
... | ... | |
const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
|
||
const int param_size = iseq->body->param.size;
|
||
const int local_size = iseq->body->local_table_size;
|
||
const int opt_pc = vm_callee_setup_arg(ec, calling, ci, cc, def_iseq_ptr(cc->me->def), cfp->sp - calling->argc, param_size, local_size);
|
||
return vm_call_iseq_setup_2(ec, cfp, calling, ci, cc, opt_pc, param_size, local_size);
|
||
int frame_flag = 0;
|
||
const int opt_pc = vm_callee_setup_arg(ec, calling, ci, cc, def_iseq_ptr(cc->me->def), cfp->sp - calling->argc, param_size, local_size, &frame_flag);
|
||
return vm_call_iseq_setup_2(ec, cfp, calling, ci, cc, opt_pc, param_size, local_size, frame_flag);
|
||
}
|
||
static inline VALUE
|
||
vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc,
|
||
int opt_pc, int param_size, int local_size)
|
||
int opt_pc, int param_size, int local_size, int frame_flag)
|
||
{
|
||
if (LIKELY(!(ci->flag & VM_CALL_TAILCALL))) {
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param_size, local_size);
|
||
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param_size, local_size, frame_flag);
|
||
}
|
||
else {
|
||
return vm_call_iseq_setup_tailcall(ec, cfp, calling, ci, cc, opt_pc);
|
||
... | ... | |
static inline VALUE
|
||
vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me,
|
||
int opt_pc, int param_size, int local_size)
|
||
int opt_pc, int param_size, int local_size, int frame_flag)
|
||
{
|
||
const rb_iseq_t *iseq = def_iseq_ptr(me->def);
|
||
VALUE *argv = cfp->sp - calling->argc;
|
||
VALUE *sp = argv + param_size;
|
||
cfp->sp = argv - 1 /* recv */;
|
||
vm_push_frame(ec, iseq, VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling->recv,
|
||
vm_push_frame(ec, iseq, frame_flag | VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling->recv,
|
||
calling->block_handler, (VALUE)me,
|
||
iseq->body->iseq_encoded + opt_pc, sp,
|
||
local_size - param_size,
|
||
... | ... | |
return 0;
|
||
}
|
||
else {
|
||
return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_type);
|
||
return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_type, 0);
|
||
}
|
||
}
|
||
vm_method.c | ||
---|---|---|
return set_visibility(argc, argv, module, METHOD_VISI_PRIVATE);
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* pass_positional_hash(method_name, ...) -> self
|
||
*
|
||
* For the given method names, marks each the method as not issuing a warning
|
||
* if converting a positional hash to a keyword argument, and automatically
|
||
* converts keyword splats inside the method into positional arguments if
|
||
* the method was called with positional hash that was converted to a keyword
|
||
* argument.
|
||
*
|
||
* This should only be used for methods that delegate keywords to another
|
||
* method, suppressing a warning when the target method does not accept
|
||
* keyword arguments the final argument is a hash.
|
||
*
|
||
* Will only be present in 2.7, will be removed in 3.0, so always check that
|
||
* the module responds to this method before calling it.
|
||
*
|
||
* module Mod
|
||
* def foo(meth, *args, **kw, &block)
|
||
* send(:"do_#{meth}", *args, **kw, &block)
|
||
* end
|
||
* pass_positional_hash(:foo) if respond_to?(:pass_positional_hash, false)
|
||
* end
|
||
*/
|
||
static VALUE
|
||
rb_mod_pass_positional_hash(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_kwrest) {
|
||
me->def->body.iseq.iseqptr->body->param.flags.pass_positional_hash = 1;
|
||
rb_clear_method_cache_by_class(module);
|
||
}
|
||
else {
|
||
rb_warn("Skipping set of pass positional hash flag for %s (method not defined in Ruby or method does not accept keyword arguments)", rb_id2name(name));
|
||
}
|
||
}
|
||
else {
|
||
rb_warn("Skipping set of pass positional hash flag for %s (can only set in method defining module)", rb_id2name(name));
|
||
}
|
||
}
|
||
return Qnil;
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* mod.public_class_method(symbol, ...) -> mod
|
||
... | ... | |
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, "pass_positional_hash", rb_mod_pass_positional_hash, -1); /* -- Remove In 3.0 -- */
|
||
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);
|