about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-12-08 17:18:50 +0100
committerGitHub <noreply@github.com>2024-12-08 17:18:50 +0100
commit1868c8f66f25c3493b26f23e36a861328cdedbdb (patch)
treee63475b9961590dbeb0524784d5f753160e945dd
parentf33a8c6426074b7ce8d08740e9805fdca96ee150 (diff)
parent2459dbb4bad87c38284b0a2ba4f2d6d37db99627 (diff)
downloadrust-1868c8f66f25c3493b26f23e36a861328cdedbdb.tar.gz
rust-1868c8f66f25c3493b26f23e36a861328cdedbdb.zip
Rollup merge of #133424 - Nadrieril:guard-patterns-parsing, r=fee1-dead
Parse guard patterns

This implements the parsing of [RFC3637 Guard Patterns](https://rust-lang.github.io/rfcs/3637-guard-patterns.html) (see also [tracking issue](https://github.com/rust-lang/rust/issues/129967)). This PR is extracted from https://github.com/rust-lang/rust/pull/129996 with minor modifications.

cc `@max-niederman`
-rw-r--r--compiler/rustc_ast/src/ast.rs11
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs4
-rw-r--r--compiler/rustc_ast/src/visit.rs4
-rw-r--r--compiler/rustc_ast_lowering/src/pat.rs2
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs1
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs8
-rw-r--r--compiler/rustc_expand/src/expand.rs2
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-rw-r--r--compiler/rustc_lint/src/unused.rs2
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs58
-rw-r--r--compiler/rustc_parse/src/parser/nonterminal.rs2
-rw-r--r--compiler/rustc_parse/src/parser/pat.rs54
-rw-r--r--compiler/rustc_parse/src/parser/path.rs2
-rw-r--r--compiler/rustc_passes/src/input_stats.rs1
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs2
-rw-r--r--src/tools/rustfmt/src/patterns.rs6
-rw-r--r--src/tools/rustfmt/tests/target/guard_patterns.rs12
-rw-r--r--tests/ui/feature-gates/feature-gate-guard-patterns.rs46
-rw-r--r--tests/ui/feature-gates/feature-gate-guard-patterns.stderr119
-rw-r--r--tests/ui/parser/issues/issue-72373.rs2
-rw-r--r--tests/ui/parser/issues/issue-72373.stderr4
-rw-r--r--tests/ui/parser/misspelled-keywords/ref.stderr4
-rw-r--r--tests/ui/parser/pat-lt-bracket-7.rs2
-rw-r--r--tests/ui/parser/pat-lt-bracket-7.stderr4
-rw-r--r--tests/ui/parser/recover/recover-pat-exprs.rs6
-rw-r--r--tests/ui/parser/recover/recover-pat-exprs.stderr12
-rw-r--r--tests/ui/parser/recover/recover-pat-wildcards.rs4
-rw-r--r--tests/ui/parser/recover/recover-pat-wildcards.stderr8
-rw-r--r--tests/ui/pattern/bindings-after-at/nested-type-ascription-syntactically-invalid.stderr4
-rw-r--r--tests/ui/pattern/rfc-3637-guard-patterns/macro-rules.rs20
31 files changed, 328 insertions, 81 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 2f55a9eaeda..69ba78282f9 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -627,9 +627,11 @@ impl Pat {
             | PatKind::Or(s) => s.iter().for_each(|p| p.walk(it)),
 
             // Trivial wrappers over inner patterns.
-            PatKind::Box(s) | PatKind::Deref(s) | PatKind::Ref(s, _) | PatKind::Paren(s) => {
-                s.walk(it)
-            }
+            PatKind::Box(s)
+            | PatKind::Deref(s)
+            | PatKind::Ref(s, _)
+            | PatKind::Paren(s)
+            | PatKind::Guard(s, _) => s.walk(it),
 
             // These patterns do not contain subpatterns, skip.
             PatKind::Wild
@@ -839,6 +841,9 @@ pub enum PatKind {
     // A never pattern `!`.
     Never,
 
+    /// A guard pattern (e.g., `x if guard(x)`).
+    Guard(P<Pat>, P<Expr>),
+
     /// Parentheses in patterns used for grouping (i.e., `(PAT)`).
     Paren(P<Pat>),
 
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index 622c260868e..3a4a8ce266e 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -1525,6 +1525,10 @@ pub fn walk_pat<T: MutVisitor>(vis: &mut T, pat: &mut P<Pat>) {
             visit_opt(e2, |e| vis.visit_expr(e));
             vis.visit_span(span);
         }
+        PatKind::Guard(p, e) => {
+            vis.visit_pat(p);
+            vis.visit_expr(e);
+        }
         PatKind::Tuple(elems) | PatKind::Slice(elems) | PatKind::Or(elems) => {
             visit_thin_vec(elems, |elem| vis.visit_pat(elem))
         }
diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs
index 2f6998783fa..0b000c8cef8 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -682,6 +682,10 @@ pub fn walk_pat<'a, V: Visitor<'a>>(visitor: &mut V, pattern: &'a Pat) -> V::Res
             visit_opt!(visitor, visit_expr, lower_bound);
             visit_opt!(visitor, visit_expr, upper_bound);
         }
+        PatKind::Guard(subpattern, guard_condition) => {
+            try_visit!(visitor.visit_pat(subpattern));
+            try_visit!(visitor.visit_expr(guard_condition));
+        }
         PatKind::Wild | PatKind::Rest | PatKind::Never => {}
         PatKind::Err(_guar) => {}
         PatKind::Tuple(elems) | PatKind::Slice(elems) | PatKind::Or(elems) => {
diff --git a/compiler/rustc_ast_lowering/src/pat.rs b/compiler/rustc_ast_lowering/src/pat.rs
index ace7bfb5c73..c4bae084a3f 100644
--- a/compiler/rustc_ast_lowering/src/pat.rs
+++ b/compiler/rustc_ast_lowering/src/pat.rs
@@ -114,6 +114,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
                             self.lower_range_end(end, e2.is_some()),
                         );
                     }
+                    // FIXME(guard_patterns): lower pattern guards to HIR
+                    PatKind::Guard(inner, _) => pattern = inner,
                     PatKind::Slice(pats) => break self.lower_pat_slice(pats),
                     PatKind::Rest => {
                         // If we reach here the `..` pattern is not semantically allowed.
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 89311516081..390a575a186 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -556,6 +556,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");
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index 479677b0a5a..49e4a559e73 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -1709,6 +1709,14 @@ impl<'a> State<'a> {
                     self.print_expr(e, FixupContext::default());
                 }
             }
+            PatKind::Guard(subpat, condition) => {
+                self.popen();
+                self.print_pat(subpat);
+                self.space();
+                self.word_space("if");
+                self.print_expr(condition, FixupContext::default());
+                self.pclose();
+            }
             PatKind::Slice(elts) => {
                 self.word("[");
                 self.commasep(Inconsistent, elts, |s, p| s.print_pat(p));
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index e5500c8bba1..6a6496f9827 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -990,7 +990,7 @@ pub fn parse_ast_fragment<'a>(
             }
         }
         AstFragmentKind::Ty => AstFragment::Ty(this.parse_ty()?),
-        AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_allow_top_alt(
+        AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_allow_top_guard(
             None,
             RecoverComma::No,
             RecoverColon::Yes,
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index abc7200699c..bf26b5d25d2 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -505,6 +505,8 @@ declare_features! (
     (incomplete, generic_const_items, "1.73.0", Some(113521)),
     /// Allows registering static items globally, possibly across crates, to iterate over at runtime.
     (unstable, global_registration, "1.80.0", Some(125119)),
+    /// Allows using guards in patterns.
+    (incomplete, guard_patterns, "CURRENT_RUSTC_VERSION", Some(129967)),
     /// Allows using `..=X` as a patterns in slices.
     (unstable, half_open_range_patterns_in_slices, "1.66.0", Some(67264)),
     /// Allows `if let` guard in match arms.
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index b775cd37409..9cad5d98562 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -1235,7 +1235,7 @@ impl EarlyLintPass for UnusedParens {
                 self.check_unused_parens_pat(cx, &f.pat, false, false, keep_space);
             },
             // Avoid linting on `i @ (p0 | .. | pn)` and `box (p0 | .. | pn)`, #64106.
-            Ident(.., Some(p)) | Box(p) | Deref(p) => self.check_unused_parens_pat(cx, p, true, false, keep_space),
+            Ident(.., Some(p)) | Box(p) | Deref(p) | Guard(p, _) => self.check_unused_parens_pat(cx, p, true, false, keep_space),
             // Avoid linting on `&(mut x)` as `&mut x` has a different meaning, #55342.
             // Also avoid linting on `& mut? (p0 | .. | pn)`, #64106.
             Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not, keep_space),
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 8d16d44b0a2..3a9e9b480ec 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -2631,7 +2631,7 @@ impl<'a> Parser<'a> {
         };
         self.bump(); // Eat `let` token
         let lo = self.prev_token.span;
-        let pat = self.parse_pat_allow_top_alt(
+        let pat = self.parse_pat_no_top_guard(
             None,
             RecoverComma::Yes,
             RecoverColon::Yes,
@@ -2778,7 +2778,7 @@ impl<'a> Parser<'a> {
         };
         // Try to parse the pattern `for ($PAT) in $EXPR`.
         let pat = match (
-            self.parse_pat_allow_top_alt(
+            self.parse_pat_allow_top_guard(
                 None,
                 RecoverComma::Yes,
                 RecoverColon::Yes,
@@ -3241,7 +3241,7 @@ impl<'a> Parser<'a> {
                     // then we should recover.
                     let mut snapshot = this.create_snapshot_for_diagnostic();
                     let pattern_follows = snapshot
-                        .parse_pat_allow_top_alt(
+                        .parse_pat_no_top_guard(
                             None,
                             RecoverComma::Yes,
                             RecoverColon::Yes,
@@ -3315,43 +3315,37 @@ 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_allow_top_alt(
+            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:
-            let pat = self.parse_pat_allow_top_alt(
+            let pat = self.parse_pat_no_top_guard(
                 None,
                 RecoverComma::Yes,
                 RecoverColon::Yes,
diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs
index 8fb6f85d0dd..752a52b382b 100644
--- a/compiler/rustc_parse/src/parser/nonterminal.rs
+++ b/compiler/rustc_parse/src/parser/nonterminal.rs
@@ -174,7 +174,7 @@ impl<'a> Parser<'a> {
             NonterminalKind::Pat(pat_kind) => {
                 NtPat(self.collect_tokens_no_attrs(|this| match pat_kind {
                     PatParam { .. } => this.parse_pat_no_top_alt(None, None),
-                    PatWithOr => this.parse_pat_allow_top_alt(
+                    PatWithOr => this.parse_pat_no_top_guard(
                         None,
                         RecoverComma::No,
                         RecoverColon::No,
diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index e08b925f008..4cda887a02b 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -99,9 +99,34 @@ pub enum PatternLocation {
 impl<'a> Parser<'a> {
     /// Parses a pattern.
     ///
-    /// Corresponds to `pat<no_top_alt>` in RFC 2535 and does not admit or-patterns
-    /// at the top level. Used when parsing the parameters of lambda expressions,
-    /// functions, function pointers, and `pat` macro fragments.
+    /// 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
+    /// or guard patterns at the top level. Used when parsing the parameters of lambda
+    /// expressions, functions, function pointers, and `pat_param` macro fragments.
     pub fn parse_pat_no_top_alt(
         &mut self,
         expected: Option<Expected>,
@@ -112,25 +137,26 @@ impl<'a> Parser<'a> {
 
     /// Parses a pattern.
     ///
-    /// Corresponds to `top_pat` in RFC 2535 and allows or-pattern at the top level.
-    /// Used for parsing patterns in all cases when `pat<no_top_alt>` is not used.
+    /// 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 (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
     /// simplify the grammar somewhat.
-    pub fn parse_pat_allow_top_alt(
+    pub fn parse_pat_no_top_guard(
         &mut self,
         expected: Option<Expected>,
         rc: RecoverComma,
         ra: RecoverColon,
         rt: CommaRecoveryMode,
     ) -> PResult<'a, P<Pat>> {
-        self.parse_pat_allow_top_alt_inner(expected, rc, ra, rt, None).map(|(pat, _)| pat)
+        self.parse_pat_no_top_guard_inner(expected, rc, ra, rt, None).map(|(pat, _)| pat)
     }
 
     /// Returns the pattern and a bool indicating whether we recovered from a trailing vert (true =
     /// recovered).
-    fn parse_pat_allow_top_alt_inner(
+    fn parse_pat_no_top_guard_inner(
         &mut self,
         expected: Option<Expected>,
         rc: RecoverComma,
@@ -231,7 +257,7 @@ impl<'a> Parser<'a> {
         // We use `parse_pat_allow_top_alt` regardless of whether we actually want top-level
         // or-patterns so that we can detect when a user tries to use it. This allows us to print a
         // better error message.
-        let (pat, trailing_vert) = self.parse_pat_allow_top_alt_inner(
+        let (pat, trailing_vert) = self.parse_pat_no_top_guard_inner(
             expected,
             rc,
             RecoverColon::No,
@@ -696,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_allow_top_alt(
+                p.parse_pat_allow_top_guard(
                     None,
                     RecoverComma::No,
                     RecoverColon::No,
@@ -944,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_allow_top_alt(
+            p.parse_pat_allow_top_guard(
                 None,
                 RecoverComma::No,
                 RecoverColon::No,
@@ -1361,7 +1387,7 @@ impl<'a> Parser<'a> {
         path: Path,
     ) -> PResult<'a, PatKind> {
         let (fields, _) = self.parse_paren_comma_seq(|p| {
-            p.parse_pat_allow_top_alt(
+            p.parse_pat_allow_top_guard(
                 None,
                 RecoverComma::No,
                 RecoverColon::No,
@@ -1396,7 +1422,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_allow_top_alt(
+                sym::deref => Some(ast::PatKind::Deref(self_.parse_pat_allow_top_guard(
                     None,
                     RecoverComma::Yes,
                     RecoverColon::Yes,
@@ -1671,7 +1697,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_allow_top_alt(
+            let pat = self.parse_pat_allow_top_guard(
                 None,
                 RecoverComma::No,
                 RecoverColon::No,
diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs
index 2f19a9b6b20..6a7029a8f1c 100644
--- a/compiler/rustc_parse/src/parser/path.rs
+++ b/compiler/rustc_parse/src/parser/path.rs
@@ -469,7 +469,7 @@ impl<'a> Parser<'a> {
             PathStyle::Pat
                 if let Ok(_) = self
                     .parse_paren_comma_seq(|p| {
-                        p.parse_pat_allow_top_alt(
+                        p.parse_pat_allow_top_guard(
                             None,
                             RecoverComma::No,
                             RecoverColon::No,
diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs
index b8f66a2b2ec..f2a37307cee 100644
--- a/compiler/rustc_passes/src/input_stats.rs
+++ b/compiler/rustc_passes/src/input_stats.rs
@@ -555,6 +555,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
             Slice,
             Rest,
             Never,
+            Guard,
             Paren,
             MacCall,
             Err
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index a7dc20a874b..818d4afffc6 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -999,6 +999,7 @@ symbols! {
         global_registration,
         globs,
         gt,
+        guard_patterns,
         half_open_range_patterns,
         half_open_range_patterns_in_slices,
         hash,
diff --git a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
index 9d26bf930a1..50a97579df7 100644
--- a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
+++ b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
@@ -234,7 +234,7 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec<P<Pat>>, focus_idx: us
         // In the case of only two patterns, replacement adds net characters.
         | Ref(_, Mutability::Not)
         // Dealt with elsewhere.
-        | Or(_) | Paren(_) | Deref(_) => false,
+        | Or(_) | Paren(_) | Deref(_) | Guard(..) => false,
         // Transform `box x | ... | box y` into `box (x | y)`.
         //
         // The cases below until `Slice(...)` deal with *singleton* products.
diff --git a/src/tools/rustfmt/src/patterns.rs b/src/tools/rustfmt/src/patterns.rs
index 6fe2d4a8520..7b4730eadc8 100644
--- a/src/tools/rustfmt/src/patterns.rs
+++ b/src/tools/rustfmt/src/patterns.rs
@@ -48,7 +48,8 @@ fn is_short_pattern_inner(pat: &ast::Pat) -> bool {
         | ast::PatKind::MacCall(..)
         | ast::PatKind::Slice(..)
         | ast::PatKind::Path(..)
-        | ast::PatKind::Range(..) => false,
+        | ast::PatKind::Range(..)
+        | ast::PatKind::Guard(..) => false,
         ast::PatKind::Tuple(ref subpats) => subpats.len() <= 1,
         ast::PatKind::TupleStruct(_, ref path, ref subpats) => {
             path.segments.len() <= 1 && subpats.len() <= 1
@@ -338,8 +339,9 @@ impl Rewrite for Pat {
                         .max_width_error(shape.width, self.span)?,
                 )
                 .map(|inner_pat| format!("({})", inner_pat)),
-            PatKind::Err(_) => Err(RewriteError::Unknown),
+            PatKind::Guard(..) => Ok(context.snippet(self.span).to_string()),
             PatKind::Deref(_) => Err(RewriteError::Unknown),
+            PatKind::Err(_) => Err(RewriteError::Unknown),
         }
     }
 }
diff --git a/src/tools/rustfmt/tests/target/guard_patterns.rs b/src/tools/rustfmt/tests/target/guard_patterns.rs
new file mode 100644
index 00000000000..2e4667b916c
--- /dev/null
+++ b/src/tools/rustfmt/tests/target/guard_patterns.rs
@@ -0,0 +1,12 @@
+#![feature(guard_patterns)]
+
+fn main() {
+    match user.subscription_plan() {
+        (Plan::Regular if user.credit() >= 100) | (Plan::Premium if user.credit() >= 80) => {
+            // Complete the transaction.
+        }
+        _ => {
+            // The user doesn't have enough credit, return an error message.
+        }
+    }
+}
diff --git a/tests/ui/feature-gates/feature-gate-guard-patterns.rs b/tests/ui/feature-gates/feature-gate-guard-patterns.rs
new file mode 100644
index 00000000000..929e8ef3181
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-guard-patterns.rs
@@ -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() {}
diff --git a/tests/ui/feature-gates/feature-gate-guard-patterns.stderr b/tests/ui/feature-gates/feature-gate-guard-patterns.stderr
new file mode 100644
index 00000000000..0613b5c95a4
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-guard-patterns.stderr
@@ -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`.
diff --git a/tests/ui/parser/issues/issue-72373.rs b/tests/ui/parser/issues/issue-72373.rs
index 4da6061c27f..ed88d53539b 100644
--- a/tests/ui/parser/issues/issue-72373.rs
+++ b/tests/ui/parser/issues/issue-72373.rs
@@ -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,
     }
 }
diff --git a/tests/ui/parser/issues/issue-72373.stderr b/tests/ui/parser/issues/issue-72373.stderr
index c596c6abda5..d566d6f5fd1 100644
--- a/tests/ui/parser/issues/issue-72373.stderr
+++ b/tests/ui/parser/issues/issue-72373.stderr
@@ -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 `@`
    |
diff --git a/tests/ui/parser/misspelled-keywords/ref.stderr b/tests/ui/parser/misspelled-keywords/ref.stderr
index b8b52702314..398d9d6bb99 100644
--- a/tests/ui/parser/misspelled-keywords/ref.stderr
+++ b/tests/ui/parser/misspelled-keywords/ref.stderr
@@ -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
    |
diff --git a/tests/ui/parser/pat-lt-bracket-7.rs b/tests/ui/parser/pat-lt-bracket-7.rs
index 327aef5ad15..abaeb4c83c0 100644
--- a/tests/ui/parser/pat-lt-bracket-7.rs
+++ b/tests/ui/parser/pat-lt-bracket-7.rs
@@ -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
diff --git a/tests/ui/parser/pat-lt-bracket-7.stderr b/tests/ui/parser/pat-lt-bracket-7.stderr
index 004dcfb2a7b..cc457a4e64e 100644
--- a/tests/ui/parser/pat-lt-bracket-7.stderr
+++ b/tests/ui/parser/pat-lt-bracket-7.stderr
@@ -1,10 +1,10 @@
-error: expected one of `)`, `,`, `@`, or `|`, found `[`
+error: expected one of `)`, `,`, `@`, `if`, or `|`, found `[`
   --> $DIR/pat-lt-bracket-7.rs:5:16
    |
 LL |     for Thing(x[]) in foo {}
    |                ^
    |                |
-   |                expected one of `)`, `,`, `@`, or `|`
+   |                expected one of `)`, `,`, `@`, `if`, or `|`
    |                help: missing `,`
 
 error[E0308]: mismatched types
diff --git a/tests/ui/parser/recover/recover-pat-exprs.rs b/tests/ui/parser/recover/recover-pat-exprs.rs
index e5e25df0c01..a78bb82828d 100644
--- a/tests/ui/parser/recover/recover-pat-exprs.rs
+++ b/tests/ui/parser/recover/recover-pat-exprs.rs
@@ -27,7 +27,7 @@ fn array_indexing() {
     { let x[0, 1, 2]; } //~ error: expected one of `:`, `;`, `=`, `@`, or `|`, found `[`
     { let x[0; 20]; } //~ error: expected one of `:`, `;`, `=`, `@`, or `|`, found `[`
     { let x[]; } //~ error: expected one of `:`, `;`, `=`, `@`, or `|`, found `[`
-    { let (x[]); } //~ error: expected one of `)`, `,`, `@`, or `|`, found `[`
+    { let (x[]); } //~ error: expected one of `)`, `,`, `@`, `if`, or `|`, found `[`
     //~^ missing `,`
 }
 
@@ -95,12 +95,12 @@ fn main() {
         f?() => (),
         //~^ error: expected a pattern, found an expression
         (_ + 1) => (),
-        //~^ error: expected one of `)`, `,`, or `|`, found `+`
+        //~^ error: expected one of `)`, `,`, `if`, or `|`, found `+`
     }
 
     let 1 + 1 = 2;
     //~^ error: expected a pattern, found an expression
 
     let b = matches!(x, (x * x | x.f()) | x[0]);
-    //~^ error: expected one of `)`, `,`, `@`, or `|`, found `*`
+    //~^ error: expected one of `)`, `,`, `@`, `if`, or `|`, found `*`
 }
diff --git a/tests/ui/parser/recover/recover-pat-exprs.stderr b/tests/ui/parser/recover/recover-pat-exprs.stderr
index 041dfd647ad..281eeced402 100644
--- a/tests/ui/parser/recover/recover-pat-exprs.stderr
+++ b/tests/ui/parser/recover/recover-pat-exprs.stderr
@@ -213,13 +213,13 @@ error: expected one of `:`, `;`, `=`, `@`, or `|`, found `[`
 LL |     { let x[]; }
    |            ^ expected one of `:`, `;`, `=`, `@`, or `|`
 
-error: expected one of `)`, `,`, `@`, or `|`, found `[`
+error: expected one of `)`, `,`, `@`, `if`, or `|`, found `[`
   --> $DIR/recover-pat-exprs.rs:30:13
    |
 LL |     { let (x[]); }
    |             ^
    |             |
-   |             expected one of `)`, `,`, `@`, or `|`
+   |             expected one of `)`, `,`, `@`, `if`, or `|`
    |             help: missing `,`
 
 error: expected a pattern, found an expression
@@ -611,11 +611,11 @@ LL |         x.sqrt() @ .. => (),
    |
    = note: bindings are `x`, `mut x`, `ref x`, and `ref mut x`
 
-error: expected one of `)`, `,`, or `|`, found `+`
+error: expected one of `)`, `,`, `if`, or `|`, found `+`
   --> $DIR/recover-pat-exprs.rs:97:12
    |
 LL |         (_ + 1) => (),
-   |            ^ expected one of `)`, `,`, or `|`
+   |            ^ expected one of `)`, `,`, `if`, or `|`
 
 error: expected a pattern, found an expression
   --> $DIR/recover-pat-exprs.rs:81:9
@@ -772,11 +772,11 @@ LL |     let 1 + 1 = 2;
    |
    = note: arbitrary expressions are not allowed in patterns: <https://doc.rust-lang.org/book/ch19-00-patterns.html>
 
-error: expected one of `)`, `,`, `@`, or `|`, found `*`
+error: expected one of `)`, `,`, `@`, `if`, or `|`, found `*`
   --> $DIR/recover-pat-exprs.rs:104:28
    |
 LL |     let b = matches!(x, (x * x | x.f()) | x[0]);
-   |                            ^ expected one of `)`, `,`, `@`, or `|`
+   |                            ^ expected one of `)`, `,`, `@`, `if`, or `|`
   --> $SRC_DIR/core/src/macros/mod.rs:LL:COL
    |
    = note: while parsing argument for this `pat` macro fragment
diff --git a/tests/ui/parser/recover/recover-pat-wildcards.rs b/tests/ui/parser/recover/recover-pat-wildcards.rs
index f506e2223d6..d4d28ce6358 100644
--- a/tests/ui/parser/recover/recover-pat-wildcards.rs
+++ b/tests/ui/parser/recover/recover-pat-wildcards.rs
@@ -8,7 +8,7 @@ fn a() {
 
 fn b() {
     match 2 {
-        (_ % 4) => () //~ error: expected one of `)`, `,`, or `|`, found `%`
+        (_ % 4) => () //~ error: expected one of `)`, `,`, `if`, or `|`, found `%`
     }
 }
 
@@ -40,7 +40,7 @@ fn f() {
 
 fn g() {
     match 7 {
-        (_ * 0)..5 => () //~ error: expected one of `)`, `,`, or `|`, found `*`
+        (_ * 0)..5 => () //~ error: expected one of `)`, `,`, `if`, or `|`, found `*`
     }
 }
 
diff --git a/tests/ui/parser/recover/recover-pat-wildcards.stderr b/tests/ui/parser/recover/recover-pat-wildcards.stderr
index 81a9920f6a2..f939e513370 100644
--- a/tests/ui/parser/recover/recover-pat-wildcards.stderr
+++ b/tests/ui/parser/recover/recover-pat-wildcards.stderr
@@ -4,11 +4,11 @@ error: expected one of `=>`, `if`, or `|`, found `+`
 LL |         _ + 1 => ()
    |           ^ expected one of `=>`, `if`, or `|`
 
-error: expected one of `)`, `,`, or `|`, found `%`
+error: expected one of `)`, `,`, `if`, or `|`, found `%`
   --> $DIR/recover-pat-wildcards.rs:11:12
    |
 LL |         (_ % 4) => ()
-   |            ^ expected one of `)`, `,`, or `|`
+   |            ^ expected one of `)`, `,`, `if`, or `|`
 
 error: expected one of `=>`, `if`, or `|`, found `.`
   --> $DIR/recover-pat-wildcards.rs:17:10
@@ -47,11 +47,11 @@ error: expected one of `=>`, `if`, or `|`, found reserved identifier `_`
 LL |         0..._ => ()
    |             ^ expected one of `=>`, `if`, or `|`
 
-error: expected one of `)`, `,`, or `|`, found `*`
+error: expected one of `)`, `,`, `if`, or `|`, found `*`
   --> $DIR/recover-pat-wildcards.rs:43:12
    |
 LL |         (_ * 0)..5 => ()
-   |            ^ expected one of `)`, `,`, or `|`
+   |            ^ expected one of `)`, `,`, `if`, or `|`
 
 error: expected one of `=>`, `if`, or `|`, found `(`
   --> $DIR/recover-pat-wildcards.rs:49:11
diff --git a/tests/ui/pattern/bindings-after-at/nested-type-ascription-syntactically-invalid.stderr b/tests/ui/pattern/bindings-after-at/nested-type-ascription-syntactically-invalid.stderr
index da8f4ca5f0c..6ce8f6d31a0 100644
--- a/tests/ui/pattern/bindings-after-at/nested-type-ascription-syntactically-invalid.stderr
+++ b/tests/ui/pattern/bindings-after-at/nested-type-ascription-syntactically-invalid.stderr
@@ -6,11 +6,11 @@ LL |     let a: u8 @ b = 0;
    |          |
    |          while parsing the type for `a`
 
-error: expected one of `)`, `,`, `@`, or `|`, found `:`
+error: expected one of `)`, `,`, `@`, `if`, or `|`, found `:`
   --> $DIR/nested-type-ascription-syntactically-invalid.rs:24:15
    |
 LL |     let a @ (b: u8);
-   |               ^ expected one of `)`, `,`, `@`, or `|`
+   |               ^ expected one of `)`, `,`, `@`, `if`, or `|`
    |
    = note: type ascription syntax has been removed, see issue #101728 <https://github.com/rust-lang/rust/issues/101728>
 
diff --git a/tests/ui/pattern/rfc-3637-guard-patterns/macro-rules.rs b/tests/ui/pattern/rfc-3637-guard-patterns/macro-rules.rs
new file mode 100644
index 00000000000..76681f45bb3
--- /dev/null
+++ b/tests/ui/pattern/rfc-3637-guard-patterns/macro-rules.rs
@@ -0,0 +1,20 @@
+//@ run-pass
+//! Tests that the addition of guard patterns does not change the behavior of the `pat` macro
+//! fragment.
+#![feature(guard_patterns)]
+#![allow(incomplete_features)]
+
+macro_rules! has_guard {
+    ($p:pat) => {
+        false
+    };
+    ($p:pat if $e:expr) => {
+        true
+    };
+}
+
+fn main() {
+    assert_eq!(has_guard!(Some(_)), false);
+    assert_eq!(has_guard!(Some(_) if true), true);
+    assert_eq!(has_guard!((Some(_) if true)), false);
+}