mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Pattern matching is no longer experimental
This commit is contained in:
parent
4f8d9b0db8
commit
b601532411
4 changed files with 46 additions and 64 deletions
18
NEWS.md
18
NEWS.md
|
@ -48,7 +48,23 @@ 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]]
|
||||
* 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]
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
= 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:
|
|||
...
|
||||
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:
|
||||
|
||||
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.
|
||||
<expression> => <pattern>
|
||||
|
||||
Therefore, +case+ statement might be used for conditional matching and unpacking:
|
||||
(Note that +in+ and +when+ branches can *not* be mixed in one +case+ expression.)
|
||||
|
||||
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
|
|||
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:
|
|||
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
|
|||
|
||||
== 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
|
||||
|
|
22
parse.y
22
parse.y
|
@ -502,7 +502,6 @@ 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 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
|
|||
{
|
||||
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
|
|||
k_end
|
||||
{
|
||||
/*%%%*/
|
||||
$$ = new_case3(p, $2, $4, &@$);
|
||||
$$ = NEW_CASE3($2, $4, &@$);
|
||||
/*% %*/
|
||||
/*% ripper: case!($2, $4) %*/
|
||||
}
|
||||
|
@ -4176,6 +4179,9 @@ p_args_tail : p_rest
|
|||
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
|
|||
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)
|
||||
{
|
||||
|
|
|
@ -1473,13 +1473,13 @@ END
|
|||
assert_warn('') {eval(code)}
|
||||
|
||||
Warning[:experimental] = true
|
||||
assert_warn(/Pattern matching is experimental/) {eval(code)}
|
||||
assert_warn(/is experimental/) {eval(code)}
|
||||
ensure
|
||||
Warning[:experimental] = w
|
||||
end
|
||||
|
||||
def test_experimental_warning
|
||||
assert_experimental_warning("case 0; in 0; end")
|
||||
assert_experimental_warning("case [0]; in [*, 0, *]; end")
|
||||
assert_experimental_warning("0 => 0")
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue