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);
|
||