diff options
author | Koichi Sasada <[email protected]> | 2023-03-14 03:42:47 +0900 |
---|---|---|
committer | Koichi Sasada <[email protected]> | 2023-03-15 18:05:13 +0900 |
commit | 6462c1a042fec4017f7e3bf2c13ec6a29efd23d6 (patch) | |
tree | 01b3c6e3819e91525acce8c68bd0de8931551fe9 | |
parent | 7fd53eeb46db261bbc20025cdab70096245a5cbe (diff) |
`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
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/7507
-rw-r--r-- | compile.c | 14 | ||||
-rw-r--r-- | test/ruby/test_keyword.rb | 10 | ||||
-rw-r--r-- | vm_args.c | 20 |
3 files changed, 40 insertions, 4 deletions
@@ -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; } diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 94b5323ab0..ef594bd52e 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -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; } |