about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNadrieril <nadrieril+git@gmail.com>2023-11-27 03:15:56 +0100
committerNadrieril <nadrieril+git@gmail.com>2023-12-03 12:25:46 +0100
commit80bdcbf50a63845dd3cfeb05751ba3dcbd1025b8 (patch)
tree3309db5b82d4bc8d8d30ffd0e02d96137f5853f9
parentcaa488b96e65131e4d70f219d5e89008031f9229 (diff)
downloadrust-80bdcbf50a63845dd3cfeb05751ba3dcbd1025b8.tar.gz
rust-80bdcbf50a63845dd3cfeb05751ba3dcbd1025b8.zip
Parse a pattern with no arm
-rw-r--r--compiler/rustc_ast/src/ast.rs22
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs2
-rw-r--r--compiler/rustc_ast/src/visit.rs2
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs29
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/expr.rs37
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs2
-rw-r--r--compiler/rustc_expand/src/build.rs2
-rw-r--r--compiler/rustc_expand/src/placeholders.rs2
-rw-r--r--compiler/rustc_lint/src/builtin.rs6
-rw-r--r--compiler/rustc_lint/src/unused.rs20
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs242
-rw-r--r--compiler/rustc_parse/src/parser/pat.rs4
-rw-r--r--compiler/rustc_resolve/src/late.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/non_expressive_names.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/redundant_else.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs2
-rw-r--r--src/tools/rustfmt/src/matches.rs8
-rw-r--r--src/tools/rustfmt/src/spanned.rs7
-rw-r--r--tests/ui/half-open-range-patterns/range_pat_interactions1.rs2
-rw-r--r--tests/ui/half-open-range-patterns/range_pat_interactions1.stderr4
-rw-r--r--tests/ui/half-open-range-patterns/range_pat_interactions2.rs2
-rw-r--r--tests/ui/half-open-range-patterns/range_pat_interactions2.stderr4
-rw-r--r--tests/ui/never_patterns/check.rs3
-rw-r--r--tests/ui/never_patterns/check.stderr41
-rw-r--r--tests/ui/never_patterns/parse.rs68
-rw-r--r--tests/ui/never_patterns/parse.stderr26
-rw-r--r--tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs6
-rw-r--r--tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr12
-rw-r--r--tests/ui/parser/issues/issue-24375.rs2
-rw-r--r--tests/ui/parser/issues/issue-24375.stderr4
-rw-r--r--tests/ui/parser/macro/macro-expand-to-match-arm.rs6
-rw-r--r--tests/ui/parser/macro/macro-expand-to-match-arm.stderr11
-rw-r--r--tests/ui/parser/match-arm-without-body.rs11
-rw-r--r--tests/ui/parser/match-arm-without-body.stderr84
-rw-r--r--tests/ui/parser/pat-lt-bracket-1.rs2
-rw-r--r--tests/ui/parser/pat-lt-bracket-1.stderr4
36 files changed, 415 insertions, 274 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index cba9121aa5a..62bacf97f49 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -658,6 +658,24 @@ impl Pat {
     pub fn is_rest(&self) -> bool {
         matches!(self.kind, PatKind::Rest)
     }
+
+    /// Could this be a never pattern? I.e. is it a never pattern modulo macro invocations that
+    /// might return never patterns?
+    pub fn could_be_never_pattern(&self) -> bool {
+        let mut could_be_never_pattern = false;
+        self.walk(&mut |pat| match &pat.kind {
+            PatKind::Never | PatKind::MacCall(_) => {
+                could_be_never_pattern = true;
+                false
+            }
+            PatKind::Or(s) => {
+                could_be_never_pattern = s.iter().all(|p| p.could_be_never_pattern());
+                false
+            }
+            _ => true,
+        });
+        could_be_never_pattern
+    }
 }
 
 /// A single field in a struct pattern.
@@ -1080,8 +1098,8 @@ pub struct Arm {
     pub pat: P<Pat>,
     /// Match arm guard, e.g. `n > 10` in `match foo { n if n > 10 => {}, _ => {} }`
     pub guard: Option<P<Expr>>,
-    /// Match arm body.
-    pub body: P<Expr>,
+    /// Match arm body. Omitted if the pattern is a never pattern.
+    pub body: Option<P<Expr>>,
     pub span: Span,
     pub id: NodeId,
     pub is_placeholder: bool,
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index 8ce86bf9ecf..e909e8dc774 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -453,7 +453,7 @@ pub fn noop_flat_map_arm<T: MutVisitor>(mut arm: Arm, vis: &mut T) -> SmallVec<[
     vis.visit_id(id);
     vis.visit_pat(pat);
     visit_opt(guard, |guard| vis.visit_expr(guard));
-    vis.visit_expr(body);
+    visit_opt(body, |body| vis.visit_expr(body));
     vis.visit_span(span);
     smallvec![arm]
 }
diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs
index 9dbadcb49d3..773a7ebc707 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -951,7 +951,7 @@ pub fn walk_param<'a, V: Visitor<'a>>(visitor: &mut V, param: &'a Param) {
 pub fn walk_arm<'a, V: Visitor<'a>>(visitor: &mut V, arm: &'a Arm) {
     visitor.visit_pat(&arm.pat);
     walk_list!(visitor, visit_expr, &arm.guard);
-    visitor.visit_expr(&arm.body);
+    walk_list!(visitor, visit_expr, &arm.body);
     walk_list!(visitor, visit_attribute, &arm.attrs);
 }
 
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index be5671f1bf7..b688950f5a3 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -566,13 +566,28 @@ impl<'hir> LoweringContext<'_, 'hir> {
         });
         let hir_id = self.next_id();
         self.lower_attrs(hir_id, &arm.attrs);
