summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.md6
-rw-r--r--compile.c1
-rw-r--r--test/ruby/test_backtrace.rb2
-rw-r--r--test/ruby/test_keyword.rb38
-rw-r--r--vm.c4
-rw-r--r--vm_args.c4
-rw-r--r--vm_insnhelper.c12
7 files changed, 60 insertions, 7 deletions
diff --git a/NEWS.md b/NEWS.md
index 6e2dcc6d24..be0c14797e 100644
--- a/NEWS.md
+++ b/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
diff --git a/compile.c b/compile.c
index 6e0456af35..2d7e81d54e 100644
--- a/compile.c
+++ b/compile.c
@@ -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[])
diff --git a/vm.c b/vm.c
index 37d631116a..a2a17f8fe7 100644
--- a/vm.c
+++ b/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;
}
diff --git a/vm_args.c b/vm_args.c
index eda46aa92c..37b765a41d 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -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);
}