diff options
-rw-r--r-- | NEWS.md | 6 | ||||
-rw-r--r-- | compile.c | 1 | ||||
-rw-r--r-- | test/ruby/test_backtrace.rb | 2 | ||||
-rw-r--r-- | test/ruby/test_keyword.rb | 38 | ||||
-rw-r--r-- | vm.c | 4 | ||||
-rw-r--r-- | vm_args.c | 4 | ||||
-rw-r--r-- | vm_insnhelper.c | 12 |
7 files changed, 60 insertions, 7 deletions
@@ -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, diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb index d35dead95b..844ec0e30c 100644 --- a/test/ruby/test_backtrace.rb +++ b/test/ruby/test_backtrace.rb @@ -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 diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 9aca787dff..7849fe285a 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -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[]) @@ -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); } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index d81a121c1f..63567e8f87 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -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); } |