summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <[email protected]>2024-02-01 12:28:32 -0800
committerJeremy Evans <[email protected]>2024-03-01 12:36:19 -0800
commit99384bac28e10895623ecbc73fc09fd7d41fc897 (patch)
treeb87147e42ea7515017a9f64a288b5bcf37f6e00e
parent6da8f04e01fd85e54a641c6ec4816153b9557095 (diff)
Correctly set anon_kwrest flag for def f(b: 1, **)
In cases where a method accepts both keywords and an anonymous keyword splat, the method was not marked as taking an anonymous keyword splat. Fix that in the compiler. Doing that broke handling of nil keyword splats in yjit, so update yjit to handle that. Add a test to check that calling a method that accepts both a keyword argument and an anonymous keyword splat does not modify a passed keyword splat hash. Move the anon_kwrest check from setup_parameters_complex to ignore_keyword_hash_p, and only use it if the keyword hash is already a hash. This should speed things up slightly as it avoids a check previously used for all callers of setup_parameters_complex. Co-authored-by: Nobuyoshi Nakada <[email protected]>
-rw-r--r--compile.c3
-rw-r--r--test/ruby/test_keyword.rb7
-rw-r--r--vm_args.c11
-rw-r--r--yjit/src/codegen.rs2
4 files changed, 16 insertions, 7 deletions
diff --git a/compile.c b/compile.c
index 7f090b6748..f1dd595e3d 100644
--- a/compile.c
+++ b/compile.c
@@ -1976,8 +1976,11 @@ iseq_set_arguments_keywords(rb_iseq_t *iseq, LINK_ANCHOR *const optargs,
keyword->num = kw;
if (RNODE_DVAR(args->kw_rest_arg)->nd_vid != 0) {
+ ID kw_id = iseq->body->local_table[arg_size];
keyword->rest_start = arg_size++;
body->param.flags.has_kwrest = TRUE;
+
+ if (kw_id == idPow) body->param.flags.anon_kwrest = TRUE;
}
keyword->required_num = rkw;
keyword->table = &body->local_table[keyword->bits_start - keyword->num];
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index a856a46569..5d0262a449 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -182,6 +182,13 @@ class TestKeywordArguments < Test::Unit::TestCase
[:keyrest, :kw], [:block, :b]], method(:f9).parameters)
end
+ def test_keyword_with_anonymous_keyword_splat
+ def self.a(b: 1, **) [b, **] end
+ kw = {b: 2, c: 3}
+ assert_equal([2, {c: 3}], a(**kw))
+ assert_equal({b: 2, c: 3}, kw)
+ end
+
def test_keyword_splat_nil
# cfunc call
assert_equal(nil, p(**nil))
diff --git a/vm_args.c b/vm_args.c
index a9c391f273..ec5df37aab 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -504,6 +504,11 @@ ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned
if (!RB_TYPE_P(keyword_hash, T_HASH)) {
keyword_hash = rb_to_hash_type(keyword_hash);
}
+ else if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.anon_kwrest)) {
+ if (!ISEQ_BODY(iseq)->param.flags.has_kw) {
+ *kw_flag |= VM_CALL_KW_SPLAT_MUT;
+ }
+ }
if (!(*kw_flag & VM_CALL_KW_SPLAT_MUT) &&
(ISEQ_BODY(iseq)->param.flags.has_kwrest ||
@@ -590,12 +595,6 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
}
}
- if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.anon_kwrest)) {
- if (kw_flag & VM_CALL_KW_SPLAT) {
- kw_flag |= VM_CALL_KW_SPLAT_MUT;
- }
- }
-
if (kw_flag & VM_CALL_KWARG) {
args->kw_arg = vm_ci_kwarg(ci);
diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs
index c150d48666..41a8cdebca 100644
--- a/yjit/src/codegen.rs
+++ b/yjit/src/codegen.rs
@@ -7510,7 +7510,7 @@ fn gen_iseq_kw_call(
unsafe { get_cikw_keyword_len(ci_kwarg) }
};
let caller_keyword_len: usize = caller_keyword_len_i32.try_into().unwrap();
- let anon_kwrest = unsafe { rb_get_iseq_flags_anon_kwrest(iseq) };
+ let anon_kwrest = unsafe { rb_get_iseq_flags_anon_kwrest(iseq) && !get_iseq_flags_has_kw(iseq) };
// This struct represents the metadata about the callee-specified
// keyword parameters.