Support keyword splatting nil
nil is treated similarly to the empty hash in this case, passing no keywords and not calling any conversion methods. Fixes [Bug #20064] Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
This commit is contained in:
parent
772413245f
commit
5c823aa686
6
NEWS.md
6
NEWS.md
@ -9,6 +9,11 @@ Note that each entry is kept to a minimum, see links for details.
|
||||
|
||||
* `it` is added to reference a block parameter. [[Feature #18980]]
|
||||
|
||||
* Keyword splatting `nil` when calling methods is now supported.
|
||||
`**nil` is treated similar to `**{}`, passing no keywords,
|
||||
and not calling any conversion methods.
|
||||
[[Bug #20064]]
|
||||
|
||||
## Core classes updates
|
||||
|
||||
Note: We're only listing outstanding class updates.
|
||||
@ -57,3 +62,4 @@ See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/log
|
||||
## JIT
|
||||
|
||||
[Feature #18980]: https://bugs.ruby-lang.org/issues/18980
|
||||
[Bug #20064]: https://bugs.ruby-lang.org/issues/20064
|
||||
|
@ -5079,6 +5079,7 @@ compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int meth
|
||||
int last_kw = !RNODE_LIST(RNODE_LIST(node)->nd_next)->nd_next; /* foo( ..., **kw) */
|
||||
int only_kw = last_kw && first_kw; /* foo(1,2,3, **kw) */
|
||||
|
||||
empty_kw = empty_kw || nd_type_p(kw, NODE_NIL); /* foo( ..., **nil, ...) */
|
||||
if (empty_kw) {
|
||||
if (only_kw && method_call_keywords) {
|
||||
/* **{} appears at the only keyword argument in method call,
|
||||
|
@ -378,7 +378,7 @@ class TestBacktrace < Test::Unit::TestCase
|
||||
|
||||
def test_core_backtrace_hash_merge
|
||||
e = assert_raise(TypeError) do
|
||||
{**nil}
|
||||
{**1}
|
||||
end
|
||||
assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label)
|
||||
end
|
||||
|
@ -182,6 +182,44 @@ class TestKeywordArguments < Test::Unit::TestCase
|
||||
[:keyrest, :kw], [:block, :b]], method(:f9).parameters)
|
||||
end
|
||||
|
||||
def test_keyword_splat_nil
|
||||
# cfunc call
|
||||
assert_equal(nil, p(**nil))
|
||||
|
||||
def self.a0; end
|
||||
assert_equal(nil, a0(**nil))
|
||||
assert_equal(nil, :a0.to_proc.call(self, **nil))
|
||||
|
||||
def self.o(x=1); x end
|
||||
assert_equal(1, o(**nil))
|
||||
assert_equal(2, o(2, **nil))
|
||||
assert_equal(1, o(*nil, **nil))
|
||||
assert_equal(1, o(**nil, **nil))
|
||||
assert_equal({a: 1}, o(a: 1, **nil))
|
||||
assert_equal({a: 1}, o(**nil, a: 1))
|
||||
|
||||
# symproc call
|
||||
assert_equal(1, :o.to_proc.call(self, **nil))
|
||||
|
||||
def self.s(*a); a end
|
||||
assert_equal([], s(**nil))
|
||||
assert_equal([1], s(1, **nil))
|
||||
assert_equal([], s(*nil, **nil))
|
||||
|
||||
def self.kws(**a); a end
|
||||
assert_equal({}, kws(**nil))
|
||||
assert_equal({}, kws(*nil, **nil))
|
||||
|
||||
def self.skws(*a, **kw); [a, kw] end
|
||||
assert_equal([[], {}], skws(**nil))
|
||||
assert_equal([[1], {}], skws(1, **nil))
|
||||
assert_equal([[], {}], skws(*nil, **nil))
|
||||
|
||||
assert_equal({}, {**nil})
|
||||
assert_equal({a: 1}, {a: 1, **nil})
|
||||
assert_equal({a: 1}, {**nil, a: 1})
|
||||
end
|
||||
|
||||
def test_lambda
|
||||
f = ->(str: "foo", num: 424242) { [str, num] }
|
||||
assert_equal(["foo", 424242], f[])
|
||||
|
4
vm.c
4
vm.c
@ -3666,7 +3666,9 @@ kwmerge_i(VALUE key, VALUE value, VALUE hash)
|
||||
static VALUE
|
||||
m_core_hash_merge_kwd(VALUE recv, VALUE hash, VALUE kw)
|
||||
{
|
||||
REWIND_CFP(hash = core_hash_merge_kwd(hash, kw));
|
||||
if (!NIL_P(kw)) {
|
||||
REWIND_CFP(hash = core_hash_merge_kwd(hash, kw));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
@ -434,6 +434,10 @@ fill_keys_values(st_data_t key, st_data_t val, st_data_t ptr)
|
||||
static inline int
|
||||
ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned int * kw_flag, VALUE * converted_keyword_hash)
|
||||
{
|
||||
if (keyword_hash == Qnil) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!RB_TYPE_P(keyword_hash, T_HASH)) {
|
||||
keyword_hash = rb_to_hash_type(keyword_hash);
|
||||
}
|
||||
|
@ -2668,8 +2668,10 @@ static inline VALUE
|
||||
vm_caller_setup_keyword_hash(const struct rb_callinfo *ci, VALUE keyword_hash)
|
||||
{
|
||||
if (UNLIKELY(!RB_TYPE_P(keyword_hash, T_HASH))) {
|
||||
/* Convert a non-hash keyword splat to a new hash */
|
||||
keyword_hash = rb_hash_dup(rb_to_hash_type(keyword_hash));
|
||||
if (keyword_hash != Qnil) {
|
||||
/* Convert a non-hash keyword splat to a new hash */
|
||||
keyword_hash = rb_hash_dup(rb_to_hash_type(keyword_hash));
|
||||
}
|
||||
}
|
||||
else if (!IS_ARGS_KW_SPLAT_MUT(ci)) {
|
||||
/* Convert a hash keyword splat to a new hash unless
|
||||
@ -2699,7 +2701,7 @@ CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
|
||||
if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) return;
|
||||
|
||||
// put kw
|
||||
if (!RHASH_EMPTY_P(kwh)) {
|
||||
if (kwh != Qnil && !RHASH_EMPTY_P(kwh)) {
|
||||
if (UNLIKELY(calling->heap_argv)) {
|
||||
rb_ary_push(calling->heap_argv, kwh);
|
||||
((struct RHash *)kwh)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
|
||||
@ -2770,7 +2772,7 @@ check_keyword:
|
||||
VM_ASSERT(calling->kw_splat == 1);
|
||||
VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[-1]);
|
||||
|
||||
if (RHASH_EMPTY_P(kwh)) {
|
||||
if (kwh == Qnil || RHASH_EMPTY_P(kwh)) {
|
||||
cfp->sp--;
|
||||
calling->argc--;
|
||||
calling->kw_splat = 0;
|
||||
@ -3596,7 +3598,7 @@ vm_call_cfunc_only_splat_kw(rb_execution_context_t *ec, rb_control_frame_t *reg_
|
||||
RB_DEBUG_COUNTER_INC(ccf_cfunc_only_splat_kw);
|
||||
VALUE keyword_hash = reg_cfp->sp[-1];
|
||||
|
||||
if (RB_TYPE_P(keyword_hash, T_HASH) && RHASH_EMPTY_P(keyword_hash)) {
|
||||
if (keyword_hash == Qnil || (RB_TYPE_P(keyword_hash, T_HASH) && RHASH_EMPTY_P(keyword_hash))) {
|
||||
return vm_call_cfunc_array_argv(ec, reg_cfp, calling, 1, 0);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user