Reintroduce `expr in pat` [Feature #17371]

This commit is contained in:
Kazuki Tsujimoto 2020-12-13 11:50:14 +09:00
Родитель a8cf526ae9
Коммит 88f3ce12d3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: BCEA306C49B81CD7
4 изменённых файлов: 56 добавлений и 15 удалений

24
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

Просмотреть файл

@ -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

29
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!");
}

Просмотреть файл

@ -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