From e878bbd641f255b5d8f9e99b84ff8082a5b7fdaf Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 8 Feb 2024 19:10:51 -0500 Subject: [PATCH] Allow `foo(**nil, &block_arg)` Previously, `**nil` by itself worked, but if you add a block argument, it raised a conversion error. The presence of the block argument shouldn't change how keyword splat works. See: --- insns.def | 7 ++++++- test/ruby/test_keyword.rb | 1 + yjit/src/codegen.rs | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/insns.def b/insns.def index bcc83438414871..d04421b38c54ae 100644 --- a/insns.def +++ b/insns.def @@ -563,7 +563,12 @@ splatkw (VALUE obj, VALUE block) // attr bool leaf = false; /* has rb_to_hash_type() */ { - obj = rb_to_hash_type(hash); + if (NIL_P(hash)) { + obj = Qnil; + } + else { + obj = rb_to_hash_type(hash); + } } /* put new Hash from n elements. n must be an even number. */ diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 06bba60ac5c98b..a856a465695ab2 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -189,6 +189,7 @@ def test_keyword_splat_nil def self.a0; end assert_equal(nil, a0(**nil)) assert_equal(nil, :a0.to_proc.call(self, **nil)) + assert_equal(nil, a0(**nil, &:block)) def self.o(x=1); x end assert_equal(1, o(**nil)) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 421fa989e23e22..46c0a1806e6f42 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -1482,7 +1482,7 @@ fn gen_splatkw( let hash_opnd = asm.stack_opnd(1); guard_object_is_hash(asm, hash_opnd, hash_opnd.into(), Counter::splatkw_not_hash); } else { - // Otherwise, call #to_hash operand to get T_HASH. + // Otherwise, call #to_hash on the operand if it's not nil. // Save the PC and SP because the callee may call #to_hash jit_prepare_non_leaf_call(jit, asm); @@ -1492,10 +1492,19 @@ fn gen_splatkw( let block_type = asm.ctx.get_opnd_type(block_opnd.into()); let hash_opnd = asm.stack_opnd(1); - let hash = asm.ccall(rb_to_hash_type as *const u8, vec![hash_opnd]); + c_callable! { + fn to_hash_if_not_nil(mut obj: VALUE) -> VALUE { + if obj != Qnil { + obj = unsafe { rb_to_hash_type(obj) }; + } + obj + } + } + + let hash = asm.ccall(to_hash_if_not_nil as _, vec![hash_opnd]); asm.stack_pop(2); // Keep it on stack during ccall for GC - let stack_ret = asm.stack_push(Type::THash); + let stack_ret = asm.stack_push(Type::Unknown); asm.mov(stack_ret, hash); asm.stack_push(block_type); // Leave block_opnd spilled by ccall as is