ruby-changes:64123
From: Kazuki <ko1@a...>
Date: Sun, 13 Dec 2020 11:52:23 +0900 (JST)
Subject: [ruby-changes:64123] 88f3ce12d3 (master): Reintroduce `expr in pat` [Feature #17371]
https://git.ruby-lang.org/ruby.git/commit/?id=88f3ce12d3 From 88f3ce12d32ffbef983b0950743c20253ea2d0c6 Mon Sep 17 00:00:00 2001 From: Kazuki Tsujimoto <kazuki@c...> Date: Sun, 13 Dec 2020 11:50:14 +0900 Subject: Reintroduce `expr in pat` [Feature #17371] diff --git a/NEWS.md b/NEWS.md index bb072f2..3cd98b2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -52,19 +52,28 @@ sufficient information, see the ChangeLog file or Redmine https://github.com/ruby/ruby/blob/trunk/NEWS.md#L52 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 https://github.com/ruby/ruby/blob/trunk/NEWS.md#L648 [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 8419af5..b4ac52e 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: https://github.com/ruby/ruby/blob/trunk/doc/syntax/pattern_matching.rdoc#L15 ... 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: <expression> => <pattern> -(Note that +in+ and +when+ branches can *not* be mixed in one +case+ expression.) + <expression> in <pattern> 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 https://github.com/ruby/ruby/blob/trunk/doc/syntax/pattern_matching.rdoc#L48 puts "Connect with user '#{user}'" # Prints: "Connect with user 'admin'" ++<expression> in <pattern>+ is the same as +case <expression>; in <pattern>; 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 bc33c6a..ff413d8 100644 --- a/parse.y +++ b/parse.y @@ -502,7 +502,7 @@ static NODE *new_find_pattern(struct parser_params *p, NODE *constant, NODE *fnd https://github.com/ruby/ruby/blob/trunk/parse.y#L502 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); https://github.com/ruby/ruby/blob/trunk/parse.y#L1243 %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 https://github.com/ruby/ruby/blob/trunk/parse.y#L1662 p->ctxt.in_kwarg = $<ctxt>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; + $<ctxt>$ = p->ctxt; + p->ctxt.in_kwarg = 1; + } + {$<tbl>$ = push_pvtbl(p);} + p_expr + {pop_pvtbl(p, $<tbl>4);} + { + p->ctxt.in_kwarg = $<ctxt>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 https://github.com/ruby/ruby/blob/trunk/parse.y#L11708 } 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 b155cb8..e553789 100644 --- a/test/ruby/test_pattern_matching.rb +++ b/test/ruby/test_pattern_matching.rb @@ -1451,7 +1451,7 @@ END https://github.com/ruby/ruby/blob/trunk/test/ruby/test_pattern_matching.rb#L1451 ################################################################ - def test_assoc + def test_one_line 1 => a assert_equal 1, a assert_raise(NoMatchingPatternError) do @@ -1464,6 +1464,9 @@ END https://github.com/ruby/ruby/blob/trunk/test/ruby/test_pattern_matching.rb#L1464 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 https://github.com/ruby/ruby/blob/trunk/test/ruby/test_pattern_matching.rb#L1484 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 -- cgit v0.10.2 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/