-        hir::Arm {
-            hir_id,
-            pat,
-            guard,
-            body: self.lower_expr(&arm.body),
-            span: self.lower_span(arm.span),
-        }
+        let body = if let Some(body) = &arm.body {
+            self.lower_expr(body)
+        } else {
+            // An arm without a body, meant for never patterns.
+            // We add a fake `loop {}` arm body so that it typecks to `!`.
+            // FIXME(never_patterns): Desugar into a call to `unreachable_unchecked`.
+            let span = pat.span;
+            let block = self.arena.alloc(hir::Block {
+                stmts: &[],
+                expr: None,
+                hir_id: self.next_id(),
+                rules: hir::BlockCheckMode::DefaultBlock,
+                span,
+                targeted_by_break: false,
+            });
+            self.arena.alloc(hir::Expr {
+                hir_id: self.next_id(),
+                kind: hir::ExprKind::Loop(block, None, hir::LoopSource::Loop, span),
+                span,
+            })
+        };
+        hir::Arm { hir_id, pat, guard, body, span: self.lower_span(arm.span) }
     }
 
     /// Lower an `async` construct to a coroutine that implements `Future`.
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
index 45a55e20ca7..d7dbc7ac035 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
@@ -631,28 +631,33 @@ impl<'a> State<'a> {
             self.print_expr(e);
             self.space();
         }
-        self.word_space("=>");
 
