Skip to content

Commit 962c014

Browse files
parse guard patterns
Co-authored-by: Max Niederman <[email protected]>
1 parent f86915a commit 962c014

17 files changed

+248
-63
lines changed

Diff for: compiler/rustc_ast_passes/src/feature_gate.rs

+1
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
551551
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
552552
gate_all!(explicit_tail_calls, "`become` expression is experimental");
553553
gate_all!(generic_const_items, "generic const items are experimental");
554+
gate_all!(guard_patterns, "guard patterns are experimental", "consider using match arm guards");
554555
gate_all!(fn_delegation, "functions delegation is not yet fully implemented");
555556
gate_all!(postfix_match, "postfix match is experimental");
556557
gate_all!(mut_ref, "mutable by-reference bindings are experimental");

Diff for: compiler/rustc_expand/src/expand.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ pub fn parse_ast_fragment<'a>(
990990
}
991991
}
992992
AstFragmentKind::Ty => AstFragment::Ty(this.parse_ty()?),
993-
AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_no_top_guard(
993+
AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_allow_top_guard(
994994
None,
995995
RecoverComma::No,
996996
RecoverColon::Yes,

Diff for: compiler/rustc_parse/src/parser/expr.rs

+23-29
Original file line numberDiff line numberDiff line change
@@ -2776,7 +2776,7 @@ impl<'a> Parser<'a> {
27762776
};
27772777
// Try to parse the pattern `for ($PAT) in $EXPR`.
27782778
let pat = match (
2779-
self.parse_pat_no_top_guard(
2779+
self.parse_pat_allow_top_guard(
27802780
None,
27812781
RecoverComma::Yes,
27822782
RecoverColon::Yes,
@@ -3313,39 +3313,33 @@ impl<'a> Parser<'a> {
33133313

33143314
fn parse_match_arm_pat_and_guard(&mut self) -> PResult<'a, (P<Pat>, Option<P<Expr>>)> {
33153315
if self.token == token::OpenDelim(Delimiter::Parenthesis) {
3316-
// Detect and recover from `($pat if $cond) => $arm`.
33173316
let left = self.token.span;
3318-
match self.parse_pat_no_top_guard(
3317+
let pat = self.parse_pat_no_top_guard(
33193318
None,
33203319
RecoverComma::Yes,
33213320
RecoverColon::Yes,
33223321
CommaRecoveryMode::EitherTupleOrPipe,
3323-
) {
3324-
Ok(pat) => Ok((pat, self.parse_match_arm_guard()?)),
3325-
Err(err)
3326-
if let prev_sp = self.prev_token.span
3327-
&& let true = self.eat_keyword(kw::If) =>
3328-
{
3329-
// We know for certain we've found `($pat if` so far.
3330-
let mut cond = match self.parse_match_guard_condition() {
3331-
Ok(cond) => cond,
3332-
Err(cond_err) => {
3333-
cond_err.cancel();
3334-
return Err(err);
3335-
}
3336-
};
3337-
err.cancel();
3338-
CondChecker::new(self).visit_expr(&mut cond);
3339-
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis)]);
3340-
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
3341-
let right = self.prev_token.span;
3342-
self.dcx().emit_err(errors::ParenthesesInMatchPat {
3343-
span: vec![left, right],
3344-
sugg: errors::ParenthesesInMatchPatSugg { left, right },
3345-
});
3346-
Ok((self.mk_pat(left.to(prev_sp), ast::PatKind::Wild), Some(cond)))
3347-
}
3348-
Err(err) => Err(err),
3322+
)?;
3323+
if let ast::PatKind::Paren(subpat) = &pat.kind
3324+
&& let ast::PatKind::Guard(..) = &subpat.kind
3325+
{
3326+
// Detect and recover from `($pat if $cond) => $arm`.
3327+
// FIXME(guard_patterns): convert this to a normal guard instead
3328+
let span = pat.span;
3329+
let ast::PatKind::Paren(subpat) = pat.into_inner().kind else { unreachable!() };
3330+
let ast::PatKind::Guard(_, mut cond) = subpat.into_inner().kind else {
3331+
unreachable!()
3332+
};
3333+
self.psess.gated_spans.ungate_last(sym::guard_patterns, cond.span);
3334+
CondChecker::new(self).visit_expr(&mut cond);
3335+
let right = self.prev_token.span;
3336+
self.dcx().emit_err(errors::ParenthesesInMatchPat {
3337+
span: vec![left, right],
3338+
sugg: errors::ParenthesesInMatchPatSugg { left, right },
3339+
});
3340+
Ok((self.mk_pat(span, ast::PatKind::Wild), Some(cond)))
3341+
} else {
3342+
Ok((pat, self.parse_match_arm_guard()?))
33493343
}
33503344
} else {
33513345
// Regular parser flow:

Diff for: compiler/rustc_parse/src/parser/pat.rs

+32-7
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,31 @@ pub enum PatternLocation {
9595
}
9696

9797
impl<'a> Parser<'a> {
98+
/// Parses a pattern.
99+
///
100+
/// Corresponds to `Pattern` in RFC 3637 and admits guard patterns at the top level.
101+
/// Used when parsing patterns in all cases where neither `PatternNoTopGuard` nor
102+
/// `PatternNoTopAlt` (see below) are used.
103+
pub fn parse_pat_allow_top_guard(
104+
&mut self,
105+
expected: Option<Expected>,
106+
rc: RecoverComma,
107+
ra: RecoverColon,
108+
rt: CommaRecoveryMode,
109+
) -> PResult<'a, P<Pat>> {
110+
let pat = self.parse_pat_no_top_guard(expected, rc, ra, rt)?;
111+
112+
if self.eat_keyword(kw::If) {
113+
let cond = self.parse_expr()?;
114+
// Feature-gate guard patterns
115+
self.psess.gated_spans.gate(sym::guard_patterns, cond.span);
116+
let span = pat.span.to(cond.span);
117+
Ok(self.mk_pat(span, PatKind::Guard(pat, cond)))
118+
} else {
119+
Ok(pat)
120+
}
121+
}
122+
98123
/// Parses a pattern.
99124
///
100125
/// Corresponds to `PatternNoTopAlt` in RFC 3637 and does not admit or-patterns
@@ -111,8 +136,8 @@ impl<'a> Parser<'a> {
111136
/// Parses a pattern.
112137
///
113138
/// Corresponds to `PatternNoTopGuard` in RFC 3637 and allows or-patterns, but not
114-
/// guard patterns, at the top level. Used for parsing patterns in `pat` fragments and
115-
/// `let`, `if let`, and `while let` expressions.
139+
/// guard patterns, at the top level. Used for parsing patterns in `pat` fragments (until
140+
/// the next edition) and `let`, `if let`, and `while let` expressions.
116141
///
117142
/// Note that after the FCP in <https://github.com/rust-lang/rust/issues/81415>,
118143
/// a leading vert is allowed in nested or-patterns, too. This allows us to
@@ -697,7 +722,7 @@ impl<'a> Parser<'a> {
697722
} else if self.check(&token::OpenDelim(Delimiter::Bracket)) {
698723
// Parse `[pat, pat,...]` as a slice pattern.
699724
let (pats, _) = self.parse_delim_comma_seq(Delimiter::Bracket, |p| {
700-
p.parse_pat_no_top_guard(
725+
p.parse_pat_allow_top_guard(
701726
None,
702727
RecoverComma::No,
703728
RecoverColon::No,
@@ -945,7 +970,7 @@ impl<'a> Parser<'a> {
945970
let open_paren = self.token.span;
946971

947972
let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| {
948-
p.parse_pat_no_top_guard(
973+
p.parse_pat_allow_top_guard(
949974
None,
950975
RecoverComma::No,
951976
RecoverColon::No,
@@ -1360,7 +1385,7 @@ impl<'a> Parser<'a> {
13601385
path: Path,
13611386
) -> PResult<'a, PatKind> {
13621387
let (fields, _) = self.parse_paren_comma_seq(|p| {
1363-
p.parse_pat_no_top_guard(
1388+
p.parse_pat_allow_top_guard(
13641389
None,
13651390
RecoverComma::No,
13661391
RecoverColon::No,
@@ -1395,7 +1420,7 @@ impl<'a> Parser<'a> {
13951420
self.parse_builtin(|self_, _lo, ident| {
13961421
Ok(match ident.name {
13971422
// builtin#deref(PAT)
1398-
sym::deref => Some(ast::PatKind::Deref(self_.parse_pat_no_top_guard(
1423+
sym::deref => Some(ast::PatKind::Deref(self_.parse_pat_allow_top_guard(
13991424
None,
14001425
RecoverComma::Yes,
14011426
RecoverColon::Yes,
@@ -1670,7 +1695,7 @@ impl<'a> Parser<'a> {
16701695
// Parsing a pattern of the form `fieldname: pat`.
16711696
let fieldname = self.parse_field_name()?;
16721697
self.bump();
1673-
let pat = self.parse_pat_no_top_guard(
1698+
let pat = self.parse_pat_allow_top_guard(
16741699
None,
16751700
RecoverComma::No,
16761701
RecoverColon::No,

Diff for: compiler/rustc_parse/src/parser/path.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ impl<'a> Parser<'a> {
469469
PathStyle::Pat
470470
if let Ok(_) = self
471471
.parse_paren_comma_seq(|p| {
472-
p.parse_pat_no_top_guard(
472+
p.parse_pat_allow_top_guard(
473473
None,
474474
RecoverComma::No,
475475
RecoverColon::No,
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
fn match_guards_still_work() {
2+
match 0 {
3+
0 if guard(0) => {},
4+
_ => {},
5+
}
6+
}
7+
8+
fn other_guards_dont() {
9+
match 0 {
10+
(0 if guard(0)) => {},
11+
//~^ ERROR unexpected parentheses surrounding `match` arm pattern
12+
_ => {},
13+
}
14+
15+
match 0 {
16+
(0 if guard(0)) | 1 => {},
17+
//~^ ERROR: guard patterns are experimental
18+
_ => {},
19+
}
20+
21+
let ((x if guard(x)) | x) = 0;
22+
//~^ ERROR: guard patterns are experimental
23+
//~| ERROR: cannot find value `x`
24+
25+
if let (x if guard(x)) = 0 {}
26+
//~^ ERROR: guard patterns are experimental
27+
//~| WARN: irrefutable
28+
29+
while let (x if guard(x)) = 0 {}
30+
//~^ ERROR: guard patterns are experimental
31+
//~| WARN: irrefutable
32+
33+
#[cfg(FALSE)]
34+
while let (x if guard(x)) = 0 {}
35+
//~^ ERROR: guard patterns are experimental
36+
}
37+
38+
fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
39+
//~^ ERROR: guard patterns are experimental
40+
//~| ERROR: cannot find value `x`
41+
42+
fn guard<T>(x: T) -> bool {
43+
unimplemented!()
44+
}
45+
46+
fn main() {}
+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
error: unexpected parentheses surrounding `match` arm pattern
2+
--> $DIR/feature-gate-guard-patterns.rs:10:9
3+
|
4+
LL | (0 if guard(0)) => {},
5+
| ^ ^
6+
|
7+
help: remove parentheses surrounding the pattern
8+
|
9+
LL - (0 if guard(0)) => {},
10+
LL + 0 if guard(0) => {},
11+
|
12+
13+
error[E0425]: cannot find value `x` in this scope
14+
--> $DIR/feature-gate-guard-patterns.rs:21:22
15+
|
16+
LL | let ((x if guard(x)) | x) = 0;
17+
| ^ not found in this scope
18+
19+
error[E0425]: cannot find value `x` in this scope
20+
--> $DIR/feature-gate-guard-patterns.rs:38:45
21+
|
22+
LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
23+
| ^
24+
|
25+
help: the binding `x` is available in a different scope in the same function
26+
--> $DIR/feature-gate-guard-patterns.rs:21:11
27+
|
28+
LL | let ((x if guard(x)) | x) = 0;
29+
| ^
30+
31+
error[E0658]: guard patterns are experimental
32+
--> $DIR/feature-gate-guard-patterns.rs:16:15
33+
|
34+
LL | (0 if guard(0)) | 1 => {},
35+
| ^^^^^^^^
36+
|
37+
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
38+
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
39+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
40+
= help: consider using match arm guards
41+
42+
error[E0658]: guard patterns are experimental
43+
--> $DIR/feature-gate-guard-patterns.rs:21:16
44+
|
45+
LL | let ((x if guard(x)) | x) = 0;
46+
| ^^^^^^^^
47+
|
48+
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
49+
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
50+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
51+
= help: consider using match arm guards
52+
53+
error[E0658]: guard patterns are experimental
54+
--> $DIR/feature-gate-guard-patterns.rs:25:18
55+
|
56+
LL | if let (x if guard(x)) = 0 {}
57+
| ^^^^^^^^
58+
|
59+
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
60+
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
61+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
62+
= help: consider using match arm guards
63+
64+
error[E0658]: guard patterns are experimental
65+
--> $DIR/feature-gate-guard-patterns.rs:29:21
66+
|
67+
LL | while let (x if guard(x)) = 0 {}
68+
| ^^^^^^^^
69+
|
70+
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
71+
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
72+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
73+
= help: consider using match arm guards
74+
75+
error[E0658]: guard patterns are experimental
76+
--> $DIR/feature-gate-guard-patterns.rs:34:21
77+
|
78+
LL | while let (x if guard(x)) = 0 {}
79+
| ^^^^^^^^
80+
|
81+
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
82+
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
83+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
84+
= help: consider using match arm guards
85+
86+
error[E0658]: guard patterns are experimental
87+
--> $DIR/feature-gate-guard-patterns.rs:38:39
88+
|
89+
LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
90+
| ^^^^^^^^
91+
|
92+
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
93+
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
94+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
95+
= help: consider using match arm guards
96+
97+
warning: irrefutable `if let` pattern
98+
--> $DIR/feature-gate-guard-patterns.rs:25:8
99+
|
100+
LL | if let (x if guard(x)) = 0 {}
101+
| ^^^^^^^^^^^^^^^^^^^^^^^
102+
|
103+
= note: this pattern will always match, so the `if let` is useless
104+
= help: consider replacing the `if let` with a `let`
105+
= note: `#[warn(irrefutable_let_patterns)]` on by default
106+
107+
warning: irrefutable `while let` pattern
108+
--> $DIR/feature-gate-guard-patterns.rs:29:11
109+
|
110+
LL | while let (x if guard(x)) = 0 {}
111+
| ^^^^^^^^^^^^^^^^^^^^^^^
112+
|
113+
= note: this pattern will always match, so the loop will never exit
114+
= help: consider instead using a `loop { ... }` with a `let` inside it
115+
116+
error: aborting due to 9 previous errors; 2 warnings emitted
117+
118+
Some errors have detailed explanations: E0425, E0658.
119+
For more information about an error, try `rustc --explain E0425`.

Diff for: tests/ui/parser/issues/issue-72373.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ fn foo(c: &[u32], n: u32) -> u32 {
33
[h, ..] if h > n => 0,
44
[h, ..] if h == n => 1,
55
[h, ref ts..] => foo(c, n - h) + foo(ts, n),
6-
//~^ ERROR expected one of `,`, `@`, `]`, or `|`, found `..`
6+
//~^ ERROR expected one of `,`, `@`, `]`, `if`, or `|`, found `..`
77
[] => 0,
88
}
99
}

Diff for: tests/ui/parser/issues/issue-72373.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: expected one of `,`, `@`, `]`, or `|`, found `..`
1+
error: expected one of `,`, `@`, `]`, `if`, or `|`, found `..`
22
--> $DIR/issue-72373.rs:5:19
33
|
44
LL | [h, ref ts..] => foo(c, n - h) + foo(ts, n),
5-
| ^^ expected one of `,`, `@`, `]`, or `|`
5+
| ^^ expected one of `,`, `@`, `]`, `if`, or `|`
66
|
77
help: if you meant to bind the contents of the rest of the array pattern into `ts`, use `@`
88
|

Diff for: tests/ui/parser/misspelled-keywords/ref.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: expected one of `)`, `,`, `@`, or `|`, found `list`
1+
error: expected one of `)`, `,`, `@`, `if`, or `|`, found `list`
22
--> $DIR/ref.rs:4:19
33
|
44
LL | Some(refe list) => println!("{list:?}"),
5-
| ^^^^ expected one of `)`, `,`, `@`, or `|`
5+
| ^^^^ expected one of `)`, `,`, `@`, `if`, or `|`
66
|
77
help: there is a keyword `ref` with a similar name
88
|

Diff for: tests/ui/parser/pat-lt-bracket-7.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ fn main() {
33
let foo = core::iter::empty();
44

55
for Thing(x[]) in foo {}
6-
//~^ ERROR: expected one of `)`, `,`, `@`, or `|`, found `[`
6+
//~^ ERROR: expected one of `)`, `,`, `@`, `if`, or `|`, found `[`
77
}
88

99
const RECOVERY_WITNESS: () = 0; //~ ERROR mismatched types

0 commit comments

Comments
 (0)