Skip to content

Commit

Permalink
parse guard patterns
Browse files Browse the repository at this point in the history
Co-authored-by: Max Niederman <[email protected]>
  • Loading branch information
Nadrieril and max-niederman committed Nov 24, 2024
1 parent f86915a commit 962c014
Show file tree
Hide file tree
Showing 17 changed files with 248 additions and 63 deletions.
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
gate_all!(explicit_tail_calls, "`become` expression is experimental");
gate_all!(generic_const_items, "generic const items are experimental");
gate_all!(guard_patterns, "guard patterns are experimental", "consider using match arm guards");
gate_all!(fn_delegation, "functions delegation is not yet fully implemented");
gate_all!(postfix_match, "postfix match is experimental");
gate_all!(mut_ref, "mutable by-reference bindings are experimental");
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_expand/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ pub fn parse_ast_fragment<'a>(
}
}
AstFragmentKind::Ty => AstFragment::Ty(this.parse_ty()?),
AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_no_top_guard(
AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::Yes,
Expand Down
52 changes: 23 additions & 29 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2776,7 +2776,7 @@ impl<'a> Parser<'a> {
};
// Try to parse the pattern `for ($PAT) in $EXPR`.
let pat = match (
self.parse_pat_no_top_guard(
self.parse_pat_allow_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
Expand Down Expand Up @@ -3313,39 +3313,33 @@ impl<'a> Parser<'a> {

fn parse_match_arm_pat_and_guard(&mut self) -> PResult<'a, (P<Pat>, Option<P<Expr>>)> {
if self.token == token::OpenDelim(Delimiter::Parenthesis) {
// Detect and recover from `($pat if $cond) => $arm`.
let left = self.token.span;
match self.parse_pat_no_top_guard(
let pat = self.parse_pat_no_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
CommaRecoveryMode::EitherTupleOrPipe,
) {
Ok(pat) => Ok((pat, self.parse_match_arm_guard()?)),
Err(err)
if let prev_sp = self.prev_token.span
&& let true = self.eat_keyword(kw::If) =>
{
// We know for certain we've found `($pat if` so far.
let mut cond = match self.parse_match_guard_condition() {
Ok(cond) => cond,
Err(cond_err) => {
cond_err.cancel();
return Err(err);
}
};
err.cancel();
CondChecker::new(self).visit_expr(&mut cond);
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis)]);
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
let right = self.prev_token.span;
self.dcx().emit_err(errors::ParenthesesInMatchPat {
span: vec![left, right],
sugg: errors::ParenthesesInMatchPatSugg { left, right },
});
Ok((self.mk_pat(left.to(prev_sp), ast::PatKind::Wild), Some(cond)))
}
Err(err) => Err(err),
)?;
if let ast::PatKind::Paren(subpat) = &pat.kind
&& let ast::PatKind::Guard(..) = &subpat.kind
{
// Detect and recover from `($pat if $cond) => $arm`.
// FIXME(guard_patterns): convert this to a normal guard instead
let span = pat.span;
let ast::PatKind::Paren(subpat) = pat.into_inner().kind else { unreachable!() };
let ast::PatKind::Guard(_, mut cond) = subpat.into_inner().kind else {
unreachable!()
};
self.psess.gated_spans.ungate_last(sym::guard_patterns, cond.span);
CondChecker::new(self).visit_expr(&mut cond);
let right = self.prev_token.span;
self.dcx().emit_err(errors::ParenthesesInMatchPat {
span: vec![left, right],
sugg: errors::ParenthesesInMatchPatSugg { left, right },
});
Ok((self.mk_pat(span, ast::PatKind::Wild), Some(cond)))
} else {
Ok((pat, self.parse_match_arm_guard()?))
}
} else {
// Regular parser flow:
Expand Down
39 changes: 32 additions & 7 deletions compiler/rustc_parse/src/parser/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ pub enum PatternLocation {
}

impl<'a> Parser<'a> {
/// Parses a pattern.
///
/// Corresponds to `Pattern` in RFC 3637 and admits guard patterns at the top level.
/// Used when parsing patterns in all cases where neither `PatternNoTopGuard` nor
/// `PatternNoTopAlt` (see below) are used.
pub fn parse_pat_allow_top_guard(
&mut self,
expected: Option<Expected>,
rc: RecoverComma,
ra: RecoverColon,
rt: CommaRecoveryMode,
) -> PResult<'a, P<Pat>> {
let pat = self.parse_pat_no_top_guard(expected, rc, ra, rt)?;

if self.eat_keyword(kw::If) {
let cond = self.parse_expr()?;
// Feature-gate guard patterns
self.psess.gated_spans.gate(sym::guard_patterns, cond.span);
let span = pat.span.to(cond.span);
Ok(self.mk_pat(span, PatKind::Guard(pat, cond)))
} else {
Ok(pat)
}
}

/// Parses a pattern.
///
/// Corresponds to `PatternNoTopAlt` in RFC 3637 and does not admit or-patterns
Expand All @@ -111,8 +136,8 @@ impl<'a> Parser<'a> {
/// Parses a pattern.
///
/// Corresponds to `PatternNoTopGuard` in RFC 3637 and allows or-patterns, but not
/// guard patterns, at the top level. Used for parsing patterns in `pat` fragments and
/// `let`, `if let`, and `while let` expressions.
/// guard patterns, at the top level. Used for parsing patterns in `pat` fragments (until
/// the next edition) and `let`, `if let`, and `while let` expressions.
///
/// Note that after the FCP in <https://github.com/rust-lang/rust/issues/81415>,
/// a leading vert is allowed in nested or-patterns, too. This allows us to
Expand Down Expand Up @@ -697,7 +722,7 @@ impl<'a> Parser<'a> {
} else if self.check(&token::OpenDelim(Delimiter::Bracket)) {
// Parse `[pat, pat,...]` as a slice pattern.
let (pats, _) = self.parse_delim_comma_seq(Delimiter::Bracket, |p| {
p.parse_pat_no_top_guard(
p.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
Expand Down Expand Up @@ -945,7 +970,7 @@ impl<'a> Parser<'a> {
let open_paren = self.token.span;

let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| {
p.parse_pat_no_top_guard(
p.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
Expand Down Expand Up @@ -1360,7 +1385,7 @@ impl<'a> Parser<'a> {
path: Path,
) -> PResult<'a, PatKind> {
let (fields, _) = self.parse_paren_comma_seq(|p| {
p.parse_pat_no_top_guard(
p.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
Expand Down Expand Up @@ -1395,7 +1420,7 @@ impl<'a> Parser<'a> {
self.parse_builtin(|self_, _lo, ident| {
Ok(match ident.name {
// builtin#deref(PAT)
sym::deref => Some(ast::PatKind::Deref(self_.parse_pat_no_top_guard(
sym::deref => Some(ast::PatKind::Deref(self_.parse_pat_allow_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
Expand Down Expand Up @@ -1670,7 +1695,7 @@ impl<'a> Parser<'a> {
// Parsing a pattern of the form `fieldname: pat`.
let fieldname = self.parse_field_name()?;
self.bump();
let pat = self.parse_pat_no_top_guard(
let pat = self.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/parser/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ impl<'a> Parser<'a> {
PathStyle::Pat
if let Ok(_) = self
.parse_paren_comma_seq(|p| {
p.parse_pat_no_top_guard(
p.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
Expand Down
46 changes: 46 additions & 0 deletions tests/ui/feature-gates/feature-gate-guard-patterns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
fn match_guards_still_work() {
match 0 {
0 if guard(0) => {},
_ => {},
}
}

fn other_guards_dont() {
match 0 {
(0 if guard(0)) => {},
//~^ ERROR unexpected parentheses surrounding `match` arm pattern
_ => {},
}

match 0 {
(0 if guard(0)) | 1 => {},
//~^ ERROR: guard patterns are experimental
_ => {},
}

let ((x if guard(x)) | x) = 0;
//~^ ERROR: guard patterns are experimental
//~| ERROR: cannot find value `x`

if let (x if guard(x)) = 0 {}
//~^ ERROR: guard patterns are experimental
//~| WARN: irrefutable

while let (x if guard(x)) = 0 {}
//~^ ERROR: guard patterns are experimental
//~| WARN: irrefutable

#[cfg(FALSE)]
while let (x if guard(x)) = 0 {}
//~^ ERROR: guard patterns are experimental
}

fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
//~^ ERROR: guard patterns are experimental
//~| ERROR: cannot find value `x`

fn guard<T>(x: T) -> bool {
unimplemented!()
}

fn main() {}
119 changes: 119 additions & 0 deletions tests/ui/feature-gates/feature-gate-guard-patterns.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
error: unexpected parentheses surrounding `match` arm pattern
--> $DIR/feature-gate-guard-patterns.rs:10:9
|
LL | (0 if guard(0)) => {},
| ^ ^
|
help: remove parentheses surrounding the pattern
|
LL - (0 if guard(0)) => {},
LL + 0 if guard(0) => {},
|

error[E0425]: cannot find value `x` in this scope
--> $DIR/feature-gate-guard-patterns.rs:21:22
|
LL | let ((x if guard(x)) | x) = 0;
| ^ not found in this scope

error[E0425]: cannot find value `x` in this scope
--> $DIR/feature-gate-guard-patterns.rs:38:45
|
LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
| ^
|
help: the binding `x` is available in a different scope in the same function
--> $DIR/feature-gate-guard-patterns.rs:21:11
|
LL | let ((x if guard(x)) | x) = 0;
| ^

error[E0658]: guard patterns are experimental
--> $DIR/feature-gate-guard-patterns.rs:16:15
|
LL | (0 if guard(0)) | 1 => {},
| ^^^^^^^^
|
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= help: consider using match arm guards

error[E0658]: guard patterns are experimental
--> $DIR/feature-gate-guard-patterns.rs:21:16
|
LL | let ((x if guard(x)) | x) = 0;
| ^^^^^^^^
|
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= help: consider using match arm guards

error[E0658]: guard patterns are experimental
--> $DIR/feature-gate-guard-patterns.rs:25:18
|
LL | if let (x if guard(x)) = 0 {}
| ^^^^^^^^
|
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= help: consider using match arm guards

error[E0658]: guard patterns are experimental
--> $DIR/feature-gate-guard-patterns.rs:29:21
|
LL | while let (x if guard(x)) = 0 {}
| ^^^^^^^^
|
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= help: consider using match arm guards

error[E0658]: guard patterns are experimental
--> $DIR/feature-gate-guard-patterns.rs:34:21
|
LL | while let (x if guard(x)) = 0 {}
| ^^^^^^^^
|
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= help: consider using match arm guards

error[E0658]: guard patterns are experimental
--> $DIR/feature-gate-guard-patterns.rs:38:39
|
LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
| ^^^^^^^^
|
= note: see issue #129967 <https://github.com/rust-lang/rust/issues/129967> for more information
= help: add `#![feature(guard_patterns)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= help: consider using match arm guards

warning: irrefutable `if let` pattern
--> $DIR/feature-gate-guard-patterns.rs:25:8
|
LL | if let (x if guard(x)) = 0 {}
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this pattern will always match, so the `if let` is useless
= help: consider replacing the `if let` with a `let`
= note: `#[warn(irrefutable_let_patterns)]` on by default

warning: irrefutable `while let` pattern
--> $DIR/feature-gate-guard-patterns.rs:29:11
|
LL | while let (x if guard(x)) = 0 {}
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this pattern will always match, so the loop will never exit
= help: consider instead using a `loop { ... }` with a `let` inside it

error: aborting due to 9 previous errors; 2 warnings emitted

Some errors have detailed explanations: E0425, E0658.
For more information about an error, try `rustc --explain E0425`.
2 changes: 1 addition & 1 deletion tests/ui/parser/issues/issue-72373.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ fn foo(c: &[u32], n: u32) -> u32 {
[h, ..] if h > n => 0,
[h, ..] if h == n => 1,
[h, ref ts..] => foo(c, n - h) + foo(ts, n),
//~^ ERROR expected one of `,`, `@`, `]`, or `|`, found `..`
//~^ ERROR expected one of `,`, `@`, `]`, `if`, or `|`, found `..`
[] => 0,
}
}
4 changes: 2 additions & 2 deletions tests/ui/parser/issues/issue-72373.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: expected one of `,`, `@`, `]`, or `|`, found `..`
error: expected one of `,`, `@`, `]`, `if`, or `|`, found `..`
--> $DIR/issue-72373.rs:5:19
|
LL | [h, ref ts..] => foo(c, n - h) + foo(ts, n),
| ^^ expected one of `,`, `@`, `]`, or `|`
| ^^ expected one of `,`, `@`, `]`, `if`, or `|`
|
help: if you meant to bind the contents of the rest of the array pattern into `ts`, use `@`
|
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/parser/misspelled-keywords/ref.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: expected one of `)`, `,`, `@`, or `|`, found `list`
error: expected one of `)`, `,`, `@`, `if`, or `|`, found `list`
--> $DIR/ref.rs:4:19
|
LL | Some(refe list) => println!("{list:?}"),
| ^^^^ expected one of `)`, `,`, `@`, or `|`
| ^^^^ expected one of `)`, `,`, `@`, `if`, or `|`
|
help: there is a keyword `ref` with a similar name
|
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/parser/pat-lt-bracket-7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ fn main() {
let foo = core::iter::empty();

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

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

0 comments on commit 962c014

Please sign in to comment.