Project

General

Profile

Bug #16154 ยป delegate-keyword-argument-separation.patch

jeremyevans0 (Jeremy Evans), 09/09/2019 02:33 AM

View differences:

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);
    (1-1/1)