ruby-changes:63492
From: Kazuki <ko1@a...>
Date: Sun, 1 Nov 2020 13:36:00 +0900 (JST)
Subject: [ruby-changes:63492] b601532411 (master): Pattern matching is no longer experimental
https://git.ruby-lang.org/ruby.git/commit/?id=b601532411 From b60153241121297c94de976419d421683da4d51b Mon Sep 17 00:00:00 2001 From: Kazuki Tsujimoto <kazuki@c...> Date: Sun, 1 Nov 2020 13:28:24 +0900 Subject: Pattern matching is no longer experimental diff --git a/NEWS.md b/NEWS.md index c7a4036..2fa1b3d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -48,7 +48,23 @@ sufficient information, see the ChangeLog file or Redmine https://github.com/ruby/ruby/blob/trunk/NEWS.md#L48 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]] +* Pattern matching is no longer experimental. [[Feature #17260]] + +* One-line pattern matching now uses `=>` instead of `in`. [EXPERIMENTAL] + [[Feature #17260]] + + ```ruby + # version 3.0 + {a: 0, b: 1} => {a:} + p a # => 0 + + # version 2.7 + {a: 0, b: 1} in {a:} + p a # => 0 + ``` + +* Find pattern is added. [EXPERIMENTAL] + [[Feature #16828]] ```ruby case ["a", 1, "b", "c", 2, "d", "e", "f", 3] diff --git a/doc/syntax/pattern_matching.rdoc b/doc/syntax/pattern_matching.rdoc index 9d3101c..8419af5 100644 --- a/doc/syntax/pattern_matching.rdoc +++ b/doc/syntax/pattern_matching.rdoc @@ -1,12 +1,8 @@ https://github.com/ruby/ruby/blob/trunk/doc/syntax/pattern_matching.rdoc#L1 = Pattern matching -Pattern matching is an experimental feature allowing deep matching of structured values: checking the structure and binding the matched parts to local variables. +Pattern matching is a feature allowing deep matching of structured values: checking the structure and binding the matched parts to local variables. -Pattern matching in Ruby is implemented with the +in+ operator, which can be used in a standalone expression: - - <expression> in <pattern> - -or within the +case+ statement: +Pattern matching in Ruby is implemented with the +case+/+in+ expression: case <expression> in <pattern1> @@ -19,11 +15,15 @@ or within the +case+ statement: https://github.com/ruby/ruby/blob/trunk/doc/syntax/pattern_matching.rdoc#L15 ... end -(Note that +in+ and +when+ branches can *not* be mixed in one +case+ statement.) +or with the +=>+ operator, which can be used in a standalone expression: + + <expression> => <pattern> -Pattern matching is _exhaustive_: if variable doesn't match pattern (in a separate +in+ statement), or doesn't matches any branch of +case+ statement (and +else+ branch is absent), +NoMatchingPatternError+ is raised. +(Note that +in+ and +when+ branches can *not* be mixed in one +case+ expression.) -Therefore, +case+ statement might be used for conditional matching and unpacking: +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. + +Therefore, +case+ expression might be used for conditional matching and unpacking: config = {db: {user: 'admin', password: 'abc123'}} @@ -37,11 +37,11 @@ Therefore, +case+ statement might be used for conditional matching and unpacking https://github.com/ruby/ruby/blob/trunk/doc/syntax/pattern_matching.rdoc#L37 end # Prints: "Connect with user 'admin'" -whilst standalone +in+ statement is most useful when expected data structure is known beforehand, to just unpack parts of it: +whilst the +=>+ operator is most useful when expected data structure is known beforehand, to just unpack parts of it: config = {db: {user: 'admin', password: 'abc123'}} - config in {db: {user:}} # will raise if the config's structure is unexpected + config => {db: {user:}} # will raise if the config's structure is unexpected puts "Connect with user '#{user}'" # Prints: "Connect with user 'admin'" @@ -113,7 +113,7 @@ Both array and hash patterns support "rest" specification: https://github.com/ruby/ruby/blob/trunk/doc/syntax/pattern_matching.rdoc#L113 end #=> "matched" -In +case+ (but not in standalone +in+) statement, parentheses around both kinds of patterns could be omitted +In +case+ (but not in +=>+) expression, parentheses around both kinds of patterns could be omitted case [1, 2] in Integer, Integer @@ -378,53 +378,23 @@ Additionally, when matching custom classes, expected class could be specified as https://github.com/ruby/ruby/blob/trunk/doc/syntax/pattern_matching.rdoc#L378 == Current feature status -As of Ruby 2.7, feature is considered _experimental_: its syntax can change in the future, and the performance is not optimized yet. Every time you use pattern matching in code, the warning will be printed: +As of Ruby 3.0, one-line pattern matching and find pattern are considered _experimental_: its syntax can change in the future. Every time you use these features in code, the warning will be printed: - {a: 1, b: 2} in {a:} - # warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby! + [0] => [*, 0, *] + # warning: Find pattern is experimental, and the behavior may change in future versions of Ruby! + # warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! To suppress this warning, one may use newly introduced Warning::[]= method: Warning[:experimental] = false - eval('{a: 1, b: 2} in {a:}') + eval('[0] => [*, 0, *]') # ...no warning printed... Note that pattern-matching warning is raised at a compile time, so this will not suppress warning: Warning[:experimental] = false # At the time this line is evaluated, the parsing happened and warning emitted - {a: 1, b: 2} in {a:} + [0] => [*, 0, *] So, only subsequently loaded files or `eval`-ed code is affected by switching the flag. Alternatively, command-line key <code>-W:no-experimental</code> can be used to turn off "experimental" feature warnings. - -One of the things developer should be aware of, which probably to be fixed in the upcoming versions, is that pattern matching statement rewrites mentioned local variables on partial match, <i>even if the whole pattern is not matched</i>. - - a = 5 - case [1, 2] - in String => a, String - "matched" - else - "not matched" - end - #=> "not matched" - a - #=> 5 -- even partial match not happened, a is not rewritten - - case [1, 2] - in a, String - "matched" - else - "not matched" - end - #=> "not matched" - a - #=> 1 -- the whole pattern not matched, but partial match happened, a is rewritten - -Currently, the only core class implementing +deconstruct+ and +deconstruct_keys+ is Struct. - - Point = Struct.new(:x, :y) - Point[1, 2] in [a, b] - # successful match - Point[1, 2] in {x:, y:} - # successful match diff --git a/parse.y b/parse.y index 256e991..98fd4ae 100644 --- a/parse.y +++ b/parse.y @@ -502,7 +502,6 @@ 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 NODE *new_case3(struct parser_params *p, NODE *val, NODE *pat, 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); @@ -1661,7 +1660,11 @@ expr : command_call https://github.com/ruby/ruby/blob/trunk/parse.y#L1660 { p->ctxt.in_kwarg = $<ctxt>3.in_kwarg; /*%%%*/ - $$ = new_case3(p, $1, NEW_IN($5, 0, 0, &@5), &@$); + $$ = NEW_CASE3($1, NEW_IN($5, 0, 0, &@5), &@$); + + if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) + rb_warn0L(nd_line($$), "One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!"); + /*% %*/ /*% ripper: case!($1, in!($5, Qnil, Qnil)) %*/ } @@ -2998,7 +3001,7 @@ primary : literal https://github.com/ruby/ruby/blob/trunk/parse.y#L3001 k_end { /*%%%*/ - $$ = new_case3(p, $2, $4, &@$); + $$ = NEW_CASE3($2, $4, &@$); /*% %*/ /*% ripper: case!($2, $4) %*/ } @@ -4176,6 +4179,9 @@ p_args_tail : p_rest https://github.com/ruby/ruby/blob/trunk/parse.y#L4179 p_find : p_rest ',' p_args_post ',' p_rest { $$ = new_find_pattern_tail(p, $1, $3, $5, &@$); + + if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) + rb_warn0L(nd_line($$), "Find pattern is experimental, and the behavior may change in future versions of Ruby!"); } ; @@ -11679,16 +11685,6 @@ 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#L11685 return node; } -static NODE * -new_case3(struct parser_params *p, NODE *val, NODE *pat, const YYLTYPE *loc) -{ - NODE *node = NEW_CASE3(val, pat, loc); - - if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) - rb_warn0L(nd_line(node), "Pattern matching is experimental, and the behavior may change in future versions of Ruby!"); - return node; -} - static NODE* dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc) { diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb index d4de685..b155cb8 100644 --- a/test/ruby/test_pattern_matching.rb +++ b/test/ruby/test_pattern_matching.rb @@ -1473,13 +1473,13 @@ END https://github.com/ruby/ruby/blob/trunk/test/ruby/test_pattern_matching.rb#L1473 assert_warn('') {eval(code)} Warning[:experimental] = true - assert_warn(/Pattern matching is experimental/) {eval(code)} + assert_warn(/is experimental/) {eval(code)} ensure Warning[:ex (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/