summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2024-01-14 11:41:02 -0800
committerGitHub <noreply@github.com>2024-01-14 11:41:02 -0800
commit5c823aa686a5549649df4af86d173bebed2418e1 (patch)
tree7044e21e631b6cb1557ef9aa42ac9b9b4d850898
parent772413245f782f538413a69a270ec75ee8b77f18 (diff)
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>
-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);
}