1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Introduce find pattern [Feature #16828]

This commit is contained in:
Kazuki Tsujimoto 2020-06-14 09:24:36 +09:00
parent f7906a7e31
commit ddded1157a
No known key found for this signature in database
GPG key ID: BCEA306C49B81CD7
10 changed files with 450 additions and 11 deletions

12
NEWS.md
View file

@ -38,6 +38,18 @@ sufficient information, see the ChangeLog file or Redmine
instead of a warning. yield in a class definition outside of a method
is now a SyntaxError instead of a LocalJumpError. [[Feature #15575]]
* Find pattern is added. [[Feature #16828]]
```ruby
case ["a", 1, "b", "c", 2, "d", "e", "f", 3]
in [*pre, String => x, String => y, *post]
p pre #=> ["a", 1]
p x #=> "b"
p y #=> "c"
p post #=> [2, "d", "e", "f", 3]
end
```
* Rightward assignment statement is added. [EXPERIMENTAL]
[[Feature #15921]]

13
ast.c
View file

@ -599,6 +599,19 @@ node_children(rb_ast_t *ast, NODE *node)
rest,
NEW_CHILD(ast, apinfo->post_args));
}
case NODE_FNDPTN:
{
struct rb_fnd_pattern_info *fpinfo = node->nd_fpinfo;
VALUE pre_rest = NODE_NAMED_REST_P(fpinfo->pre_rest_arg) ? NEW_CHILD(ast, fpinfo->pre_rest_arg) :
ID2SYM(rb_intern("NODE_SPECIAL_NO_NAME_REST"));
VALUE post_rest = NODE_NAMED_REST_P(fpinfo->post_rest_arg) ? NEW_CHILD(ast, fpinfo->post_rest_arg) :
ID2SYM(rb_intern("NODE_SPECIAL_NO_NAME_REST"));
return rb_ary_new_from_args(4,
NEW_CHILD(ast, node->nd_pconst),
pre_rest,
NEW_CHILD(ast, fpinfo->args),
post_rest);
}
case NODE_HSHPTN:
{
VALUE kwrest = node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD ? ID2SYM(rb_intern("NODE_SPECIAL_NO_REST_KEYWORD")) :

167
compile.c
View file

@ -5752,6 +5752,173 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c
break;
}
case NODE_FNDPTN: {
/*
* 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
* unless d.length >= pattern.args_num
* goto match_failed
* end
*
* begin
* len = d.length
* limit = d.length - pattern.args_num
* i = 0
* while i <= limit
* if pattern.args_num.times.all? {|j| pattern.args[j].match?(d[i+j]) }
* if pattern.has_pre_rest_arg_id
* unless pattern.pre_rest_arg.match?(d[0, i])
* goto find_failed
* end
* end
* if pattern.has_post_rest_arg_id
* unless pattern.post_rest_arg.match?(d[i+pattern.args_num, len])
* goto find_failed
* end
* end
* goto find_succeeded
* end
* i+=1
* end
* find_failed:
* goto match_failed
* find_succeeded:
* end
*
* goto matched
* type_error:
* FrozenCore.raise TypeError
* match_failed:
* goto unmatched
*/
struct rb_fnd_pattern_info *fpinfo = node->nd_fpinfo;
const NODE *args = fpinfo->args;
const int args_num = fpinfo->args ? rb_long2int(fpinfo->args->nd_alen) : 0;
LABEL *match_failed, *type_error;
match_failed = NEW_LABEL(line);
type_error = NEW_LABEL(line);
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, checktype, INT2FIX(T_ARRAY));
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(args_num));
ADD_SEND(ret, line, idGE, INT2FIX(1));
ADD_INSNL(ret, line, branchunless, match_failed);
{
LABEL *while_begin = NEW_LABEL(nd_line(node));
LABEL *next_loop = NEW_LABEL(nd_line(node));
LABEL *find_succeeded = NEW_LABEL(line);
LABEL *find_failed = NEW_LABEL(nd_line(node));
int j;
ADD_INSN(ret, line, dup); /* allocate stack for len */
ADD_SEND(ret, line, idLength, INT2FIX(0));
ADD_INSN(ret, line, dup); /* allocate stack for limit */
ADD_INSN1(ret, line, putobject, INT2FIX(args_num));
ADD_SEND(ret, line, idMINUS, INT2FIX(1));
ADD_INSN1(ret, line, putobject, INT2FIX(0)); /* allocate stack for i */
ADD_LABEL(ret, while_begin);
ADD_INSN(ret, line, dup);
ADD_INSN1(ret, line, topn, INT2FIX(2));
ADD_SEND(ret, line, idLE, INT2FIX(1));
ADD_INSNL(ret, line, branchunless, find_failed);
for (j = 0; j < args_num; j++) {
ADD_INSN1(ret, line, topn, INT2FIX(3));
ADD_INSN1(ret, line, topn, INT2FIX(1));
if (j != 0) {
ADD_INSN1(ret, line, putobject, INT2FIX(j));
ADD_SEND(ret, line, idPLUS, INT2FIX(1));
}
ADD_SEND(ret, line, idAREF, INT2FIX(1));
CHECK(iseq_compile_pattern_match(iseq, ret, args->nd_head, next_loop, in_alt_pattern));
args = args->nd_next;
}
if (NODE_NAMED_REST_P(fpinfo->pre_rest_arg)) {
ADD_INSN1(ret, line, topn, INT2FIX(3));
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));
}
if (NODE_NAMED_REST_P(fpinfo->post_rest_arg)) {
ADD_INSN1(ret, line, topn, INT2FIX(3));
ADD_INSN1(ret, line, topn, INT2FIX(1));
ADD_INSN1(ret, line, putobject, INT2FIX(args_num));
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));
}
ADD_INSNL(ret, line, jump, find_succeeded);
ADD_LABEL(ret, next_loop);
ADD_INSN1(ret, line, putobject, INT2FIX(1));
ADD_SEND(ret, line, idPLUS, INT2FIX(1));
ADD_INSNL(ret, line, jump, while_begin);
ADD_LABEL(ret, find_failed);
ADD_INSN(ret, line, pop);
ADD_INSN(ret, line, pop);
ADD_INSN(ret, line, pop);
ADD_INSNL(ret, line, jump, match_failed);
ADD_LABEL(ret, find_succeeded);
ADD_INSN(ret, line, pop);
ADD_INSN(ret, line, pop);
ADD_INSN(ret, line, pop);
}
ADD_INSN(ret, line, pop);
ADD_INSNL(ret, line, jump, matched);
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);
ADD_INSNL(ret, line, jump, unmatched);
break;
}
case NODE_HSHPTN: {
/*
* keys = nil

View file

@ -479,6 +479,7 @@ count_nodes(int argc, VALUE *argv, VALUE os)
COUNT_NODE(NODE_ATTRASGN);
COUNT_NODE(NODE_LAMBDA);
COUNT_NODE(NODE_ARYPTN);
COUNT_NODE(NODE_FNDPTN);
COUNT_NODE(NODE_HSHPTN);
#undef COUNT_NODE
case NODE_LAST: break;

34
node.c
View file

@ -1053,6 +1053,27 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node)
F_NODE(nd_apinfo->post_args, "post arguments");
return;
case NODE_FNDPTN:
ANN("find pattern");
ANN("format: [nd_pconst](*[pre_rest_arg], args, ..., *[post_rest_arg])");
F_NODE(nd_pconst, "constant");
if (NODE_NAMED_REST_P(node->nd_fpinfo->pre_rest_arg)) {
F_NODE(nd_fpinfo->pre_rest_arg, "pre rest argument");
}
else {
F_MSG(nd_fpinfo->pre_rest_arg, "pre rest argument", "NODE_SPECIAL_NO_NAME_REST (rest argument without name)");
}
F_NODE(nd_fpinfo->args, "arguments");
LAST_NODE;
if (NODE_NAMED_REST_P(node->nd_fpinfo->post_rest_arg)) {
F_NODE(nd_fpinfo->post_rest_arg, "post rest argument");
}
else {
F_MSG(nd_fpinfo->post_rest_arg, "post rest argument", "NODE_SPECIAL_NO_NAME_REST (rest argument without name)");
}
return;
case NODE_HSHPTN:
ANN("hash pattern");
ANN("format: [nd_pconst]([nd_pkwargs], ..., **[nd_pkwrestarg])");
@ -1204,6 +1225,7 @@ rb_ast_newnode(rb_ast_t *ast, enum node_type type)
case NODE_ARGS:
case NODE_SCOPE:
case NODE_ARYPTN:
case NODE_FNDPTN:
return ast_newnode_in_bucket(&nb->markable);
default:
return ast_newnode_in_bucket(&nb->unmarkable);
@ -1271,6 +1293,12 @@ mark_ast_value(void *ctx, NODE * node)
rb_gc_mark_movable(apinfo->imemo);
break;
}
case NODE_FNDPTN:
{
struct rb_fnd_pattern_info *fpinfo = node->nd_fpinfo;
rb_gc_mark_movable(fpinfo->imemo);
break;
}
case NODE_ARGS:
{
struct rb_args_info *args = node->nd_ainfo;
@ -1311,6 +1339,12 @@ update_ast_value(void *ctx, NODE * node)
apinfo->imemo = rb_gc_location(apinfo->imemo);
break;
}
case NODE_FNDPTN:
{
struct rb_fnd_pattern_info *fpinfo = node->nd_fpinfo;
fpinfo->imemo = rb_gc_location(fpinfo->imemo);
break;
}
case NODE_ARGS:
{
struct rb_args_info *args = node->nd_ainfo;

11
node.h
View file

@ -123,6 +123,7 @@ enum node_type {
NODE_LAMBDA,
NODE_ARYPTN,
NODE_HSHPTN,
NODE_FNDPTN,
NODE_LAST
};
@ -166,6 +167,7 @@ typedef struct RNode {
struct rb_global_entry *entry;
struct rb_args_info *args;
struct rb_ary_pattern_info *apinfo;
struct rb_fnd_pattern_info *fpinfo;
VALUE value;
} u3;
rb_code_location_t nd_loc;
@ -278,6 +280,8 @@ typedef struct RNode {
#define nd_apinfo u3.apinfo
#define nd_fpinfo u3.fpinfo
#define NEW_NODE(t,a0,a1,a2,loc) rb_node_newnode((t),(VALUE)(a0),(VALUE)(a1),(VALUE)(a2),loc)
#define NEW_NODE_WITH_LOCALS(t,a1,a2,loc) node_newnode_with_locals(p, (t),(VALUE)(a1),(VALUE)(a2),loc)
@ -456,6 +460,13 @@ struct rb_ary_pattern_info {
VALUE imemo;
};
struct rb_fnd_pattern_info {
NODE *pre_rest_arg;
NODE *args;
NODE *post_rest_arg;
VALUE imemo;
};
struct parser_params;
void *rb_parser_malloc(struct parser_params *, size_t);
void *rb_parser_realloc(struct parser_params *, void *, size_t);

123
parse.y
View file

@ -496,6 +496,8 @@ static NODE *new_args(struct parser_params*,NODE*,NODE*,ID,NODE*,NODE*,const YYL
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_find_pattern(struct parser_params *p, NODE *constant, NODE *fndptn, const YYLTYPE *loc);
static NODE *new_find_pattern_tail(struct parser_params *p, ID pre_rest_arg, NODE *args, ID post_rest_arg, 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_case3(struct parser_params *p, NODE *val, NODE *pat, const YYLTYPE *loc);
@ -871,6 +873,42 @@ new_array_pattern_tail(struct parser_params *p, VALUE pre_args, VALUE has_rest,
return (VALUE)t;
}
static VALUE
new_find_pattern(struct parser_params *p, VALUE constant, VALUE fndptn, const YYLTYPE *loc)
{
NODE *t = (NODE *)fndptn;
struct rb_fnd_pattern_info *fpinfo = t->nd_fpinfo;
VALUE pre_rest_arg = Qnil, args = Qnil, post_rest_arg = Qnil;
if (fpinfo) {
pre_rest_arg = rb_ary_entry(fpinfo->imemo, 0);
args = rb_ary_entry(fpinfo->imemo, 1);
post_rest_arg = rb_ary_entry(fpinfo->imemo, 2);
}
return dispatch4(fndptn, constant, pre_rest_arg, args, post_rest_arg);
}
static VALUE
new_find_pattern_tail(struct parser_params *p, VALUE pre_rest_arg, VALUE args, VALUE post_rest_arg, const YYLTYPE *loc)
{
NODE *t;
struct rb_fnd_pattern_info *fpinfo;
pre_rest_arg = dispatch1(var_field, pre_rest_arg ? pre_rest_arg : Qnil);
post_rest_arg = dispatch1(var_field, post_rest_arg ? post_rest_arg : Qnil);
VALUE tmpbuf = rb_imemo_tmpbuf_auto_free_pointer();
fpinfo = ZALLOC(struct rb_fnd_pattern_info);
rb_imemo_tmpbuf_set_ptr(tmpbuf, fpinfo);
fpinfo->imemo = rb_ary_new_from_args(4, pre_rest_arg, args, post_rest_arg, tmpbuf);
t = rb_node_newnode(NODE_FNDPTN, Qnil, Qnil, (VALUE)fpinfo, &NULL_LOC);
RB_OBJ_WRITTEN(p->ast, Qnil, fpinfo->imemo);
return (VALUE)t;
}
#define new_hash(p,h,l) rb_ary_new_from_args(0)
static VALUE
@ -1139,14 +1177,14 @@ static int looking_at_eol_p(struct parser_params *p);
%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_expr p_as p_alt p_expr_basic p_find
%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 p_kwnorest p_any_kwrest p_kw_label
%type <id> p_rest p_kwrest p_kwnorest p_any_kwrest p_kw_label
%type <id> f_no_kwarg f_any_kwrest args_forward excessed_comma
%token END_OF_INPUT 0 "end-of-input"
%token <id> '.'
@ -3963,6 +4001,10 @@ p_top_expr_body : p_expr
/*%
%*/
}
| p_find
{
$$ = new_find_pattern(p, Qnone, $1, &@$);
}
| p_args_tail
{
$$ = new_array_pattern(p, Qnone, Qnone, $1, &@$);
@ -4011,6 +4053,15 @@ p_expr_basic : p_value
/*%
%*/
}
| p_const p_lparen p_find rparen
{
pop_pktbl(p, $<tbl>2);
$$ = new_find_pattern(p, $1, $3, &@$);
/*%%%*/
nd_set_first_loc($$, @1.beg_pos);
/*%
%*/
}
| p_const p_lparen p_kwargs rparen
{
pop_pktbl(p, $<tbl>2);
@ -4034,6 +4085,15 @@ p_expr_basic : p_value
/*%
%*/
}
| p_const p_lbracket p_find rbracket
{
pop_pktbl(p, $<tbl>2);
$$ = new_find_pattern(p, $1, $3, &@$);
/*%%%*/
nd_set_first_loc($$, @1.beg_pos);
/*%
%*/
}
| p_const p_lbracket p_kwargs rbracket
{
pop_pktbl(p, $<tbl>2);
@ -4052,6 +4112,10 @@ p_expr_basic : p_value
{
$$ = new_array_pattern(p, Qnone, Qnone, $2, &@$);
}
| tLBRACK p_find rbracket
{
$$ = new_find_pattern(p, Qnone, $2, &@$);
}
| tLBRACK rbracket
{
$$ = new_array_pattern_tail(p, Qnone, 0, 0, Qnone, &@$);
@ -4135,21 +4199,30 @@ p_args_head : p_arg ','
}
;
p_args_tail : tSTAR tIDENTIFIER
p_args_tail : p_rest
{
$$ = new_array_pattern_tail(p, Qnone, 1, $2, Qnone, &@$);
$$ = new_array_pattern_tail(p, Qnone, 1, $1, Qnone, &@$);
}
| tSTAR tIDENTIFIER ',' p_args_post
| p_rest ',' p_args_post
{
$$ = new_array_pattern_tail(p, Qnone, 1, $2, $4, &@$);
$$ = new_array_pattern_tail(p, Qnone, 1, $1, $3, &@$);
}
;
p_find : p_rest ',' p_args_post ',' p_rest
{
$$ = new_find_pattern_tail(p, $1, $3, $5, &@$);
}
;
p_rest : tSTAR tIDENTIFIER
{
$$ = $2;
}
| tSTAR
{
$$ = new_array_pattern_tail(p, Qnone, 1, 0, Qnone, &@$);
}
| tSTAR ',' p_args_post
{
$$ = new_array_pattern_tail(p, Qnone, 1, 0, $3, &@$);
$$ = 0;
}
;
@ -11536,6 +11609,34 @@ new_array_pattern_tail(struct parser_params *p, NODE *pre_args, int has_rest, ID
return node;
}
static NODE*
new_find_pattern(struct parser_params *p, NODE *constant, NODE *fndptn, const YYLTYPE *loc)
{
fndptn->nd_pconst = constant;
return fndptn;
}
static NODE*
new_find_pattern_tail(struct parser_params *p, ID pre_rest_arg, NODE *args, ID post_rest_arg, const YYLTYPE *loc)
{
int saved_line = p->ruby_sourceline;
NODE *node;
VALUE tmpbuf = rb_imemo_tmpbuf_auto_free_pointer();
struct rb_fnd_pattern_info *fpinfo = ZALLOC(struct rb_fnd_pattern_info);
rb_imemo_tmpbuf_set_ptr(tmpbuf, fpinfo);
node = NEW_NODE(NODE_FNDPTN, 0, 0, fpinfo, loc);
fpinfo->imemo = tmpbuf;
RB_OBJ_WRITTEN(p->ast, Qnil, tmpbuf);
fpinfo->pre_rest_arg = pre_rest_arg ? assignable(p, pre_rest_arg, 0, loc) : NODE_SPECIAL_NO_NAME_REST;
fpinfo->args = args;
fpinfo->post_rest_arg = post_rest_arg ? assignable(p, post_rest_arg, 0, loc) : NODE_SPECIAL_NO_NAME_REST;
p->ruby_sourceline = saved_line;
return node;
}
static NODE*
new_hash_pattern(struct parser_params *p, NODE *constant, NODE *hshptn, const YYLTYPE *loc)
{

View file

@ -1548,6 +1548,12 @@ class TestRipper::ParserEvents < Test::Unit::TestCase
assert_equal true, thru_aryptn
end
def test_fndptn
thru_fndptn = false
parse('case 0; in [*,0,*]; end', :on_fndptn) {thru_fndptn = true}
assert_equal true, thru_fndptn
end
def test_hshptn
thru_hshptn = false
parse('case 0; in {a:}; end', :on_hshptn) {thru_hshptn = true}

View file

@ -412,6 +412,43 @@ eot
[:@int, "0", [1, 5]],
[:in, [:aryptn, nil, nil, nil, nil], [[:void_stmt]], nil]],
[__LINE__, %q{ 0 in [*, a, *] }] =>
[:case,
[:@int, "0", [1, 0]],
[:in,
[:fndptn,
nil,
[:var_field, nil],
[[:var_field, [:@ident, "a", [1, 9]]]],
[:var_field, nil]],
nil,
nil]],
[__LINE__, %q{ 0 in [*a, b, *c] }] =>
[:case,
[:@int, "0", [1, 0]],
[:in,
[:fndptn,
nil,
[:var_field, [:@ident, "a", [1, 7]]],
[[:var_field, [:@ident, "b", [1, 10]]]],
[:var_field, [:@ident, "c", [1, 14]]]],
nil,
nil]],
[__LINE__, %q{ 0 in A(*a, b, c, *d) }] =>
[:case,
[:@int, "0", [1, 0]],
[:in,
[:fndptn,
[:var_ref, [:@const, "A", [1, 5]]],
[:var_field, [:@ident, "a", [1, 8]]],
[[:var_field, [:@ident, "b", [1, 11]]],
[:var_field, [:@ident, "c", [1, 14]]]],
[:var_field, [:@ident, "d", [1, 18]]]],
nil,
nil]],
[__LINE__, %q{ case 0; in {a: 0}; end }] =>
[:case,
[:@int, "0", [1, 5]],

View file

@ -736,6 +736,63 @@ END
end
end
def test_find_pattern
[0, 1, 2] in [*, 1 => a, *]
assert_equal(1, a)
[0, 1, 2] in [*a, 1 => b, *c]
assert_equal([0], a)
assert_equal(1, b)
assert_equal([2], c)
assert_block do
case [0, 1, 2]
in [*, 9, *]
false
else
true
end
end
assert_block do
case [0, 1, 2]
in [*, Integer, String, *]
false
else
true
end
end
[0, 1, 2] in [*a, 1 => b, 2 => c, *d]
assert_equal([0], a)
assert_equal(1, b)
assert_equal(2, c)
assert_equal([], d)
case [0, 1, 2]
in *, 1 => a, *;
assert_equal(1, a)
end
assert_block do
case [0, 1, 2]
in String(*, 1, *)
false
in Array(*, 1, *)
true
end
end
assert_block do
case [0, 1, 2]
in String[*, 1, *]
false
in Array[*, 1, *]
true
end
end
end
def test_hash_pattern
assert_block do
[{}, C.new({})].all? do |i|