hash.c: change __except to take array argument

mrb_get_args(mrb, "*", ...) internally allocates an array when
arguments are on the stack, so passing keys as direct arguments
did not actually avoid allocation. Change __except to take a
single array argument instead, which is simpler and GC-safe.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-02-17 10:37:09 +09:00
parent 0209e8d6fd
commit d98d62ee35
2 changed files with 15 additions and 22 deletions
+6 -14
View File
@@ -5026,7 +5026,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos,
pop();
}
else if (pat_hash->rest && pat_hash->rest != (node*)-2) {
/* **var: capture remaining keys via hash.__except(key1, key2, ...) */
/* **var: capture remaining keys via hash.__except(keys_array) */
struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_hash->rest);
if (rest_var->name) {
int var_idx = lv_idx(s, rest_var->name);
@@ -5034,7 +5034,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos,
gen_move(s, recv, hash_reg, 0);
push();
if (num_keys > 0) {
/* Pass matched keys as arguments */
/* Build array of matched keys */
int i = 0;
for (pair = pat_hash->pairs; pair; pair = pair->cdr, i++) {
node *key = pair->car->car;
@@ -5046,18 +5046,10 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos,
}
push();
}
if (num_keys < CALL_MAXARGS) {
/* Direct arguments */
genop_3(s, OP_SEND, recv, sym_idx(s, MRB_SYM_2(s->mrb, __except)), num_keys);
for (i = 0; i < num_keys; i++) pop();
}
else {
/* Too many keys: pack into array */
genop_2(s, OP_ARRAY, recv + 1, num_keys);
for (i = 1; i < num_keys; i++) pop();
genop_3(s, OP_SEND, recv, sym_idx(s, MRB_SYM_2(s->mrb, __except)), CALL_MAXARGS);
pop();
}
genop_2(s, OP_ARRAY, recv + 1, num_keys);
for (i = 1; i < num_keys; i++) pop();
genop_3(s, OP_SEND, recv, sym_idx(s, MRB_SYM_2(s->mrb, __except)), 1);
pop();
}
else {
/* No keys to exclude: rest = hash.dup */
+9 -8
View File
@@ -2044,25 +2044,26 @@ mrb_hash_compact(mrb_state *mrb, mrb_value hash)
/*
* Internal method for pattern matching **rest.
* Returns a new hash excluding specified keys.
* Returns a new hash excluding keys in the given array.
*
* {a: 1, b: 2, c: 3}.__except(:a, :c) #=> {b: 2}
* {a: 1, b: 2, c: 3}.__except([:a, :c]) #=> {b: 2}
*/
static mrb_value
mrb_hash_except_keys(mrb_state *mrb, mrb_value hash)
{
const mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
mrb_value keys;
mrb_get_args(mrb, "A", &keys);
const mrb_value *ary = RARRAY_PTR(keys);
mrb_int klen = RARRAY_LEN(keys);
mrb_value result = mrb_hash_new(mrb);
struct RHash *h = mrb_hash_ptr(hash);
int ai = mrb_gc_arena_save(mrb);
H_EACH(h, entry) {
mrb_bool found = FALSE;
for (mrb_int i = 0; i < argc; i++) {
if (mrb_equal(mrb, entry->key, argv[i])) {
for (mrb_int i = 0; i < klen; i++) {
if (mrb_equal(mrb, entry->key, ary[i])) {
found = TRUE;
break;
}
@@ -2313,6 +2314,6 @@ mrb_init_hash(mrb_state *mrb)
mrb_define_method_id(mrb, h, MRB_SYM(rassoc), mrb_hash_rassoc, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, h, MRB_SYM(__merge), mrb_hash_merge_m, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, h, MRB_SYM(__compact), mrb_hash_compact, MRB_ARGS_NONE()); /* implementation of Hash#compact! */
mrb_define_method_id(mrb, h, MRB_SYM(__except), mrb_hash_except_keys, MRB_ARGS_ANY()); /* for pattern matching **rest */
mrb_define_method_id(mrb, h, MRB_SYM(__except), mrb_hash_except_keys, MRB_ARGS_REQ(1)); /* for pattern matching **rest */
}
#undef lesser