summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKazuki Tsujimoto <kazuki@callcc.net>2020-11-01 13:28:24 +0900
committerKazuki Tsujimoto <kazuki@callcc.net>2020-11-01 13:33:58 +0900
commitb60153241121297c94de976419d421683da4d51b (patch)
treeb1c1c8f8ca5d89f72de73d1fece79c4d490fb8df
parent4f8d9b0db84c42c8d37f75de885de1c0a5cb542c (diff)
Pattern matching is no longer experimental
-rw-r--r--NEWS.md18
-rw-r--r--doc/syntax/pattern_matching.rdoc66
-rw-r--r--parse.y22
-rw-r--r--test/ruby/test_pattern_matching.rb4
4 files changed, 46 insertions, 64 deletions
diff --git a/NEWS.md b/NEWS.md
index c7a4036a0a..2fa1b3db2e 100644
--- a/NEWS.md
+++ b/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]
diff --git a/doc/syntax/pattern_matching.rdoc b/doc/syntax/pattern_matching.rdoc
index 9d3101c86b..8419af58bb 100644
--- a/doc/syntax/pattern_matching.rdoc
+++ b/doc/syntax/pattern_matching.rdoc
@@ -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:
+
+ <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
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
diff --git a/parse.y b/parse.y
index 256e9915ad..98fd4ae921 100644
--- a/parse.y
+++ b/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)
{
diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb
index d4de685495..b155cb8579 100644
--- a/test/ruby/test_pattern_matching.rb
+++ b/test/ruby/test_pattern_matching.rb
@@ -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