diff --git a/compile.c b/compile.c index 0bdb7eea13..a28c023ef0 100644 --- a/compile.c +++ b/compile.c @@ -5611,6 +5611,8 @@ compile_case2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no 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_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_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, LABEL *matched, LABEL *unmatched, int in_alt_pattern, int deconstructed_pos) { @@ -5701,52 +5703,8 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSNL(ret, line, branchunless, match_failed); } - // 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 value is nil then we haven't tried to deconstruct - ADD_INSN1(ret, line, topn, INT2FIX(deconstructed_pos)); - ADD_INSNL(ret, line, branchnil, deconstruct); + CHECK(iseq_compile_array_deconstruct(iseq, ret, node, deconstruct, deconstructed, match_failed, type_error, deconstructed_pos)); - // If false then the value is not deconstructable - ADD_INSN1(ret, line, topn, INT2FIX(deconstructed_pos)); - ADD_INSNL(ret, line, branchunless, match_failed); - - // Drop value, add deconstructed to the stack and jump - ADD_INSN(ret, line, pop); - ADD_INSN1(ret, line, topn, INT2FIX(deconstructed_pos - 1)); - ADD_INSNL(ret, line, jump, deconstructed); - } else { - ADD_INSNL(ret, line, jump, deconstruct); - } - - ADD_LABEL(ret, deconstruct); - ADD_INSN(ret, line, dup); - ADD_INSN1(ret, line, putobject, ID2SYM(rb_intern("deconstruct"))); - ADD_SEND(ret, line, idRespond_to, INT2FIX(1)); - - // 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, setn, INT2FIX(deconstructed_pos + 1)); - } - - ADD_INSNL(ret, line, branchunless, match_failed); - - ADD_SEND(ret, line, 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, setn, INT2FIX(deconstructed_pos)); - } - - ADD_INSN(ret, line, dup); - ADD_INSN1(ret, line, checktype, INT2FIX(T_ARRAY)); - ADD_INSNL(ret, line, branchunless, type_error); - ADD_INSNL(ret, line, jump, deconstructed); - - ADD_LABEL(ret, deconstructed); ADD_INSN(ret, line, dup); ADD_SEND(ret, line, idLength, INT2FIX(0)); ADD_INSN1(ret, line, putobject, INT2FIX(min_argc)); @@ -5873,9 +5831,11 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c const NODE *args = fpinfo->args; const int args_num = fpinfo->args ? rb_long2int(fpinfo->args->nd_alen) : 0; - LABEL *match_failed, *type_error; + LABEL *match_failed, *type_error, *deconstruct, *deconstructed; match_failed = NEW_LABEL(line); type_error = NEW_LABEL(line); + deconstruct = NEW_LABEL(line); + deconstructed = NEW_LABEL(line); if (node->nd_pconst) { ADD_INSN(ret, line, dup); @@ -5884,16 +5844,7 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSNL(ret, line, branchunless, match_failed); } - ADD_INSN(ret, line, dup); - ADD_INSN1(ret, line, putobject, ID2SYM(rb_intern("deconstruct"))); - ADD_SEND(ret, line, idRespond_to, INT2FIX(1)); - ADD_INSNL(ret, line, branchunless, match_failed); - - ADD_SEND(ret, line, rb_intern("deconstruct"), INT2FIX(0)); - - ADD_INSN(ret, line, dup); - ADD_INSN1(ret, line, checktype, INT2FIX(T_ARRAY)); - ADD_INSNL(ret, line, branchunless, type_error); + CHECK(iseq_compile_array_deconstruct(iseq, ret, node, deconstruct, deconstructed, match_failed, type_error, deconstructed_pos)); ADD_INSN(ret, line, dup); ADD_SEND(ret, line, idLength, INT2FIX(0)); @@ -5933,7 +5884,7 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c } ADD_SEND(ret, line, idAREF, INT2FIX(1)); - CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, next_loop, in_alt_pattern)); + CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, next_loop, in_alt_pattern, FALSE)); args = args->nd_next; } @@ -5942,7 +5893,7 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSN1(ret, line, putobject, INT2FIX(0)); ADD_INSN1(ret, line, topn, INT2FIX(2)); ADD_SEND(ret, line, idAREF, INT2FIX(2)); - CHECK(iseq_compile_pattern_match(iseq, ret, fpinfo->pre_rest_arg, find_failed, in_alt_pattern)); + CHECK(iseq_compile_pattern_match(iseq, ret, fpinfo->pre_rest_arg, find_failed, in_alt_pattern, FALSE)); } if (NODE_NAMED_REST_P(fpinfo->post_rest_arg)) { ADD_INSN1(ret, line, topn, INT2FIX(3)); @@ -5951,7 +5902,7 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_SEND(ret, line, idPLUS, INT2FIX(1)); ADD_INSN1(ret, line, topn, INT2FIX(3)); ADD_SEND(ret, line, idAREF, INT2FIX(2)); - CHECK(iseq_compile_pattern_match(iseq, ret, fpinfo->post_rest_arg, find_failed, in_alt_pattern)); + CHECK(iseq_compile_pattern_match(iseq, ret, fpinfo->post_rest_arg, find_failed, in_alt_pattern, FALSE)); } ADD_INSNL(ret, line, jump, find_succeeded); @@ -6288,6 +6239,61 @@ iseq_compile_pattern_match(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE * 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) +{ + const int line = nd_line(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 value is nil then we haven't tried to deconstruct + ADD_INSN1(ret, line, topn, INT2FIX(deconstructed_pos)); + ADD_INSNL(ret, line, branchnil, deconstruct); + + // If false then the value is not deconstructable + ADD_INSN1(ret, line, topn, INT2FIX(deconstructed_pos)); + ADD_INSNL(ret, line, branchunless, match_failed); + + // Drop value, add deconstructed to the stack and jump + ADD_INSN(ret, line, pop); + ADD_INSN1(ret, line, topn, INT2FIX(deconstructed_pos - 1)); + ADD_INSNL(ret, line, jump, deconstructed); + } else { + ADD_INSNL(ret, line, jump, deconstruct); + } + + ADD_LABEL(ret, deconstruct); + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, putobject, ID2SYM(rb_intern("deconstruct"))); + ADD_SEND(ret, line, idRespond_to, INT2FIX(1)); + + // 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, setn, INT2FIX(deconstructed_pos + 1)); + } + + ADD_INSNL(ret, line, branchunless, match_failed); + + ADD_SEND(ret, line, 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, setn, INT2FIX(deconstructed_pos)); + } + + ADD_INSN(ret, line, dup); + ADD_INSN1(ret, line, checktype, INT2FIX(T_ARRAY)); + ADD_INSNL(ret, line, branchunless, type_error); + ADD_INSNL(ret, line, jump, deconstructed); + + ADD_LABEL(ret, deconstructed); + + return COMPILE_OK; +} + static int compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_node, int popped) { diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb index 78e3a4e8f3..8c529851b2 100644 --- a/test/ruby/test_pattern_matching.rb +++ b/test/ruby/test_pattern_matching.rb @@ -1340,6 +1340,15 @@ END true end end + + assert_block do + case CDeconstructCache.new([[0, :a, 1]]) + in [*, String => x, *] + false + in [*, Symbol => x, *] + true + end + end end ################################################################