-        match &arm.body.kind {
-            ast::ExprKind::Block(blk, opt_label) => {
-                if let Some(label) = opt_label {
-                    self.print_ident(label.ident);
-                    self.word_space(":");
-                }
+        if let Some(body) = &arm.body {
+            self.word_space("=>");
+
+            match &body.kind {
+                ast::ExprKind::Block(blk, opt_label) => {
+                    if let Some(label) = opt_label {
+                        self.print_ident(label.ident);
+                        self.word_space(":");
+                    }
 
-                // The block will close the pattern's ibox.
-                self.print_block_unclosed_indent(blk);
+                    // The block will close the pattern's ibox.
+                    self.print_block_unclosed_indent(blk);
 
-                // If it is a user-provided unsafe block, print a comma after it.
-                if let BlockCheckMode::Unsafe(ast::UserProvided) = blk.rules {
+                    // If it is a user-provided unsafe block, print a comma after it.
+                    if let BlockCheckMode::Unsafe(ast::UserProvided) = blk.rules {
+                        self.word(",");
+                    }
+                }
+                _ => {
+                    self.end(); // Close the ibox for the pattern.
+                    self.print_expr(body);
                     self.word(",");
                 }
             }
-            _ => {
-                self.end(); // Close the ibox for the pattern.
-                self.print_expr(&arm.body);
-                self.word(",");
-            }
+        } else {
+            self.word(",");
         }
         self.end(); // Close enclosing cbox.
     }
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
index f3164bd2c2a..7f5589210d4 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
@@ -136,7 +136,7 @@ fn cs_partial_cmp(
                     && let Some(last) = arms.last_mut()
                     && let PatKind::Wild = last.pat.kind
                 {
-                    last.body = expr2;
+                    last.body = Some(expr2);
                     expr1
                 } else {
                     let eq_arm = cx.arm(
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index 9a8d0d691f0..0996a45d900 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -505,7 +505,7 @@ impl<'a> ExtCtxt<'a> {
             attrs: AttrVec::new(),
             pat,
             guard: None,
-            body: expr,
+            body: Some(expr),
             span,
             id: ast::DUMMY_NODE_ID,
             is_placeholder: false,
diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs
index 1292a855230..ded0baa9563 100644
--- a/compiler/rustc_expand/src/placeholders.rs
+++ b/compiler/rustc_expand/src/placeholders.rs
@@ -119,7 +119,7 @@ pub fn placeholder(
         }]),
         AstFragmentKind::Arms => AstFragment::Arms(smallvec![ast::Arm {
             attrs: Default::default(),
-            body: expr_placeholder(),
+            body: Some(expr_placeholder()),
             guard: None,
             id,
             pat: pat(),
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 8932200c5b7..64de5e92abf 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -1000,8 +1000,10 @@ impl EarlyLintPass for UnusedDocComment {
     }
 
     fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
-        let arm_span = arm.pat.span.with_hi(arm.body.span.hi());
-        warn_if_doc(cx, arm_span, "match arms", &arm.attrs);
+        if let Some(body) = &arm.body {
+            let arm_span = arm.pat.span.with_hi(body.span.hi());
+            warn_if_doc(cx, arm_span, "match arms", &arm.attrs);
+        }
     }
 
     fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) {
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index 0a167b0893c..92deebb9d2c 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -1113,15 +1113,17 @@ impl EarlyLintPass for UnusedParens {
             }
             ExprKind::Match(ref _expr, ref arm) => {
                 for a in arm {
-                    self.check_unused_delims_expr(
-                        cx,
-                        &a.body,
-                        UnusedDelimsCtx::MatchArmExpr,
-                        false,
-                        None,
-                        None,
-                        true,
-                    );
+                    if let Some(body) = &a.body {
+                        self.check_unused_delims_expr(
+                            cx,
+                            body,
+                            UnusedDelimsCtx::MatchArmExpr,
+                            false,
+                            None,
+                            None,
+                            true,
+                        );
+                    }
                 }
             }
             _ => {}
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index b1b77305e4f..09c0dd78581 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -2899,127 +2899,155 @@ impl<'a> Parser<'a> {
         self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
             let lo = this.token.span;
             let (pat, guard) = this.parse_match_arm_pat_and_guard()?;
-            let arrow_span = this.token.span;
-            if let Err(mut err) = this.expect(&token::FatArrow) {
-                // We might have a `=>` -> `=` or `->` typo (issue #89396).
-                if TokenKind::FatArrow
-                    .similar_tokens()
-                    .is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind))
-                {
-                    err.span_suggestion(
-                        this.token.span,
-                        "use a fat arrow to start a match arm",
-                        "=>",
-                        Applicability::MachineApplicable,
-                    );
-                    if matches!(
-                        (&this.prev_token.kind, &this.token.kind),
-                        (token::DotDotEq, token::Gt)
-                    ) {
-                        // `error_inclusive_range_match_arrow` handles cases like `0..=> {}`,
-                        // so we suppress the error here
-                        err.delay_as_bug();
+
+            let span_before_body = this.prev_token.span;
+            let arm_body;
+            let is_fat_arrow = this.check(&token::FatArrow);
+            let is_almost_fat_arrow = TokenKind::FatArrow
+                .similar_tokens()
+                .is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind));
+            let mut result = if !is_fat_arrow && !is_almost_fat_arrow {
+                // A pattern without a body, allowed for never patterns.
+                arm_body = None;
+                this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)])
+            } else {
+                if let Err(mut err) = this.expect(&token::FatArrow) {
+                    // We might have a `=>` -> `=` or `->` typo (issue #89396).
+                    if is_almost_fat_arrow {
+                        err.span_suggestion(
+                            this.token.span,
+                            "use a fat arrow to start a match arm",
+                            "=>",
+                            Applicability::MachineApplicable,
+                        );
+                        if matches!(
+                            (&this.prev_token.kind, &this.token.kind),
+                            (token::DotDotEq, token::Gt)
+                        ) {
+                            // `error_inclusive_range_match_arrow` handles cases like `0..=> {}`,
+                            // so we suppress the error here
+                            err.delay_as_bug();
+                        } else {
+                            err.emit();
+                        }
+                        this.bump();
                     } else {
-                        err.emit();
+                        return Err(err);
                     }
-                    this.bump();
-                } else {
-                    return Err(err);
                 }
-            }
-            let arm_start_span = this.token.span;
+                let arrow_span = this.prev_token.span;
+                let arm_start_span = this.token.span;
 
-            let expr = this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| {
-                err.span_label(arrow_span, "while parsing the `match` arm starting here");
-                err
-            })?;
+                let expr =
+                    this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| {
+                        err.span_label(arrow_span, "while parsing the `match` arm starting here");
+                        err
+                    })?;
 
-            let require_comma = classify::expr_requires_semi_to_be_stmt(&expr)
-                && this.token != token::CloseDelim(Delimiter::Brace);
-
-            let hi = this.prev_token.span;
-
-            if require_comma {
-                let sm = this.sess.source_map();
-                if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) {
-                    let span = body.span;
-                    return Ok((
-                        ast::Arm {
-                            attrs,
-                            pat,
-                            guard,
-                            body,
-                            span,
-                            id: DUMMY_NODE_ID,
-                            is_placeholder: false,
-                        },
-                        TrailingToken::None,
-                    ));
-                }
-                this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)])
-                    .or_else(|mut err| {
-                        if this.token == token::FatArrow {
-                            if let Ok(expr_lines) = sm.span_to_lines(expr.span)
-                                && let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span)
-                                && arm_start_lines.lines[0].end_col == expr_lines.lines[0].end_col
-                                && expr_lines.lines.len() == 2
-                            {
-                                // We check whether there's any trailing code in the parse span,
-                                // if there isn't, we very likely have the following:
-                                //
-                                // X |     &Y => "y"
-                                //   |        --    - missing comma
-                                //   |        |
-                                //   |        arrow_span
-                                // X |     &X => "x"
-                                //   |      - ^^ self.token.span
-                                //   |      |
-                                //   |      parsed until here as `"y" & X`
-                                err.span_suggestion_short(
-                                    arm_start_span.shrink_to_hi(),
-                                    "missing a comma here to end this `match` arm",
-                                    ",",
-                                    Applicability::MachineApplicable,
+                let require_comma = classify::expr_requires_semi_to_be_stmt(&expr)
+                    && this.token != token::CloseDelim(Delimiter::Brace);
+
+                if !require_comma {
+                    arm_body = Some(expr);
+                    this.eat(&token::Comma);
+                    Ok(false)
+                } else if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) {
+                    arm_body = Some(body);
+                    Ok(true)
+                } else {
+                    let expr_span = expr.span;
+                    arm_body = Some(expr);
+                    this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)])
+                        .map_err(|mut err| {
+                            if this.token == token::FatArrow {
+                                let sm = this.sess.source_map();
+                                if let Ok(expr_lines) = sm.span_to_lines(expr_span)
+                                    && let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span)
+                                    && arm_start_lines.lines[0].end_col
+                                        == expr_lines.lines[0].end_col
+                                    && expr_lines.lines.len() == 2
+                                {
+                                    // We check whether there's any trailing code in the parse span,
+                                    // if there isn't, we very likely have the following:
+                                    //
+                                    // X |     &Y => "y"
+                                    //   |        --    - missing comma
+                                    //   |        |
+                                    //   |        arrow_span
+                                    // X |     &X => "x"
+                                    //   |      - ^^ self.token.span
+                                    //   |      |
+                                    //   |      parsed until here as `"y" & X`
+                                    err.span_suggestion_short(
+                                        arm_start_span.shrink_to_hi(),
+                                        "missing a comma here to end this `match` arm",
+                                        ",",
+                                        Applicability::MachineApplicable,
+                                    );
+                                }
+                            } else {
+                                err.span_label(
+                                    arrow_span,
+                                    "while parsing the `match` arm starting here",
                                 );
-                                return Err(err);
-                            }
-                        } else {
-                            // FIXME(compiler-errors): We could also recover `; PAT =>` here
-
-                            // Try to parse a following `PAT =>`, if successful
-                            // then we should recover.
-                            let mut snapshot = this.create_snapshot_for_diagnostic();
-                            let pattern_follows = snapshot
-                                .parse_pat_allow_top_alt(
-                                    None,
-                                    RecoverComma::Yes,
-                                    RecoverColon::Yes,
-                                    CommaRecoveryMode::EitherTupleOrPipe,
-                                )
-                                .map_err(|err| err.cancel())
-                                .is_ok();
-                            if pattern_follows && snapshot.check(&TokenKind::FatArrow) {
-                                err.cancel();
-                                this.sess.emit_err(errors::MissingCommaAfterMatchArm {
-                                    span: hi.shrink_to_hi(),
-                                });
-                                return Ok(true);
                             }
-                        }
-                        err.span_label(arrow_span, "while parsing the `match` arm starting here");
-                        Err(err)
-                    })?;
-            } else {
-                this.eat(&token::Comma);
+                            err
+                        })
+                }
+            };
+
+            let hi_span = arm_body.as_ref().map_or(span_before_body, |body| body.span);
+            let arm_span = lo.to(hi_span);
+
+            // We want to recover:
+            // X |     Some(_) => foo()
+            //   |                     - missing comma
+            // X |     None => "x"
+            //   |     ^^^^ self.token.span
+            // as well as:
+            // X |     Some(!)
+            //   |            - missing comma
+            // X |     None => "x"
+            //   |     ^^^^ self.token.span
+            // But we musn't recover
+            // X |     pat[0] => {}
+            //   |        ^ self.token.span
+            let recover_missing_comma = arm_body.is_some() || pat.could_be_never_pattern();
+            if recover_missing_comma {
+                result = result.or_else(|err| {
+                    // FIXME(compiler-errors): We could also recover `; PAT =>` here
+
+                    // Try to parse a following `PAT =>`, if successful
+                    // then we should recover.
+                    let mut snapshot = this.create_snapshot_for_diagnostic();
+                    let pattern_follows = snapshot
+                        .parse_pat_allow_top_alt(
+                            None,
+                            RecoverComma::Yes,
+                            RecoverColon::Yes,
+                            CommaRecoveryMode::EitherTupleOrPipe,
+                        )
+                        .map_err(|err| err.cancel())
+                        .is_ok();
+                    if pattern_follows && snapshot.check(&TokenKind::FatArrow) {
+                        err.cancel();
+                        this.sess.emit_err(errors::MissingCommaAfterMatchArm {
+                            span: arm_span.shrink_to_hi(),
+                        });
+                        return Ok(true);
+                    }
+                    Err(err)
+                });
             }
