`Hash#dup` for kwsplat arguments

On `f(*a, **kw)` method calls, a rest keyword parameter is identically
same Hash object is passed and it should make `#dup`ed Hahs.

fix https://bugs.ruby-lang.org/issues/19526
This commit is contained in:
Koichi Sasada 2023-03-14 03:42:47 +09:00
Родитель 7fd53eeb46
Коммит 6462c1a042
3 изменённых файлов: 40 добавлений и 4 удалений

Просмотреть файл

@ -5789,6 +5789,16 @@ check_keyword(const NODE *node)
}
#endif
static bool
keyword_node_single_splat_p(NODE *kwnode)
{
RUBY_ASSERT(keyword_node_p(kwnode));
NODE *node = kwnode->nd_head;
return node->nd_head == NULL &&
node->nd_next->nd_next == NULL;
}
static int
setup_args_core(rb_iseq_t *iseq, LINK_ANCHOR *const args, const NODE *argn,
int dup_rest, unsigned int *flag_ptr, struct rb_callinfo_kwarg **kwarg_ptr)
@ -5881,7 +5891,9 @@ setup_args_core(rb_iseq_t *iseq, LINK_ANCHOR *const args, const NODE *argn,
if (kwnode) {
// f(*a, k:1)
*flag_ptr |= VM_CALL_KW_SPLAT;
*flag_ptr |= VM_CALL_KW_SPLAT_MUT;
if (!keyword_node_single_splat_p(kwnode)) {
*flag_ptr |= VM_CALL_KW_SPLAT_MUT;
}
compile_hash(iseq, args, kwnode, TRUE, FALSE);
argc += 1;
}

Просмотреть файл

@ -447,6 +447,16 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(false, public_send(:yo, **{}).frozen?)
assert_equal_not_same(kw, public_send(:yo, **kw))
assert_equal_not_same(h, public_send(:yo, **h))
def self.yo(*a, **kw) = kw
assert_equal_not_same kw, yo(**kw)
assert_equal_not_same kw, yo(**kw, **kw)
singleton_class.send(:remove_method, :yo)
def self.yo(opts) = opts
assert_equal_not_same h, yo(*[], **h)
a = []
assert_equal_not_same h, yo(*a, **h)
end
def test_regular_kwsplat

Просмотреть файл

@ -450,6 +450,18 @@ ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned
RHASH_EMPTY_P(keyword_hash);
}
static VALUE
check_kwrestarg(VALUE keyword_hash, unsigned int *kw_flag)
{
if (!(*kw_flag & VM_CALL_KW_SPLAT_MUT)) {
*kw_flag |= VM_CALL_KW_SPLAT_MUT;
return rb_hash_dup(keyword_hash);
}
else {
return keyword_hash;
}
}
static int
setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * const iseq,
struct rb_calling_info *const calling,
@ -528,12 +540,14 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
keyword_hash = Qnil;
}
else if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.ruby2_keywords)) {
flag_keyword_hash = keyword_hash;
rb_ary_push(args->rest, keyword_hash);
converted_keyword_hash = check_kwrestarg(converted_keyword_hash, &kw_flag);
flag_keyword_hash = converted_keyword_hash;
rb_ary_push(args->rest, converted_keyword_hash);
keyword_hash = Qnil;
}
else if (!ISEQ_BODY(iseq)->param.flags.has_kwrest && !ISEQ_BODY(iseq)->param.flags.has_kw) {
rb_ary_push(args->rest, keyword_hash);
converted_keyword_hash = check_kwrestarg(converted_keyword_hash, &kw_flag);
rb_ary_push(args->rest, converted_keyword_hash);
keyword_hash = Qnil;
}