From 29dafa5fc21343803127dda7d608f1f1f7908e7b Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Wed, 2 Apr 2025 09:27:47 -0700 Subject: [PATCH] Fix assertion failure with anonymous splats When calling a method that accepts an anonymous splat and literal keywords without any arguments, an assertion failure was previously raised. Set rest_index to 0 when setting rest to the frozen hash, so the args_argc calculation is accurate. While here, add more tests for methods with anonymous splats with and without keywords and keyword splats to confirm behavior is correct. Also add a basic bootstrap test that would hit the previous assertion failure. Co-authored-by: Jean Boussier --- bootstraptest/test_method.rb | 8 ++++++ test/ruby/test_call.rb | 49 ++++++++++++++++++++++++++++++++++++ vm_args.c | 1 + 3 files changed, 58 insertions(+) diff --git a/bootstraptest/test_method.rb b/bootstraptest/test_method.rb index 41823b5007..997675200e 100644 --- a/bootstraptest/test_method.rb +++ b/bootstraptest/test_method.rb @@ -1419,3 +1419,11 @@ assert_equal 'ok', %q{ "ok" end } + +assert_equal 'ok', <<~RUBY + def test(*, kw: false) + "ok" + end + + test +RUBY diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index ffbda1fdb9..7843f3b476 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -374,6 +374,55 @@ class TestCall < Test::Unit::TestCase assert_equal({splat_modified: false}, b) end + def test_anon_splat + r2kh = Hash.ruby2_keywords_hash(kw: 2) + r2kea = [r2kh] + r2ka = [1, r2kh] + + def self.s(*) ->(*a){a}.call(*) end + assert_equal([], s) + assert_equal([1], s(1)) + assert_equal([{kw: 2}], s(kw: 2)) + assert_equal([{kw: 2}], s(**{kw: 2})) + assert_equal([1, {kw: 2}], s(1, kw: 2)) + assert_equal([1, {kw: 2}], s(1, **{kw: 2})) + assert_equal([{kw: 2}], s(*r2kea)) + assert_equal([1, {kw: 2}], s(*r2ka)) + + singleton_class.remove_method(:s) + def self.s(*, kw: 0) [*->(*a){a}.call(*), kw] end + assert_equal([0], s) + assert_equal([1, 0], s(1)) + assert_equal([2], s(kw: 2)) + assert_equal([2], s(**{kw: 2})) + assert_equal([1, 2], s(1, kw: 2)) + assert_equal([1, 2], s(1, **{kw: 2})) + assert_equal([2], s(*r2kea)) + assert_equal([1, 2], s(*r2ka)) + + singleton_class.remove_method(:s) + def self.s(*, **kw) [*->(*a){a}.call(*), kw] end + assert_equal([{}], s) + assert_equal([1, {}], s(1)) + assert_equal([{kw: 2}], s(kw: 2)) + assert_equal([{kw: 2}], s(**{kw: 2})) + assert_equal([1, {kw: 2}], s(1, kw: 2)) + assert_equal([1, {kw: 2}], s(1, **{kw: 2})) + assert_equal([{kw: 2}], s(*r2kea)) + assert_equal([1, {kw: 2}], s(*r2ka)) + + singleton_class.remove_method(:s) + def self.s(*, kw: 0, **kws) [*->(*a){a}.call(*), kw, kws] end + assert_equal([0, {}], s) + assert_equal([1, 0, {}], s(1)) + assert_equal([2, {}], s(kw: 2)) + assert_equal([2, {}], s(**{kw: 2})) + assert_equal([1, 2, {}], s(1, kw: 2)) + assert_equal([1, 2, {}], s(1, **{kw: 2})) + assert_equal([2, {}], s(*r2kea)) + assert_equal([1, 2, {}], s(*r2ka)) + end + def test_kwsplat_block_eval_order def self.t(**kw, &b) [kw, b] end diff --git a/vm_args.c b/vm_args.c index a84540c61b..4738eda72c 100644 --- a/vm_args.c +++ b/vm_args.c @@ -896,6 +896,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co if (ISEQ_BODY(iseq)->param.flags.has_rest) { if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.anon_rest && args->argc == 0 && !args->rest && !ISEQ_BODY(iseq)->param.flags.has_post)) { *(locals + ISEQ_BODY(iseq)->param.rest_start) = args->rest = rb_cArray_empty_frozen; + args->rest_index = 0; } else { args_setup_rest_parameter(args, locals + ISEQ_BODY(iseq)->param.rest_start);