summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.md24
-rw-r--r--doc/syntax/pattern_matching.rdoc12
-rw-r--r--parse.y29
-rw-r--r--test/ruby/test_pattern_matching.rb6
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:
<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
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 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 = $<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
}
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