From 94d6ec1d90bb28e5f303867b048e6322d8781cb1 Mon Sep 17 00:00:00 2001 From: Kazuki Tsujimoto Date: Sun, 1 Sep 2019 16:39:34 +0900 Subject: [PATCH] Make pattern matching support **nil syntax --- ast.c | 5 +++- compile.c | 23 ++++++++++++++---- node.c | 7 +++++- node.h | 1 + parse.y | 21 +++++++++++++++-- test/ruby/test_pattern_matching.rb | 38 ++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 8 deletions(-) diff --git a/ast.c b/ast.c index 601a819c1b..fdb4b7a621 100644 --- a/ast.c +++ b/ast.c @@ -652,10 +652,13 @@ node_children(rb_ast_t *ast, NODE *node) } case NODE_HSHPTN: { + VALUE kwrest = node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD ? ID2SYM(rb_intern("NODE_SPECIAL_NO_REST_KEYWORD")) : + NEW_CHILD(ast, node->nd_pkwrestarg); + 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)); + kwrest); } case NODE_ARGS_AUX: case NODE_LAST: diff --git a/compile.c b/compile.c index d11e2f9f32..02171c6349 100644 --- a/compile.c +++ b/compile.c @@ -5467,7 +5467,15 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c * end * end * if pattern.has_kw_rest_arg_node? - * pattern.kw_rest_arg_node.match?(d) + * if pattern.no_rest_keyword? + * unless d.empty? + * goto match_failed + * end + * else + * unless pattern.kw_rest_arg_node.match?(d) + * goto match_failed + * end + * end * end * true * goto fin @@ -5563,9 +5571,16 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c } 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); + if (node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD) { + ADD_INSN(ret, line, dup); + ADD_SEND(ret, line, idEmptyP, INT2FIX(0)); + ADD_INSNL(ret, line, branchunless, match_failed); + } + else { + 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); diff --git a/node.c b/node.c index 4c2810b0e2..f4df845d2c 100644 --- a/node.c +++ b/node.c @@ -1065,7 +1065,12 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) F_NODE(nd_pconst, "constant"); F_NODE(nd_pkwargs, "keyword arguments"); LAST_NODE; - F_NODE(nd_pkwrestarg, "keyword rest argument"); + if (node->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD) { + F_MSG(nd_pkwrestarg, "keyword rest argument", "NODE_SPECIAL_NO_REST_KEYWORD (**nil)"); + } + else { + F_NODE(nd_pkwrestarg, "keyword rest argument"); + } return; case NODE_ARGS_AUX: diff --git a/node.h b/node.h index 3b565fc083..dbc3162512 100644 --- a/node.h +++ b/node.h @@ -385,6 +385,7 @@ typedef struct RNode { #define NODE_SPECIAL_NO_NAME_REST ((NODE *)-1) #define NODE_NAMED_REST_P(node) ((node) != NODE_SPECIAL_NO_NAME_REST) #define NODE_SPECIAL_EXCESSIVE_COMMA ((ID)1) +#define NODE_SPECIAL_NO_REST_KEYWORD ((NODE *)-1) VALUE rb_node_case_when_optimizable_literal(const NODE *const node); diff --git a/parse.y b/parse.y index f6d6e27eb7..6729e104be 100644 --- a/parse.y +++ b/parse.y @@ -1033,7 +1033,7 @@ static void token_info_warn(struct parser_params *p, const char *token, token_in %type keyword_variable user_variable sym operation operation2 operation3 %type cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg %type f_kwrest f_label f_arg_asgn call_op call_op2 reswords relop dot_or_colon -%type p_kwrest +%type p_kwrest p_kwnorest %type f_no_kwarg %token END_OF_INPUT 0 "end-of-input" %token '.' @@ -3950,6 +3950,14 @@ p_kwargs : p_kwarg ',' p_kwrest { $$ = new_hash_pattern_tail(p, new_hash(p, Qnone, &@$), $1, &@$); } + | p_kwarg ',' p_kwnorest + { + $$ = new_hash_pattern_tail(p, new_unique_key_hash(p, $1, &@$), ID2SYM(idNil), &@$); + } + | p_kwnorest + { + $$ = new_hash_pattern_tail(p, new_hash(p, Qnone, &@$), ID2SYM(idNil), &@$); + } ; p_kwarg : p_kw @@ -4026,6 +4034,12 @@ p_kwrest : kwrest_mark tIDENTIFIER } ; +p_kwnorest : kwrest_mark keyword_nil + { + $$ = 0; + } + ; + p_value : p_primitive | p_primitive tDOT2 p_primitive { @@ -11253,7 +11267,10 @@ new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, co int saved_line = p->ruby_sourceline; NODE *node, *kw_rest_arg_node; - if (kw_rest_arg) { + if (kw_rest_arg == ID2SYM(idNil)) { + kw_rest_arg_node = NODE_SPECIAL_NO_REST_KEYWORD; + } + else if (kw_rest_arg) { kw_rest_arg_node = assignable(p, kw_rest_arg, 0, loc); } else { diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb index 988a9dfe39..1f209b0081 100644 --- a/test/ruby/test_pattern_matching.rb +++ b/test/ruby/test_pattern_matching.rb @@ -846,6 +846,44 @@ END end end + assert_block do + [{}, C.new({})].all? do |i| + case i + in **nil + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **nil + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a:, **nil + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a:, **nil + else + true + end + end + end + assert_block do case C.new({a: 0}) in C(a: 0)