diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index e148b76cbe..f3ad64546f 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -59,7 +59,7 @@ jobs: timeout-minutes: 30 env: RUBY_TESTOPTS: "-q --tty=no" - TEST_BUNDLED_GEMS_ALLOW_FAILURES: "" + TEST_BUNDLED_GEMS_ALLOW_FAILURES: "typeprof" - uses: k0kubun/action-slack@v2.0.0 with: payload: | diff --git a/compile.c b/compile.c index 6695a0293b..d73a42a86f 100644 --- a/compile.c +++ b/compile.c @@ -5939,12 +5939,22 @@ compile_case2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no return COMPILE_OK; } -static int iseq_compile_pattern_match(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *unmatched, int in_alt_pattern, int deconstructed_pos); +static int iseq_compile_pattern_match(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *unmatched, bool in_single_pattern, bool in_alt_pattern, int base_index, bool use_deconstructed_cache); -static int iseq_compile_array_deconstruct(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *deconstruct, LABEL *deconstructed, LABEL *match_failed, LABEL *type_error, int deconstructed_pos); +static int iseq_compile_pattern_constant(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *match_failed, bool in_single_pattern, int base_index); +static int iseq_compile_array_deconstruct(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *deconstruct, LABEL *deconstructed, LABEL *match_failed, LABEL *type_error, bool in_single_pattern, int base_index, bool use_deconstructed_cache); +static int iseq_compile_pattern_set_general_errmsg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, VALUE errmsg, int base_index); +static int iseq_compile_pattern_set_length_errmsg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, VALUE errmsg, VALUE pattern_length, int base_index); +static int iseq_compile_pattern_set_eqq_errmsg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int base_index); + +#define CASE3_BI_OFFSET_DECONSTRUCTED_CACHE 0 +#define CASE3_BI_OFFSET_ERROR_STRING 1 +#define CASE3_BI_OFFSET_KEY_ERROR_P 2 +#define CASE3_BI_OFFSET_KEY_ERROR_MATCHEE 3 +#define CASE3_BI_OFFSET_KEY_ERROR_KEY 4 static int -iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *matched, LABEL *unmatched, int in_alt_pattern, int deconstructed_pos) +iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *matched, LABEL *unmatched, bool in_single_pattern, bool in_alt_pattern, int base_index, bool use_deconstructed_cache) { const int line = nd_line(node); const NODE *line_node = node; @@ -6022,31 +6032,32 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c if (use_rest_num) { ADD_INSN1(ret, line_node, putobject, INT2FIX(0)); /* allocate stack for rest_num */ ADD_INSN(ret, line_node, swap); - if (deconstructed_pos) { - deconstructed_pos++; + if (base_index) { + base_index++; } } - if (node->nd_pconst) { - ADD_INSN(ret, line_node, dup); - CHECK(COMPILE(ret, "constant", node->nd_pconst)); - ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); - ADD_INSNL(ret, line_node, branchunless, match_failed); - } + CHECK(iseq_compile_pattern_constant(iseq, ret, node, match_failed, in_single_pattern, base_index)); - CHECK(iseq_compile_array_deconstruct(iseq, ret, node, deconstruct, deconstructed, match_failed, type_error, deconstructed_pos)); + CHECK(iseq_compile_array_deconstruct(iseq, ret, node, deconstruct, deconstructed, match_failed, type_error, in_single_pattern, base_index, use_deconstructed_cache)); ADD_INSN(ret, line_node, dup); ADD_SEND(ret, line_node, idLength, INT2FIX(0)); ADD_INSN1(ret, line_node, putobject, INT2FIX(min_argc)); - ADD_SEND(ret, line_node, apinfo->rest_arg ? idGE : idEq, INT2FIX(1)); + ADD_SEND(ret, line_node, apinfo->rest_arg ? idGE : idEq, INT2FIX(1)); // (1) + if (in_single_pattern) { + CHECK(iseq_compile_pattern_set_length_errmsg(iseq, ret, node, + apinfo->rest_arg ? rb_fstring_lit("%p length mismatch (given %p, expected %p+)") : + rb_fstring_lit("%p length mismatch (given %p, expected %p)"), + INT2FIX(min_argc), base_index + 1 /* (1) */)); + } ADD_INSNL(ret, line_node, branchunless, match_failed); for (i = 0; i < pre_args_num; i++) { ADD_INSN(ret, line_node, dup); ADD_INSN1(ret, line_node, putobject, INT2FIX(i)); - ADD_SEND(ret, line_node, idAREF, INT2FIX(1)); - CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, match_failed, in_alt_pattern, FALSE)); + ADD_SEND(ret, line_node, idAREF, INT2FIX(1)); // (2) + CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, match_failed, in_single_pattern, in_alt_pattern, base_index + 1 /* (2) */, false)); args = args->nd_next; } @@ -6059,9 +6070,9 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN1(ret, line_node, putobject, INT2FIX(min_argc)); ADD_SEND(ret, line_node, idMINUS, INT2FIX(1)); ADD_INSN1(ret, line_node, setn, INT2FIX(4)); - ADD_SEND(ret, line_node, idAREF, INT2FIX(2)); + ADD_SEND(ret, line_node, idAREF, INT2FIX(2)); // (3) - CHECK(iseq_compile_pattern_match(iseq, ret, apinfo->rest_arg, match_failed, in_alt_pattern, FALSE)); + CHECK(iseq_compile_pattern_match(iseq, ret, apinfo->rest_arg, match_failed, in_single_pattern, in_alt_pattern, base_index + 1 /* (3) */, false)); } else { if (post_args_num > 0) { @@ -6083,8 +6094,8 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN1(ret, line_node, topn, INT2FIX(3)); ADD_SEND(ret, line_node, idPLUS, INT2FIX(1)); - ADD_SEND(ret, line_node, idAREF, INT2FIX(1)); - CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, match_failed, in_alt_pattern, FALSE)); + ADD_SEND(ret, line_node, idAREF, INT2FIX(1)); // (4) + CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, match_failed, in_single_pattern, in_alt_pattern, base_index + 1 /* (4) */, false)); args = args->nd_next; } @@ -6173,19 +6184,17 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c deconstruct = NEW_LABEL(line); deconstructed = NEW_LABEL(line); - if (node->nd_pconst) { - ADD_INSN(ret, line_node, dup); - CHECK(COMPILE(ret, "constant", node->nd_pconst)); - ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); - ADD_INSNL(ret, line_node, branchunless, match_failed); - } + CHECK(iseq_compile_pattern_constant(iseq, ret, node, match_failed, in_single_pattern, base_index)); - CHECK(iseq_compile_array_deconstruct(iseq, ret, node, deconstruct, deconstructed, match_failed, type_error, deconstructed_pos)); + CHECK(iseq_compile_array_deconstruct(iseq, ret, node, deconstruct, deconstructed, match_failed, type_error, in_single_pattern, base_index, use_deconstructed_cache)); ADD_INSN(ret, line_node, dup); ADD_SEND(ret, line_node, idLength, INT2FIX(0)); ADD_INSN1(ret, line_node, putobject, INT2FIX(args_num)); - ADD_SEND(ret, line_node, idGE, INT2FIX(1)); + ADD_SEND(ret, line_node, idGE, INT2FIX(1)); // (1) + if (in_single_pattern) { + CHECK(iseq_compile_pattern_set_length_errmsg(iseq, ret, node, rb_fstring_lit("%p length mismatch (given %p, expected %p+)"), INT2FIX(args_num), base_index + 1 /* (1) */)); + } ADD_INSNL(ret, line_node, branchunless, match_failed); { @@ -6196,13 +6205,13 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c int j; ADD_INSN(ret, line_node, dup); /* allocate stack for len */ - ADD_SEND(ret, line_node, idLength, INT2FIX(0)); + ADD_SEND(ret, line_node, idLength, INT2FIX(0)); // (2) ADD_INSN(ret, line_node, dup); /* allocate stack for limit */ ADD_INSN1(ret, line_node, putobject, INT2FIX(args_num)); - ADD_SEND(ret, line_node, idMINUS, INT2FIX(1)); + ADD_SEND(ret, line_node, idMINUS, INT2FIX(1)); // (3) - ADD_INSN1(ret, line_node, putobject, INT2FIX(0)); /* allocate stack for i */ + ADD_INSN1(ret, line_node, putobject, INT2FIX(0)); /* allocate stack for i */ // (4) ADD_LABEL(ret, while_begin); @@ -6218,9 +6227,9 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN1(ret, line_node, putobject, INT2FIX(j)); ADD_SEND(ret, line_node, idPLUS, INT2FIX(1)); } - ADD_SEND(ret, line_node, idAREF, INT2FIX(1)); + ADD_SEND(ret, line_node, idAREF, INT2FIX(1)); // (5) - CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, next_loop, in_alt_pattern, FALSE)); + CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, next_loop, in_single_pattern, in_alt_pattern, base_index + 4 /* (2), (3), (4), (5) */, false)); args = args->nd_next; } @@ -6228,8 +6237,8 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN1(ret, line_node, topn, INT2FIX(3)); ADD_INSN1(ret, line_node, putobject, INT2FIX(0)); ADD_INSN1(ret, line_node, topn, INT2FIX(2)); - ADD_SEND(ret, line_node, idAREF, INT2FIX(2)); - CHECK(iseq_compile_pattern_match(iseq, ret, fpinfo->pre_rest_arg, find_failed, in_alt_pattern, FALSE)); + ADD_SEND(ret, line_node, idAREF, INT2FIX(2)); // (6) + CHECK(iseq_compile_pattern_match(iseq, ret, fpinfo->pre_rest_arg, find_failed, in_single_pattern, in_alt_pattern, base_index + 4 /* (2), (3), (4), (6) */, false)); } if (NODE_NAMED_REST_P(fpinfo->post_rest_arg)) { ADD_INSN1(ret, line_node, topn, INT2FIX(3)); @@ -6237,8 +6246,8 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN1(ret, line_node, putobject, INT2FIX(args_num)); ADD_SEND(ret, line_node, idPLUS, INT2FIX(1)); ADD_INSN1(ret, line_node, topn, INT2FIX(3)); - ADD_SEND(ret, line_node, idAREF, INT2FIX(2)); - CHECK(iseq_compile_pattern_match(iseq, ret, fpinfo->post_rest_arg, find_failed, in_alt_pattern, FALSE)); + ADD_SEND(ret, line_node, idAREF, INT2FIX(2)); // (7) + CHECK(iseq_compile_pattern_match(iseq, ret, fpinfo->post_rest_arg, find_failed, in_single_pattern, in_alt_pattern, base_index + 4 /* (2), (3),(4), (7) */, false)); } ADD_INSNL(ret, line_node, jump, find_succeeded); @@ -6248,16 +6257,25 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSNL(ret, line_node, jump, while_begin); ADD_LABEL(ret, find_failed); - ADD_INSN(ret, line_node, pop); - ADD_INSN(ret, line_node, pop); - ADD_INSN(ret, line_node, pop); + ADD_INSN1(ret, line_node, adjuststack, INT2FIX(3)); + if (in_single_pattern) { + ADD_INSN1(ret, line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(ret, line_node, putobject, rb_fstring_lit("%p does not match to find pattern")); + ADD_INSN1(ret, line_node, topn, INT2FIX(2)); + ADD_SEND(ret, line_node, id_core_sprintf, INT2FIX(2)); // (8) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_ERROR_STRING + 1 /* (8) */)); // (9) + + ADD_INSN1(ret, line_node, putobject, Qfalse); + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_P + 2 /* (8), (9) */)); + + ADD_INSN(ret, line_node, pop); + ADD_INSN(ret, line_node, pop); + } ADD_INSNL(ret, line_node, jump, match_failed); ADD_INSN1(ret, line_node, dupn, INT2FIX(3)); ADD_LABEL(ret, find_succeeded); - ADD_INSN(ret, line_node, pop); - ADD_INSN(ret, line_node, pop); - ADD_INSN(ret, line_node, pop); + ADD_INSN1(ret, line_node, adjuststack, INT2FIX(3)); } ADD_INSN(ret, line_node, pop); @@ -6352,16 +6370,14 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c } } - if (node->nd_pconst) { - ADD_INSN(ret, line_node, dup); - CHECK(COMPILE(ret, "constant", node->nd_pconst)); - ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); - ADD_INSNL(ret, line_node, branchunless, match_failed); - } + CHECK(iseq_compile_pattern_constant(iseq, ret, node, match_failed, in_single_pattern, base_index)); ADD_INSN(ret, line_node, dup); ADD_INSN1(ret, line_node, putobject, ID2SYM(rb_intern("deconstruct_keys"))); - ADD_SEND(ret, line_node, idRespond_to, INT2FIX(1)); + ADD_SEND(ret, line_node, idRespond_to, INT2FIX(1)); // (1) + if (in_single_pattern) { + CHECK(iseq_compile_pattern_set_general_errmsg(iseq, ret, node, rb_fstring_lit("%p does not respond to #deconstruct_keys"), base_index + 1 /* (1) */)); + } ADD_INSNL(ret, line_node, branchunless, match_failed); if (NIL_P(keys)) { @@ -6371,7 +6387,7 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN1(ret, line_node, duparray, keys); RB_OBJ_WRITTEN(iseq, Qundef, rb_obj_hide(keys)); } - ADD_SEND(ret, line_node, rb_intern("deconstruct_keys"), INT2FIX(1)); + ADD_SEND(ret, line_node, rb_intern("deconstruct_keys"), INT2FIX(1)); // (2) ADD_INSN(ret, line_node, dup); ADD_INSN1(ret, line_node, checktype, INT2FIX(T_HASH)); @@ -6402,13 +6418,33 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN(ret, line_node, dup); ADD_INSN1(ret, line_node, putobject, key); - ADD_SEND(ret, line_node, rb_intern("key?"), INT2FIX(1)); + ADD_SEND(ret, line_node, rb_intern("key?"), INT2FIX(1)); // (3) + if (in_single_pattern) { + LABEL *match_succeeded; + match_succeeded = NEW_LABEL(line); + + ADD_INSN(ret, line_node, dup); + ADD_INSNL(ret, line_node, branchif, match_succeeded); + + ADD_INSN1(ret, line_node, putobject, rb_str_freeze(rb_sprintf("key not found: %+"PRIsVALUE, key))); // (4) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_ERROR_STRING + 2 /* (3), (4) */)); + ADD_INSN1(ret, line_node, putobject, Qtrue); // (5) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_P + 3 /* (3), (4), (5) */)); + ADD_INSN1(ret, line_node, topn, INT2FIX(3)); // (6) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_MATCHEE + 4 /* (3), (4), (5), (6) */)); + ADD_INSN1(ret, line_node, putobject, key); // (7) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_KEY + 5 /* (3), (4), (5), (6), (7) */)); + + ADD_INSN1(ret, line_node, adjuststack, INT2FIX(4)); + + ADD_LABEL(ret, match_succeeded); + } ADD_INSNL(ret, line_node, branchunless, match_failed); ADD_INSN(match_values, line_node, dup); ADD_INSN1(match_values, line_node, putobject, key); - ADD_SEND(match_values, line_node, node->nd_pkwrestarg ? rb_intern("delete") : idAREF, INT2FIX(1)); - CHECK(iseq_compile_pattern_match(iseq, match_values, value_node, match_failed, in_alt_pattern, FALSE)); + ADD_SEND(match_values, line_node, node->nd_pkwrestarg ? rb_intern("delete") : idAREF, INT2FIX(1)); // (8) + CHECK(iseq_compile_pattern_match(iseq, match_values, value_node, match_failed, in_single_pattern, in_alt_pattern, base_index + 1 /* (8) */, false)); args = args->nd_next->nd_next; } ADD_SEQ(ret, match_values); @@ -6416,19 +6452,25 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c } else { ADD_INSN(ret, line_node, dup); - ADD_SEND(ret, line_node, idEmptyP, INT2FIX(0)); + ADD_SEND(ret, line_node, idEmptyP, INT2FIX(0)); // (9) + if (in_single_pattern) { + CHECK(iseq_compile_pattern_set_general_errmsg(iseq, ret, node, rb_fstring_lit("%p is not empty"), base_index + 1 /* (9) */)); + } ADD_INSNL(ret, line_node, branchunless, match_failed); } if (node->nd_pkwrestarg) { if (node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD) { ADD_INSN(ret, line_node, dup); - ADD_SEND(ret, line_node, idEmptyP, INT2FIX(0)); + ADD_SEND(ret, line_node, idEmptyP, INT2FIX(0)); // (10) + if (in_single_pattern) { + CHECK(iseq_compile_pattern_set_general_errmsg(iseq, ret, node, rb_fstring_lit("rest of %p is not empty"), base_index + 1 /* (10) */)); + } ADD_INSNL(ret, line_node, branchunless, match_failed); } else { - ADD_INSN(ret, line_node, dup); - CHECK(iseq_compile_pattern_match(iseq, ret, node->nd_pkwrestarg, match_failed, in_alt_pattern, FALSE)); + ADD_INSN(ret, line_node, dup); // (11) + CHECK(iseq_compile_pattern_match(iseq, ret, node->nd_pkwrestarg, match_failed, in_single_pattern, in_alt_pattern, base_index + 1 /* (11) */, false)); } } @@ -6472,8 +6514,14 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c case NODE_COLON2: case NODE_COLON3: case NODE_BEGIN: - CHECK(COMPILE(ret, "case in literal", node)); - ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); + CHECK(COMPILE(ret, "case in literal", node)); // (1) + if (in_single_pattern) { + ADD_INSN1(ret, line_node, dupn, INT2FIX(2)); + } + ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); // (2) + if (in_single_pattern) { + CHECK(iseq_compile_pattern_set_eqq_errmsg(iseq, ret, node, base_index + 2 /* (1), (2) */)); + } ADD_INSNL(ret, line_node, branchif, matched); ADD_INSNL(ret, line_node, jump, unmatched); break; @@ -6524,8 +6572,30 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c case NODE_UNLESS: { LABEL *match_failed; match_failed = unmatched; - CHECK(iseq_compile_pattern_match(iseq, ret, node->nd_body, unmatched, in_alt_pattern, deconstructed_pos)); + CHECK(iseq_compile_pattern_match(iseq, ret, node->nd_body, unmatched, in_single_pattern, in_alt_pattern, base_index, use_deconstructed_cache)); CHECK(COMPILE(ret, "case in if", node->nd_cond)); + if (in_single_pattern) { + LABEL *match_succeeded; + match_succeeded = NEW_LABEL(line); + + ADD_INSN(ret, line_node, dup); + if (nd_type(node) == NODE_IF) { + ADD_INSNL(ret, line_node, branchif, match_succeeded); + } + else { + ADD_INSNL(ret, line_node, branchunless, match_succeeded); + } + + ADD_INSN1(ret, line_node, putobject, rb_fstring_lit("guard clause does not return true")); // (1) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_ERROR_STRING + 1 /* (1) */)); // (2) + ADD_INSN1(ret, line_node, putobject, Qfalse); + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_P + 2 /* (1), (2) */)); + + ADD_INSN(ret, line_node, pop); + ADD_INSN(ret, line_node, pop); + + ADD_LABEL(ret, match_succeeded); + } if (nd_type(node) == NODE_IF) { ADD_INSNL(ret, line_node, branchunless, match_failed); } @@ -6546,9 +6616,9 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c return COMPILE_NG; } - ADD_INSN(ret, line_node, dup); - CHECK(iseq_compile_pattern_match(iseq, ret, n->nd_head, match_failed, in_alt_pattern, deconstructed_pos ? deconstructed_pos + 1 : FALSE)); - CHECK(iseq_compile_pattern_each(iseq, ret, n->nd_next->nd_head, matched, match_failed, in_alt_pattern, FALSE)); + ADD_INSN(ret, line_node, dup); // (1) + CHECK(iseq_compile_pattern_match(iseq, ret, n->nd_head, match_failed, in_single_pattern, in_alt_pattern, base_index + 1 /* (1) */, use_deconstructed_cache)); + CHECK(iseq_compile_pattern_each(iseq, ret, n->nd_next->nd_head, matched, match_failed, in_single_pattern, in_alt_pattern, base_index, false)); ADD_INSN(ret, line_node, putnil); ADD_LABEL(ret, match_failed); @@ -6561,14 +6631,14 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c match_succeeded = NEW_LABEL(line); fin = NEW_LABEL(line); - ADD_INSN(ret, line_node, dup); - CHECK(iseq_compile_pattern_each(iseq, ret, node->nd_1st, match_succeeded, fin, TRUE, deconstructed_pos ? deconstructed_pos + 1 : FALSE)); + ADD_INSN(ret, line_node, dup); // (1) + CHECK(iseq_compile_pattern_each(iseq, ret, node->nd_1st, match_succeeded, fin, in_single_pattern, true, base_index + 1 /* (1) */, use_deconstructed_cache)); ADD_LABEL(ret, match_succeeded); ADD_INSN(ret, line_node, pop); ADD_INSNL(ret, line_node, jump, matched); ADD_INSN(ret, line_node, putnil); ADD_LABEL(ret, fin); - CHECK(iseq_compile_pattern_each(iseq, ret, node->nd_2nd, matched, unmatched, TRUE, deconstructed_pos)); + CHECK(iseq_compile_pattern_each(iseq, ret, node->nd_2nd, matched, unmatched, in_single_pattern, true, base_index, use_deconstructed_cache)); break; } default: @@ -6578,35 +6648,54 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c } static int -iseq_compile_pattern_match(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *unmatched, int in_alt_pattern, int deconstructed_pos) +iseq_compile_pattern_match(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *unmatched, bool in_single_pattern, bool in_alt_pattern, int base_index, bool use_deconstructed_cache) { LABEL *fin = NEW_LABEL(nd_line(node)); - CHECK(iseq_compile_pattern_each(iseq, ret, node, fin, unmatched, in_alt_pattern, deconstructed_pos)); + CHECK(iseq_compile_pattern_each(iseq, ret, node, fin, unmatched, in_single_pattern, in_alt_pattern, base_index, use_deconstructed_cache)); ADD_LABEL(ret, fin); return COMPILE_OK; } static int -iseq_compile_array_deconstruct(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *deconstruct, LABEL *deconstructed, LABEL *match_failed, LABEL *type_error, int deconstructed_pos) +iseq_compile_pattern_constant(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *match_failed, bool in_single_pattern, int base_index) +{ + const NODE *line_node = node; + + if (node->nd_pconst) { + ADD_INSN(ret, line_node, dup); // (1) + CHECK(COMPILE(ret, "constant", node->nd_pconst)); // (2) + if (in_single_pattern) { + ADD_INSN1(ret, line_node, dupn, INT2FIX(2)); + } + ADD_INSN1(ret, line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); // (3) + if (in_single_pattern) { + CHECK(iseq_compile_pattern_set_eqq_errmsg(iseq, ret, node, base_index + 3 /* (1), (2), (3) */)); + } + ADD_INSNL(ret, line_node, branchunless, match_failed); + } + return COMPILE_OK; +} + + +static int +iseq_compile_array_deconstruct(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *deconstruct, LABEL *deconstructed, LABEL *match_failed, LABEL *type_error, bool in_single_pattern, int base_index, bool use_deconstructed_cache) { const NODE *line_node = node; // NOTE: this optimization allows us to re-use the #deconstruct value // (or its absence). - // `deconstructed_pos` contains the distance to the stack relative location - // where the value is stored. - if (deconstructed_pos) { + if (use_deconstructed_cache) { // If value is nil then we haven't tried to deconstruct - ADD_INSN1(ret, line_node, topn, INT2FIX(deconstructed_pos)); + ADD_INSN1(ret, line_node, topn, INT2FIX(base_index + CASE3_BI_OFFSET_DECONSTRUCTED_CACHE)); ADD_INSNL(ret, line_node, branchnil, deconstruct); // If false then the value is not deconstructable - ADD_INSN1(ret, line_node, topn, INT2FIX(deconstructed_pos)); + ADD_INSN1(ret, line_node, topn, INT2FIX(base_index + CASE3_BI_OFFSET_DECONSTRUCTED_CACHE)); ADD_INSNL(ret, line_node, branchunless, match_failed); // Drop value, add deconstructed to the stack and jump - ADD_INSN(ret, line_node, pop); - ADD_INSN1(ret, line_node, topn, INT2FIX(deconstructed_pos - 1)); + ADD_INSN(ret, line_node, pop); // (1) + ADD_INSN1(ret, line_node, topn, INT2FIX(base_index + CASE3_BI_OFFSET_DECONSTRUCTED_CACHE - 1 /* (1) */)); ADD_INSNL(ret, line_node, jump, deconstructed); } else { @@ -6616,11 +6705,15 @@ iseq_compile_array_deconstruct(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NO ADD_LABEL(ret, deconstruct); ADD_INSN(ret, line_node, dup); ADD_INSN1(ret, line_node, putobject, ID2SYM(rb_intern("deconstruct"))); - ADD_SEND(ret, line_node, idRespond_to, INT2FIX(1)); + ADD_SEND(ret, line_node, idRespond_to, INT2FIX(1)); // (2) // Cache the result of respond_to? (in case it's false is stays there, if true - it's overwritten after #deconstruct) - if (deconstructed_pos) { - ADD_INSN1(ret, line_node, setn, INT2FIX(deconstructed_pos + 1)); + if (use_deconstructed_cache) { + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_DECONSTRUCTED_CACHE + 1 /* (2) */)); + } + + if (in_single_pattern) { + CHECK(iseq_compile_pattern_set_general_errmsg(iseq, ret, node, rb_fstring_lit("%p does not respond to #deconstruct"), base_index + 1 /* (2) */)); } ADD_INSNL(ret, line_node, branchunless, match_failed); @@ -6628,20 +6721,129 @@ iseq_compile_array_deconstruct(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NO ADD_SEND(ret, line_node, rb_intern("deconstruct"), INT2FIX(0)); // Cache the result (if it's cacheable - currently, only top-level array patterns) - if (deconstructed_pos) { - ADD_INSN1(ret, line_node, setn, INT2FIX(deconstructed_pos)); + if (use_deconstructed_cache) { + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_DECONSTRUCTED_CACHE)); } ADD_INSN(ret, line_node, dup); ADD_INSN1(ret, line_node, checktype, INT2FIX(T_ARRAY)); ADD_INSNL(ret, line_node, branchunless, type_error); - ADD_INSNL(ret, line_node, jump, deconstructed); ADD_LABEL(ret, deconstructed); return COMPILE_OK; } +static int +iseq_compile_pattern_set_general_errmsg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, VALUE errmsg, int base_index) +{ + /* + * if match_succeeded? + * goto match_succeeded + * end + * error_string = FrozenCore.sprintf(errmsg, matchee) + * key_error_p = false + * match_succeeded: + */ + const int line = nd_line(node); + const NODE *line_node = node; + LABEL *match_succeeded = NEW_LABEL(line); + + ADD_INSN(ret, line_node, dup); + ADD_INSNL(ret, line_node, branchif, match_succeeded); + + ADD_INSN1(ret, line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(ret, line_node, putobject, errmsg); + ADD_INSN1(ret, line_node, topn, INT2FIX(3)); + ADD_SEND(ret, line_node, id_core_sprintf, INT2FIX(2)); // (1) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_ERROR_STRING + 1 /* (1) */)); // (2) + + ADD_INSN1(ret, line_node, putobject, Qfalse); + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_P + 2 /* (1), (2) */)); + + ADD_INSN(ret, line_node, pop); + ADD_INSN(ret, line_node, pop); + ADD_LABEL(ret, match_succeeded); + + return COMPILE_OK; +} + +static int +iseq_compile_pattern_set_length_errmsg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, VALUE errmsg, VALUE pattern_length, int base_index) +{ + /* + * if match_succeeded? + * goto match_succeeded + * end + * error_string = FrozenCore.sprintf(errmsg, matchee, matchee.length, pat.length) + * key_error_p = false + * match_succeeded: + */ + const int line = nd_line(node); + const NODE *line_node = node; + LABEL *match_succeeded = NEW_LABEL(line); + + ADD_INSN(ret, line_node, dup); + ADD_INSNL(ret, line_node, branchif, match_succeeded); + + ADD_INSN1(ret, line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(ret, line_node, putobject, errmsg); + ADD_INSN1(ret, line_node, topn, INT2FIX(3)); + ADD_INSN(ret, line_node, dup); + ADD_SEND(ret, line_node, idLength, INT2FIX(0)); + ADD_INSN1(ret, line_node, putobject, pattern_length); + ADD_SEND(ret, line_node, id_core_sprintf, INT2FIX(4)); // (1) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_ERROR_STRING + 1 /* (1) */)); // (2) + + ADD_INSN1(ret, line_node, putobject, Qfalse); + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_P + 2/* (1), (2) */)); + + ADD_INSN(ret, line_node, pop); + ADD_INSN(ret, line_node, pop); + ADD_LABEL(ret, match_succeeded); + + return COMPILE_OK; +} + +static int +iseq_compile_pattern_set_eqq_errmsg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int base_index) +{ + /* + * if match_succeeded? + * goto match_succeeded + * end + * error_string = FrozenCore.sprintf("%p === %p does not return true", pat, matchee) + * key_error_p = false + * match_succeeded: + */ + const int line = nd_line(node); + const NODE *line_node = node; + LABEL *match_succeeded = NEW_LABEL(line); + + ADD_INSN(ret, line_node, dup); + ADD_INSNL(ret, line_node, branchif, match_succeeded); + + ADD_INSN1(ret, line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(ret, line_node, putobject, rb_fstring_lit("%p === %p does not return true")); + ADD_INSN1(ret, line_node, topn, INT2FIX(3)); + ADD_INSN1(ret, line_node, topn, INT2FIX(5)); + ADD_SEND(ret, line_node, id_core_sprintf, INT2FIX(3)); // (1) + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_ERROR_STRING + 1 /* (1) */)); // (2) + + ADD_INSN1(ret, line_node, putobject, Qfalse); + ADD_INSN1(ret, line_node, setn, INT2FIX(base_index + CASE3_BI_OFFSET_KEY_ERROR_P + 2 /* (1), (2) */)); + + ADD_INSN(ret, line_node, pop); + ADD_INSN(ret, line_node, pop); + + ADD_LABEL(ret, match_succeeded); + ADD_INSN1(ret, line_node, setn, INT2FIX(2)); + ADD_INSN(ret, line_node, pop); + ADD_INSN(ret, line_node, pop); + + return COMPILE_OK; +} + static int compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_node, int popped) { @@ -6656,6 +6858,7 @@ compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no const NODE *line_node; VALUE branches = 0; int branch_id = 0; + bool single_pattern; INIT_ANCHOR(head); INIT_ANCHOR(body_seq); @@ -6668,10 +6871,18 @@ compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no type = nd_type(node); line = nd_line(node); line_node = node; + single_pattern = !node->nd_next; endlabel = NEW_LABEL(line); elselabel = NEW_LABEL(line); + if (single_pattern) { + /* allocate stack for ... */ + ADD_INSN(head, line_node, putnil); /* key_error_key */ + ADD_INSN(head, line_node, putnil); /* key_error_matchee */ + ADD_INSN1(head, line_node, putobject, Qfalse); /* key_error_p */ + ADD_INSN(head, line_node, putnil); /* error_string */ + } ADD_INSN(head, line_node, putnil); /* allocate stack for cached #deconstruct value */ CHECK(COMPILE(head, "case base", orig_node->nd_head)); @@ -6686,8 +6897,7 @@ compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no } l1 = NEW_LABEL(line); ADD_LABEL(body_seq, l1); - ADD_INSN(body_seq, line_node, pop); - ADD_INSN(body_seq, line_node, pop); /* discard cached #deconstruct value */ + ADD_INSN1(body_seq, line_node, adjuststack, INT2FIX(single_pattern ? 6 : 2)); add_trace_branch_coverage( iseq, body_seq, @@ -6702,10 +6912,9 @@ compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no if (pattern) { int pat_line = nd_line(pattern); LABEL *next_pat = NEW_LABEL(pat_line); - ADD_INSN (cond_seq, pattern, dup); - // NOTE: set deconstructed_pos to the current cached value location - // (it's "under" the matchee value, so it's position is 2) - CHECK(iseq_compile_pattern_each(iseq, cond_seq, pattern, l1, next_pat, FALSE, 2)); + ADD_INSN (cond_seq, pattern, dup); /* dup case VAL */ + // NOTE: set base_index (it's "under" the matchee value, so it's position is 2) + CHECK(iseq_compile_pattern_each(iseq, cond_seq, pattern, l1, next_pat, single_pattern, false, 2, true)); ADD_LABEL(cond_seq, next_pat); LABEL_UNREMOVABLE(next_pat); } @@ -6740,17 +6949,62 @@ compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no ADD_LABEL(cond_seq, elselabel); add_trace_branch_coverage(iseq, cond_seq, orig_node, branch_id, "else", branches); ADD_INSN1(cond_seq, orig_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(cond_seq, orig_node, putobject, rb_eNoMatchingPatternError); - ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(2)); - ADD_SEND(cond_seq, orig_node, id_core_raise, INT2FIX(2)); - ADD_INSN(cond_seq, orig_node, pop); - ADD_INSN(cond_seq, orig_node, pop); - ADD_INSN(cond_seq, orig_node, pop); /* discard cached #deconstruct value */ + + if (single_pattern) { + /* + * if key_error_p + * FrozenCore.raise NoMatchingPatternKeyError.new(FrozenCore.sprintf("%p: %s", case_val, error_string), matchee: key_error_matchee, key: key_error_key) + * else + * FrozenCore.raise NoMatchingPatternError, FrozenCore.sprintf("%p: %s", case_val, error_string) + * end + */ + LABEL *key_error, *fin; + struct rb_callinfo_kwarg *kw_arg; + + key_error = NEW_LABEL(line); + fin = NEW_LABEL(line); + + kw_arg = rb_xmalloc_mul_add(2, sizeof(VALUE), sizeof(struct rb_callinfo_kwarg)); + kw_arg->keyword_len = 2; + kw_arg->keywords[0] = ID2SYM(rb_intern("matchee")); + kw_arg->keywords[1] = ID2SYM(rb_intern("key")); + + ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(CASE3_BI_OFFSET_KEY_ERROR_P + 2)); + ADD_INSNL(cond_seq, orig_node, branchif, key_error); + ADD_INSN1(cond_seq, orig_node, putobject, rb_eNoMatchingPatternError); + ADD_INSN1(cond_seq, orig_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(cond_seq, orig_node, putobject, rb_fstring_lit("%p: %s")); + ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(4)); /* case VAL */ + ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(CASE3_BI_OFFSET_ERROR_STRING + 6)); + ADD_SEND(cond_seq, orig_node, id_core_sprintf, INT2FIX(3)); + ADD_SEND(cond_seq, orig_node, id_core_raise, INT2FIX(2)); + ADD_INSNL(cond_seq, orig_node, jump, fin); + + ADD_LABEL(cond_seq, key_error); + ADD_INSN1(cond_seq, orig_node, putobject, rb_eNoMatchingPatternKeyError); + ADD_INSN1(cond_seq, orig_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + ADD_INSN1(cond_seq, orig_node, putobject, rb_fstring_lit("%p: %s")); + ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(4)); /* case VAL */ + ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(CASE3_BI_OFFSET_ERROR_STRING + 6)); + ADD_SEND(cond_seq, orig_node, id_core_sprintf, INT2FIX(3)); + ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(CASE3_BI_OFFSET_KEY_ERROR_MATCHEE + 4)); + ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(CASE3_BI_OFFSET_KEY_ERROR_KEY + 5)); + ADD_SEND_R(cond_seq, orig_node, rb_intern("new"), INT2FIX(1), NULL, INT2FIX(VM_CALL_KWARG), kw_arg); + ADD_SEND(cond_seq, orig_node, id_core_raise, INT2FIX(1)); + + ADD_LABEL(cond_seq, fin); + } + else { + ADD_INSN1(cond_seq, orig_node, putobject, rb_eNoMatchingPatternError); + ADD_INSN1(cond_seq, orig_node, topn, INT2FIX(2)); + ADD_SEND(cond_seq, orig_node, id_core_raise, INT2FIX(2)); + } + ADD_INSN1(cond_seq, orig_node, adjuststack, INT2FIX(single_pattern ? 7 : 3)); if (!popped) { ADD_INSN(cond_seq, orig_node, putnil); } ADD_INSNL(cond_seq, orig_node, jump, endlabel); - ADD_INSN(cond_seq, line_node, putnil); + ADD_INSN1(cond_seq, orig_node, dupn, INT2FIX(single_pattern ? 5 : 1)); if (popped) { ADD_INSN(cond_seq, line_node, putnil); } @@ -6762,6 +7016,12 @@ compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no return COMPILE_OK; } +#undef CASE3_BI_OFFSET_DECONSTRUCTED_CACHE +#undef CASE3_BI_OFFSET_ERROR_STRING +#undef CASE3_BI_OFFSET_KEY_ERROR_P +#undef CASE3_BI_OFFSET_KEY_ERROR_MATCHEE +#undef CASE3_BI_OFFSET_KEY_ERROR_KEY + static int compile_loop(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped, const enum node_type type) { @@ -11558,6 +11818,7 @@ enum ibf_object_class_index { IBF_OBJECT_CLASS_STANDARD_ERROR, IBF_OBJECT_CLASS_NO_MATCHING_PATTERN_ERROR, IBF_OBJECT_CLASS_TYPE_ERROR, + IBF_OBJECT_CLASS_NO_MATCHING_PATTERN_KEY_ERROR, }; struct ibf_object_regexp { @@ -11647,6 +11908,9 @@ ibf_dump_object_class(struct ibf_dump *dump, VALUE obj) else if (obj == rb_eTypeError) { cindex = IBF_OBJECT_CLASS_TYPE_ERROR; } + else if (obj == rb_eNoMatchingPatternKeyError) { + cindex = IBF_OBJECT_CLASS_NO_MATCHING_PATTERN_KEY_ERROR; + } else { rb_obj_info_dump(obj); rb_p(obj); @@ -11671,6 +11935,8 @@ ibf_load_object_class(const struct ibf_load *load, const struct ibf_object_heade return rb_eNoMatchingPatternError; case IBF_OBJECT_CLASS_TYPE_ERROR: return rb_eTypeError; + case IBF_OBJECT_CLASS_NO_MATCHING_PATTERN_KEY_ERROR: + return rb_eNoMatchingPatternKeyError; } rb_raise(rb_eArgError, "ibf_load_object_class: unknown class (%d)", (int)cindex); diff --git a/defs/id.def b/defs/id.def index 506dc95050..8df6cf12e2 100644 --- a/defs/id.def +++ b/defs/id.def @@ -87,6 +87,7 @@ firstline, predefined = __LINE__+1, %[\ core#hash_merge_ptr core#hash_merge_kwd core#raise + core#sprintf - debug#created_info diff --git a/error.c b/error.c index f231c7458c..79a24a71fd 100644 --- a/error.c +++ b/error.c @@ -1104,6 +1104,7 @@ VALUE rb_eNotImpError; VALUE rb_eNoMemError; VALUE rb_cNameErrorMesg; VALUE rb_eNoMatchingPatternError; +VALUE rb_eNoMatchingPatternKeyError; VALUE rb_eScriptError; VALUE rb_eSyntaxError; @@ -1116,7 +1117,7 @@ static VALUE rb_eNOERROR; ID ruby_static_id_cause; #define id_cause ruby_static_id_cause static ID id_message, id_backtrace; -static ID id_key, id_args, id_Errno, id_errno, id_i_path; +static ID id_key, id_matchee, id_args, id_Errno, id_errno, id_i_path; static ID id_receiver, id_recv, id_iseq, id_local_variables; static ID id_private_call_p, id_top, id_bottom; #define id_bt idBt @@ -2162,6 +2163,73 @@ key_err_initialize(int argc, VALUE *argv, VALUE self) return self; } +/* + * call-seq: + * no_matching_pattern_key_error.matchee -> object + * + * Return the matchee associated with this NoMatchingPatternKeyError exception. + */ + +static VALUE +no_matching_pattern_key_err_matchee(VALUE self) +{ + VALUE matchee; + + matchee = rb_ivar_lookup(self, id_matchee, Qundef); + if (matchee != Qundef) return matchee; + rb_raise(rb_eArgError, "no matchee is available"); +} + +/* + * call-seq: + * no_matching_pattern_key_error.key -> object + * + * Return the key caused this NoMatchingPatternKeyError exception. + */ + +static VALUE +no_matching_pattern_key_err_key(VALUE self) +{ + VALUE key; + + key = rb_ivar_lookup(self, id_key, Qundef); + if (key != Qundef) return key; + rb_raise(rb_eArgError, "no key is available"); +} + +/* + * call-seq: + * NoMatchingPatternKeyError.new(message=nil, matchee: nil, key: nil) -> no_matching_pattern_key_error + * + * Construct a new +NoMatchingPatternKeyError+ exception with the given message, + * matchee and key. + */ + +static VALUE +no_matching_pattern_key_err_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE options; + + rb_call_super(rb_scan_args(argc, argv, "01:", NULL, &options), argv); + + if (!NIL_P(options)) { + ID keywords[2]; + VALUE values[numberof(keywords)]; + int i; + keywords[0] = id_matchee; + keywords[1] = id_key; + rb_get_kwargs(options, keywords, 0, numberof(values), values); + for (i = 0; i < numberof(values); ++i) { + if (values[i] != Qundef) { + rb_ivar_set(self, keywords[i], values[i]); + } + } + } + + return self; +} + + /* * call-seq: * SyntaxError.new([msg]) -> syntax_error @@ -2875,6 +2943,10 @@ Init_Exception(void) rb_eEncodingError = rb_define_class("EncodingError", rb_eStandardError); rb_eEncCompatError = rb_define_class_under(rb_cEncoding, "CompatibilityError", rb_eEncodingError); rb_eNoMatchingPatternError = rb_define_class("NoMatchingPatternError", rb_eStandardError); + rb_eNoMatchingPatternKeyError = rb_define_class("NoMatchingPatternKeyError", rb_eNoMatchingPatternError); + rb_define_method(rb_eNoMatchingPatternKeyError, "initialize", no_matching_pattern_key_err_initialize, -1); + rb_define_method(rb_eNoMatchingPatternKeyError, "matchee", no_matching_pattern_key_err_matchee, 0); + rb_define_method(rb_eNoMatchingPatternKeyError, "key", no_matching_pattern_key_err_key, 0); syserr_tbl = st_init_numtable(); rb_eSystemCallError = rb_define_class("SystemCallError", rb_eStandardError); @@ -2898,6 +2970,7 @@ Init_Exception(void) id_message = rb_intern_const("message"); id_backtrace = rb_intern_const("backtrace"); id_key = rb_intern_const("key"); + id_matchee = rb_intern_const("matchee"); id_args = rb_intern_const("args"); id_receiver = rb_intern_const("receiver"); id_private_call_p = rb_intern_const("private_call?"); diff --git a/include/ruby/internal/globals.h b/include/ruby/internal/globals.h index ddd731349e..d54f31d871 100644 --- a/include/ruby/internal/globals.h +++ b/include/ruby/internal/globals.h @@ -107,6 +107,7 @@ RUBY_EXTERN VALUE rb_eRegexpError; RUBY_EXTERN VALUE rb_eEncodingError; RUBY_EXTERN VALUE rb_eEncCompatError; RUBY_EXTERN VALUE rb_eNoMatchingPatternError; +RUBY_EXTERN VALUE rb_eNoMatchingPatternKeyError; RUBY_EXTERN VALUE rb_eScriptError; RUBY_EXTERN VALUE rb_eNameError; diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb index 2fdad78699..42b6802fe6 100644 --- a/test/ruby/test_pattern_matching.rb +++ b/test/ruby/test_pattern_matching.rb @@ -1546,6 +1546,133 @@ END def test_experimental_warning assert_experimental_warning("case [0]; in [*, 0, *]; end") end + + ################################################################ + + def test_single_pattern_error_value_pattern + assert_raise_with_message(NoMatchingPatternError, "0: 1 === 0 does not return true") do + 0 => 1 + end + end + + def test_single_pattern_error_array_pattern + assert_raise_with_message(NoMatchingPatternError, "[]: Hash === [] does not return true") do + [] => Hash[] + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct") do + 0 => [] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] length mismatch (given 1, expected 0)") do + [0] => [] + end + + assert_raise_with_message(NoMatchingPatternError, "[]: [] length mismatch (given 0, expected 1+)") do + [] => [_, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0, 0]: 1 === 0 does not return true") do + [0, 0] => [0, 1] + end + + assert_raise_with_message(NoMatchingPatternError, "[0, 0]: 1 === 0 does not return true") do + [0, 0] => [*, 0, 1] + end + end + + def test_single_pattern_error_find_pattern + assert_raise_with_message(NoMatchingPatternError, "[]: Hash === [] does not return true") do + [] => Hash[*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct") do + 0 => [*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[]: [] length mismatch (given 0, expected 1+)") do + [] => [*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] does not match to find pattern") do + [0] => [*, 1, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] does not match to find pattern") do + [0] => [*, {a:}, *] + raise a # suppress "unused variable: a" warning + end + end + + def test_single_pattern_error_hash_pattern + assert_raise_with_message(NoMatchingPatternError, "{}: Array === {} does not return true") do + {} => Array[a:] + raise a # suppress "unused variable: a" warning + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct_keys") do + 0 => {a:} + raise a # suppress "unused variable: a" warning + end + + assert_raise_with_message(NoMatchingPatternKeyError, "{:a=>0}: key not found: :aa") do + {a: 0} => {aa:} + raise aa # suppress "unused variable: aa" warning + rescue NoMatchingPatternKeyError => e + assert_equal({a: 0}, e.matchee) + assert_equal(:aa, e.key) + raise e + end + + assert_raise_with_message(NoMatchingPatternKeyError, "{:a=>{:b=>0}}: key not found: :bb") do + {a: {b: 0}} => {a: {bb:}} + raise bb # suppress "unused variable: bb" warning + rescue NoMatchingPatternKeyError => e + assert_equal({b: 0}, e.matchee) + assert_equal(:bb, e.key) + raise e + end + + assert_raise_with_message(NoMatchingPatternError, "{:a=>0}: 1 === 0 does not return true") do + {a: 0} => {a: 1} + end + + assert_raise_with_message(NoMatchingPatternError, "{:a=>0}: {:a=>0} is not empty") do + {a: 0} => {} + end + + assert_raise_with_message(NoMatchingPatternError, "[{:a=>0}]: rest of {:a=>0} is not empty") do + [{a: 0}] => [{**nil}] + end + end + + def test_single_pattern_error_as_pattern + assert_raise_with_message(NoMatchingPatternError, "[0]: 1 === 0 does not return true") do + case [0] + in [1] => _ + end + end + end + + def test_single_pattern_error_alternative_pattern + assert_raise_with_message(NoMatchingPatternError, "0: 2 === 0 does not return true") do + 0 => 1 | 2 + end + end + + def test_single_pattern_error_guard_clause + assert_raise_with_message(NoMatchingPatternError, "0: guard clause does not return true") do + case 0 + in _ if false + end + end + + assert_raise_with_message(NoMatchingPatternError, "0: guard clause does not return true") do + case 0 + in _ unless true + end + end + end end END_of_GUARD Warning[:experimental] = experimental diff --git a/vm.c b/vm.c index 307c5952f4..f349094190 100644 --- a/vm.c +++ b/vm.c @@ -3317,6 +3317,12 @@ f_lambda(VALUE _) return rb_block_lambda(); } +static VALUE +f_sprintf(int c, const VALUE *v, VALUE _) +{ + return rb_f_sprintf(c, v); +} + static VALUE vm_mtbl(VALUE self, VALUE obj, VALUE sym) { @@ -3371,6 +3377,7 @@ Init_VM(void) rb_define_method_id(klass, id_core_hash_merge_ptr, m_core_hash_merge_ptr, -1); rb_define_method_id(klass, id_core_hash_merge_kwd, m_core_hash_merge_kwd, 2); rb_define_method_id(klass, id_core_raise, f_raise, -1); + rb_define_method_id(klass, id_core_sprintf, f_sprintf, -1); rb_define_method_id(klass, idProc, f_proc, 0); rb_define_method_id(klass, idLambda, f_lambda, 0); rb_define_method(klass, "make_shareable", m_core_make_shareable, 1);