diff options
author | Jeremy Evans <code@jeremyevans.net> | 2019-09-21 09:03:36 -0700 |
---|---|---|
committer | Jeremy Evans <code@jeremyevans.net> | 2019-09-25 12:33:52 -0700 |
commit | 3b302ea8c95d34d5ef072d7e3b326f28a611e479 (patch) | |
tree | 5a0a5cadb3511d6a3ecf4f234abffecafbeec9d8 /vm_args.c | |
parent | 80b5a0ff2a7709367178f29d4ebe1c54122b1c27 (diff) |
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.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/2477
Diffstat (limited to 'vm_args.c')
-rw-r--r-- | vm_args.c | 60 |
1 files changed, 50 insertions, 10 deletions
@@ -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"); } |