From 88f3ce12d32ffbef983b0950743c20253ea2d0c6 Mon Sep 17 00:00:00 2001 From: Kazuki Tsujimoto Date: Sun, 13 Dec 2020 11:50:14 +0900 Subject: [PATCH] Reintroduce `expr in pat` [Feature #17371] --- NEWS.md | 24 +++++++++++++++++------- doc/syntax/pattern_matching.rdoc | 12 ++++++++++-- parse.y | 29 ++++++++++++++++++++++++----- test/ruby/test_pattern_matching.rb | 6 +++++- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index bb072f239e..3cd98b25b5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -52,19 +52,28 @@ 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]] -* Pattern matching is no longer experimental. [[Feature #17260]] +* Pattern matching(`case/in`) is no longer experimental. [[Feature #17260]] -* One-line pattern matching now uses `=>` instead of `in`. [EXPERIMENTAL] - [[Feature #17260]] +* One-line pattern matching is redesgined. [EXPERIMENTAL] + * `=>` is added. It can be used as like rightward assignment. + [[Feature #17260]] + + ```ruby + 0 => a + p a #=> 0 + + {b: 0, c: 1} => {b:} + p b #=> 0 + ``` + + * `in` is changed to return `true` or `false`. [[Feature #17371]] ```ruby # version 3.0 - {a: 0, b: 1} => {a:} - p a # => 0 + 0 in 1 #=> false # version 2.7 - {a: 0, b: 1} in {a:} - p a # => 0 + 0 in 1 #=> raise NoMatchingPatternError ``` * Find pattern is added. [EXPERIMENTAL] @@ -639,4 +648,5 @@ end [Feature #17187]: https://bugs.ruby-lang.org/issues/17187 [Bug #17221]: https://bugs.ruby-lang.org/issues/17221 [Feature #17260]: https://bugs.ruby-lang.org/issues/17260 +[Feature #17371]: https://bugs.ruby-lang.org/issues/17371 [GH-2991]: https://github.com/ruby/ruby/pull/2991 diff --git a/doc/syntax/pattern_matching.rdoc b/doc/syntax/pattern_matching.rdoc index 8419af58bb..b4ac52e0a9 100644 --- a/doc/syntax/pattern_matching.rdoc +++ b/doc/syntax/pattern_matching.rdoc @@ -15,11 +15,13 @@ Pattern matching in Ruby is implemented with the +case+/+in+ expression: ... end -or with the +=>+ operator, which can be used in a standalone expression: +(Note that +in+ and +when+ branches can *not* be mixed in one +case+ expression.) + +or with the +=>+ operator and the +in+ operator, which can be used in a standalone expression: => -(Note that +in+ and +when+ branches can *not* be mixed in one +case+ expression.) + in Pattern matching is _exhaustive_: if variable doesn't match pattern (in a separate +in+ clause), or doesn't matches any branch of +case+ expression (and +else+ branch is absent), +NoMatchingPatternError+ is raised. @@ -46,6 +48,12 @@ whilst the +=>+ operator is most useful when expected data structure is known be puts "Connect with user '#{user}'" # Prints: "Connect with user 'admin'" ++ in + is the same as +case ; in ; true; else false; end+. +You can use it when you only want to know if a pattern has been matched or not: + + users = [{name: "Alice", age: 12}, {name: "Bob", age: 23}] + users.any? {|u| u in {name: /B/, age: 20..} } #=> true + See below for more examples and explanations of the syntax. == Patterns diff --git a/parse.y b/parse.y index bc33c6a23f..ff413d8b1b 100644 --- a/parse.y +++ b/parse.y @@ -502,7 +502,7 @@ static NODE *new_find_pattern(struct parser_params *p, NODE *constant, NODE *fnd 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 void warn_one_line_pattern_matching(struct parser_params *p, NODE *node, NODE *pattern); +static void warn_one_line_pattern_matching(struct parser_params *p, NODE *node, NODE *pattern, bool right_assign); static NODE *new_kw_arg(struct parser_params *p, NODE *k, const YYLTYPE *loc); static NODE *args_with_numbered(struct parser_params*,NODE*,int); @@ -1243,7 +1243,7 @@ static int looking_at_eol_p(struct parser_params *p); %nonassoc tLOWEST %nonassoc tLBRACE_ARG -%nonassoc modifier_if modifier_unless modifier_while modifier_until +%nonassoc modifier_if modifier_unless modifier_while modifier_until keyword_in %left keyword_or keyword_and %right keyword_not %nonassoc keyword_defined @@ -1662,7 +1662,26 @@ expr : command_call p->ctxt.in_kwarg = $3.in_kwarg; /*%%%*/ $$ = NEW_CASE3($1, NEW_IN($5, 0, 0, &@5), &@$); - warn_one_line_pattern_matching(p, $$, $5); + warn_one_line_pattern_matching(p, $$, $5, true); + /*% %*/ + /*% ripper: case!($1, in!($5, Qnil, Qnil)) %*/ + } + | arg keyword_in + { + value_expr($1); + SET_LEX_STATE(EXPR_BEG|EXPR_LABEL); + p->command_start = FALSE; + $$ = p->ctxt; + p->ctxt.in_kwarg = 1; + } + {$$ = push_pvtbl(p);} + p_expr + {pop_pvtbl(p, $4);} + { + p->ctxt.in_kwarg = $3.in_kwarg; + /*%%%*/ + $$ = NEW_CASE3($1, NEW_IN($5, NEW_TRUE(&@5), NEW_FALSE(&@5), &@5), &@$); + warn_one_line_pattern_matching(p, $$, $5, false); /*% %*/ /*% ripper: case!($1, in!($5, Qnil, Qnil)) %*/ } @@ -11689,13 +11708,13 @@ new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, co } static void -warn_one_line_pattern_matching(struct parser_params *p, NODE *node, NODE *pattern) +warn_one_line_pattern_matching(struct parser_params *p, NODE *node, NODE *pattern, bool right_assign) { enum node_type type; type = nd_type(pattern); if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL) && - !(type == NODE_LASGN || type == NODE_DASGN || type == NODE_DASGN_CURR)) + !(right_assign && (type == NODE_LASGN || type == NODE_DASGN || type == NODE_DASGN_CURR))) rb_warn0L(nd_line(node), "One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!"); } diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb index b155cb8579..e553789756 100644 --- a/test/ruby/test_pattern_matching.rb +++ b/test/ruby/test_pattern_matching.rb @@ -1451,7 +1451,7 @@ END ################################################################ - def test_assoc + def test_one_line 1 => a assert_equal 1, a assert_raise(NoMatchingPatternError) do @@ -1464,6 +1464,9 @@ END assert_syntax_error(%q{ 1 => a: }, /unexpected/, '[ruby-core:95098]') + + assert_equal true, (1 in 1) + assert_equal false, (1 in 2) end def assert_experimental_warning(code) @@ -1481,6 +1484,7 @@ END def test_experimental_warning assert_experimental_warning("case [0]; in [*, 0, *]; end") assert_experimental_warning("0 => 0") + assert_experimental_warning("0 in a") end end END_of_GUARD