+            result?;
 
             Ok((
                 ast::Arm {
                     attrs,
                     pat,
                     guard,
-                    body: expr,
-                    span: lo.to(hi),
+                    body: arm_body,
+                    span: arm_span,
                     id: DUMMY_NODE_ID,
                     is_placeholder: false,
                 },
diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index 3d1d1ec8108..9b8a34cf0cc 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -154,7 +154,7 @@ impl<'a> Parser<'a> {
             }
             Err(err) => return Err(err),
         };
-        if rc == RecoverComma::Yes {
+        if rc == RecoverComma::Yes && !first_pat.could_be_never_pattern() {
             self.maybe_recover_unexpected_comma(
                 first_pat.span,
                 matches!(first_pat.kind, PatKind::MacCall(_)),
@@ -200,7 +200,7 @@ impl<'a> Parser<'a> {
                 err.span_label(lo, WHILE_PARSING_OR_MSG);
                 err
             })?;
-            if rc == RecoverComma::Yes {
+            if rc == RecoverComma::Yes && !pat.could_be_never_pattern() {
                 self.maybe_recover_unexpected_comma(pat.span, false, rt)?;
             }
             pats.push(pat);
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index 75a0541b89b..5a139244640 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -3294,7 +3294,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
         self.with_rib(ValueNS, RibKind::Normal, |this| {
             this.resolve_pattern_top(&arm.pat, PatternSource::Match);
             walk_list!(this, visit_expr, &arm.guard);
-            this.visit_expr(&arm.body);
+            walk_list!(this, visit_expr, &arm.body);
         });
     }
 
diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
index 649a23565a9..107f488484d 100644
--- a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
+++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs
@@ -341,7 +341,9 @@ impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> {
 
         self.apply(|this| {
             SimilarNamesNameVisitor(this).visit_pat(&arm.pat);
-            this.apply(|this| walk_expr(this, &arm.body));
+            if let Some(body) = &arm.body {
+                this.apply(|this| walk_expr(this, body));
+            }
         });
 
         self.check_single_char_names();
diff --git a/src/tools/clippy/clippy_lints/src/redundant_else.rs b/src/tools/clippy/clippy_lints/src/redundant_else.rs
index 221aa317e5d..530fc664657 100644
--- a/src/tools/clippy/clippy_lints/src/redundant_else.rs
+++ b/src/tools/clippy/clippy_lints/src/redundant_else.rs
@@ -105,7 +105,9 @@ impl<'ast> Visitor<'ast> for BreakVisitor {
     fn visit_expr(&mut self, expr: &'ast Expr) {
         self.is_break = match expr.kind {
             ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) => true,
-            ExprKind::Match(_, ref arms) => arms.iter().all(|arm| self.check_expr(&arm.body)),
+            ExprKind::Match(_, ref arms) => arms.iter().all(|arm|
+                arm.body.is_none() || arm.body.as_deref().is_some_and(|body| self.check_expr(body))
+            ),
             ExprKind::If(_, ref then, Some(ref els)) => self.check_block(then) && self.check_expr(els),
             ExprKind::If(_, _, None)
             // ignore loops for simplicity
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index a2c61e07b70..b2cff164661 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -236,7 +236,7 @@ pub fn eq_field(l: &ExprField, r: &ExprField) -> bool {
 pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
     l.is_placeholder == r.is_placeholder
         && eq_pat(&l.pat, &r.pat)
-        && eq_expr(&l.body, &r.body)
+        && eq_expr_opt(&l.body, &r.body)
         && eq_expr_opt(&l.guard, &r.guard)
         && over(&l.attrs, &r.attrs, eq_attr)
 }
diff --git a/src/tools/rustfmt/src/matches.rs b/src/tools/rustfmt/src/matches.rs
index 95b0ed16db8..ef509b56837 100644
--- a/src/tools/rustfmt/src/matches.rs
+++ b/src/tools/rustfmt/src/matches.rs
@@ -223,7 +223,7 @@ fn rewrite_match_arm(
 ) -> Option<String> {
     let (missing_span, attrs_str) = if !arm.attrs.is_empty() {
         if contains_skip(&arm.attrs) {
-            let (_, body) = flatten_arm_body(context, &arm.body, None);
+            let (_, body) = flatten_arm_body(context, arm.body.as_deref()?, None);
             // `arm.span()` does not include trailing comma, add it manually.
             return Some(format!(
                 "{}{}",
@@ -246,7 +246,7 @@ fn rewrite_match_arm(
     };
 
     // Patterns
-    let pat_shape = match &arm.body.kind {
+    let pat_shape = match &arm.body.as_ref()?.kind {
         ast::ExprKind::Block(_, Some(label)) => {
             // Some block with a label ` => 'label: {`
             // 7 = ` => : {`
@@ -280,10 +280,10 @@ fn rewrite_match_arm(
         false,
     )?;
 
-    let arrow_span = mk_sp(arm.pat.span.hi(), arm.body.span().lo());
+    let arrow_span = mk_sp(arm.pat.span.hi(), arm.body.as_ref()?.span().lo());
     rewrite_match_body(
         context,
-        &arm.body,
+        arm.body.as_ref()?,
         &lhs_str,
         shape,
         guard_str.contains('\n'),
diff --git a/src/tools/rustfmt/src/spanned.rs b/src/tools/rustfmt/src/spanned.rs
index 2136cfeae1a..5960b144499 100644
--- a/src/tools/rustfmt/src/spanned.rs
+++ b/src/tools/rustfmt/src/spanned.rs
@@ -97,7 +97,12 @@ impl Spanned for ast::Arm {
         } else {
             self.attrs[0].span.lo()
         };
-        span_with_attrs_lo_hi!(self, lo, self.body.span.hi())
+        let hi = if let Some(body) = &self.body {
+            body.span.hi()
+        } else {
+            self.pat.span.hi()
+        };
+        span_with_attrs_lo_hi!(self, lo, hi)
     }
 }
 
diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions1.rs b/tests/ui/half-open-range-patterns/range_pat_interactions1.rs
index 55353999b67..9ffc2190d20 100644
--- a/tests/ui/half-open-range-patterns/range_pat_interactions1.rs
+++ b/tests/ui/half-open-range-patterns/range_pat_interactions1.rs
@@ -17,7 +17,7 @@ fn main() {
         }
         match x as i32 {
             0..5+1 => errors_only.push(x),
-            //~^ error: expected one of `=>`, `if`, or `|`, found `+`
+            //~^ error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `+`
             1 | -3..0 => first_or.push(x),
             y @ (0..5 | 6) => or_two.push(y),
             y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr b/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr
index 19ebcaf0f36..05235c9b922 100644
--- a/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr
+++ b/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr
@@ -1,8 +1,8 @@
-error: expected one of `=>`, `if`, or `|`, found `+`
+error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `+`
   --> $DIR/range_pat_interactions1.rs:19:17
    |
 LL |             0..5+1 => errors_only.push(x),
-   |                 ^ expected one of `=>`, `if`, or `|`
+   |                 ^ expected one of `,`, `=>`, `if`, `|`, or `}`
 
 error[E0408]: variable `n` is not bound in all patterns
   --> $DIR/range_pat_interactions1.rs:10:25
diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions2.rs b/tests/ui/half-open-range-patterns/range_pat_interactions2.rs
index 4615ebd688a..b212bfbe093 100644
--- a/tests/ui/half-open-range-patterns/range_pat_interactions2.rs
+++ b/tests/ui/half-open-range-patterns/range_pat_interactions2.rs
@@ -9,7 +9,7 @@ fn main() {
         match x as i32 {
             0..=(5+1) => errors_only.push(x),
             //~^ error: inclusive range with no end
-            //~| error: expected one of `=>`, `if`, or `|`, found `(`
+            //~| error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `(`
             1 | -3..0 => first_or.push(x),
             y @ (0..5 | 6) => or_two.push(y),
             y @ 0..const { 5 + 1 } => assert_eq!(y, 5),
diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr b/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr
index 13a5542a474..0129f927e34 100644
--- a/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr
+++ b/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr
@@ -6,11 +6,11 @@ LL |             0..=(5+1) => errors_only.push(x),
    |
    = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`)
 
-error: expected one of `=>`, `if`, or `|`, found `(`
+error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `(`
   --> $DIR/range_pat_interactions2.rs:10:17
    |
 LL |             0..=(5+1) => errors_only.push(x),
-   |                 ^ expected one of `=>`, `if`, or `|`
+   |                 ^ expected one of `,`, `=>`, `if`, `|`, or `}`
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/never_patterns/check.rs b/tests/ui/never_patterns/check.rs
index bcc3a760c10..9b02fc7996d 100644
--- a/tests/ui/never_patterns/check.rs
+++ b/tests/ui/never_patterns/check.rs
@@ -15,11 +15,12 @@ fn no_arms_or_guards(x: Void) {
         None => {}
     }
     match None::<Void> {
+        //~^ ERROR non-exhaustive
         Some(!) if true,
-        //~^ ERROR expected one of
         None => {}
     }
     match None::<Void> {
+        //~^ ERROR non-exhaustive
         Some(!) if true => {}
         None => {}
     }
diff --git a/tests/ui/never_patterns/check.stderr b/tests/ui/never_patterns/check.stderr
index d7cdd64840f..945812225c4 100644
--- a/tests/ui/never_patterns/check.stderr
+++ b/tests/ui/never_patterns/check.stderr
@@ -1,8 +1,39 @@
-error: expected one of `.`, `=>`, `?`, or an operator, found `,`
-  --> $DIR/check.rs:18:24
+error[E0004]: non-exhaustive patterns: `Some(_)` not covered
+  --> $DIR/check.rs:17:11
+   |
+LL |     match None::<Void> {
+   |           ^^^^^^^^^^^^ pattern `Some(_)` not covered
+   |
+note: `Option<Void>` defined here
+  --> $SRC_DIR/core/src/option.rs:LL:COL
+  ::: $SRC_DIR/core/src/option.rs:LL:COL
+   |
+   = note: not covered
+   = note: the matched value is of type `Option<Void>`
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
+   |
+LL ~         None => {},
+LL +         Some(_) => todo!()
+   |
+
+error[E0004]: non-exhaustive patterns: `Some(_)` not covered
+  --> $DIR/check.rs:22:11
+   |
+LL |     match None::<Void> {
+   |           ^^^^^^^^^^^^ pattern `Some(_)` not covered
+   |
+note: `Option<Void>` defined here
+  --> $SRC_DIR/core/src/option.rs:LL:COL
+  ::: $SRC_DIR/core/src/option.rs:LL:COL
+   |
+   = note: not covered
+   = note: the matched value is of type `Option<Void>`
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
+   |
+LL ~         None => {},
+LL +         Some(_) => todo!()
    |
-LL |         Some(!) if true,
-   |                        ^ expected one of `.`, `=>`, `?`, or an operator
 
-error: aborting due to 1 previous error
+error: aborting due to 2 previous errors
 
+For more information about this error, try `rustc --explain E0004`.
diff --git a/tests/ui/never_patterns/parse.rs b/tests/ui/never_patterns/parse.rs
new file mode 100644
index 00000000000..850416d723a
--- /dev/null
+++ b/tests/ui/never_patterns/parse.rs
@@ -0,0 +1,68 @@
+#![feature(never_patterns)]
+#![allow(incomplete_features)]
+
+enum Void {}
+
+fn main() {}
+
+macro_rules! never {
+    () => { ! }
+}
+
+fn parse(x: Void) {
+    match None::<Void> {
+        None => {}
+        Some(!),
+    }
+    match None::<Void> {
+        Some(!),
+        None => {}
+    }
+    match None::<Void> {
+        None => {}
+        Some(!)
+    }
+    match None::<Void> {
+        Some(!)
+        //~^ ERROR expected `,` following `match` arm
+        None => {}
+    }
+    match None::<Void> {
+        Some(!) if true
+        //~^ ERROR expected `,` following `match` arm
+        None => {}
+    }
+    match None::<Void> {
+        Some(!) if true,
+        None => {}
+    }
+    match None::<Void> {
+        Some(!) <=
+        //~^ ERROR expected one of
+    }
+    match x {
+        never!(),
+    }
+    match x {
+        never!() if true,
+    }
+    match x {
+        never!()
+    }
+    match &x {
+        &never!(),
+    }
+    match None::<Void> {
+        Some(never!()),
+        None => {}
+    }
+    match x { ! }
+    match &x { &! }
+
+    let res: Result<bool, Void> = Ok(false);
+    let Ok(_) = res;
+    let Ok(_) | Err(!) = &res; // Disallowed; see #82048.
+    //~^ ERROR top-level or-patterns are not allowed in `let` bindings
+    let (Ok(_) | Err(!)) = &res;
+    let (Ok(_) | Err(&!)) = res.as_ref();
+}
diff --git a/tests/ui/never_patterns/parse.stderr b/tests/ui/never_patterns/parse.stderr
new file mode 100644
index 00000000000..7ea33540c5e
--- /dev/null
+++ b/tests/ui/never_patterns/parse.stderr
@@ -0,0 +1,26 @@
+error: expected `,` following `match` arm
+  --> $DIR/parse.rs:26:16
+   |
+LL |         Some(!)
+   |                ^ help: missing a comma here to end this `match` arm: `,`
+
+error: expected `,` following `match` arm
+  --> $DIR/parse.rs:31:24
+   |
+LL |         Some(!) if true
+   |                        ^ help: missing a comma here to end this `match` arm: `,`
+
+error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `<=`
+  --> $DIR/parse.rs:40:17
+   |
+LL |         Some(!) <=
+   |                 ^^ expected one of `,`, `=>`, `if`, `|`, or `}`
+
+error: top-level or-patterns are not allowed in `let` bindings
+  --> $DIR/parse.rs:64:9
+   |
+LL |     let Ok(_) | Err(!) = &res; // Disallowed; see #82048.
+   |         ^^^^^^^^^^^^^^ help: wrap the pattern in parentheses: `(Ok(_) | Err(!))`
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs
index d1950087c4c..2c402e4c65e 100644
--- a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs
+++ b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs
@@ -84,15 +84,15 @@ fn main() {}
 
 #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } }
 //~^ ERROR inclusive range with no end
-//~| ERROR expected one of `=>`, `if`, or `|`, found `#`
+//~| ERROR expected one of `,`, `=>`, `if`, `|`, or `}`, found `#`
 #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } }
 //~^ ERROR inclusive range with no end
-//~| ERROR expected one of `=>`, `if`, or `|`, found `#`
+//~| ERROR expected one of `,`, `=>`, `if`, `|`, or `}`, found `#`
 #[cfg(FALSE)] fn e() { match 0 { 0..=-#[attr] 10 => () } }
 //~^ ERROR unexpected token: `#`
 #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } }
 //~^ ERROR inclusive range with no end
-//~| ERROR expected one of `=>`, `if`, or `|`, found `#`
+//~| ERROR expected one of `,`, `=>`, `if`, `|`, or `}`, found `#`
 
 #[cfg(FALSE)] fn e() { let _ = x.#![attr]foo(); }
 //~^ ERROR unexpected token: `#`
diff --git a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr
index e46c591080d..a0e95c5c1ed 100644
--- a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr
+++ b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr
@@ -365,11 +365,11 @@ LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } }
    |
    = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`)
 
-error: expected one of `=>`, `if`, or `|`, found `#`
+error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:85:38
    |
 LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } }
-   |                                      ^ expected one of `=>`, `if`, or `|`
+   |                                      ^ expected one of `,`, `=>`, `if`, `|`, or `}`
 
 error[E0586]: inclusive range with no end
   --> $DIR/attr-stmt-expr-attr-bad.rs:88:35
@@ -379,11 +379,11 @@ LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } }
    |
    = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`)
 
-error: expected one of `=>`, `if`, or `|`, found `#`
+error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:88:38
    |
 LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } }
-   |                                      ^ expected one of `=>`, `if`, or `|`
+   |                                      ^ expected one of `,`, `=>`, `if`, `|`, or `}`
 
 error: unexpected token: `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:91:39
@@ -399,11 +399,11 @@ LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } }
    |
    = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`)
 
-error: expected one of `=>`, `if`, or `|`, found `#`
+error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:93:38
    |
 LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } }
-   |                                      ^ expected one of `=>`, `if`, or `|`
+   |                                      ^ expected one of `,`, `=>`, `if`, `|`, or `}`
 
 error: unexpected token: `#`
   --> $DIR/attr-stmt-expr-attr-bad.rs:97:34
diff --git a/tests/ui/parser/issues/issue-24375.rs b/tests/ui/parser/issues/issue-24375.rs
index 1d128d33e4f..8d1bc579e7b 100644
--- a/tests/ui/parser/issues/issue-24375.rs
+++ b/tests/ui/parser/issues/issue-24375.rs
@@ -3,7 +3,7 @@ static tmp : [&'static str; 2]  = ["hello", "he"];
 fn main() {
     let z = "hello";
     match z {
-        tmp[0] => {} //~ ERROR expected one of `=>`, `@`, `if`, or `|`, found `[`
+        tmp[0] => {} //~ ERROR expected one of `,`, `=>`, `@`, `if`, `|`, or `}`, found `[`
         _ => {}
     }
 }
diff --git a/tests/ui/parser/issues/issue-24375.stderr b/tests/ui/parser/issues/issue-24375.stderr
index bb1e19e9e6d..2b980a5520f 100644
--- a/tests/ui/parser/issues/issue-24375.stderr
+++ b/tests/ui/parser/issues/issue-24375.stderr
@@ -1,8 +1,8 @@
-error: expected one of `=>`, `@`, `if`, or `|`, found `[`
+error: expected one of `,`, `=>`, `@`, `if`, `|`, or `}`, found `[`
   --> $DIR/issue-24375.rs:6:12
    |
 LL |         tmp[0] => {}
-   |            ^ expected one of `=>`, `@`, `if`, or `|`
+   |            ^ expected one of `,`, `=>`, `@`, `if`, `|`, or `}`
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/parser/macro/macro-expand-to-match-arm.rs b/tests/ui/parser/macro/macro-expand-to-match-arm.rs
index 39d1d065ed9..972ca61cc84 100644
--- a/tests/ui/parser/macro/macro-expand-to-match-arm.rs
+++ b/tests/ui/parser/macro/macro-expand-to-match-arm.rs
@@ -1,6 +1,8 @@
 macro_rules! arm {
     ($pattern:pat => $block:block) => {
         $pattern => $block
+        //~^ ERROR macro expansion ignores token `=>` and any following
+        //~| NOTE the usage of `arm!` is likely invalid in pattern context
     };
 }
 
@@ -9,9 +11,7 @@ fn main() {
     match x {
         Some(1) => {},
         arm!(None => {}),
-        //~^ NOTE macros cannot expand to match arms
-        //~| ERROR unexpected `,` in pattern
-        // doesn't recover
+        //~^ NOTE caused by the macro expansion here
         Some(2) => {},
         _ => {},
     };
diff --git a/tests/ui/parser/macro/macro-expand-to-match-arm.stderr b/tests/ui/parser/macro/macro-expand-to-match-arm.stderr
index 1b34d2d12b2..a62109c5050 100644
--- a/tests/ui/parser/macro/macro-expand-to-match-arm.stderr
+++ b/tests/ui/parser/macro/macro-expand-to-match-arm.stderr
@@ -1,10 +1,13 @@
-error: unexpected `,` in pattern
-  --> $DIR/macro-expand-to-match-arm.rs:11:25
+error: macro expansion ignores token `=>` and any following
+  --> $DIR/macro-expand-to-match-arm.rs:3:18
    |
+LL |         $pattern => $block
+   |                  ^^
+...
 LL |         arm!(None => {}),
-   |                         ^
+   |         ---------------- caused by the macro expansion here
    |
-   = note: macros cannot expand to match arms
+   = note: the usage of `arm!` is likely invalid in pattern context
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/parser/match-arm-without-body.rs b/tests/ui/parser/match-arm-without-body.rs
index 5f009c7a355..c33bd0c3031 100644
--- a/tests/ui/parser/match-arm-without-body.rs
+++ b/tests/ui/parser/match-arm-without-body.rs
@@ -6,7 +6,6 @@ fn main() {
     match Some(false) {
         Some(_)
     }
-    //~^ ERROR expected one of
     match Some(false) {
         Some(_)
         _ => {}
@@ -28,7 +27,6 @@ fn main() {
     match Some(false) {
         Some(_) if true
     }
-    //~^ ERROR expected one of
     match Some(false) {
         Some(_) if true
         _ => {}
@@ -36,33 +34,28 @@ fn main() {
     }
     match Some(false) {
         Some(_) if true,
-        //~^ ERROR expected one of
     }
     match Some(false) {
         Some(_) if true,
-        //~^ ERROR expected one of
         _ => {}
     }
     match Some(false) {
         pat!()
     }
-    //~^ ERROR expected one of
     match Some(false) {
         pat!(),
-        //~^ ERROR unexpected `,` in pattern
     }
     match Some(false) {
         pat!() if true,
-        //~^ ERROR expected one of
     }
     match Some(false) {
         pat!()
+        //~^ ERROR expected `,` following `match` arm
+        //~| HELP missing a comma here
         _ => {}
-        //~^ ERROR expected one of
     }
     match Some(false) {
         pat!(),
-        //~^ ERROR unexpected `,` in pattern
         _ => {}
     }
 }
diff --git a/tests/ui/parser/match-arm-without-body.stderr b/tests/ui/parser/match-arm-without-body.stderr
index 210007628db..77fd9176633 100644
--- a/tests/ui/parser/match-arm-without-body.stderr
+++ b/tests/ui/parser/match-arm-without-body.stderr
@@ -1,21 +1,13 @@
-error: expected one of `=>`, `if`, or `|`, found `}`
-  --> $DIR/match-arm-without-body.rs:8:5
+error: expected one of `,`, `=>`, `if`, `|`, or `}`, found reserved identifier `_`
+  --> $DIR/match-arm-without-body.rs:11:9
    |
 LL |         Some(_)
-   |                - expected one of `=>`, `if`, or `|`
-LL |     }
-   |     ^ unexpected token
-
-error: expected one of `=>`, `if`, or `|`, found reserved identifier `_`
-  --> $DIR/match-arm-without-body.rs:12:9
-   |
-LL |         Some(_)
-   |                - expected one of `=>`, `if`, or `|`
+   |                - expected one of `,`, `=>`, `if`, `|`, or `}`
 LL |         _ => {}
    |         ^ unexpected token
 
 error: unexpected `,` in pattern
-  --> $DIR/match-arm-without-body.rs:16:16
+  --> $DIR/match-arm-without-body.rs:15:16
    |
 LL |         Some(_),
    |                ^
@@ -30,7 +22,7 @@ LL |         Some(_) |
    |
 
 error: unexpected `,` in pattern
-  --> $DIR/match-arm-without-body.rs:22:16
+  --> $DIR/match-arm-without-body.rs:21:16
    |
 LL |         Some(_),
    |                ^
@@ -52,71 +44,19 @@ LL +
 LL ~         _ => {}
    |
 
-error: expected one of `.`, `=>`, `?`, or an operator, found `}`
-  --> $DIR/match-arm-without-body.rs:30:5
+error: expected one of `,`, `.`, `=>`, `?`, `}`, or an operator, found reserved identifier `_`
+  --> $DIR/match-arm-without-body.rs:32:9
    |
 LL |         Some(_) if true
-   |                        - expected one of `.`, `=>`, `?`, or an operator
-LL |     }
-   |     ^ unexpected token
-
-error: expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_`
-  --> $DIR/match-arm-without-body.rs:34:9
-   |
-LL |         Some(_) if true
-   |                        - expected one of `.`, `=>`, `?`, or an operator
+   |                        - expected one of `,`, `.`, `=>`, `?`, `}`, or an operator
 LL |         _ => {}
    |         ^ unexpected token
 
-error: expected one of `.`, `=>`, `?`, or an operator, found `,`
-  --> $DIR/match-arm-without-body.rs:38:24
-   |
-LL |         Some(_) if true,
-   |                        ^ expected one of `.`, `=>`, `?`, or an operator
-
-error: expected one of `.`, `=>`, `?`, or an operator, found `,`
-  --> $DIR/match-arm-without-body.rs:42:24
-   |
-LL |         Some(_) if true,
-   |                        ^ expected one of `.`, `=>`, `?`, or an operator
-
-error: expected one of `=>`, `if`, or `|`, found `}`
-  --> $DIR/match-arm-without-body.rs:48:5
+error: expected `,` following `match` arm
+  --> $DIR/match-arm-without-body.rs:52:15
    |
 LL |         pat!()
-   |               - expected one of `=>`, `if`, or `|`
-LL |     }
-   |     ^ unexpected token
-
-error: unexpected `,` in pattern
-  --> $DIR/match-arm-without-body.rs:51:15
-   |
-LL |         pat!(),
-   |               ^
-   |
-   = note: macros cannot expand to match arms
-
-error: expected one of `.`, `=>`, `?`, or an operator, found `,`
-  --> $DIR/match-arm-without-body.rs:55:23
-   |
-LL |         pat!() if true,
-   |                       ^ expected one of `.`, `=>`, `?`, or an operator
-
-error: expected one of `=>`, `if`, or `|`, found reserved identifier `_`
-  --> $DIR/match-arm-without-body.rs:60:9
-   |
-LL |         pat!()
-   |               - expected one of `=>`, `if`, or `|`
-LL |         _ => {}
-   |         ^ unexpected token
-
-error: unexpected `,` in pattern
-  --> $DIR/match-arm-without-body.rs:64:15
-   |
-LL |         pat!(),
-   |               ^
-   |
-   = note: macros cannot expand to match arms
+   |               ^ help: missing a comma here to end this `match` arm: `,`
 
-error: aborting due to 13 previous errors
+error: aborting due to 5 previous errors
 
diff --git a/tests/ui/parser/pat-lt-bracket-1.rs b/tests/ui/parser/pat-lt-bracket-1.rs
index 2e2001434f2..33da15adb9e 100644
--- a/tests/ui/parser/pat-lt-bracket-1.rs
+++ b/tests/ui/parser/pat-lt-bracket-1.rs
@@ -1,7 +1,7 @@
 fn main() {
   match 42 {
     x < 7 => (),
-   //~^ error: expected one of `=>`, `@`, `if`, or `|`, found `<`
+   //~^ error: expected one of `,`, `=>`, `@`, `if`, `|`, or `}`, found `<`
     _ => ()
   }
 }
diff --git a/tests/ui/parser/pat-lt-bracket-1.stderr b/tests/ui/parser/pat-lt-bracket-1.stderr
index 14e679bbee0..f39487052ad 100644
--- a/tests/ui/parser/pat-lt-bracket-1.stderr
+++ b/tests/ui/parser/pat-lt-bracket-1.stderr
@@ -1,8 +1,8 @@
-error: expected one of `=>`, `@`, `if`, or `|`, found `<`
+error: expected one of `,`, `=>`, `@`, `if`, `|`, or `}`, found `<`
   --> $DIR/pat-lt-bracket-1.rs:3:7
    |
 LL |     x < 7 => (),
-   |       ^ expected one of `=>`, `@`, `if`, or `|`
+   |       ^ expected one of `,`, `=>`, `@`, `if`, `|`, or `}`
 
 error: aborting due to 1 previous error