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 <jean.boussier@gmail.com>
This commit is contained in:
Jeremy Evans 2025-04-02 09:27:47 -07:00
parent b8e2bec914
commit 29dafa5fc2
Notes: git 2025-04-03 02:31:22 +00:00
3 changed files with 58 additions and 0 deletions

View File

@ -1419,3 +1419,11 @@ assert_equal 'ok', %q{
"ok" "ok"
end end
} }
assert_equal 'ok', <<~RUBY
def test(*, kw: false)
"ok"
end
test
RUBY

View File

@ -374,6 +374,55 @@ class TestCall < Test::Unit::TestCase
assert_equal({splat_modified: false}, b) assert_equal({splat_modified: false}, b)
end 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 test_kwsplat_block_eval_order
def self.t(**kw, &b) [kw, b] end def self.t(**kw, &b) [kw, b] end

View File

@ -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 (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)) { 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; *(locals + ISEQ_BODY(iseq)->param.rest_start) = args->rest = rb_cArray_empty_frozen;
args->rest_index = 0;
} }
else { else {
args_setup_rest_parameter(args, locals + ISEQ_BODY(iseq)->param.rest_start); args_setup_rest_parameter(args, locals + ISEQ_BODY(iseq)->param.rest_start);