summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorktsj <ktsj@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2019-04-17 06:48:03 +0000
committerktsj <ktsj@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2019-04-17 06:48:03 +0000
commit9738f96fcfe50b2a605e350bdd40bd7a85665f54 (patch)
treea8495fa0a315ef4015f01db4d158b74987d18277
parentb077654a2c89485c086e77c337d30a11ff3781c3 (diff)
Introduce pattern matching [EXPERIMENTAL]
[ruby-core:87945] [Feature #14912] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@67586 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--NEWS2
-rw-r--r--array.c8
-rw-r--r--ast.c20
-rw-r--r--compile.c590
-rw-r--r--defs/id.def1
-rw-r--r--error.c2
-rw-r--r--eval.c2
-rw-r--r--ext/objspace/objspace.c4
-rw-r--r--ext/ripper/tools/dsl.rb2
-rw-r--r--hash.c8
-rw-r--r--include/ruby/ruby.h1
-rw-r--r--internal.h1
-rw-r--r--lib/pp.rb4
-rw-r--r--node.c37
-rw-r--r--node.h19
-rw-r--r--parse.y714
-rw-r--r--test/coverage/test_coverage.rb32
-rw-r--r--test/ripper/test_parser_events.rb18
-rw-r--r--test/ripper/test_sexp.rb302
-rw-r--r--test/ruby/test_pattern_matching.rb1075
-rw-r--r--vm.c1
21 files changed, 2840 insertions, 3 deletions
diff --git a/NEWS b/NEWS
index ca078f9c51..49debe9a97 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,8 @@ sufficient information, see the ChangeLog file or Redmine
=== Language changes
+* Introduce pattern matching [Feature #14912]
+
* Method reference operator, <code>.:</code> is introduced as an
experimental feature. [Feature #12125] [Feature #13581]
diff --git a/array.c b/array.c
index bcc653f387..45ca8e4bcd 100644
--- a/array.c
+++ b/array.c
@@ -6544,6 +6544,12 @@ rb_ary_sum(int argc, VALUE *argv, VALUE ary)
return v;
}
+static VALUE
+rb_ary_deconstruct(VALUE ary)
+{
+ return ary;
+}
+
/*
* Arrays are ordered, integer-indexed collections of any object.
*
@@ -6910,5 +6916,7 @@ Init_Array(void)
rb_define_method(rb_cArray, "dig", rb_ary_dig, -1);
rb_define_method(rb_cArray, "sum", rb_ary_sum, -1);
+ rb_define_method(rb_cArray, "deconstruct", rb_ary_deconstruct, 0);
+
id_random = rb_intern("random");
}
diff --git a/ast.c b/ast.c
index 18562f252c..9b57a09198 100644
--- a/ast.c
+++ b/ast.c
@@ -364,8 +364,12 @@ node_children(rb_ast_t *ast, NODE *node)
return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body);
case NODE_CASE2:
return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body);
+ case NODE_CASE3:
+ return rb_ary_new_from_node_args(ast, 2, node->nd_head, node->nd_body);
case NODE_WHEN:
return rb_ary_new_from_node_args(ast, 3, node->nd_head, node->nd_body, node->nd_next);
+ case NODE_IN:
+ return rb_ary_new_from_node_args(ast, 3, node->nd_head, node->nd_body, node->nd_next);
case NODE_WHILE:
goto loop;
case NODE_UNTIL:
@@ -622,6 +626,22 @@ node_children(rb_ast_t *ast, NODE *node)
}
return rb_ary_new_from_args(3, locals, NEW_CHILD(ast, node->nd_args), NEW_CHILD(ast, node->nd_body));
}
+ case NODE_ARYPTN:
+ {
+ struct rb_ary_pattern_info *apinfo = node->nd_apinfo;
+ return rb_ary_new_from_args(4,
+ NEW_CHILD(ast, node->nd_pconst),
+ NEW_CHILD(ast, apinfo->pre_args),
+ NEW_CHILD(ast, apinfo->rest_arg),
+ NEW_CHILD(ast, apinfo->post_args));
+ }
+ case NODE_HSHPTN:
+ {
+ return rb_ary_new_from_args(3,
+ NEW_CHILD(ast, node->nd_pconst),
+ NEW_CHILD(ast, node->nd_pkwargs),
+ NEW_CHILD(ast, node->nd_pkwrestarg));
+ }
case NODE_ARGS_AUX:
case NODE_LAST:
break;
diff --git a/compile.c b/compile.c
index 36541b5dd7..6d6d9e1c8f 100644
--- a/compile.c
+++ b/compile.c
@@ -5237,6 +5237,593 @@ compile_case2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_no
}
static int
+iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int in_alt_pattern)
+{
+ const int line = nd_line(node);
+
+ switch (nd_type(node)) {
+ case NODE_ARYPTN: {
+ /*
+ * if pattern.use_rest_num?
+ * rest_num = 0
+ * end
+ * if pattern.has_constant_node?
+ * unless pattern.constant === obj
+ * goto match_failed
+ * end
+ * end
+ * unless obj.respond_to?(:deconstruct)
+ * goto match_failed
+ * end
+ * d = obj.deconstruct
+ * unless Array === d
+ * goto type_error
+ * end
+ * min_argc = pattern.pre_args_num + pattern.post_args_num
+ * if pattern.has_rest_arg?
+ * unless d.length >= min_argc
+ * goto match_failed
+ * end
+ * else
+ * unless d.length == min_argc
+ * goto match_failed
+ * end
+ * end
+ * pattern.pre_args_num.each do |i|
+ * unless pattern.pre_args[i].match?(d[i])
+ * goto match_failed
+ * end
+ * end
+ * if pattern.use_rest_num?
+ * rest_num = d.length - min_argc
+ * if pattern.has_rest_arg? && pattern.has_rest_arg_id # not `*`, but `*rest`
+ * unless pattern.rest_arg.match?(d[pattern.pre_args_num, rest_num])
+ * goto match_failed
+ * end
+ * end
+ * end
+ * pattern.post_args_num.each do |i|
+ * j = pattern.pre_args_num + i
+ * if pattern.use_rest_num?
+ * j += rest_num
+ * end
+ * unless pattern.post_args[i].match?(d[j])
+ * goto match_failed
+ * end
+ * end
+ * true
+ * goto fin
+ * type_error:
+ * FrozenCore.raise TypeError
+ * match_failed:
+ * false
+ * fin:
+ */
+ struct rb_ary_pattern_info *apinfo = node->nd_apinfo;
+ const NODE *args = apinfo->pre_args;
+ const int pre_args_num = apinfo->pre_args ? rb_long2int(apinfo->pre_args->nd_alen) : 0;
+ const int post_args_num = apinfo->post_args ? rb_long2int(apinfo->post_args->nd_alen) : 0;
+
+ const int min_argc = pre_args_num + post_args_num;
+ const int use_rest_num = apinfo->rest_arg && ((nd_type(apinfo->rest_arg) != NODE_BEGIN) ||
+ (nd_type(apinfo->rest_arg) == NODE_BEGIN && post_args_num > 0));
+
+ LABEL *match_failed, *type_error, *fin;
+ int i;
+ match_failed = NEW_LABEL(line);
+ type_error = NEW_LABEL(line);
+ fin = NEW_LABEL(line);
+
+ if (use_rest_num) {
+ ADD_INSN1(ret, line, putobject, INT2FIX(0)); /* allocate stack for rest_num */
+ ADD_INSN(ret, line, swap);
+ }
+
+ if (node->nd_pconst) {
+ ADD_INSN(ret, line, dup);
+ CHECK(COMPILE(ret, "constant", node->nd_pconst));
+ ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE));
+ 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, putobject, rb_cArray);
+ ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE));
+ ADD_INSNL(ret, line, branchunless, type_error);
+
+ ADD_INSN(ret, line, dup);
+ ADD_SEND(ret, line, idLength, INT2FIX(0));
+ ADD_INSN1(ret, line, putobject, INT2FIX(min_argc));
+ ADD_SEND(ret, line, apinfo->rest_arg ? idGE : idEq, INT2FIX(1));
+ ADD_INSNL(ret, line, branchunless, match_failed);
+
+ for (i = 0; i < pre_args_num; i++) {
+ ADD_INSN(ret, line, dup);
+ ADD_INSN1(ret, line, putobject, INT2FIX(i));
+ ADD_SEND(ret, line, idAREF, INT2FIX(1));
+ iseq_compile_pattern_each(iseq, ret, args->nd_head, in_alt_pattern);
+ args = args->nd_next;
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ }
+
+ if (apinfo->rest_arg) {
+ if (nd_type(apinfo->rest_arg) == NODE_BEGIN) {
+ if (post_args_num > 0) {
+ ADD_INSN(ret, line, dup);
+ ADD_SEND(ret, line, idLength, INT2FIX(0));
+ ADD_INSN1(ret, line, putobject, INT2FIX(min_argc));
+ ADD_SEND(ret, line, idMINUS, INT2FIX(1));
+ ADD_INSN1(ret, line, setn, INT2FIX(2));
+ ADD_INSN(ret, line, pop);
+ }
+ }
+ else {
+ ADD_INSN(ret, line, dup);
+ ADD_INSN1(ret, line, putobject, INT2FIX(pre_args_num));
+ ADD_INSN1(ret, line, topn, INT2FIX(1));
+ ADD_SEND(ret, line, idLength, INT2FIX(0));
+ ADD_INSN1(ret, line, putobject, INT2FIX(min_argc));
+ ADD_SEND(ret, line, idMINUS, INT2FIX(1));
+ ADD_INSN1(ret, line, setn, INT2FIX(4));
+ ADD_SEND(ret, line, idAREF, INT2FIX(2));
+
+ iseq_compile_pattern_each(iseq, ret, apinfo->rest_arg, in_alt_pattern);
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ }
+ }
+
+ args = apinfo->post_args;
+ for (i = 0; i < post_args_num; i++) {
+ ADD_INSN(ret, line, dup);
+
+ ADD_INSN1(ret, line, putobject, INT2FIX(pre_args_num + i));
+ if (use_rest_num) {
+ ADD_INSN1(ret, line, topn, INT2FIX(3));
+ ADD_SEND(ret, line, idPLUS, INT2FIX(1));
+ }
+
+ ADD_SEND(ret, line, idAREF, INT2FIX(1));
+ iseq_compile_pattern_each(iseq, ret, args->nd_head, in_alt_pattern);
+ args = args->nd_next;
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ }
+
+ ADD_INSN(ret, line, pop);
+ if (use_rest_num) {
+ ADD_INSN(ret, line, pop);
+ }
+ ADD_INSN1(ret, line, putobject, Qtrue);
+ ADD_INSNL(ret, line, jump, fin);
+
+ ADD_LABEL(ret, type_error);
+ ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ ADD_INSN1(ret, line, putobject, rb_eTypeError);
+ ADD_INSN1(ret, line, putobject, rb_fstring_lit("deconstruct must return Array"));
+ ADD_SEND(ret, line, id_core_raise, INT2FIX(2));
+
+ ADD_LABEL(ret, match_failed);
+ ADD_INSN(ret, line, pop);
+ if (use_rest_num) {
+ ADD_INSN(ret, line, pop);
+ }
+ ADD_INSN1(ret, line, putobject, Qfalse);
+ ADD_LABEL(ret, fin);
+
+ break;
+ }
+ case NODE_HSHPTN: {
+ /*
+ * keys = nil
+ * if pattern.has_kw_args_node? && !pattern.has_kw_rest_arg_node?
+ * keys = pattern.kw_args_node.keys
+ * end
+ * if pattern.has_constant_node?
+ * unless pattern.constant === obj
+ * goto match_failed
+ * end
+ * end
+ * unless obj.respond_to?(:deconstruct_keys)
+ * goto match_failed
+ * end
+ * d = obj.deconstruct_keys(keys)
+ * unless Hash === d
+ * goto type_error
+ * end
+ * if pattern.has_kw_rest_arg_node?
+ * d = d.dup
+ * end
+ * if pattern.has_kw_args_node?
+ * pattern.kw_args_node.each |k,|
+ * unless d.key?(k)
+ * goto match_failed
+ * end
+ * end
+ * pattern.kw_args_node.each |k, pat|
+ * if pattern.has_kw_rest_arg_node?
+ * unless pat.match?(d.delete(k))
+ * goto match_failed
+ * end
+ * else
+ * unless pat.match?(d[k])
+ * goto match_failed
+ * end
+ * end
+ * end
+ * else
+ * unless d.empty?
+ * goto match_failed
+ * end
+ * end
+ * if pattern.has_kw_rest_arg_node?
+ * pattern.kw_rest_arg_node.match?(d)
+ * end
+ * true
+ * goto fin
+ * type_error:
+ * FrozenCore.raise TypeError
+ * match_failed:
+ * false
+ * fin:
+ */
+ LABEL *match_failed, *type_error, *fin;
+ VALUE keys = Qnil;
+
+ match_failed = NEW_LABEL(line);
+ type_error = NEW_LABEL(line);
+ fin = NEW_LABEL(line);
+
+ if (node->nd_pkwargs && !node->nd_pkwrestarg) {
+ const NODE *kw_args = node->nd_pkwargs->nd_head;
+ keys = rb_ary_new_capa(kw_args ? kw_args->nd_alen/2 : 0);
+ iseq_add_mark_object_compile_time(iseq, rb_obj_hide(keys));
+ while (kw_args) {
+ rb_ary_push(keys, kw_args->nd_head->nd_lit);
+ kw_args = kw_args->nd_next->nd_next;
+ }
+ }
+
+ if (node->nd_pconst) {
+ ADD_INSN(ret, line, dup);
+ CHECK(COMPILE(ret, "constant", node->nd_pconst));
+ ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE));
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ }
+
+ ADD_INSN(ret, line, dup);
+ ADD_INSN1(ret, line, putobject, ID2SYM(rb_intern("deconstruct_keys")));
+ ADD_SEND(ret, line, idRespond_to, INT2FIX(1));
+ ADD_INSNL(ret, line, branchunless, match_failed);
+
+ if (NIL_P(keys)) {
+ ADD_INSN(ret, line, putnil);
+ }
+ else {
+ ADD_INSN1(ret, line, duparray, keys);
+ }
+ ADD_SEND(ret, line, rb_intern("deconstruct_keys"), INT2FIX(1));
+
+ ADD_INSN(ret, line, dup);
+ ADD_INSN1(ret, line, putobject, rb_cHash);
+ ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE));
+ ADD_INSNL(ret, line, branchunless, type_error);
+
+ if (node->nd_pkwrestarg) {
+ ADD_SEND(ret, line, rb_intern("dup"), INT2FIX(0));
+ }
+
+ if (node->nd_pkwargs) {
+ int i;
+ int keys_num;
+ const NODE *args;
+ args = node->nd_pkwargs->nd_head;
+ if (args) {
+ DECL_ANCHOR(match_values);
+ INIT_ANCHOR(match_values);
+ keys_num = rb_long2int(args->nd_alen) / 2;
+ for (i = 0; i < keys_num; i++) {
+ NODE *key_node = args->nd_head;
+ NODE *value_node = args->nd_next->nd_head;
+ VALUE key;
+
+ if (nd_type(key_node) != NODE_LIT) {
+ UNKNOWN_NODE("NODE_IN", key_node, COMPILE_NG);
+ }
+ key = key_node->nd_lit;
+
+ ADD_INSN(ret, line, dup);
+ ADD_INSN1(ret, line, putobject, key);
+ ADD_SEND(ret, line, rb_intern("key?"), INT2FIX(1));
+ ADD_INSNL(ret, line, branchunless, match_failed);
+
+ ADD_INSN(match_values, line, dup);
+ ADD_INSN1(match_values, line, putobject, key);
+ ADD_SEND(match_values, line, node->nd_pkwrestarg ? rb_intern("delete") : idAREF, INT2FIX(1));
+ iseq_compile_pattern_each(iseq, match_values, value_node, in_alt_pattern);
+ ADD_INSNL(match_values, line, branchunless, match_failed);
+ args = args->nd_next->nd_next;
+ }
+ ADD_SEQ(ret, match_values);
+ }
+ }
+ else {
+ ADD_INSN(ret, line, dup);
+ ADD_SEND(ret, line, idEmptyP, INT2FIX(0));
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ }
+
+ if (node->nd_pkwrestarg) {
+ ADD_INSN(ret, line, dup);
+ iseq_compile_pattern_each(iseq, ret, node->nd_pkwrestarg, in_alt_pattern);
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ }
+
+ ADD_INSN(ret, line, pop);
+ ADD_INSN1(ret, line, putobject, Qtrue);
+ ADD_INSNL(ret, line, jump, fin);
+
+ ADD_LABEL(ret, type_error);
+ ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ ADD_INSN1(ret, line, putobject, rb_eTypeError);
+ ADD_INSN1(ret, line, putobject, rb_fstring_lit("deconstruct_keys must return Hash"));
+ ADD_SEND(ret, line, id_core_raise, INT2FIX(2));
+
+ ADD_LABEL(ret, match_failed);
+ ADD_INSN(ret, line, pop);
+ ADD_INSN1(ret, line, putobject, Qfalse);
+
+ ADD_LABEL(ret, fin);
+ break;
+ }
+ case NODE_LIT:
+ case NODE_STR:
+ case NODE_XSTR:
+ case NODE_DSTR:
+ case NODE_DSYM:
+ case NODE_DREGX:
+ case NODE_ARRAY:
+ case NODE_ZARRAY:
+ case NODE_LAMBDA:
+ case NODE_DOT2:
+ case NODE_DOT3:
+ case NODE_CONST:
+ case NODE_LVAR:
+ case NODE_DVAR:
+ case NODE_TRUE:
+ case NODE_FALSE:
+ case NODE_SELF:
+ case NODE_NIL:
+ case NODE_COLON2:
+ case NODE_COLON3:
+ CHECK(COMPILE(ret, "case in literal", node));
+ ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE));
+ break;
+ case NODE_LASGN: {
+ struct rb_iseq_constant_body *const body = iseq->body;
+ ID id = node->nd_vid;
+ int idx = body->local_iseq->body->local_table_size - get_local_var_idx(iseq, id);
+
+ if (in_alt_pattern) {
+ const char *name = rb_id2name(id);
+ if (name && strlen(name) > 0 && name[0] != '_') {
+ COMPILE_ERROR(ERROR_ARGS "illegal variable in alternative pattern (%"PRIsVALUE")",
+ rb_id2str(id));
+ return COMPILE_NG;
+ }
+ }
+
+ ADD_SETLOCAL(ret, line, idx, get_lvar_level(iseq));
+ ADD_INSN1(ret, line, putobject, Qtrue);
+ break;
+ }
+ case NODE_DASGN:
+ case NODE_DASGN_CURR: {
+ int idx, lv, ls;
+ ID id = node->nd_vid;
+
+ idx = get_dyna_var_idx(iseq, id, &lv, &ls);
+
+ if (in_alt_pattern) {
+ const char *name = rb_id2name(id);
+ if (name && strlen(name) > 0 && name[0] != '_') {
+ COMPILE_ERROR(ERROR_ARGS "illegal variable in alternative pattern (%"PRIsVALUE")",
+ rb_id2str(id));
+ return COMPILE_NG;
+ }
+ }
+
+ if (idx < 0) {
+ COMPILE_ERROR(ERROR_ARGS "NODE_DASGN(_CURR): unknown id (%"PRIsVALUE")",
+ rb_id2str(id));
+ return COMPILE_NG;
+ }
+ ADD_SETLOCAL(ret, line, ls - idx, lv);
+ ADD_INSN1(ret, line, putobject, Qtrue);
+ break;
+ }
+ case NODE_IF:
+ case NODE_UNLESS: {
+ LABEL *match_failed, *fin;
+ match_failed = NEW_LABEL(line);
+ fin = NEW_LABEL(line);
+ iseq_compile_pattern_each(iseq, ret, node->nd_body, in_alt_pattern);
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ CHECK(COMPILE(ret, "case in if", node->nd_cond));
+ if (nd_type(node) == NODE_IF) {
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ }
+ else {
+ ADD_INSNL(ret, line, branchif, match_failed);
+ }
+ ADD_INSN1(ret, line, putobject, Qtrue);
+ ADD_INSNL(ret, line, jump, fin);
+
+ ADD_LABEL(ret, match_failed);
+ ADD_INSN1(ret, line, putobject, Qfalse);
+
+ ADD_LABEL(ret, fin);
+ break;
+ }
+ case NODE_HASH: {
+ NODE *n;
+ LABEL *match_failed, *fin;
+ match_failed = NEW_LABEL(line);
+ fin = NEW_LABEL(line);
+
+ n = node->nd_head;
+ if (! (nd_type(n) == NODE_ARRAY && n->nd_alen == 2)) {
+ COMPILE_ERROR(ERROR_ARGS "unexpected node");
+ return COMPILE_NG;
+ }
+
+ ADD_INSN(ret, line, dup);
+ iseq_compile_pattern_each(iseq, ret, n->nd_head, in_alt_pattern);
+ ADD_INSNL(ret, line, branchunless, match_failed);
+ iseq_compile_pattern_each(iseq, ret, n->nd_next->nd_head, in_alt_pattern);
+ ADD_INSNL(ret, line, jump, fin);
+
+ ADD_LABEL(ret, match_failed);
+ ADD_INSN(ret, line, pop);
+ ADD_INSN1(ret, line, putobject, Qfalse);
+
+ ADD_LABEL(ret, fin);
+ break;
+ }
+ case NODE_OR: {
+ LABEL *match_succeeded, *fin;
+ match_succeeded = NEW_LABEL(line);
+ fin = NEW_LABEL(line);
+
+ ADD_INSN(ret, line, dup);
+ iseq_compile_pattern_each(iseq, ret, node->nd_1st, TRUE);
+ ADD_INSNL(ret, line, branchif, match_succeeded);
+ iseq_compile_pattern_each(iseq, ret, node->nd_2nd, TRUE);
+ ADD_INSNL(ret, line, jump, fin);
+
+ ADD_LABEL(ret, match_succeeded);
+ ADD_INSN(ret, line, pop);
+ ADD_INSN1(ret, line, putobject, Qtrue);
+
+ ADD_LABEL(ret, fin);
+ break;
+ }
+ default:
+ UNKNOWN_NODE("NODE_IN", node, COMPILE_NG);
+ }
+ return COMPILE_OK;
+}
+
+static int
+compile_case3(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const orig_node, int popped)
+{
+ const NODE *pattern;
+ const NODE *node = orig_node;
+ LABEL *endlabel, *elselabel;
+ DECL_ANCHOR(head);
+ DECL_ANCHOR(body_seq);
+ DECL_ANCHOR(cond_seq);
+ int line, lineno, column, last_lineno, last_column;
+ enum node_type type;
+ VALUE branches = 0;
+
+ INIT_ANCHOR(head);
+ INIT_ANCHOR(body_seq);
+ INIT_ANCHOR(cond_seq);
+
+ CHECK(COMPILE(head, "case base", node->nd_head));
+
+ DECL_BRANCH_BASE(branches, nd_first_lineno(node), nd_first_column(node), nd_last_lineno(node), nd_last_column(node), "case");
+
+ node = node->nd_body;
+ EXPECT_NODE("NODE_CASE3", node, NODE_IN, COMPILE_NG);
+ type = nd_type(node);
+ line = nd_line(node);
+ lineno = nd_first_lineno(node);
+ column = nd_first_column(node);
+ last_lineno = nd_last_lineno(node);
+ last_column = nd_last_column(node);
+
+ endlabel = NEW_LABEL(line);
+ elselabel = NEW_LABEL(line);
+
+ ADD_SEQ(ret, head); /* case VAL */
+
+ while (type == NODE_IN) {
+ LABEL *l1;
+
+ l1 = NEW_LABEL(line);
+ ADD_LABEL(body_seq, l1);
+ ADD_INSN(body_seq, line, pop);
+ ADD_TRACE_BRANCH_COVERAGE(
+ body_seq,
+ node->nd_body ? nd_first_lineno(node->nd_body) : lineno,
+ node->nd_body ? nd_first_column(node->nd_body) : column,
+ node->nd_body ? nd_last_lineno(node->nd_body) : last_lineno,
+ node->nd_body ? nd_last_column(node->nd_body) : last_column,
+ "in",
+ branches);
+ CHECK(COMPILE_(body_seq, "in body", node->nd_body, popped));
+ ADD_INSNL(body_seq, line, jump, endlabel);
+
+ pattern = node->nd_head;
+ if (pattern) {
+ ADD_INSN (cond_seq, nd_line(pattern), dup);
+ iseq_compile_pattern_each(iseq, cond_seq, pattern, FALSE);
+ ADD_INSNL(cond_seq, nd_line(pattern), branchif, l1);
+ }
+ else {
+ COMPILE_ERROR(ERROR_ARGS "unexpected node");
+ return COMPILE_NG;
+ }
+
+ node = node->nd_next;
+ if (!node) {
+ break;
+ }
+ type = nd_type(node);
+ line = nd_line(node);
+ lineno = nd_first_lineno(node);
+ column = nd_first_column(node);
+ last_lineno = nd_last_lineno(node);
+ last_column = nd_last_column(node);
+ }
+ /* else */
+ if (node) {
+ ADD_LABEL(cond_seq, elselabel);
+ ADD_INSN(cond_seq, line, pop);
+ ADD_TRACE_BRANCH_COVERAGE(cond_seq, nd_first_lineno(node), nd_first_column(node), nd_last_lineno(node), nd_last_column(node), "else", branches);
+ CHECK(COMPILE_(cond_seq, "else", node, popped));
+ ADD_INSNL(cond_seq, line, jump, endlabel);
+ }
+ else {
+ debugs("== else (implicit)\n");
+ ADD_LABEL(cond_seq, elselabel);
+ ADD_TRACE_BRANCH_COVERAGE(cond_seq, nd_first_lineno(orig_node), nd_first_column(orig_node), nd_last_lineno(orig_node), nd_last_column(orig_node), "else", branches);
+ ADD_INSN1(cond_seq, nd_line(orig_node), putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
+ ADD_INSN1(cond_seq, nd_line(orig_node), putobject, rb_eNoMatchingPatternError);
+ ADD_INSN1(cond_seq, nd_line(orig_node), topn, INT2FIX(2));
+ ADD_SEND(cond_seq, nd_line(orig_node), id_core_raise, INT2FIX(2));
+ ADD_INSN(cond_seq, nd_line(orig_node), pop);
+ ADD_INSN(cond_seq, nd_line(orig_node), pop);
+ if (!popped) {
+ ADD_INSN(cond_seq, nd_line(orig_node), putnil);
+ }
+ ADD_INSNL(cond_seq, nd_line(orig_node), jump, endlabel);
+ }
+
+ ADD_SEQ(ret, cond_seq);
+ ADD_SEQ(ret, body_seq);
+ ADD_LABEL(ret, endlabel);
+ return COMPILE_OK;
+}
+
+static int
compile_loop(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped, const enum node_type type)
{
const int line = (int)nd_line(node);
@@ -6153,6 +6740,9 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, in
case NODE_CASE2:
CHECK(compile_case2(iseq, ret, node, popped));
break;
+ case NODE_CASE3:
+ CHECK(compile_case3(iseq, ret, node, popped));
+ break;
case NODE_WHILE:
case NODE_UNTIL:
CHECK(compile_loop(iseq, ret, node, popped, type));
diff --git a/defs/id.def b/defs/id.def
index 24ca5d3f9f..44890cf352 100644
--- a/defs/id.def
+++ b/defs/id.def
@@ -65,6 +65,7 @@ firstline, predefined = __LINE__+1, %[\
core#set_postexe
core#hash_merge_ptr
core#hash_merge_kwd
+ core#raise
- debug#created_info
diff --git a/error.c b/error.c
index e61a3844cf..403516a683 100644
--- a/error.c
+++ b/error.c
@@ -871,6 +871,7 @@ VALUE rb_eSecurityError;
VALUE rb_eNotImpError;
VALUE rb_eNoMemError;
VALUE rb_cNameErrorMesg;
+VALUE rb_eNoMatchingPatternError;
VALUE rb_eScriptError;
VALUE rb_eSyntaxError;
@@ -2486,6 +2487,7 @@ Init_Exception(void)
rb_eNoMemError = rb_define_class("NoMemoryError", rb_eException);
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_eRuntimeError);
syserr_tbl = st_init_numtable();
rb_eSystemCallError = rb_define_class("SystemCallError", rb_eStandardError);
diff --git a/eval.c b/eval.c
index 59b3b30293..48473c79e2 100644
--- a/eval.c
+++ b/eval.c
@@ -733,7 +733,7 @@ extract_raise_opts(int argc, const VALUE *argv, VALUE *opts)
* the +:cause+ argument.
*/
-static VALUE
+VALUE
rb_f_raise(int argc, VALUE *argv)
{
VALUE err;
diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c
index 7cef5d42a3..950168868e 100644
--- a/ext/objspace/objspace.c
+++ b/ext/objspace/objspace.c
@@ -377,7 +377,9 @@ count_nodes(int argc, VALUE *argv, VALUE os)
COUNT_NODE(NODE_UNLESS);
COUNT_NODE(NODE_CASE);
COUNT_NODE(NODE_CASE2);
+ COUNT_NODE(NODE_CASE3);
COUNT_NODE(NODE_WHEN);
+ COUNT_NODE(NODE_IN);
COUNT_NODE(NODE_WHILE);
COUNT_NODE(NODE_UNTIL);
COUNT_NODE(NODE_ITER);
@@ -472,6 +474,8 @@ count_nodes(int argc, VALUE *argv, VALUE os)
COUNT_NODE(NODE_ATTRASGN);
COUNT_NODE(NODE_LAMBDA);
COUNT_NODE(NODE_METHREF);
+ COUNT_NODE(NODE_ARYPTN);
+ COUNT_NODE(NODE_HSHPTN);
#undef COUNT_NODE
case NODE_LAST: break;
}
diff --git a/ext/ripper/tools/dsl.rb b/ext/ripper/tools/dsl.rb
index aafaa6f407..34b435e7c3 100644
--- a/ext/ripper/tools/dsl.rb
+++ b/ext/ripper/tools/dsl.rb
@@ -72,7 +72,7 @@ class DSL
def method_missing(event, *args)
if event.to_s =~ /!\z/
add_event(event, args)
- elsif args.empty? and /\Aid[A-Z]/ =~ event.to_s
+ elsif args.empty? and /\Aid[A-Z_]/ =~ event.to_s
event
else
"#{ event }(#{ args.join(", ") })"
diff --git a/hash.c b/hash.c
index 36d5a86760..4841cbb198 100644
--- a/hash.c
+++ b/hash.c
@@ -4349,6 +4349,12 @@ rb_hash_to_proc(VALUE hash)
return rb_func_proc_new(hash_proc_call, hash);
}
+static VALUE
+rb_hash_deconstruct_keys(VALUE hash, VALUE keys)
+{
+ return hash;
+}
+
static int
add_new_i(st_data_t *key, st_data_t *val, st_data_t arg, int existing)
{
@@ -6020,6 +6026,8 @@ Init_Hash(void)
rb_define_method(rb_cHash, ">=", rb_hash_ge, 1);
rb_define_method(rb_cHash, ">", rb_hash_gt, 1);
+ rb_define_method(rb_cHash, "deconstruct_keys", rb_hash_deconstruct_keys, 1);
+
/* Document-class: ENV
*
* ENV is a hash-like accessor for environment variables.
diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h
index c0be6c1a50..c73fd63941 100644
--- a/include/ruby/ruby.h
+++ b/include/ruby/ruby.h
@@ -2053,6 +2053,7 @@ RUBY_EXTERN VALUE rb_eSysStackError;
RUBY_EXTERN VALUE rb_eRegexpError;
RUBY_EXTERN VALUE rb_eEncodingError;
RUBY_EXTERN VALUE rb_eEncCompatError;
+RUBY_EXTERN VALUE rb_eNoMatchingPatternError;
RUBY_EXTERN VALUE rb_eScriptError;
RUBY_EXTERN VALUE rb_eNameError;
diff --git a/internal.h b/internal.h
index 46a25ff77f..17bd7f1fe9 100644
--- a/internal.h
+++ b/internal.h
@@ -1456,6 +1456,7 @@ extern ID ruby_static_id_signo, ruby_static_id_status;
void rb_class_modify_check(VALUE);
#define id_signo ruby_static_id_signo
#define id_status ruby_static_id_status
+NORETURN(VALUE rb_f_raise(int argc, VALUE *argv));
/* eval_error.c */
VALUE rb_get_backtrace(VALUE info);
diff --git a/lib/pp.rb b/lib/pp.rb
index cbc49e72e9..ef559eff7c 100644
--- a/lib/pp.rb
+++ b/lib/pp.rb
@@ -537,6 +537,10 @@ class RubyVM::AbstractSyntaxTree::Node
pretty_print_children(q, %w[pre_num pre_init opt first_post post_num post_init rest kw kwrest block])
when :DEFN
pretty_print_children(q, %w[mid body])
+ when :ARYPTN
+ pretty_print_children(q, %w[const pre rest post])
+ when :HSHPTN
+ pretty_print_children(q, %w[const kw kwrest])
else
pretty_print_children(q)
end
diff --git a/node.c b/node.c
index 74a7b63644..521ddf690a 100644
--- a/node.c
+++ b/node.c
@@ -195,6 +195,14 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node)
LAST_NODE;
F_NODE(nd_body, "when clauses");
return;
+ case NODE_CASE3:
+ ANN("case statement (pattern matching)");
+ ANN("format: case [nd_head]; [nd_body]; end");
+ ANN("example: case x; in 1; foo; in 2; bar; else baz; end");
+ F_NODE(nd_head, "case expr");
+ LAST_NODE;
+ F_NODE(nd_body, "in clauses");
+ return;
case NODE_WHEN:
ANN("when clause");
@@ -206,6 +214,16 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node)
F_NODE(nd_next, "next when clause");
return;
+ case NODE_IN:
+ ANN("in clause");
+ ANN("format: in [nd_head]; [nd_body]; (when or else) [nd_next]");
+ ANN("example: case x; in 1; foo; in 2; bar; else baz; end");
+ F_NODE(nd_head, "in value");
+ F_NODE(nd_body, "in body");
+ LAST_NODE;
+ F_NODE(nd_next, "next in clause");
+ return;
+
case NODE_WHILE:
ANN("while statement");
ANN("format: while [nd_cond]; [nd_body]; end");
@@ -1017,6 +1035,25 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node)
F_NODE(nd_body, "body");
return;
+ case NODE_ARYPTN:
+ ANN("array pattern");
+ ANN("format: [nd_pconst]([pre_args], ..., *[rest_arg], [post_args], ...)");
+ F_NODE(nd_pconst, "constant");
+ F_NODE(nd_apinfo->pre_args, "pre arguments");
+ F_NODE(nd_apinfo->rest_arg, "rest argument");
+ LAST_NODE;
+ F_NODE(nd_apinfo->post_args, "post arguments");
+ return;
+
+ case NODE_HSHPTN:
+ ANN("hash pattern");
+ ANN("format: [nd_pconst]([nd_pkwargs], ..., **[nd_pkwrestarg])");
+ F_NODE(nd_pconst, "constant");
+ F_NODE(nd_pkwargs, "keyword arguments");
+ LAST_NODE;
+ F_NODE(nd_pkwrestarg, "keyword rest argument");
+ return;
+
case NODE_ARGS_AUX:
case NODE_LAST:
break;
diff --git a/node.h b/node.h
index 5b917daae4..f70cdc959a 100644
--- a/node.h
+++ b/node.h
@@ -26,7 +26,9 @@ enum node_type {
NODE_UNLESS,
NODE_CASE,
NODE_CASE2,
+ NODE_CASE3,
NODE_WHEN,
+ NODE_IN,
NODE_WHILE,
NODE_UNTIL,
NODE_ITER,
@@ -121,6 +123,8 @@ enum node_type {
NODE_ATTRASGN,
NODE_LAMBDA,
NODE_METHREF,
+ NODE_ARYPTN,
+ NODE_HSHPTN,
NODE_LAST
};
@@ -162,6 +166,7 @@ typedef struct RNode {
long state;
struct rb_global_entry *entry;
struct rb_args_info *args;
+ struct rb_ary_pattern_info *apinfo;
VALUE value;
} u3;
rb_code_location_t nd_loc;
@@ -268,6 +273,12 @@ typedef struct RNode {
#define nd_brace u2.argc
+#define nd_pconst u1.node
+#define nd_pkwargs u2.node
+#define nd_pkwrestarg u3.node
+
+#define nd_apinfo u3.apinfo
+
#define NEW_NODE(t,a0,a1,a2,loc) rb_node_newnode((t),(VALUE)(a0),(VALUE)(a1),(VALUE)(a2),loc)
#define NEW_DEFN(i,a,d,loc) NEW_NODE(NODE_DEFN,0,i,NEW_SCOPE(a,d,loc),loc)
@@ -278,7 +289,9 @@ typedef struct RNode {
#define NEW_UNLESS(c,t,e,loc) NEW_NODE(NODE_UNLESS,c,t,e,loc)
#define NEW_CASE(h,b,loc) NEW_NODE(NODE_CASE,h,b,0,loc)
#define NEW_CASE2(b,loc) NEW_NODE(NODE_CASE2,0,b,0,loc)
+#define NEW_CASE3(h,b,loc) NEW_NODE(NODE_CASE3,h,b,0,loc)
#define NEW_WHEN(c,t,e,loc) NEW_NODE(NODE_WHEN,c,t,e,loc)
+#define NEW_IN(c,t,e,loc) NEW_NODE(NODE_IN,c,t,e,loc)
#define NEW_WHILE(c,b,n,loc) NEW_NODE(NODE_WHILE,c,b,n,loc)
#define NEW_UNTIL(c,b,n,loc) NEW_NODE(NODE_UNTIL,c,b,n,loc)
#define NEW_FOR(i,b,loc) NEW_NODE(NODE_FOR,0,b,i,loc)
@@ -434,6 +447,12 @@ struct rb_args_info {
NODE *opt_args;
};
+struct rb_ary_pattern_info {
+ NODE *pre_args;
+ NODE *rest_arg;
+ NODE *post_args;
+};
+
struct parser_params;
void *rb_parser_malloc(struct parser_params *, size_t);
void *rb_parser_realloc(struct parser_params *, void *, size_t);
diff --git a/parse.y b/parse.y
index 90652686db..845f968873 100644
--- a/parse.y
+++ b/parse.y
@@ -417,6 +417,11 @@ static NODE *method_add_block(struct parser_params*p, NODE *m, NODE *b, const YY
static bool args_info_empty_p(struct rb_args_info *args);
static NODE *new_args(struct parser_params*,NODE*,NODE*,ID,NODE*,NODE*,const YYLTYPE*);
static NODE *new_args_tail(struct parser_params*,NODE*,ID,ID,const YYLTYPE*);
+static NODE *new_array_pattern(struct parser_params *p, NODE *constant, NODE *pre_arg, NODE *aryptn, const YYLTYPE *loc);
+static NODE *new_array_pattern_tail(struct parser_params *p, NODE *pre_args, int has_rest, ID rest_arg, NODE *post_args, const YYLTYPE *loc);
+static NODE *new_hash_pattern(struct parser_params *p, NODE *constant, NODE *hshptn, const YYLTYPE *loc);
+static NODE *new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, const YYLTYPE *loc);
+
static NODE *new_kw_arg(struct parser_params *p, NODE *k, const YYLTYPE *loc);
static NODE *args_with_numbered(struct parser_params*,NODE*,int);
@@ -447,6 +452,7 @@ static NODE *opt_arg_append(NODE*, NODE*);
static NODE *kwd_append(NODE*, NODE*);
static NODE *new_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc);
+static NODE *new_unique_key_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc);
static NODE *new_defined(struct parser_params *p, NODE *expr, const YYLTYPE *loc);
@@ -703,6 +709,105 @@ args_with_numbered(struct parser_params *p, VALUE args, int max_numparam)
return args;
}
+static VALUE
+new_array_pattern(struct parser_params *p, VALUE constant, VALUE pre_arg, VALUE aryptn, const YYLTYPE *loc)
+{
+ NODE *t = (NODE *)aryptn;
+ VALUE pre_args = t->u1.value, rest_arg = t->u2.value, post_args = t->u3.value;
+ if (!NIL_P(pre_arg)) {
+ if (!NIL_P(pre_args)) {
+ rb_ary_unshift(pre_args, pre_arg);
+ }
+ else {
+ pre_args = rb_ary_new_from_args(1, pre_arg);
+ }
+ }
+ return dispatch4(aryptn, constant, pre_args, rest_arg, post_args);
+}
+
+static VALUE
+new_array_pattern_tail(struct parser_params *p, VALUE pre_args, VALUE has_rest, VALUE rest_arg, VALUE post_args, const YYLTYPE *loc)
+{
+ NODE *t;
+ if (has_rest) {
+ rest_arg = dispatch1(var_field, rest_arg ? rest_arg : Qnil);
+ } else {
+ rest_arg = Qnil;
+ }
+ t = rb_node_newnode(NODE_ARYPTN, pre_args, rest_arg, post_args, &NULL_LOC);
+
+ add_mark_object(p, pre_args);
+ add_mark_object(p, rest_arg);
+ add_mark_object(p, post_args);
+ return (VALUE)t;
+}
+
+#define new_hash(p,h,l) rb_ary_new_from_args(0)
+
+static VALUE
+new_unique_key_hash(struct parser_params *p, VALUE ary, const YYLTYPE *loc)
+{
+ const long len = RARRAY_LEN(ary);
+ st_table *tbl;
+ long i;
+
+ tbl = st_init_strtable_with_size(len);
+ for (i = 0; i < len; i++) {
+ VALUE key, a1, a2, a3;
+ a1 = RARRAY_AREF(ary, i);
+ if (!(RB_TYPE_P(a1, T_ARRAY) && RARRAY_LEN(a1) == 2)) goto error;
+ a2 = RARRAY_AREF(a1, 0);
+ if (!RB_TYPE_P(a2, T_ARRAY)) goto error;
+ switch (RARRAY_LEN(a2)) {
+ case 2: /* "key": */
+ a3 = RARRAY_AREF(a2, 1);
+ if (!(RB_TYPE_P(a3, T_ARRAY) && RARRAY_LEN(a3) == 3)) goto error;
+ key = RARRAY_AREF(a3, 1);
+ break;
+ case 3: /* key: */
+ key = RARRAY_AREF(a2, 1);
+ break;
+ default:
+ goto error;
+ }
+ if (!RB_TYPE_P(key, T_STRING)) goto error;
+ if (st_lookup(tbl, (st_data_t)RSTRING_PTR(key), 0)) goto error;
+ st_insert(tbl, (st_data_t)RSTRING_PTR(key), (st_data_t)ary);
+ }
+ st_free_table(tbl);
+ return ary;
+
+ error:
+ ripper_error(p);
+ st_free_table(tbl);
+ return Qnil;
+}
+
+static VALUE
+new_hash_pattern(struct parser_params *p, VALUE constant, VALUE hshptn, const YYLTYPE *loc)
+{
+ NODE *t = (NODE *)hshptn;
+ VALUE kw_args = t->u1.value, kw_rest_arg = t->u2.value;
+ return dispatch3(hshptn, constant, kw_args, kw_rest_arg);
+}
+
+static VALUE
+new_hash_pattern_tail(struct parser_params *p, VALUE kw_args, VALUE kw_rest_arg, const YYLTYPE *loc)
+{
+ NODE *t;
+ if (kw_rest_arg) {
+ kw_rest_arg = dispatch1(var_field, kw_rest_arg);
+ }
+ else {
+ kw_rest_arg = Qnil;
+ }
+ t = rb_node_newnode(NODE_HSHPTN, kw_args, kw_rest_arg, 0, &NULL_LOC);
+
+ add_mark_object(p, kw_args);
+ add_mark_object(p, kw_rest_arg);
+ return (VALUE)t;
+}
+
#define new_defined(p,expr,loc) dispatch1(defined, (expr))
static VALUE heredoc_dedent(struct parser_params*,VALUE);
@@ -744,7 +849,7 @@ static VALUE heredoc_dedent(struct parser_params*,VALUE);
# define rb_warning3L(l,fmt,a,b,c) WARNING_CALL(WARNING_ARGS_L(l, fmt, 4), (a), (b), (c))
# define rb_warning4L(l,fmt,a,b,c,d) WARNING_CALL(WARNING_ARGS_L(l, fmt, 5), (a), (b), (c), (d))
#ifdef RIPPER
-static ID id_warn, id_warning, id_gets;
+static ID id_warn, id_warning, id_gets, id_assoc, id_or;
# define WARN_S_L(s,l) STR_NEW(s,l)
# define WARN_S(s) STR_NEW2(s)
# define WARN_I(i) INT2NUM(i)
@@ -895,9 +1000,15 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in
%type <node> lambda f_larglist lambda_body brace_body do_body
%type <node> brace_block cmd_brace_block do_block lhs none fitem
%type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner
+%type <node> p_case_body p_cases p_top_expr p_top_expr_body
+%type <node> p_expr p_as p_alt p_expr_basic
+%type <node> p_args p_args_head p_args_tail p_args_post p_arg
+%type <node> p_value p_primitive p_variable p_var_ref p_const
+%type <node> p_kwargs p_kwarg p_kw
%type <id> keyword_variable user_variable sym operation operation2 operation3
%type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg
%type <id> f_kwrest f_label f_arg_asgn call_op call_op2 reswords relop dot_or_colon
+%type <id> p_kwrest
%token END_OF_INPUT 0 "end-of-input"
%token <id> '.'
/* escaped chars, should be ignored otherwise */
@@ -2592,6 +2703,16 @@ primary : literal
/*% %*/
/*% ripper: case!(Qnil, $3) %*/
}
+ | k_case expr_value opt_terms
+ p_case_body
+ k_end
+ {
+ /*%%%*/
+ $$ = NEW_CASE3($2, $4, &@$);
+ rb_warn0L(nd_line($$), "Pattern matching is experimental, and the behavior may change in future versions of Ruby!");
+ /*% %*/
+ /*% ripper: case!($2, $4) %*/
+ }
| k_for for_var keyword_in expr_value_do
compstmt
k_end
@@ -3467,6 +3588,489 @@ cases : opt_else
| case_body
;
+p_case_body : keyword_in
+ {
+ SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
+ p->command_start = FALSE;
+ $<num>$ = p->in_kwarg;
+ p->in_kwarg = 1;
+ }
+ p_top_expr then
+ {
+ p->in_kwarg = !!$<num>2;
+ }
+ compstmt
+ p_cases
+ {
+ /*%%%*/
+ $$ = NEW_IN($3, $6, $7, &@$);
+ /*% %*/
+ /*% ripper: in!($3, $6, escape_Qundef($7)) %*/
+ }
+ ;
+
+p_cases : opt_else
+ | p_case_body
+ ;
+
+p_top_expr : p_top_expr_body
+ | p_top_expr_body modifier_if expr_value
+ {
+ /*%%%*/
+ $$ = new_if(p, $3, remove_begin($1), 0, &@$);
+ fixpos($$, $3);
+ /*% %*/
+ /*% ripper: if_mod!($3, $1) %*/
+ }
+ | p_top_expr_body modifier_unless expr_value
+ {
+ /*%%%*/
+ $$ = new_unless(p, $3, remove_begin($1), 0, &@$);
+ fixpos($$, $3);
+ /*% %*/
+ /*% ripper: unless_mod!($3, $1) %*/
+ }
+ ;
+
+p_top_expr_body : p_expr
+ | p_expr ','
+ {
+ $$ = new_array_pattern_tail(p, Qnone, 1, 0, Qnone, &@$);
+ $$ = new_array_pattern(p, Qnone, get_value($1), $$, &@$);
+ }
+ | p_expr ',' p_args
+ {
+ $$ = new_array_pattern(p, Qnone, get_value($1), $3, &@$);
+ /*%%%*/
+ nd_set_first_loc($$, @1.beg_pos);
+ /*%
+ %*/
+ }
+ | p_args_tail
+ {
+ $$ = new_array_pattern(p, Qnone, Qnone, $1, &@$);
+ }
+ | p_kwargs
+ {
+ $$ = new_hash_pattern(p, Qnone, $1, &@$);
+ }
+ ;
+
+p_expr : p_as
+ ;
+
+p_as : p_expr tASSOC p_variable
+ {
+ /*%%%*/
+ NODE *n = NEW_LIST($1, &@$);
+ n = list_append(p, n, $3);
+ $$ = new_hash(p, n, &@$);
+ /*% %*/
+ /*% ripper: binary!($1, STATIC_ID2SYM((id_assoc)), $3) %*/
+ }
+ | p_alt
+ ;
+
+p_alt : p_alt '|' p_expr_basic
+ {
+ /*%%%*/
+ $$ = NEW_NODE(NODE_OR, $1, $3, 0, &@$);
+ /*% %*/
+ /*% ripper: binary!($1, STATIC_ID2SYM((id_or)), $3) %*/
+ }
+ | p_expr_basic
+ ;
+
+p_expr_basic : p_value
+ | p_const '(' p_args rparen
+ {
+ $$ = new_array_pattern(p, $1, Qnone, $3, &@$);
+ /*%%%*/
+ nd_set_first_loc($$, @1.beg_pos);
+ /*%
+ %*/
+ }
+ | p_const '(' p_kwargs rparen
+ {
+ $$ = new_hash_pattern(p, $1, $3, &@$);
+ /*%%%*/
+ nd_set_first_loc($$, @1.beg_pos);
+ /*%
+ %*/
+ }
+ | p_const '(' rparen
+ {
+ $$ = new_array_pattern_tail(p, Qnone, 0, 0, Qnone, &@$);
+ $$ = new_array_pattern(p, $1, Qnone, $$, &@$);
+ }
+ | p_const '[' p_args rbracket
+ {
+ $$ = new_array_pattern(p, $1, Qnone, $3, &@$);
+ /*%%%*/
+ nd_set_first_loc($$, @1.beg_pos);
+ /*%
+ %*/
+ }
+ | p_const '[' p_kwargs rbracket
+ {
+ $$ = new_hash_pattern(p, $1, $3, &@$);
+ /*%%%*/
+ nd_set_first_loc($$, @1.beg_pos);
+ /*%
+ %*/
+ }
+ | p_const '[' rbracket
+ {
+ $$ = new_array_pattern_tail(p, Qnone, 0, 0, Qnone, &@$);
+ $$ = new_array_pattern(p, $1, Qnone, $$, &@$);
+ }
+ | tLBRACK p_args rbracket
+ {
+ $$ = new_array_pattern(p, Qnone, Qnone, $2, &@$);
+ }
+ | tLBRACK rbracket
+ {
+ $$ = new_array_pattern_tail(p, Qnone, 0, 0, Qnone, &@$);
+ $$ = new_array_pattern(p, Qnone, Qnone, $$, &@$);
+ }
+ | tLBRACE p_kwargs '}'
+ {
+ $$ = new_hash_pattern(p, Qnone, $2, &@$);
+ }
+ | tLBRACE '}'
+ {
+ $$ = new_hash_pattern_tail(p, Qnone, 0, &@$);
+ $$ = new_hash_pattern(p, Qnone, $$, &@$);
+ }
+ | tLPAREN p_expr rparen
+ {
+ $$ = $2;
+ }
+ ;
+
+p_args : p_expr
+ {
+ /*%%%*/
+ NODE *pre_args = NEW_LIST($1, &@$);
+ $$ = new_array_pattern_tail(p, pre_args, 0, 0, Qnone, &@$);
+ /*%
+ $$ = new_array_pattern_tail(p, rb_ary_new_from_args(1, get_value($1)), 0, 0, Qnone, &@$);
+ %*/
+ }
+ | p_args_head
+ {
+ $$ = new_array_pattern_tail(p, $1, 1, 0, Qnone, &@$);
+ }
+ | p_args_head p_arg
+ {
+ /*%%%*/
+ $$ = new_array_pattern_tail(p, list_concat($1, $2), 0, 0, Qnone, &@$);
+ /*%
+ VALUE pre_args = rb_ary_concat($1, get_value($2));
+ $$ = new_array_pattern_tail(p, pre_args, 0, 0, Qnone, &@$);
+ %*/
+ }
+ | p_args_head tSTAR tIDENTIFIER
+ {
+ $$ = new_array_pattern_tail(p, $1, 1, $3, Qnone, &@$);
+ }
+ | p_args_head tSTAR tIDENTIFIER ',' p_args_post
+ {
+ $$ = new_array_pattern_tail(p, $1, 1, $3, $5, &@$);
+ }
+ | p_args_head tSTAR
+ {
+ $$ = new_array_pattern_tail(p, $1, 1, 0, Qnone, &@$);
+ }
+ | p_args_head tSTAR ',' p_args_post
+ {
+ $$ = new_array_pattern_tail(p, $1, 1, 0, $4, &@$);
+ }
+ | p_args_tail
+ ;
+
+p_args_head : p_arg ','
+ {
+ $$ = $1;
+ }
+ | p_args_head p_arg ','
+ {
+ /*%%%*/
+ $$ = list_concat($1, $2);
+ /*% %*/
+ /*% ripper: rb_ary_concat($1, get_value($2)) %*/
+ }
+ ;
+
+p_args_tail : tSTAR tIDENTIFIER
+ {
+ $$ = new_array_pattern_tail(p, Qnone, 1, $2, Qnone, &@$);
+ }
+ | tSTAR tIDENTIFIER ',' p_args_post
+ {
+ $$ = new_array_pattern_tail(p, Qnone, 1, $2, $4, &@$);
+ }
+ | tSTAR
+ {
+ $$ = new_array_pattern_tail(p, Qnone, 1, 0, Qnone, &@$);
+ }
+ | tSTAR ',' p_args_post
+ {
+ $$ = new_array_pattern_tail(p, Qnone, 1, 0, $3, &@$);
+ }
+
+p_args_post : p_arg
+ | p_args_post ',' p_arg
+ {
+ /*%%%*/
+ $$ = list_concat($1, $3);
+ /*% %*/
+ /*% ripper: rb_ary_concat($1, get_value($3)) %*/
+ }
+ ;
+
+p_arg : p_expr
+ {
+ /*%%%*/
+ $$ = NEW_LIST($1, &@$);
+ /*% %*/
+ /*% ripper: rb_ary_new_from_args(1, get_value($1)) %*/
+ }
+ ;
+
+p_kwargs : p_kwarg ',' p_kwrest
+ {
+ $$ = new_hash_pattern_tail(p, new_unique_key_hash(p, $1, &@$), $3, &@$);
+ }
+ | p_kwarg
+ {
+ $$ = new_hash_pattern_tail(p, new_unique_key_hash(p, $1, &@$), 0, &@$);
+ }
+ | p_kwrest
+ {
+ $$ = new_hash_pattern_tail(p, new_hash(p, Qnone, &@$), $1, &@$);
+ }
+ ;
+
+p_kwarg : p_kw
+ | p_kwarg ',' p_kw
+ {
+ /*%%%*/
+ $$ = list_concat($1, $3);
+ /*% %*/
+ /*% ripper: rb_ary_concat($1, $3) %*/
+ }
+ ;
+
+p_kw : tLABEL p_expr
+ {
+ /*%%%*/
+ $$ = list_append(p, NEW_LIST(NEW_LIT(ID2SYM($1), &@$), &@$), $2);
+ /*% %*/
+ /*% ripper: rb_ary_new_from_args(1, rb_ary_new_from_args(2, get_value($1), get_value($2))) %*/
+ }
+ | tLABEL
+ {
+ if (!is_local_id(get_id($1))) {
+ yyerror1(&@1, "key must be valid as local variables");
+ }
+ /*%%%*/
+ $$ = list_append(p, NEW_LIST(NEW_LIT(ID2SYM($1), &@$), &@$), assignable(p, $1, 0, &@$));
+ /*% %*/
+ /*% ripper: rb_ary_new_from_args(1, rb_ary_new_from_args(2, get_value($1), Qnil)) %*/
+ }
+ | tSTRING_BEG string_contents tLABEL_END p_expr
+ {
+ /*%%%*/
+ YYLTYPE loc = code_loc_gen(&@1, &@3);
+ NODE *node = dsym_node(p, $2, &loc);
+ if (nd_type(node) == NODE_LIT) {
+ $$ = list_append(p, NEW_LIST(node, &loc), $4);
+ }
+ else {
+ yyerror1(&loc, "symbol literal with interpolation is not allowed");
+ $$ = 0;
+ }
+ /*% %*/
+ /*% ripper: rb_ary_new_from_args(1, rb_ary_new_from_args(2, $2, get_value($4))) %*/
+ }
+ | tSTRING_BEG string_contents tLABEL_END
+ {
+ /*%%%*/
+ YYLTYPE loc = code_loc_gen(&@1, &@3);
+ NODE *node = dsym_node(p, $2, &loc);
+ ID id;
+ if (nd_type(node) == NODE_LIT) {
+ id = SYM2ID(node->nd_lit);
+ if (!is_local_id(id)) {
+ yyerror1(&loc, "key must be valid as local variables");
+ }
+ $$ = list_append(p, NEW_LIST(node, &loc), assignable(p, id, 0, &@$));
+ }
+ else {
+ yyerror1(&loc, "symbol literal with interpolation is not allowed");
+ $$ = 0;
+ }
+ /*% %*/
+ /*% ripper: rb_ary_new_from_args(1, rb_ary_new_from_args(2, $2, Qnil)) %*/
+ }
+ ;
+
+p_kwrest : kwrest_mark tIDENTIFIER
+ {
+ $$ = $2;
+ }
+ | kwrest_mark
+ {
+ $$ = 0;
+ }
+ ;
+
+p_value : p_primitive
+ | p_primitive tDOT2 p_primitive
+ {
+ /*%%%*/
+ value_expr($1);
+ value_expr($3);
+ $$ = NEW_DOT2($1, $3, &@$);
+ /*% %*/
+ /*% ripper: dot2!($1, $3) %*/
+ }
+ | p_primitive tDOT3 p_primitive
+ {
+ /*%%%*/
+ value_expr($1);
+ value_expr($3);
+ $$ = NEW_DOT3($1, $3, &@$);
+ /*% %*/
+ /*% ripper: dot3!($1, $3) %*/
+ }
+ | p_primitive tDOT2
+ {
+ /*%%%*/
+ YYLTYPE loc;
+ loc.beg_pos = @2.end_pos;
+ loc.end_pos = @2.end_pos;
+
+ value_expr($1);
+ $$ = NEW_DOT2($1, new_nil(&loc), &@$);
+ /*% %*/
+ /*% ripper: dot2!($1, Qnil) %*/
+ }
+ | p_primitive tDOT3
+ {
+ /*%%%*/
+ YYLTYPE loc;
+ loc.beg_pos = @2.end_pos;
+ loc.end_pos = @2.end_pos;
+
+ value_expr($1);
+ $$ = NEW_DOT3($1, new_nil(&loc), &@$);
+ /*% %*/
+ /*% ripper: dot3!($1, Qnil) %*/
+ }
+ | p_variable
+ | p_var_ref
+ | p_const
+ | tBDOT2 p_primitive
+ {
+ /*%%%*/
+ YYLTYPE loc;
+ loc.beg_pos = @1.beg_pos;
+ loc.end_pos = @1.beg_pos;
+
+ value_expr($2);
+ $$ = NEW_DOT2(new_nil(&loc), $2, &@$);
+ /*% %*/
+ /*% ripper: dot2!(Qnil, $2) %*/
+ }
+ | tBDOT3 p_primitive
+ {
+ /*%%%*/
+ YYLTYPE loc;
+ loc.beg_pos = @1.beg_pos;
+ loc.end_pos = @1.beg_pos;
+
+ value_expr($2);
+ $$ = NEW_DOT3(new_nil(&loc), $2, &@$);
+ /*% %*/
+ /*% ripper: dot3!(Qnil, $2) %*/
+ }
+ ;
+
+p_primitive : literal
+ | strings
+ | xstring
+ | regexp
+ | words
+ | qwords
+ | symbols
+ | qsymbols
+ | keyword_variable
+ {
+ /*%%%*/
+ if (!($$ = gettable(p, $1, &@$))) $$ = NEW_BEGIN(0, &@$);
+ /*% %*/
+ /*% ripper: var_ref!($1) %*/
+ }
+ | tLAMBDA
+ {
+ token_info_push(p, "->", &@1);
+ }
+ lambda
+ {
+ $$ = $3;
+ /*%%%*/
+ nd_set_first_loc($$, @1.beg_pos);
+ /*% %*/
+ }
+ ;
+
+p_variable : tIDENTIFIER
+ {
+ /*%%%*/
+ $$ = assignable(p, $1, 0, &@$);
+ /*% %*/
+ /*% ripper: assignable(p, var_field(p, $1)) %*/
+ }
+ ;
+
+p_var_ref : '^' tIDENTIFIER
+ {
+ /*%%%*/
+ NODE *n = gettable(p, $2, &@$);
+ if (!(nd_type(n) == NODE_LVAR || nd_type(n) == NODE_DVAR)) {
+ compile_error(p, "%"PRIsVALUE": no such local variable", rb_id2str($2));
+ }
+ $$ = n;
+ /*% %*/
+ /*% ripper: var_ref!($2) %*/
+ }
+ ;
+
+p_const : tCOLON3 cname
+ {
+ /*%%%*/
+ $$ = NEW_COLON3($2, &@$);
+ /*% %*/
+ /*% ripper: top_const_ref!($2) %*/
+ }
+ | p_const tCOLON2 cname
+ {
+ /*%%%*/
+ $$ = NEW_COLON2($1, $3, &@$);
+ /*% %*/
+ /*% ripper: const_path_ref!($1, $3) %*/
+ }
+ | tCONSTANT
+ {
+ /*%%%*/
+ $$ = gettable(p, $1, &@$);
+ /*% %*/
+ /*% ripper: var_ref!($1) %*/
+ }
+ ;
+
opt_rescue : k_rescue exc_list exc_var then
compstmt
opt_rescue
@@ -10398,6 +11002,80 @@ rb_parser_numparam_id(struct parser_params *p, int idx)
}
static NODE*
+new_array_pattern(struct parser_params *p, NODE *constant, NODE *pre_arg, NODE *aryptn, const YYLTYPE *loc)
+{
+ struct rb_ary_pattern_info *apinfo = aryptn->nd_apinfo;
+
+ aryptn->nd_pconst = constant;
+
+ if (pre_arg) {
+ NODE *pre_args = NEW_LIST(pre_arg, loc);
+ if (apinfo->pre_args) {
+ apinfo->pre_args = list_concat(pre_args, apinfo->pre_args);
+ } else {
+ apinfo->pre_args = pre_args;
+ }
+ }
+ return aryptn;
+}
+
+static NODE*
+new_array_pattern_tail(struct parser_params *p, NODE *pre_args, int has_rest, ID rest_arg, NODE *post_args, const YYLTYPE *loc)
+{
+ int saved_line = p->ruby_sourceline;
+ struct rb_ary_pattern_info *apinfo;
+ NODE *node;
+ rb_imemo_tmpbuf_t *tmpbuf = new_tmpbuf();
+
+ apinfo = ZALLOC(struct rb_ary_pattern_info);
+ tmpbuf->ptr = (VALUE *)apinfo;
+ node = NEW_NODE(NODE_ARYPTN, 0, 0, apinfo, loc);
+
+ apinfo->pre_args = pre_args;
+
+ if (has_rest) {
+ if (rest_arg) {
+ apinfo->rest_arg = assignable(p, rest_arg, 0, loc);
+ } else {
+ apinfo->rest_arg = NEW_BEGIN(0, loc);
+ }
+ } else {
+ apinfo->rest_arg = NULL;
+ }
+
+ apinfo->post_args = post_args;
+
+ p->ruby_sourceline = saved_line;
+ return node;
+}
+
+static NODE*
+new_hash_pattern(struct parser_params *p, NODE *constant, NODE *hshptn, const YYLTYPE *loc)
+{
+ hshptn->nd_pconst = constant;
+ return hshptn;
+}
+
+static NODE*
+new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, const YYLTYPE *loc)
+{
+ int saved_line = p->ruby_sourceline;
+ NODE *node, *kw_rest_arg_node;
+
+ if (kw_rest_arg) {
+ kw_rest_arg_node = assignable(p, kw_rest_arg, 0, loc);
+ }
+ else {
+ kw_rest_arg_node = NULL;
+ }
+
+ node = NEW_NODE(NODE_HSHPTN, 0, kw_args, kw_rest_arg_node, loc);
+
+ p->ruby_sourceline = saved_line;
+ return node;
+}
+
+static NODE*
dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc)
{
VALUE lit;
@@ -10478,6 +11156,38 @@ new_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc)
if (hash) hash = remove_duplicate_keys(p, hash);
return NEW_HASH(hash, loc);
}
+
+static void
+error_duplicate_keys(struct parser_params *p, NODE *hash)
+{
+ st_table *literal_keys = st_init_numtable_with_size(hash->nd_alen / 2);
+ while (hash && hash->nd_head && hash->nd_next) {
+ NODE *head = hash->nd_head;
+ NODE *next = hash->nd_next->nd_next;
+ VALUE key = (VALUE)head;
+ if (nd_type(head) != NODE_LIT) {
+ yyerror1(&head->nd_loc, "key must be symbol literal");
+ }
+ if (st_lookup(literal_keys, (key = head->nd_lit), 0)) {
+ yyerror1(&head->nd_loc, "duplicated key name");
+ }
+ else {
+ st_insert(literal_keys, (st_data_t)key, (st_data_t)hash);
+ }
+ hash = next;
+ }
+ st_free_table(literal_keys);
+ return;
+}
+
+static NODE *
+new_unique_key_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc)
+{
+ if (hash) {
+ error_duplicate_keys(p, hash);
+ }
+ return NEW_HASH(hash, loc);
+}
#endif /* !RIPPER */
#ifndef RIPPER
@@ -11933,6 +12643,8 @@ Init_ripper(void)
id_warn = rb_intern_const("warn");
id_warning = rb_intern_const("warning");
id_gets = rb_intern_const("gets");
+ id_assoc = rb_intern_const("=>");
+ id_or = rb_intern_const("|");
(void)yystpcpy; /* may not used in newer bison */
diff --git a/test/coverage/test_coverage.rb b/test/coverage/test_coverage.rb
index 30523c341a..f88e97e9d6 100644
--- a/test/coverage/test_coverage.rb
+++ b/test/coverage/test_coverage.rb
@@ -359,6 +359,38 @@ class TestCoverage < Test::Unit::TestCase
end;
end
+ def test_branch_coverage_for_pattern_matching
+ result = {
+ :branches=> {
+ [:case, 0, 3, 4, 8, 7] => {[:in, 1, 5, 6, 5, 7]=>2, [:in, 2, 7, 6, 7, 7]=>0, [:else, 3, 3, 4, 8, 7]=>1},
+ [:case, 4, 12, 2, 17, 5] => {[:in, 5, 14, 4, 14, 5]=>2, [:else, 6, 16, 4, 16, 5]=>1}},
+ }
+ assert_coverage(<<~"end;", { branches: true }, result)
+ def foo(x)
+ begin
+ case x
+ in 0
+ 0
+ in 1
+ 1
+ end
+ rescue NoMatchingPatternError
+ end
+
+ case x
+ in 0
+ 0
+ else
+ 1
+ end
+ end
+
+ foo(0)
+ foo(0)
+ foo(2)
+ end;
+ end
+
def test_branch_coverage_for_safe_method_invocation
result = {
:branches=>{
diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb
index be46ad917d..1d2c501c3b 100644
--- a/test/ripper/test_parser_events.rb
+++ b/test/ripper/test_parser_events.rb
@@ -1509,4 +1509,22 @@ class TestRipper::ParserEvents < Test::Unit::TestCase
assert_warn("") {fmt, = warn("\r;")}
assert_match(/encountered/, fmt)
end
+
+ def test_in
+ thru_in = false
+ parse('case 0; in 0; end', :on_in) {thru_in = true}
+ assert_equal true, thru_in
+ end
+
+ def test_aryptn
+ thru_aryptn = false
+ parse('case 0; in [0]; end', :on_aryptn) {thru_aryptn = true}
+ assert_equal true, thru_aryptn
+ end
+
+ def test_hshptn
+ thru_hshptn = false
+ parse('case 0; in {a:}; end', :on_hshptn) {thru_hshptn = true}
+ assert_equal true, thru_hshptn
+ end
end if ripper_test
diff --git a/test/ripper/test_sexp.rb b/test/ripper/test_sexp.rb
index d63464d5a7..93e40f3bd7 100644
--- a/test/ripper/test_sexp.rb
+++ b/test/ripper/test_sexp.rb
@@ -140,4 +140,306 @@ eot
s,
bug15670)
end
+
+ pattern_matching_data = {
+ %q{ case 0; in 0; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in, [:@int, "0", [1, 11]], [[:void_stmt]], nil]],
+
+ %q{ case 0; in 0 if a; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:if_mod, [:vcall, [:@ident, "a", [1, 16]]], [:@int, "0", [1, 11]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in 0 unless a; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:unless_mod, [:vcall, [:@ident, "a", [1, 20]]], [:@int, "0", [1, 11]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in a; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in, [:var_field, [:@ident, "a", [1, 11]]], [[:void_stmt]], nil]],
+
+ %q{ case 0; in a,; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn,
+ nil,
+ [[:var_field, [:@ident, "a", [1, 11]]]],
+ [:var_field, nil],
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in a,b; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn,
+ nil,
+ [[:var_field, [:@ident, "a", [1, 11]]],
+ [:var_field, [:@ident, "b", [1, 13]]]],
+ nil,
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in *a; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn, nil, nil, [:var_field, [:@ident, "a", [1, 12]]], nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in *a,b; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn,
+ nil,
+ nil,
+ [:var_field, [:@ident, "a", [1, 12]]],
+ [[:var_field, [:@ident, "b", [1, 14]]]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in *a,b,c; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn,
+ nil,
+ nil,
+ [:var_field, [:@ident, "a", [1, 12]]],
+ [[:var_field, [:@ident, "b", [1, 14]]],
+ [:var_field, [:@ident, "c", [1, 16]]]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in *; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in, [:aryptn, nil, nil, [:var_field, nil], nil], [[:void_stmt]], nil]],
+
+ %q{ case 0; in *,a; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn,
+ nil,
+ nil,
+ [:var_field, nil],
+ [[:var_field, [:@ident, "a", [1, 13]]]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in a:,**b; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn,
+ nil,
+ [[[:@label, "a:", [1, 11]], nil]],
+ [:var_field, [:@ident, "b", [1, 16]]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in **a; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn, nil, [], [:var_field, [:@ident, "a", [1, 13]]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in **; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in, [:hshptn, nil, [], nil], [[:void_stmt]], nil]],
+
+ %q{ case 0; in a: 0; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn, nil, [[[:@label, "a:", [1, 11]], [:@int, "0", [1, 14]]]], nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in a:; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn, nil, [[[:@label, "a:", [1, 11]], nil]], nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in "a": 0; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn,
+ nil,
+ [[[:string_content, [:@tstring_content, "a", [1, 12]]],
+ [:@int, "0", [1, 16]]]],
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in "a":; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn,
+ nil,
+ [[[:string_content, [:@tstring_content, "a", [1, 12]]], nil]],
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in a: 0, b: 0; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn,
+ nil,
+ [[[:@label, "a:", [1, 11]], [:@int, "0", [1, 14]]],
+ [[:@label, "b:", [1, 17]], [:@int, "0", [1, 20]]]],
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in 0 => a; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:binary,
+ [:@int, "0", [1, 11]],
+ :"=>",
+ [:var_field, [:@ident, "a", [1, 16]]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in 0 | 1; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:binary, [:@int, "0", [1, 11]], :|, [:@int, "1", [1, 15]]],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in A(0); end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn,
+ [:var_ref, [:@const, "A", [1, 11]]],
+ [[:@int, "0", [1, 13]]],
+ nil,
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in A(a:); end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn,
+ [:var_ref, [:@const, "A", [1, 11]]],
+ [[[:@label, "a:", [1, 13]], nil]],
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in A(); end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn, [:var_ref, [:@const, "A", [1, 11]]], nil, nil, nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in A[a]; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn,
+ [:var_ref, [:@const, "A", [1, 11]]],
+ [[:var_field, [:@ident, "a", [1, 13]]]],
+ nil,
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in A[a:]; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn,
+ [:var_ref, [:@const, "A", [1, 11]]],
+ [[[:@label, "a:", [1, 13]], nil]],
+ nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in A[]; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn, [:var_ref, [:@const, "A", [1, 11]]], nil, nil, nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in [a]; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:aryptn, nil, [[:var_field, [:@ident, "a", [1, 12]]]], nil, nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in []; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in, [:aryptn, nil, nil, nil, nil], [[:void_stmt]], nil]],
+
+ %q{ case 0; in {a: 0}; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in,
+ [:hshptn, nil, [[[:@label, "a:", [1, 12]], [:@int, "0", [1, 15]]]], nil],
+ [[:void_stmt]],
+ nil]],
+
+ %q{ case 0; in {}; end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in, [:hshptn, nil, nil, nil], [[:void_stmt]], nil]],
+
+ %q{ case 0; in (0); end } =>
+ [:case,
+ [:@int, "0", [1, 5]],
+ [:in, [:@int, "0", [1, 12]], [[:void_stmt]], nil]],
+
+ %q{ case 0; in a:, a:; end } =>
+ nil,
+
+ %q{ case 0; in a?:; end } =>
+ nil,
+ }
+ pattern_matching_data.each_with_index do |(src, expected), i|
+ define_method(:"test_pattern_matching_#{i}") do
+ sexp = Ripper.sexp(src.strip)
+ assert_equal expected, sexp && sexp[1][0], src
+ end
+ end
end if ripper_test
diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb
new file mode 100644
index 0000000000..b0d8e4778b
--- /dev/null
+++ b/test/ruby/test_pattern_matching.rb
@@ -0,0 +1,1075 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class TestPatternMatching < Test::Unit::TestCase
+ class C
+ class << self
+ attr_accessor :keys
+ end
+
+ def initialize(obj)
+ @obj = obj
+ end
+
+ def deconstruct
+ @obj
+ end
+
+ def deconstruct_keys(keys)
+ C.keys = keys
+ @obj
+ end
+ end
+
+ def test_basic
+ assert_block do
+ case 0
+ in 0
+ true
+ else
+ false
+ end
+ end
+
+ assert_block do
+ case 0
+ in 1
+ false
+ else
+ true
+ end
+ end
+
+ assert_raise(NoMatchingPatternError) do
+ case 0
+ in 1
+ false
+ end
+ end
+
+ begin
+ o = [0]
+ case o
+ in 1
+ false
+ end
+ rescue => e
+ assert_match o.inspect, e.message
+ end
+
+ assert_block do
+ begin
+ true
+ ensure
+ case 0
+ in 0
+ false
+ end
+ end
+ end
+
+ assert_block do
+ begin
+ true
+ ensure
+ case 0
+ in 1
+ else
+ false
+ end
+ end
+ end
+
+ assert_raise(NoMatchingPatternError) do
+ begin
+ ensure
+ case 0
+ in 1
+ end
+ end
+ end
+
+ assert_block do
+ eval(%q{
+ case true
+ in a
+ a
+ end
+ })
+ end
+
+ assert_block do
+ tap do |a|
+ tap do
+ case true
+ in a
+ a
+ end
+ end
+ end
+ end
+
+ assert_raise(NoMatchingPatternError) do
+ o = BasicObject.new
+ def o.match
+ case 0
+ in 1
+ end
+ end
+ o.match
+ end
+ end
+
+ def test_modifier
+ assert_block do
+ case 0
+ in a if a == 0
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in a if a != 0
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in a unless a != 0
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in a unless a == 0
+ else
+ true
+ end
+ end
+ end
+
+ def test_as_pattern
+ assert_block do
+ case 0
+ in 0 => a
+ a == 0
+ end
+ end
+ end
+
+ def test_alternative_pattern
+ assert_block do
+ [0, 1].all? do |i|
+ case i
+ in 0 | 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ case 0
+ in _ | _a
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case 0
+ in a | 0
+ end
+ }, /illegal variable in alternative pattern/)
+ end
+
+ def test_var_pattern
+ assert_block do
+ case [0, 1]
+ in a, a
+ a == 1
+ end
+ end
+ end
+
+ def test_literal_value_pattern
+ assert_block do
+ case [nil, self, true, false]
+ in [nil, self, true, false]
+ true
+ end
+ end
+
+ assert_block do
+ case [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252]
+ in [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252]
+ true
+ end
+
+ case [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri]
+ in [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri]
+ true
+ end
+ end
+
+ assert_block do
+ x = 'x'
+ case ['a', 'a', x]
+ in ['a', "a", "#{x}"]
+ true
+ end
+ end
+
+ assert_block do
+ case ["a\n"]
+ in [<<END]
+a
+END
+ true
+ end
+ end
+
+ assert_block do
+ case [:a, :"a"]
+ in [:a, :"a"]
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1, 2, 3, 4, 5]
+ in [0..1, 0...2, 0.., 0..., (...5), (..5)]
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case 0
+ in a..b
+ end
+ }, /unexpected/)
+
+ assert_block do
+ case 'abc'
+ in /a/
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in ->(i) { i == 0 }
+ true
+ end
+ end
+
+ assert_block do
+ case [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), 'a']
+ in [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), %r(a)]
+ true
+ end
+ end
+
+ assert_block do
+ case [__FILE__, __LINE__ + 1, __ENCODING__]
+ in [__FILE__, __LINE__, __ENCODING__]
+ true
+ end
+ end
+ end
+
+ def test_constant_value_pattern
+ assert_block do
+ case 0
+ in Integer
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in Object::Integer
+ true
+ end
+ end
+
+ assert_block do
+ case 0
+ in ::Object::Integer
+ true
+ end
+ end
+ end
+
+ def test_pin_operator_value_pattern
+ assert_block do
+ a = /a/
+ case 'abc'
+ in ^a
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 0]
+ in a, ^a
+ a == 0
+ end
+ end
+ end
+
+ def test_array_pattern
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in 0,;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1], C.new([0, 1])].all? do |i|
+ case i
+ in 0,;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[], C.new([])].all? do |i|
+ case i
+ in 0,;
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1], C.new([0, 1])].all? do |i|
+ case i
+ in 0, 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in 0, 1
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[], C.new([])].all? do |i|
+ case i
+ in *a
+ a == []
+ end
+ end
+ end
+
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in *a
+ a == [0]
+ end
+ end
+ end
+
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in *a, 0, 1
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1], C.new([0, 1])].all? do |i|
+ case i
+ in *a, 0, 1
+ a == []
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1, 2], C.new([0, 1, 2])].all? do |i|
+ case i
+ in *a, 1, 2
+ a == [0]
+ end
+ end
+ end
+
+ assert_block do
+ [[], C.new([])].all? do |i|
+ case i
+ in *;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0], C.new([0])].all? do |i|
+ case i
+ in *, 0, 1
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1], C.new([0, 1])].all? do |i|
+ case i
+ in *, 0, 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [[0, 1, 2], C.new([0, 1, 2])].all? do |i|
+ case i
+ in *, 1, 2
+ true
+ end
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in C(0)
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in Array(0)
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in C()
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in Array()
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in C[0]
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in Array[0]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in C[]
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in Array[]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case []
+ in []
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([])
+ in []
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0]
+ true
+ end
+ end
+
+ assert_block do
+ case C.new([0])
+ in [0]
+ true
+ end
+ end
+
+ assert_block do
+ case []
+ in [0, *a]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *a]
+ a == []
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *a, 1]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1]
+ in [0, *a, 1]
+ a == []
+ end
+ end
+
+ assert_block do
+ case [0, 1, 2]
+ in [0, *a, 2]
+ a == [1]
+ end
+ end
+
+ assert_block do
+ case []
+ in [0, *]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *]
+ true
+ end
+ end
+
+ assert_block do
+ case [0, 1]
+ in [0, *]
+ true
+ end
+ end
+
+ assert_block do
+ case []
+ in [0, *a]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case [0]
+ in [0, *a]
+ a == []
+ end
+ end
+
+ assert_block do
+ case [0, 1]
+ in [0, *a]
+ a == [1]
+ end
+ end
+ end
+
+ def test_hash_pattern
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in a: 0
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a: 0
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a: 0
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a: 0, b: 1
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a: 0, b: 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1, c: 2}, C.new({a: 0, b: 1, c: 2})].all? do |i|
+ case i
+ in a: 0, b: 1
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in a:
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a:
+ a == 0
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a:
+ a == 0
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in "a": 0
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in "a":;
+ a == 0
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in **a
+ a == {}
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in **a
+ a == {a: 0}
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in **;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in **;
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in a:, **b
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in a:, **b
+ a == 0 && b == {}
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in a:, **b
+ a == 0 && b == {b: 1}
+ end
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0})
+ in C(a: 0)
+ true
+ end
+ end
+
+ assert_block do
+ case {a: 0}
+ in C(a: 0)
+ else
+ true
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0})
+ in C[a: 0]
+ true
+ end
+ end
+
+ assert_block do
+ case {a: 0}
+ in C[a: 0]
+ else
+ true
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in {a: 0}
+ else
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in {a: 0}
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i|
+ case i
+ in {a: 0}
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{}, C.new({})].all? do |i|
+ case i
+ in {}
+ true
+ end
+ end
+ end
+
+ assert_block do
+ [{a: 0}, C.new({a: 0})].all? do |i|
+ case i
+ in {}
+ else
+ true
+ end
+ end
+ end
+
+ assert_syntax_error(%q{
+ case _
+ in a:, a:
+ end
+ }, /duplicated key name/)
+
+ assert_syntax_error(%q{
+ case _
+ in a?:
+ end
+ }, /key must be valid as local variables/)
+
+ assert_block do
+ case {a?: true}
+ in a?: true
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case _
+ in "a-b":
+ end
+ }, /key must be valid as local variables/)
+
+ assert_block do
+ case {"a-b": true}
+ in "a-b": true
+ true
+ end
+ end
+
+ assert_syntax_error(%q{
+ case _
+ in "#{a}": a
+ end
+ }, /symbol literal with interpolation is not allowed/)
+
+ assert_syntax_error(%q{
+ case _
+ in "#{a}":
+ end
+ }, /symbol literal with interpolation is not allowed/)
+ end
+
+ def test_paren
+ assert_block do
+ case 0
+ in (0)
+ true
+ end
+ end
+ end
+
+ def test_invalid_syntax
+ assert_syntax_error(%q{
+ case 0
+ in a, b:
+ end
+ }, /unexpected/)
+
+ assert_syntax_error(%q{
+ case 0
+ in [a:]
+ end
+ }, /unexpected/)
+
+ assert_syntax_error(%q{
+ case 0
+ in {a}
+ end
+ }, /unexpected/)
+
+ assert_syntax_error(%q{
+ case 0
+ in {0 => a}
+ end
+ }, /unexpected/)
+ end
+
+ ################################################################
+
+ class CTypeError
+ def deconstruct
+ nil
+ end
+
+ def deconstruct_keys(keys)
+ nil
+ end
+ end
+
+ def test_deconstruct
+ assert_raise(TypeError) do
+ case CTypeError.new
+ in []
+ end
+ end
+ end
+
+ def test_deconstruct_keys
+ assert_raise(TypeError) do
+ case CTypeError.new
+ in {}
+ end
+ end
+
+ assert_block do
+ case {}
+ in {}
+ $keys == nil
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {a: 0, b:}
+ C.keys == [:a, :b]
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {a: 0, b:, **}
+ C.keys == [:a, :b]
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {a: 0, b:, **r}
+ C.keys == nil
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {**}
+ C.keys == []
+ end
+ end
+
+ assert_block do
+ case C.new({a: 0, b: 0, c: 0})
+ in {**r}
+ C.keys == nil
+ end
+ end
+ end
+
+ ################################################################
+
+ class TestPatternMatchingRefinements < Test::Unit::TestCase
+ class C1
+ def deconstruct
+ [:C1]
+ end
+ end
+
+ class C2
+ end
+
+ module M
+ refine Array do
+ def deconstruct
+ [0]
+ end
+ end
+
+ refine Hash do
+ def deconstruct_keys(_)
+ {a: 0}
+ end
+ end
+
+ refine C2.singleton_class do
+ def ===(obj)
+ obj.kind_of?(C1)
+ end
+ end
+ end
+
+ using M
+
+ def test_refinements
+ assert_block do
+ case []
+ in [0]
+ true
+ end
+ end
+
+ assert_block do
+ case {}
+ in {a: 0}
+ true
+ end
+ end
+
+ assert_block do
+ case C1.new
+ in C2(:C1)
+ true
+ end
+ end
+ end
+ end
+end
diff --git a/vm.c b/vm.c
index 41c50c898d..c4144b5b99 100644
--- a/vm.c
+++ b/vm.c
@@ -2915,6 +2915,7 @@ Init_VM(void)
rb_define_method_id(klass, id_core_set_postexe, m_core_set_postexe, 0);
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, rb_f_raise, -1);
rb_define_method_id(klass, idProc, rb_block_proc, 0);
rb_define_method_id(klass, idLambda, rb_block_lambda, 0);
rb_obj_freeze(fcore);