about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLieselotte <52315535+she3py@users.noreply.github.com>2024-02-25 22:22:11 +0100
committerLieselotte <52315535+she3py@users.noreply.github.com>2024-02-25 22:24:31 +0100
commitc440a5b814005c85ec903f9b9e44e25bf5c9c565 (patch)
treeb1d4d9004e25f842949a6348a4e4269dd2a840c9
parenta3fce72a27ee41077c3752851ff778f886f0a4fa (diff)
downloadrust-c440a5b814005c85ec903f9b9e44e25bf5c9c565.tar.gz
rust-c440a5b814005c85ec903f9b9e44e25bf5c9c565.zip
Add `ErrorGuaranteed` to `ast::ExprKind::Err`
-rw-r--r--compiler/rustc_ast/src/ast.rs4
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs2
-rw-r--r--compiler/rustc_ast/src/util/classify.rs2
-rw-r--r--compiler/rustc_ast/src/visit.rs2
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs4
-rw-r--r--compiler/rustc_ast_lowering/src/pat.rs2
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/expr.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/asm.rs71
-rw-r--r--compiler/rustc_builtin_macros/src/assert.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/assert/context.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/cfg.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/compile_error.rs14
-rw-r--r--compiler/rustc_builtin_macros/src/concat.rs35
-rw-r--r--compiler/rustc_builtin_macros/src/concat_bytes.rs131
-rw-r--r--compiler/rustc_builtin_macros/src/concat_idents.rs12
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/default.rs53
-rw-r--r--compiler/rustc_builtin_macros/src/env.rs36
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs90
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs1
-rw-r--r--compiler/rustc_builtin_macros/src/source_util.rs43
-rw-r--r--compiler/rustc_builtin_macros/src/type_ascribe.rs4
-rw-r--r--compiler/rustc_expand/src/base.rs99
-rw-r--r--compiler/rustc_expand/src/expand.rs82
-rw-r--r--compiler/rustc_expand/src/mbe/diagnostics.rs18
-rw-r--r--compiler/rustc_expand/src/mbe/macro_check.rs62
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs132
-rw-r--r--compiler/rustc_lint/src/unused.rs2
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs90
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs165
-rw-r--r--compiler/rustc_parse/src/parser/item.rs4
-rw-r--r--compiler/rustc_parse/src/parser/pat.rs18
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs64
-rw-r--r--compiler/rustc_parse/src/validate_attr.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/sugg.rs2
-rw-r--r--src/tools/rustfmt/src/expr.rs2
-rw-r--r--src/tools/rustfmt/src/utils.rs2
37 files changed, 661 insertions, 603 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index b30324c7666..8ba2f222fcf 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -1296,7 +1296,7 @@ impl Expr {
             ExprKind::Yeet(..) => ExprPrecedence::Yeet,
             ExprKind::FormatArgs(..) => ExprPrecedence::FormatArgs,
             ExprKind::Become(..) => ExprPrecedence::Become,
-            ExprKind::Err | ExprKind::Dummy => ExprPrecedence::Err,
+            ExprKind::Err(_) | ExprKind::Dummy => ExprPrecedence::Err,
         }
     }
 
@@ -1518,7 +1518,7 @@ pub enum ExprKind {
     FormatArgs(P<FormatArgs>),
 
     /// Placeholder for an expression that wasn't syntactically well formed in some way.
-    Err,
+    Err(ErrorGuaranteed),
 
     /// Acts as a null expression. Lowering it will always emit a bug.
     Dummy,
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index b2f3bb303de..60bc21c6441 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -1526,7 +1526,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
         }
         ExprKind::Try(expr) => vis.visit_expr(expr),
         ExprKind::TryBlock(body) => vis.visit_block(body),
-        ExprKind::Lit(_) | ExprKind::IncludedBytes(..) | ExprKind::Err | ExprKind::Dummy => {}
+        ExprKind::Lit(_) | ExprKind::IncludedBytes(..) | ExprKind::Err(_) | ExprKind::Dummy => {}
     }
     vis.visit_id(id);
     vis.visit_span(span);
diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs
index e70a75622e6..f21a9cabb81 100644
--- a/compiler/rustc_ast/src/util/classify.rs
+++ b/compiler/rustc_ast/src/util/classify.rs
@@ -89,7 +89,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
             | Paren(_)
             | Try(_)
             | Yeet(None)
-            | Err
+            | Err(_)
             | Dummy => break None,
         }
     }
diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs
index ab3d9b49633..f29022386a9 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -1063,7 +1063,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) -> V
         }
         ExprKind::Try(subexpression) => try_visit!(visitor.visit_expr(subexpression)),
         ExprKind::TryBlock(body) => try_visit!(visitor.visit_block(body)),
-        ExprKind::Lit(_) | ExprKind::IncludedBytes(..) | ExprKind::Err | ExprKind::Dummy => {}
+        ExprKind::Lit(_) | ExprKind::IncludedBytes(..) | ExprKind::Err(_) | ExprKind::Dummy => {}
     }
 
     visitor.visit_expr_post(expression)
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 5d33a7a9142..9950db4784b 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -329,9 +329,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                     )
                 }
                 ExprKind::Yield(opt_expr) => self.lower_expr_yield(e.span, opt_expr.as_deref()),
-                ExprKind::Err => {
-                    hir::ExprKind::Err(self.dcx().span_delayed_bug(e.span, "lowered ExprKind::Err"))
-                }
+                ExprKind::Err(guar) => hir::ExprKind::Err(*guar),
 
                 ExprKind::Dummy => {
                     span_bug!(e.span, "lowered ExprKind::Dummy")
diff --git a/compiler/rustc_ast_lowering/src/pat.rs b/compiler/rustc_ast_lowering/src/pat.rs
index 755b073b2c3..469cdac11e4 100644
--- a/compiler/rustc_ast_lowering/src/pat.rs
+++ b/compiler/rustc_ast_lowering/src/pat.rs
@@ -331,7 +331,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
             ExprKind::Lit(..)
             | ExprKind::ConstBlock(..)
             | ExprKind::IncludedBytes(..)
-            | ExprKind::Err
+            | ExprKind::Err(_)
             | ExprKind::Dummy => {}
             ExprKind::Path(..) if allow_paths => {}
             ExprKind::Unary(UnOp::Neg, inner) if matches!(inner.kind, ExprKind::Lit(_)) => {}
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
index 0f9fe89402b..433ef03b6e5 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
@@ -893,7 +893,7 @@ impl<'a> State<'a> {
                 self.word_nbsp("try");
                 self.print_block_with_attrs(blk, attrs)
             }
-            ast::ExprKind::Err => {
+            ast::ExprKind::Err(_) => {
                 self.popen();
                 self.word("/*ERROR*/");
                 self.pclose()
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs
index 263081ea19e..29bf5e9f304 100644
--- a/compiler/rustc_builtin_macros/src/asm.rs
+++ b/compiler/rustc_builtin_macros/src/asm.rs
@@ -13,7 +13,7 @@ use rustc_session::lint;
 use rustc_session::parse::ParseSess;
 use rustc_span::symbol::Ident;
 use rustc_span::symbol::{kw, sym, Symbol};
-use rustc_span::{InnerSpan, Span};
+use rustc_span::{ErrorGuaranteed, InnerSpan, Span};
 use rustc_target::asm::InlineAsmArch;
 use smallvec::smallvec;
 
@@ -433,7 +433,10 @@ fn parse_reg<'a>(
     Ok(result)
 }
 
-fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::InlineAsm> {
+fn expand_preparsed_asm(
+    ecx: &mut ExtCtxt<'_>,
+    args: AsmArgs,
+) -> Result<ast::InlineAsm, ErrorGuaranteed> {
     let mut template = vec![];
     // Register operands are implicitly used since they are not allowed to be
     // referenced in the template string.
@@ -459,10 +462,10 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::Inl
             match expr_to_spanned_string(ecx, template_expr, msg) {
                 Ok(template_part) => template_part,
                 Err(err) => {
-                    if let Some((err, _)) = err {
-                        err.emit();
-                    }
-                    return None;
+                    return Err(match err {
+                        Ok((err, _)) => err.emit(),
+                        Err(guar) => guar,
+                    });
                 }
             };
 
@@ -551,8 +554,8 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::Inl
                 let err_sp = template_span.from_inner(InnerSpan::new(span.start, span.end));
                 e.span_label(err_sp, label);
             }
-            e.emit();
-            return None;
+            let guar = e.emit();
+            return Err(guar);
         }
 
         curarg = parser.curarg;
@@ -719,7 +722,7 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::Inl
         }
     }
 
-    Some(ast::InlineAsm {
+    Ok(ast::InlineAsm {
         template,
         template_strs: template_strs.into_boxed_slice(),
         operands: args.operands,
@@ -736,22 +739,21 @@ pub(super) fn expand_asm<'cx>(
 ) -> Box<dyn base::MacResult + 'cx> {
     match parse_args(ecx, sp, tts, false) {
         Ok(args) => {
-            let expr = if let Some(inline_asm) = expand_preparsed_asm(ecx, args) {
-                P(ast::Expr {
+            let expr = match expand_preparsed_asm(ecx, args) {
+                Ok(inline_asm) => P(ast::Expr {
                     id: ast::DUMMY_NODE_ID,
                     kind: ast::ExprKind::InlineAsm(P(inline_asm)),
                     span: sp,
                     attrs: ast::AttrVec::new(),
                     tokens: None,
-                })
-            } else {
-                DummyResult::raw_expr(sp, true)
+                }),
+                Err(guar) => DummyResult::raw_expr(sp, Some(guar)),
             };
             MacEager::expr(expr)
         }
         Err(err) => {
-            err.emit();
-            DummyResult::any(sp)
+            let guar = err.emit();
+            DummyResult::any(sp, guar)
         }
     }
 }
@@ -762,28 +764,25 @@ pub(super) fn expand_global_asm<'cx>(
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'cx> {
     match parse_args(ecx, sp, tts, true) {
-        Ok(args) => {
-            if let Some(inline_asm) = expand_preparsed_asm(ecx, args) {
-                MacEager::items(smallvec![P(ast::Item {
-                    ident: Ident::empty(),
-                    attrs: ast::AttrVec::new(),
-                    id: ast::DUMMY_NODE_ID,
-                    kind: ast::ItemKind::GlobalAsm(Box::new(inline_asm)),
-                    vis: ast::Visibility {
-                        span: sp.shrink_to_lo(),
-                        kind: ast::VisibilityKind::Inherited,
-                        tokens: None,
-                    },
-                    span: sp,
+        Ok(args) => match expand_preparsed_asm(ecx, args) {
+            Ok(inline_asm) => MacEager::items(smallvec![P(ast::Item {
+                ident: Ident::empty(),
+                attrs: ast::AttrVec::new(),
+                id: ast::DUMMY_NODE_ID,
+                kind: ast::ItemKind::GlobalAsm(Box::new(inline_asm)),
+                vis: ast::Visibility {
+                    span: sp.shrink_to_lo(),
+                    kind: ast::VisibilityKind::Inherited,
                     tokens: None,
-                })])
-            } else {
-                DummyResult::any(sp)
-            }
-        }
+                },
+                span: sp,
+                tokens: None,
+            })]),
+            Err(guar) => DummyResult::any(sp, guar),
+        },
         Err(err) => {
-            err.emit();
-            DummyResult::any(sp)
+            let guar = err.emit();
+            DummyResult::any(sp, guar)
         }
     }
 }
diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs
index 501d557f3ab..613ce43dec2 100644
--- a/compiler/rustc_builtin_macros/src/assert.rs
+++ b/compiler/rustc_builtin_macros/src/assert.rs
@@ -23,8 +23,8 @@ pub fn expand_assert<'cx>(
     let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
         Ok(assert) => assert,
         Err(err) => {
-            err.emit();
-            return DummyResult::any(span);
+            let guar = err.emit();
+            return DummyResult::any(span, guar);
         }
     };
 
diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs
index 7737041090b..56c56b2704b 100644
--- a/compiler/rustc_builtin_macros/src/assert/context.rs
+++ b/compiler/rustc_builtin_macros/src/assert/context.rs
@@ -304,7 +304,7 @@ impl<'cx, 'a> Context<'cx, 'a> {
             | ExprKind::ConstBlock(_)
             | ExprKind::Continue(_)
             | ExprKind::Dummy
-            | ExprKind::Err
+            | ExprKind::Err(_)
             | ExprKind::Field(_, _)
             | ExprKind::ForLoop { .. }
             | ExprKind::FormatArgs(_)
diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs
index 581d390992a..ca66bc0da0c 100644
--- a/compiler/rustc_builtin_macros/src/cfg.rs
+++ b/compiler/rustc_builtin_macros/src/cfg.rs
@@ -29,8 +29,8 @@ pub fn expand_cfg(
             MacEager::expr(cx.expr_bool(sp, matches_cfg))
         }
         Err(err) => {
-            err.emit();
-            DummyResult::any(sp)
+            let guar = err.emit();
+            DummyResult::any(sp, guar)
         }
     }
 }
diff --git a/compiler/rustc_builtin_macros/src/compile_error.rs b/compiler/rustc_builtin_macros/src/compile_error.rs
index f157575da79..66500351272 100644
--- a/compiler/rustc_builtin_macros/src/compile_error.rs
+++ b/compiler/rustc_builtin_macros/src/compile_error.rs
@@ -9,16 +9,14 @@ pub fn expand_compile_error<'cx>(
     sp: Span,
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'cx> {
-    let Some(var) = get_single_str_from_tts(cx, sp, tts, "compile_error!") else {
-        return DummyResult::any(sp);
+    let var = match get_single_str_from_tts(cx, sp, tts, "compile_error!") {
+        Ok(var) => var,
+        Err(guar) => return DummyResult::any(sp, guar),
     };
 
-    #[expect(
-        rustc::diagnostic_outside_of_impl,
-        reason = "diagnostic message is specified by user"
-    )]
+    #[expect(rustc::diagnostic_outside_of_impl, reason = "diagnostic message is specified by user")]
     #[expect(rustc::untranslatable_diagnostic, reason = "diagnostic message is specified by user")]
-    cx.dcx().span_err(sp, var.to_string());
+    let guar = cx.dcx().span_err(sp, var.to_string());
 
-    DummyResult::any(sp)
+    DummyResult::any(sp, guar)
 }
diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs
index 691142c03ed..dd4a0e7f228 100644
--- a/compiler/rustc_builtin_macros/src/concat.rs
+++ b/compiler/rustc_builtin_macros/src/concat.rs
@@ -11,12 +11,13 @@ pub fn expand_concat(
     sp: rustc_span::Span,
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'static> {
-    let Some(es) = base::get_exprs_from_tts(cx, tts) else {
-        return DummyResult::any(sp);
+    let es = match base::get_exprs_from_tts(cx, tts) {
+        Ok(es) => es,
+        Err(guar) => return DummyResult::any(sp, guar),
     };
     let mut accumulator = String::new();
     let mut missing_literal = vec![];
-    let mut has_errors = false;
+    let mut guar = None;
     for e in es {
         match e.kind {
             ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
@@ -33,19 +34,16 @@ pub fn expand_concat(
                     accumulator.push_str(&b.to_string());
                 }
                 Ok(ast::LitKind::CStr(..)) => {
-                    cx.dcx().emit_err(errors::ConcatCStrLit { span: e.span });
-                    has_errors = true;
+                    guar = Some(cx.dcx().emit_err(errors::ConcatCStrLit { span: e.span }));
                 }
                 Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => {
-                    cx.dcx().emit_err(errors::ConcatBytestr { span: e.span });
-                    has_errors = true;
+                    guar = Some(cx.dcx().emit_err(errors::ConcatBytestr { span: e.span }));
                 }
-                Ok(ast::LitKind::Err(_)) => {
-                    has_errors = true;
+                Ok(ast::LitKind::Err(guarantee)) => {
+                    guar = Some(guarantee);
                 }
                 Err(err) => {
-                    report_lit_error(&cx.sess.parse_sess, err, token_lit, e.span);
-                    has_errors = true;
+                    guar = Some(report_lit_error(&cx.sess.parse_sess, err, token_lit, e.span));
                 }
             },
             // We also want to allow negative numeric literals.
@@ -56,8 +54,7 @@ pub fn expand_concat(
                     Ok(ast::LitKind::Int(i, _)) => accumulator.push_str(&format!("-{i}")),
                     Ok(ast::LitKind::Float(f, _)) => accumulator.push_str(&format!("-{f}")),
                     Err(err) => {
-                        report_lit_error(&cx.sess.parse_sess, err, token_lit, e.span);
-                        has_errors = true;
+                        guar = Some(report_lit_error(&cx.sess.parse_sess, err, token_lit, e.span));
                     }
                     _ => missing_literal.push(e.span),
                 }
@@ -65,8 +62,8 @@ pub fn expand_concat(
             ast::ExprKind::IncludedBytes(..) => {
                 cx.dcx().emit_err(errors::ConcatBytestr { span: e.span });
             }
-            ast::ExprKind::Err => {
-                has_errors = true;
+            ast::ExprKind::Err(guarantee) => {
+                guar = Some(guarantee);
             }
             ast::ExprKind::Dummy => cx.dcx().span_bug(e.span, "concatenating `ExprKind::Dummy`"),
             _ => {
@@ -76,10 +73,10 @@ pub fn expand_concat(
     }
 
     if !missing_literal.is_empty() {
-        cx.dcx().emit_err(errors::ConcatMissingLiteral { spans: missing_literal });
-        return DummyResult::any(sp);
-    } else if has_errors {
-        return DummyResult::any(sp);
+        let guar = cx.dcx().emit_err(errors::ConcatMissingLiteral { spans: missing_literal });
+        return DummyResult::any(sp, guar);
+    } else if let Some(guar) = guar {
+        return DummyResult::any(sp, guar);
     }
     let sp = cx.with_def_site_ctxt(sp);
     base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&accumulator)))
diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs
index 8ce004586ce..32e17112d8c 100644
--- a/compiler/rustc_builtin_macros/src/concat_bytes.rs
+++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs
@@ -2,7 +2,7 @@ use rustc_ast as ast;
 use rustc_ast::{ptr::P, tokenstream::TokenStream};
 use rustc_expand::base::{self, DummyResult};
 use rustc_session::errors::report_lit_error;
-use rustc_span::Span;
+use rustc_span::{ErrorGuaranteed, Span};
 
 use crate::errors;
 
@@ -12,7 +12,7 @@ fn invalid_type_err(
     token_lit: ast::token::Lit,
     span: Span,
     is_nested: bool,
-) {
+) -> ErrorGuaranteed {
     use errors::{
         ConcatBytesInvalid, ConcatBytesInvalidSuggestion, ConcatBytesNonU8, ConcatBytesOob,
     };
@@ -22,12 +22,12 @@ fn invalid_type_err(
         Ok(ast::LitKind::CStr(_, _)) => {
             // Avoid ambiguity in handling of terminal `NUL` by refusing to
             // concatenate C string literals as bytes.
-            dcx.emit_err(errors::ConcatCStrLit { span: span });
+            dcx.emit_err(errors::ConcatCStrLit { span })
         }
         Ok(ast::LitKind::Char(_)) => {
             let sugg =
                 snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet });
-            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg });
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg })
         }
         Ok(ast::LitKind::Str(_, _)) => {
             // suggestion would be invalid if we are nested
@@ -36,86 +36,79 @@ fn invalid_type_err(
             } else {
                 None
             };
-            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg });
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg })
         }
         Ok(ast::LitKind::Float(_, _)) => {
-            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None });
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None })
         }
         Ok(ast::LitKind::Bool(_)) => {
-            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "boolean", sugg: None });
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "boolean", sugg: None })
         }
-        Ok(ast::LitKind::Err(_)) => {}
         Ok(ast::LitKind::Int(_, _)) if !is_nested => {
             let sugg =
                 snippet.map(|snippet| ConcatBytesInvalidSuggestion::IntLit { span: span, snippet });
-            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg });
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg })
         }
         Ok(ast::LitKind::Int(
             val,
             ast::LitIntType::Unsuffixed | ast::LitIntType::Unsigned(ast::UintTy::U8),
         )) => {
             assert!(val.get() > u8::MAX.into()); // must be an error
-            dcx.emit_err(ConcatBytesOob { span });
-        }
-        Ok(ast::LitKind::Int(_, _)) => {
-            dcx.emit_err(ConcatBytesNonU8 { span });
+            dcx.emit_err(ConcatBytesOob { span })
         }
+        Ok(ast::LitKind::Int(_, _)) => dcx.emit_err(ConcatBytesNonU8 { span }),
         Ok(ast::LitKind::ByteStr(..) | ast::LitKind::Byte(_)) => unreachable!(),
-        Err(err) => {
-            report_lit_error(&cx.sess.parse_sess, err, token_lit, span);
-        }
+        Ok(ast::LitKind::Err(guar)) => guar,
+        Err(err) => report_lit_error(&cx.sess.parse_sess, err, token_lit, span),
     }
 }
 
+/// Returns `expr` as a *single* byte literal if applicable.
+///
+/// Otherwise, returns `None`, and either pushes the `expr`'s span to `missing_literals` or
+/// updates `guar` accordingly.
 fn handle_array_element(
     cx: &mut base::ExtCtxt<'_>,
-    has_errors: &mut bool,
+    guar: &mut Option<ErrorGuaranteed>,
     missing_literals: &mut Vec<rustc_span::Span>,
     expr: &P<rustc_ast::Expr>,
 ) -> Option<u8> {
     let dcx = cx.dcx();
-    match expr.kind {
-        ast::ExprKind::Array(_) | ast::ExprKind::Repeat(_, _) => {
-            if !*has_errors {
-                dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false });
-            }
-            *has_errors = true;
-            None
-        }
-        ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
-            Ok(ast::LitKind::Int(
-                val,
-                ast::LitIntType::Unsuffixed | ast::LitIntType::Unsigned(ast::UintTy::U8),
-            )) if val.get() <= u8::MAX.into() => Some(val.get() as u8),
 
-            Ok(ast::LitKind::Byte(val)) => Some(val),
-            Ok(ast::LitKind::ByteStr(..)) => {
-                if !*has_errors {
-                    dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: true });
+    match expr.kind {
+        ast::ExprKind::Lit(token_lit) => {
+            match ast::LitKind::from_token_lit(token_lit) {
+                Ok(ast::LitKind::Int(
+                    val,
+                    ast::LitIntType::Unsuffixed | ast::LitIntType::Unsigned(ast::UintTy::U8),
+                )) if let Ok(val) = u8::try_from(val.get()) => {
+                    return Some(val);
                 }
-                *has_errors = true;
-                None
-            }
-            _ => {
-                if !*has_errors {
-                    invalid_type_err(cx, token_lit, expr.span, true);
+                Ok(ast::LitKind::Byte(val)) => return Some(val),
+                Ok(ast::LitKind::ByteStr(..)) => {
+                    guar.get_or_insert_with(|| {
+                        dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: true })
+                    });
                 }
-                *has_errors = true;
-                None
-            }
-        },
-        ast::ExprKind::IncludedBytes(..) => {
-            if !*has_errors {
-                dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false });
-            }
-            *has_errors = true;
-            None
+                _ => {
+                    guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, expr.span, true));
+                }
+            };
         }
-        _ => {
-            missing_literals.push(expr.span);
-            None
+        ast::ExprKind::Array(_) | ast::ExprKind::Repeat(_, _) => {
+            guar.get_or_insert_with(|| {
+                dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false })
+            });
+        }
+        ast::ExprKind::IncludedBytes(..) => {
+            guar.get_or_insert_with(|| {
+                dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false })
+            });
         }
+        _ => missing_literals.push(expr.span),
     }
+
+    None
 }
 
 pub fn expand_concat_bytes(
@@ -123,18 +116,19 @@ pub fn expand_concat_bytes(
     sp: rustc_span::Span,
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'static> {
-    let Some(es) = base::get_exprs_from_tts(cx, tts) else {
-        return DummyResult::any(sp);
+    let es = match base::get_exprs_from_tts(cx, tts) {
+        Ok(es) => es,
+        Err(guar) => return DummyResult::any(sp, guar),
     };
     let mut accumulator = Vec::new();
     let mut missing_literals = vec![];
-    let mut has_errors = false;
+    let mut guar = None;
     for e in es {
         match &e.kind {
             ast::ExprKind::Array(exprs) => {
                 for expr in exprs {
                     if let Some(elem) =
-                        handle_array_element(cx, &mut has_errors, &mut missing_literals, expr)
+                        handle_array_element(cx, &mut guar, &mut missing_literals, expr)
                     {
                         accumulator.push(elem);
                     }
@@ -146,14 +140,16 @@ pub fn expand_concat_bytes(
                         ast::LitKind::from_token_lit(token_lit)
                 {
                     if let Some(elem) =
-                        handle_array_element(cx, &mut has_errors, &mut missing_literals, expr)
+                        handle_array_element(cx, &mut guar, &mut missing_literals, expr)
                     {
                         for _ in 0..count_val.get() {
                             accumulator.push(elem);
                         }
                     }
                 } else {
-                    cx.dcx().emit_err(errors::ConcatBytesBadRepeat { span: count.value.span });
+                    guar = Some(
+                        cx.dcx().emit_err(errors::ConcatBytesBadRepeat { span: count.value.span }),
+                    );
                 }
             }
             &ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
@@ -164,17 +160,14 @@ pub fn expand_concat_bytes(
                     accumulator.extend_from_slice(bytes);
                 }
                 _ => {
-                    if !has_errors {
-                        invalid_type_err(cx, token_lit, e.span, false);
-                    }
-                    has_errors = true;
+                    guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, e.span, false));
                 }
             },
             ast::ExprKind::IncludedBytes(bytes) => {
                 accumulator.extend_from_slice(bytes);
             }
-            ast::ExprKind::Err => {
-                has_errors = true;
+            ast::ExprKind::Err(guarantee) => {
+                guar = Some(*guarantee);
             }
             ast::ExprKind::Dummy => cx.dcx().span_bug(e.span, "concatenating `ExprKind::Dummy`"),
             _ => {
@@ -183,10 +176,10 @@ pub fn expand_concat_bytes(
         }
     }
     if !missing_literals.is_empty() {
-        cx.dcx().emit_err(errors::ConcatBytesMissingLiteral { spans: missing_literals });
-        return base::MacEager::expr(DummyResult::raw_expr(sp, true));
-    } else if has_errors {
-        return base::MacEager::expr(DummyResult::raw_expr(sp, true));
+        let guar = cx.dcx().emit_err(errors::ConcatBytesMissingLiteral { spans: missing_literals });
+        return base::MacEager::expr(DummyResult::raw_expr(sp, Some(guar)));
+    } else if let Some(guar) = guar {
+        return base::MacEager::expr(DummyResult::raw_expr(sp, Some(guar)));
     }
     let sp = cx.with_def_site_ctxt(sp);
     base::MacEager::expr(cx.expr_byte_str(sp, accumulator))
diff --git a/compiler/rustc_builtin_macros/src/concat_idents.rs b/compiler/rustc_builtin_macros/src/concat_idents.rs
index 17fd3901cc6..29d3f6164aa 100644
--- a/compiler/rustc_builtin_macros/src/concat_idents.rs
+++ b/compiler/rustc_builtin_macros/src/concat_idents.rs
@@ -14,8 +14,8 @@ pub fn expand_concat_idents<'cx>(
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'cx> {
     if tts.is_empty() {
-        cx.dcx().emit_err(errors::ConcatIdentsMissingArgs { span: sp });
-        return DummyResult::any(sp);
+        let guar = cx.dcx().emit_err(errors::ConcatIdentsMissingArgs { span: sp });
+        return DummyResult::any(sp, guar);
     }
 
     let mut res_str = String::new();
@@ -24,8 +24,8 @@ pub fn expand_concat_idents<'cx>(
             match e {
                 TokenTree::Token(Token { kind: token::Comma, .. }, _) => {}
                 _ => {
-                    cx.dcx().emit_err(errors::ConcatIdentsMissingComma { span: sp });
-                    return DummyResult::any(sp);
+                    let guar = cx.dcx().emit_err(errors::ConcatIdentsMissingComma { span: sp });
+                    return DummyResult::any(sp, guar);
                 }
             }
         } else {
@@ -36,8 +36,8 @@ pub fn expand_concat_idents<'cx>(
                 }
             }
 
-            cx.dcx().emit_err(errors::ConcatIdentsIdentArgs { span: sp });
-            return DummyResult::any(sp);
+            let guar = cx.dcx().emit_err(errors::ConcatIdentsIdentArgs { span: sp });
+            return DummyResult::any(sp, guar);
         }
     }
 
diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs
index 0bd2d423a29..386d4a54b65 100644
--- a/compiler/rustc_builtin_macros/src/deriving/default.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/default.rs
@@ -6,7 +6,7 @@ use rustc_ast::{attr, walk_list, EnumDef, VariantData};
 use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
 use rustc_span::symbol::Ident;
 use rustc_span::symbol::{kw, sym};
-use rustc_span::Span;
+use rustc_span::{ErrorGuaranteed, Span};
 use smallvec::SmallVec;
 use thin_vec::{thin_vec, ThinVec};
 
@@ -83,16 +83,19 @@ fn default_enum_substructure(
     trait_span: Span,
     enum_def: &EnumDef,
 ) -> BlockOrExpr {
-    let expr = if let Ok(default_variant) = extract_default_variant(cx, enum_def, trait_span)
-        && let Ok(_) = validate_default_attribute(cx, default_variant)
-    {
-        // We now know there is exactly one unit variant with exactly one `#[default]` attribute.
-        cx.expr_path(cx.path(
-            default_variant.span,
-            vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident],
-        ))
-    } else {
-        DummyResult::raw_expr(trait_span, true)
+    let expr = match try {
+        let default_variant = extract_default_variant(cx, enum_def, trait_span)?;
+        validate_default_attribute(cx, default_variant)?;
+        default_variant
+    } {
+        Ok(default_variant) => {
+            // We now know there is exactly one unit variant with exactly one `#[default]` attribute.
+            cx.expr_path(cx.path(
+                default_variant.span,
+                vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident],
+            ))
+        }
+        Err(guar) => DummyResult::raw_expr(trait_span, Some(guar)),
     };
     BlockOrExpr::new_expr(expr)
 }
@@ -101,7 +104,7 @@ fn extract_default_variant<'a>(
     cx: &mut ExtCtxt<'_>,
     enum_def: &'a EnumDef,
     trait_span: Span,
-) -> Result<&'a rustc_ast::Variant, ()> {
+) -> Result<&'a rustc_ast::Variant, ErrorGuaranteed> {
     let default_variants: SmallVec<[_; 1]> = enum_def
         .variants
         .iter()
@@ -120,9 +123,9 @@ fn extract_default_variant<'a>(
             let suggs = possible_defaults
                 .map(|v| errors::NoDefaultVariantSugg { span: v.span, ident: v.ident })
                 .collect();
-            cx.dcx().emit_err(errors::NoDefaultVariant { span: trait_span, suggs });
+            let guar = cx.dcx().emit_err(errors::NoDefaultVariant { span: trait_span, suggs });
 
-            return Err(());
+            return Err(guar);
         }
         [first, rest @ ..] => {
             let suggs = default_variants
@@ -140,28 +143,28 @@ fn extract_default_variant<'a>(
                         .then_some(errors::MultipleDefaultsSugg { spans, ident: variant.ident })
                 })
                 .collect();
-            cx.dcx().emit_err(errors::MultipleDefaults {
+            let guar = cx.dcx().emit_err(errors::MultipleDefaults {
                 span: trait_span,
                 first: first.span,
                 additional: rest.iter().map(|v| v.span).collect(),
                 suggs,
             });
-            return Err(());
+            return Err(guar);
         }
     };
 
     if !matches!(variant.data, VariantData::Unit(..)) {
-        cx.dcx().emit_err(errors::NonUnitDefault { span: variant.ident.span });
-        return Err(());
+        let guar = cx.dcx().emit_err(errors::NonUnitDefault { span: variant.ident.span });
+        return Err(guar);
     }
 
     if let Some(non_exhaustive_attr) = attr::find_by_name(&variant.attrs, sym::non_exhaustive) {
-        cx.dcx().emit_err(errors::NonExhaustiveDefault {
+        let guar = cx.dcx().emit_err(errors::NonExhaustiveDefault {
             span: variant.ident.span,
             non_exhaustive: non_exhaustive_attr.span,
         });
 
-        return Err(());
+        return Err(guar);
     }
 
     Ok(variant)
@@ -170,7 +173,7 @@ fn extract_default_variant<'a>(
 fn validate_default_attribute(
     cx: &mut ExtCtxt<'_>,
     default_variant: &rustc_ast::Variant,
-) -> Result<(), ()> {
+) -> Result<(), ErrorGuaranteed> {
     let attrs: SmallVec<[_; 1]> =
         attr::filter_by_name(&default_variant.attrs, kw::Default).collect();
 
@@ -183,7 +186,7 @@ fn validate_default_attribute(
             let sugg = errors::MultipleDefaultAttrsSugg {
                 spans: rest.iter().map(|attr| attr.span).collect(),
             };
-            cx.dcx().emit_err(errors::MultipleDefaultAttrs {
+            let guar = cx.dcx().emit_err(errors::MultipleDefaultAttrs {
                 span: default_variant.ident.span,
                 first: first.span,
                 first_rest: rest[0].span,
@@ -192,13 +195,13 @@ fn validate_default_attribute(
                 sugg,
             });
 
-            return Err(());
+            return Err(guar);
         }
     };
     if !attr.is_word() {
-        cx.dcx().emit_err(errors::DefaultHasArg { span: attr.span });
+        let guar = cx.dcx().emit_err(errors::DefaultHasArg { span: attr.span });
 
-        return Err(());
+        return Err(guar);
     }
     Ok(())
 }
diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs
index a0fd0e3f9be..67ace546ad0 100644
--- a/compiler/rustc_builtin_macros/src/env.rs
+++ b/compiler/rustc_builtin_macros/src/env.rs
@@ -28,8 +28,9 @@ pub fn expand_option_env<'cx>(
     sp: Span,
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'cx> {
-    let Some(var) = get_single_str_from_tts(cx, sp, tts, "option_env!") else {
-        return DummyResult::any(sp);
+    let var = match get_single_str_from_tts(cx, sp, tts, "option_env!") {
+        Ok(var) => var,
+        Err(guar) => return DummyResult::any(sp, guar),
     };
 
     let sp = cx.with_def_site_ctxt(sp);
@@ -65,24 +66,25 @@ pub fn expand_env<'cx>(
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'cx> {
     let mut exprs = match get_exprs_from_tts(cx, tts) {
-        Some(exprs) if exprs.is_empty() || exprs.len() > 2 => {
-            cx.dcx().emit_err(errors::EnvTakesArgs { span: sp });
-            return DummyResult::any(sp);
+        Ok(exprs) if exprs.is_empty() || exprs.len() > 2 => {
+            let guar = cx.dcx().emit_err(errors::EnvTakesArgs { span: sp });
+            return DummyResult::any(sp, guar);
         }
-        None => return DummyResult::any(sp),
-        Some(exprs) => exprs.into_iter(),
+        Err(guar) => return DummyResult::any(sp, guar),
+        Ok(exprs) => exprs.into_iter(),
     };
 
     let var_expr = exprs.next().unwrap();
-    let Some((var, _)) = expr_to_string(cx, var_expr.clone(), "expected string literal") else {
-        return DummyResult::any(sp);
+    let var = match expr_to_string(cx, var_expr.clone(), "expected string literal") {
+        Ok((var, _)) => var,
+        Err(guar) => return DummyResult::any(sp, guar),
     };
 
     let custom_msg = match exprs.next() {
         None => None,
         Some(second) => match expr_to_string(cx, second, "expected string literal") {
-            None => return DummyResult::any(sp),
-            Some((s, _)) => Some(s),
+            Ok((s, _)) => Some(s),
+            Err(guar) => return DummyResult::any(sp, guar),
         },
     };
 
@@ -100,23 +102,23 @@ pub fn expand_env<'cx>(
                 unreachable!("`expr_to_string` ensures this is a string lit")
             };
 
-            if let Some(msg_from_user) = custom_msg {
-                cx.dcx().emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user });
+            let guar = if let Some(msg_from_user) = custom_msg {
+                cx.dcx().emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user })
             } else if is_cargo_env_var(var.as_str()) {
                 cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVar {
                     span,
                     var: *symbol,
                     var_expr: var_expr.ast_deref(),
-                });
+                })
             } else {
                 cx.dcx().emit_err(errors::EnvNotDefined::CustomEnvVar {
                     span,
                     var: *symbol,
                     var_expr: var_expr.ast_deref(),
-                });
-            }
+                })
+            };
 
-            return DummyResult::any(sp);
+            return DummyResult::any(sp, guar);
         }
         Some(value) => cx.expr_str(span, value),
     };
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index 3366378d38d..06c2b617706 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -13,7 +13,7 @@ use rustc_expand::base::{self, *};
 use rustc_parse::parser::Recovered;
 use rustc_parse_format as parse;
 use rustc_span::symbol::{Ident, Symbol};
-use rustc_span::{BytePos, InnerSpan, Span};
+use rustc_span::{BytePos, ErrorGuaranteed, InnerSpan, Span};
 
 use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
 use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
@@ -160,7 +160,7 @@ fn make_format_args(
     ecx: &mut ExtCtxt<'_>,
     input: MacroInput,
     append_newline: bool,
-) -> Result<FormatArgs, ()> {
+) -> Result<FormatArgs, ErrorGuaranteed> {
     let msg = "format argument must be a string literal";
     let unexpanded_fmt_span = input.fmtstr.span;
 
@@ -173,38 +173,41 @@ fn make_format_args(
         }
         Ok(fmt) => fmt,
         Err(err) => {
-            if let Some((mut err, suggested)) = err {
-                if !suggested {
-                    if let ExprKind::Block(block, None) = &efmt.kind
-                        && block.stmts.len() == 1
-                        && let StmtKind::Expr(expr) = &block.stmts[0].kind
-                        && let ExprKind::Path(None, path) = &expr.kind
-                        && path.is_potential_trivial_const_arg()
-                    {
-                        err.multipart_suggestion(
-                            "quote your inlined format argument to use as string literal",
-                            vec![
-                                (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()),
-                                (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()),
-                            ],
-                            Applicability::MaybeIncorrect,
-                        );
-                    } else {
-                        let sugg_fmt = match args.explicit_args().len() {
-                            0 => "{}".to_string(),
-                            _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())),
-                        };
-                        err.span_suggestion(
-                            unexpanded_fmt_span.shrink_to_lo(),
-                            "you might be missing a string literal to format with",
-                            format!("\"{sugg_fmt}\", "),
-                            Applicability::MaybeIncorrect,
-                        );
+            let guar = match err {
+                Ok((mut err, suggested)) => {
+                    if !suggested {
+                        if let ExprKind::Block(block, None) = &efmt.kind
+                            && block.stmts.len() == 1
+                            && let StmtKind::Expr(expr) = &block.stmts[0].kind
+                            && let ExprKind::Path(None, path) = &expr.kind
+                            && path.is_potential_trivial_const_arg()
+                        {
+                            err.multipart_suggestion(
+                                "quote your inlined format argument to use as string literal",
+                                vec![
+                                    (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()),
+                                    (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()),
+                                ],
+                                Applicability::MaybeIncorrect,
+                            );
+                        } else {
+                            let sugg_fmt = match args.explicit_args().len() {
+                                0 => "{}".to_string(),
+                                _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())),
+                            };
+                            err.span_suggestion(
+                                unexpanded_fmt_span.shrink_to_lo(),
+                                "you might be missing a string literal to format with",
+                                format!("\"{sugg_fmt}\", "),
+                                Applicability::MaybeIncorrect,
+                            );
+                        }
                     }
+                    err.emit()
                 }
-                err.emit();
-            }
-            return Err(());
+                Err(guar) => guar,
+            };
+            return Err(guar);
         }
     };
 
@@ -293,8 +296,8 @@ fn make_format_args(
                 }
             }
         }
-        ecx.dcx().emit_err(e);
-        return Err(());
+        let guar = ecx.dcx().emit_err(e);
+        return Err(guar);
     }
 
     let to_span = |inner_span: rustc_parse_format::InnerSpan| {
@@ -353,9 +356,9 @@ fn make_format_args(
                     } else {
                         // For the moment capturing variables from format strings expanded from macros is
                         // disabled (see RFC #2795)
-                        ecx.dcx().emit_err(errors::FormatNoArgNamed { span, name });
+                        let guar = ecx.dcx().emit_err(errors::FormatNoArgNamed { span, name });
                         unnamed_arg_after_named_arg = true;
-                        DummyResult::raw_expr(span, true)
+                        DummyResult::raw_expr(span, Some(guar))
                     };
                     Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
                 }
@@ -972,16 +975,13 @@ fn expand_format_args_impl<'cx>(
 ) -> Box<dyn base::MacResult + 'cx> {
     sp = ecx.with_def_site_ctxt(sp);
     match parse_args(ecx, sp, tts) {
-        Ok(input) => {
-            if let Ok(format_args) = make_format_args(ecx, input, nl) {
-                MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args))))
-            } else {
-                MacEager::expr(DummyResult::raw_expr(sp, true))
-            }
-        }
+        Ok(input) => match make_format_args(ecx, input, nl) {
+            Ok(format_args) => MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args)))),
+            Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))),
+        },
         Err(err) => {
-            err.emit();
-            DummyResult::any(sp)
+            let guar = err.emit();
+            DummyResult::any(sp, guar)
         }
     }
 }
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
index d30ccab2394..f344dbcd10c 100644
--- a/compiler/rustc_builtin_macros/src/lib.rs
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -15,6 +15,7 @@
 #![feature(lint_reasons)]
 #![feature(proc_macro_internals)]
 #![feature(proc_macro_quote)]
+#![feature(try_blocks)]
 
 extern crate proc_macro;
 
diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs
index 43d13569d1e..3860057e1d4 100644
--- a/compiler/rustc_builtin_macros/src/source_util.rs
+++ b/compiler/rustc_builtin_macros/src/source_util.rs
@@ -103,15 +103,16 @@ pub fn expand_include<'cx>(
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'cx> {
     let sp = cx.with_def_site_ctxt(sp);
-    let Some(file) = get_single_str_from_tts(cx, sp, tts, "include!") else {
-        return DummyResult::any(sp);
+    let file = match get_single_str_from_tts(cx, sp, tts, "include!") {
+        Ok(file) => file,
+        Err(guar) => return DummyResult::any(sp, guar),
     };
     // The file will be added to the code map by the parser
     let file = match resolve_path(&cx.sess, file.as_str(), sp) {
         Ok(f) => f,
         Err(err) => {
-            err.emit();
-            return DummyResult::any(sp);
+            let guar = err.emit();
+            return DummyResult::any(sp, guar);
         }
     };
     let p = new_parser_from_file(cx.parse_sess(), &file, Some(sp));
@@ -130,7 +131,7 @@ pub fn expand_include<'cx>(
     }
     impl<'a> base::MacResult for ExpandResult<'a> {
         fn make_expr(mut self: Box<ExpandResult<'a>>) -> Option<P<ast::Expr>> {
-            let r = base::parse_expr(&mut self.p)?;
+            let expr = base::parse_expr(&mut self.p).ok()?;
             if self.p.token != token::Eof {
                 self.p.sess.buffer_lint(
                     INCOMPLETE_INCLUDE,
@@ -139,7 +140,7 @@ pub fn expand_include<'cx>(
                     "include macro expected single expression in source",
                 );
             }
-            Some(r)
+            Some(expr)
         }
 
         fn make_items(mut self: Box<ExpandResult<'a>>) -> Option<SmallVec<[P<ast::Item>; 1]>> {
@@ -176,14 +177,15 @@ pub fn expand_include_str(
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'static> {
     let sp = cx.with_def_site_ctxt(sp);
-    let Some(file) = get_single_str_from_tts(cx, sp, tts, "include_str!") else {
-        return DummyResult::any(sp);
+    let file = match get_single_str_from_tts(cx, sp, tts, "include_str!") {
+        Ok(file) => file,
+        Err(guar) => return DummyResult::any(sp, guar),
     };
     let file = match resolve_path(&cx.sess, file.as_str(), sp) {
         Ok(f) => f,
         Err(err) => {
-            err.emit();
-            return DummyResult::any(sp);
+            let guar = err.emit();
+            return DummyResult::any(sp, guar);
         }
     };
     match cx.source_map().load_binary_file(&file) {
@@ -193,13 +195,13 @@ pub fn expand_include_str(
                 base::MacEager::expr(cx.expr_str(sp, interned_src))
             }
             Err(_) => {
-                cx.dcx().span_err(sp, format!("{} wasn't a utf-8 file", file.display()));
-                DummyResult::any(sp)
+                let guar = cx.dcx().span_err(sp, format!("{} wasn't a utf-8 file", file.display()));
+                DummyResult::any(sp, guar)
             }
         },
         Err(e) => {
-            cx.dcx().span_err(sp, format!("couldn't read {}: {}", file.display(), e));
-            DummyResult::any(sp)
+            let guar = cx.dcx().span_err(sp, format!("couldn't read {}: {}", file.display(), e));
+            DummyResult::any(sp, guar)
         }
     }
 }
@@ -210,14 +212,15 @@ pub fn expand_include_bytes(
     tts: TokenStream,
 ) -> Box<dyn base::MacResult + 'static> {
     let sp = cx.with_def_site_ctxt(sp);
-    let Some(file) = get_single_str_from_tts(cx, sp, tts, "include_bytes!") else {
-        return DummyResult::any(sp);
+    let file = match get_single_str_from_tts(cx, sp, tts, "include_bytes!") {
+        Ok(file) => file,
+        Err(guar) => return DummyResult::any(sp, guar),
     };
     let file = match resolve_path(&cx.sess, file.as_str(), sp) {
         Ok(f) => f,
         Err(err) => {
-            err.emit();
-            return DummyResult::any(sp);
+            let guar = err.emit();
+            return DummyResult::any(sp, guar);
         }
     };
     match cx.source_map().load_binary_file(&file) {
@@ -226,8 +229,8 @@ pub fn expand_include_bytes(
             base::MacEager::expr(expr)
         }
         Err(e) => {
-            cx.dcx().span_err(sp, format!("couldn't read {}: {}", file.display(), e));
-            DummyResult::any(sp)
+            let guar = cx.dcx().span_err(sp, format!("couldn't read {}: {}", file.display(), e));
+            DummyResult::any(sp, guar)
         }
     }
 }
diff --git a/compiler/rustc_builtin_macros/src/type_ascribe.rs b/compiler/rustc_builtin_macros/src/type_ascribe.rs
index 564797012ae..e8b8fe75338 100644
--- a/compiler/rustc_builtin_macros/src/type_ascribe.rs
+++ b/compiler/rustc_builtin_macros/src/type_ascribe.rs
@@ -13,8 +13,8 @@ pub fn expand_type_ascribe(
     let (expr, ty) = match parse_ascribe(cx, tts) {
         Ok(parsed) => parsed,
         Err(err) => {
-            err.emit();
-            return DummyResult::any(span);
+            let guar = err.emit();
+            return DummyResult::any(span, guar);
         }
     };
 
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 762d6745341..7ece46523db 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -532,7 +532,7 @@ impl MacResult for MacEager {
 /// after hitting errors.
 #[derive(Copy, Clone)]
 pub struct DummyResult {
-    is_error: bool,
+    guar: Option<ErrorGuaranteed>,
     span: Span,
 }
 
@@ -541,20 +541,24 @@ impl DummyResult {
     ///
     /// Use this as a return value after hitting any errors and
     /// calling `span_err`.
-    pub fn any(span: Span) -> Box<dyn MacResult + 'static> {
-        Box::new(DummyResult { is_error: true, span })
+    pub fn any(span: Span, guar: ErrorGuaranteed) -> Box<dyn MacResult + 'static> {
+        Box::new(DummyResult { guar: Some(guar), span })
     }
 
     /// Same as `any`, but must be a valid fragment, not error.
     pub fn any_valid(span: Span) -> Box<dyn MacResult + 'static> {
-        Box::new(DummyResult { is_error: false, span })
+        Box::new(DummyResult { guar: None, span })
     }
 
     /// A plain dummy expression.
-    pub fn raw_expr(sp: Span, is_error: bool) -> P<ast::Expr> {
+    pub fn raw_expr(sp: Span, guar: Option<ErrorGuaranteed>) -> P<ast::Expr> {
         P(ast::Expr {
             id: ast::DUMMY_NODE_ID,
-            kind: if is_error { ast::ExprKind::Err } else { ast::ExprKind::Tup(ThinVec::new()) },
+            kind: if let Some(guar) = guar {
+                ast::ExprKind::Err(guar)
+            } else {
+                ast::ExprKind::Tup(ThinVec::new())
+            },
             span: sp,
             attrs: ast::AttrVec::new(),
             tokens: None,
@@ -582,7 +586,7 @@ impl DummyResult {
 
 impl MacResult for DummyResult {
     fn make_expr(self: Box<DummyResult>) -> Option<P<ast::Expr>> {
-        Some(DummyResult::raw_expr(self.span, self.is_error))
+        Some(DummyResult::raw_expr(self.span, self.guar))
     }
 
     fn make_pat(self: Box<DummyResult>) -> Option<P<ast::Pat>> {
@@ -608,7 +612,7 @@ impl MacResult for DummyResult {
     fn make_stmts(self: Box<DummyResult>) -> Option<SmallVec<[ast::Stmt; 1]>> {
         Some(smallvec![ast::Stmt {
             id: ast::DUMMY_NODE_ID,
-            kind: ast::StmtKind::Expr(DummyResult::raw_expr(self.span, self.is_error)),
+            kind: ast::StmtKind::Expr(DummyResult::raw_expr(self.span, self.guar)),
             span: self.span,
         }])
     }
@@ -884,17 +888,19 @@ impl SyntaxExtension {
         }
     }
 
+    /// A dummy bang macro `foo!()`.
     pub fn dummy_bang(edition: Edition) -> SyntaxExtension {
         fn expander<'cx>(
-            _: &'cx mut ExtCtxt<'_>,
+            cx: &'cx mut ExtCtxt<'_>,
             span: Span,
             _: TokenStream,
         ) -> Box<dyn MacResult + 'cx> {
-            DummyResult::any(span)
+            DummyResult::any(span, cx.dcx().span_delayed_bug(span, "expanded a dummy bang macro"))
         }
         SyntaxExtension::default(SyntaxExtensionKind::LegacyBang(Box::new(expander)), edition)
     }
 
+    /// A dummy derive macro `#[derive(Foo)]`.
     pub fn dummy_derive(edition: Edition) -> SyntaxExtension {
         fn expander(
             _: &mut ExtCtxt<'_>,
@@ -1066,7 +1072,7 @@ pub struct ExtCtxt<'a> {
     pub sess: &'a Session,
     pub ecfg: expand::ExpansionConfig<'a>,
     pub num_standard_library_imports: usize,
-    pub reduced_recursion_limit: Option<Limit>,
+    pub reduced_recursion_limit: Option<(Limit, ErrorGuaranteed)>,
     pub root_path: PathBuf,
     pub resolver: &'a mut dyn ResolverExpand,
     pub current_expansion: ExpansionData,
@@ -1244,7 +1250,7 @@ pub fn resolve_path(
 /// Extracts a string literal from the macro expanded version of `expr`,
 /// returning a diagnostic error of `err_msg` if `expr` is not a string literal.
 /// The returned bool indicates whether an applicable suggestion has already been
-/// added to the diagnostic to avoid emitting multiple suggestions. `Err(None)`
+/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))`
 /// indicates that an ast error was encountered.
 // FIXME(Nilstrieb) Make this function setup translatable
 #[allow(rustc::untranslatable_diagnostic)]
@@ -1252,7 +1258,10 @@ pub fn expr_to_spanned_string<'a>(
     cx: &'a mut ExtCtxt<'_>,
     expr: P<ast::Expr>,
     err_msg: &'static str,
-) -> Result<(Symbol, ast::StrStyle, Span), Option<(DiagnosticBuilder<'a>, bool)>> {
+) -> Result<
+    (Symbol, ast::StrStyle, Span),
+    Result<(DiagnosticBuilder<'a>, bool /* has_suggestions */), ErrorGuaranteed>,
+> {
     // Perform eager expansion on the expression.
     // We want to be able to handle e.g., `concat!("foo", "bar")`.
     let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
@@ -1269,38 +1278,33 @@ pub fn expr_to_spanned_string<'a>(
                     "",
                     Applicability::MaybeIncorrect,
                 );
-                Some((err, true))
-            }
-            Ok(ast::LitKind::Err(_)) => None,
-            Err(err) => {
-                report_lit_error(&cx.sess.parse_sess, err, token_lit, expr.span);
-                None
+                Ok((err, true))
             }
-            _ => Some((cx.dcx().struct_span_err(expr.span, err_msg), false)),
+            Ok(ast::LitKind::Err(guar)) => Err(guar),
+            Err(err) => Err(report_lit_error(&cx.sess.parse_sess, err, token_lit, expr.span)),
+            _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
         },
-        ast::ExprKind::Err => None,
+        ast::ExprKind::Err(guar) => Err(guar),
         ast::ExprKind::Dummy => {
             cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`")
         }
-        _ => Some((cx.dcx().struct_span_err(expr.span, err_msg), false)),
+        _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
     })
 }
 
 /// Extracts a string literal from the macro expanded version of `expr`,
 /// emitting `err_msg` if `expr` is not a string literal. This does not stop
-/// compilation on error, merely emits a non-fatal error and returns `None`.
+/// compilation on error, merely emits a non-fatal error and returns `Err`.
 pub fn expr_to_string(
     cx: &mut ExtCtxt<'_>,
     expr: P<ast::Expr>,
     err_msg: &'static str,
-) -> Option<(Symbol, ast::StrStyle)> {
+) -> Result<(Symbol, ast::StrStyle), ErrorGuaranteed> {
     expr_to_spanned_string(cx, expr, err_msg)
-        .map_err(|err| {
-            err.map(|(err, _)| {
-                err.emit();
-            })
+        .map_err(|err| match err {
+            Ok((err, _)) => err.emit(),
+            Err(guar) => guar,
         })
-        .ok()
         .map(|(symbol, style, _)| (symbol, style))
 }
 
@@ -1314,32 +1318,30 @@ pub fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str
     }
 }
 
-/// Parse an expression. On error, emit it, advancing to `Eof`, and return `None`.
-pub fn parse_expr(p: &mut parser::Parser<'_>) -> Option<P<ast::Expr>> {
-    match p.parse_expr() {
-        Ok(e) => return Some(e),
-        Err(err) => {
-            err.emit();
-        }
-    }
+/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`.
+pub fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> {
+    let guar = match p.parse_expr() {
+        Ok(expr) => return Ok(expr),
+        Err(err) => err.emit(),
+    };
     while p.token != token::Eof {
         p.bump();
     }
-    None
+    Err(guar)
 }
 
 /// Interpreting `tts` as a comma-separated sequence of expressions,
-/// expect exactly one string literal, or emit an error and return `None`.
+/// expect exactly one string literal, or emit an error and return `Err`.
 pub fn get_single_str_from_tts(
     cx: &mut ExtCtxt<'_>,
     span: Span,
     tts: TokenStream,
     name: &str,
-) -> Option<Symbol> {
+) -> Result<Symbol, ErrorGuaranteed> {
     let mut p = cx.new_parser_from_tts(tts);
     if p.token == token::Eof {
-        cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
-        return None;
+        let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
+        return Err(guar);
     }
     let ret = parse_expr(&mut p)?;
     let _ = p.eat(&token::Comma);
@@ -1351,8 +1353,11 @@ pub fn get_single_str_from_tts(
 }
 
 /// Extracts comma-separated expressions from `tts`.
-/// On error, emit it, and return `None`.
-pub fn get_exprs_from_tts(cx: &mut ExtCtxt<'_>, tts: TokenStream) -> Option<Vec<P<ast::Expr>>> {
+/// On error, emit it, and return `Err`.
+pub fn get_exprs_from_tts(
+    cx: &mut ExtCtxt<'_>,
+    tts: TokenStream,
+) -> Result<Vec<P<ast::Expr>>, ErrorGuaranteed> {
     let mut p = cx.new_parser_from_tts(tts);
     let mut es = Vec::new();
     while p.token != token::Eof {
@@ -1367,11 +1372,11 @@ pub fn get_exprs_from_tts(cx: &mut ExtCtxt<'_>, tts: TokenStream) -> Option<Vec<
             continue;
         }
         if p.token != token::Eof {
-            cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
-            return None;
+            let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
+            return Err(guar);
         }
     }
-    Some(es)
+    Ok(es)
 }
 
 pub fn parse_macro_name_and_helper_attrs(
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 2752d3ebd68..9bc7b4bdd1e 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -34,7 +34,7 @@ use rustc_session::lint::BuiltinLintDiagnostics;
 use rustc_session::parse::feature_err;
 use rustc_session::{Limit, Session};
 use rustc_span::symbol::{sym, Ident};
-use rustc_span::{FileName, LocalExpnId, Span};
+use rustc_span::{ErrorGuaranteed, FileName, LocalExpnId, Span};
 
 use smallvec::SmallVec;
 use std::ops::Deref;
@@ -232,8 +232,8 @@ pub enum SupportsMacroExpansion {
 }
 
 impl AstFragmentKind {
-    pub(crate) fn dummy(self, span: Span) -> AstFragment {
-        self.make_from(DummyResult::any(span)).expect("couldn't create a dummy AST fragment")
+    pub(crate) fn dummy(self, span: Span, guar: ErrorGuaranteed) -> AstFragment {
+        self.make_from(DummyResult::any(span, guar)).expect("couldn't create a dummy AST fragment")
     }
 
     pub fn supports_macro_expansion(self) -> SupportsMacroExpansion {
@@ -604,14 +604,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         (fragment, invocations)
     }
 
-    fn error_recursion_limit_reached(&mut self) {
+    fn error_recursion_limit_reached(&mut self) -> ErrorGuaranteed {
         let expn_data = self.cx.current_expansion.id.expn_data();
         let suggested_limit = match self.cx.ecfg.recursion_limit {
             Limit(0) => Limit(2),
             limit => limit * 2,
         };
 
-        self.cx.dcx().emit_err(RecursionLimitReached {
+        let guar = self.cx.dcx().emit_err(RecursionLimitReached {
             span: expn_data.call_site,
             descr: expn_data.kind.descr(),
             suggested_limit,
@@ -619,14 +619,21 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         });
 
         self.cx.trace_macros_diag();
+        guar
     }
 
     /// A macro's expansion does not fit in this fragment kind.
     /// For example, a non-type macro in a type position.
-    fn error_wrong_fragment_kind(&mut self, kind: AstFragmentKind, mac: &ast::MacCall, span: Span) {
-        self.cx.dcx().emit_err(WrongFragmentKind { span, kind: kind.name(), name: &mac.path });
-
+    fn error_wrong_fragment_kind(
+        &mut self,
+        kind: AstFragmentKind,
+        mac: &ast::MacCall,
+        span: Span,
+    ) -> ErrorGuaranteed {
+        let guar =
+            self.cx.dcx().emit_err(WrongFragmentKind { span, kind: kind.name(), name: &mac.path });
         self.cx.trace_macros_diag();
+        guar
     }
 
     fn expand_invoc(
@@ -634,36 +641,41 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
         invoc: Invocation,
         ext: &SyntaxExtensionKind,
     ) -> ExpandResult<AstFragment, Invocation> {
-        let recursion_limit =
-            self.cx.reduced_recursion_limit.unwrap_or(self.cx.ecfg.recursion_limit);
+        let recursion_limit = match self.cx.reduced_recursion_limit {
+            Some((limit, _)) => limit,
+            None => self.cx.ecfg.recursion_limit,
+        };
+
         if !recursion_limit.value_within_limit(self.cx.current_expansion.depth) {
-            if self.cx.reduced_recursion_limit.is_none() {
-                self.error_recursion_limit_reached();
-            }
+            let guar = match self.cx.reduced_recursion_limit {
+                Some((_, guar)) => guar,
+                None => self.error_recursion_limit_reached(),
+            };
 
             // Reduce the recursion limit by half each time it triggers.
-            self.cx.reduced_recursion_limit = Some(recursion_limit / 2);
+            self.cx.reduced_recursion_limit = Some((recursion_limit / 2, guar));
 
-            return ExpandResult::Ready(invoc.fragment_kind.dummy(invoc.span()));
+            return ExpandResult::Ready(invoc.fragment_kind.dummy(invoc.span(), guar));
         }
 
         let (fragment_kind, span) = (invoc.fragment_kind, invoc.span());
         ExpandResult::Ready(match invoc.kind {
             InvocationKind::Bang { mac, .. } => match ext {
                 SyntaxExtensionKind::Bang(expander) => {
-                    let Ok(tok_result) = expander.expand(self.cx, span, mac.args.tokens.clone())
-                    else {
-                        return ExpandResult::Ready(fragment_kind.dummy(span));
-                    };
-                    self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span)
+                    match expander.expand(self.cx, span, mac.args.tokens.clone()) {
+                        Ok(tok_result) => {
+                            self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span)
+                        }
+                        Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
+                    }
                 }
                 SyntaxExtensionKind::LegacyBang(expander) => {
                     let tok_result = expander.expand(self.cx, span, mac.args.tokens.clone());
                     let result = if let Some(result) = fragment_kind.make_from(tok_result) {
                         result
                     } else {
-                        self.error_wrong_fragment_kind(fragment_kind, &mac, span);
-                        fragment_kind.dummy(span)
+                        let guar = self.error_wrong_fragment_kind(fragment_kind, &mac, span);
+                        fragment_kind.dummy(span, guar)
                     };
                     result
                 }
@@ -705,11 +717,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                         self.cx.dcx().emit_err(UnsupportedKeyValue { span });
                     }
                     let inner_tokens = attr_item.args.inner_tokens();
-                    let Ok(tok_result) = expander.expand(self.cx, span, inner_tokens, tokens)
-                    else {
-                        return ExpandResult::Ready(fragment_kind.dummy(span));
-                    };
-                    self.parse_ast_fragment(tok_result, fragment_kind, &attr_item.path, span)
+                    match expander.expand(self.cx, span, inner_tokens, tokens) {
+                        Ok(tok_result) => self.parse_ast_fragment(
+                            tok_result,
+                            fragment_kind,
+                            &attr_item.path,
+                            span,
+                        ),
+                        Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
+                    }
                 }
                 SyntaxExtensionKind::LegacyAttr(expander) => {
                     match validate_attr::parse_meta(&self.cx.sess.parse_sess, &attr) {
@@ -729,15 +745,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                                 AstFragmentKind::Expr | AstFragmentKind::MethodReceiverExpr
                             ) && items.is_empty()
                             {
-                                self.cx.dcx().emit_err(RemoveExprNotSupported { span });
-                                fragment_kind.dummy(span)
+                                let guar = self.cx.dcx().emit_err(RemoveExprNotSupported { span });
+                                fragment_kind.dummy(span, guar)
                             } else {
                                 fragment_kind.expect_from_annotatables(items)
                             }
                         }
                         Err(err) => {
-                            err.emit();
-                            fragment_kind.dummy(span)
+                            let guar = err.emit();
+                            fragment_kind.dummy(span, guar)
                         }
                     }
                 }
@@ -857,9 +873,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                     err.span(span);
                 }
                 annotate_err_with_kind(&mut err, kind, span);
-                err.emit();
+                let guar = err.emit();
                 self.cx.trace_macros_diag();
-                kind.dummy(span)
+                kind.dummy(span, guar)
             }
         }
     }
diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs
index 25af974d326..5629c5ef5fa 100644
--- a/compiler/rustc_expand/src/mbe/diagnostics.rs
+++ b/compiler/rustc_expand/src/mbe/diagnostics.rs
@@ -11,7 +11,7 @@ use rustc_errors::{Applicability, DiagCtxt, DiagnosticBuilder, DiagnosticMessage
 use rustc_parse::parser::{Parser, Recovery};
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::Ident;
-use rustc_span::Span;
+use rustc_span::{ErrorGuaranteed, Span};
 use std::borrow::Cow;
 
 use super::macro_rules::{parser_from_cx, NoopTracker};
@@ -47,7 +47,7 @@ pub(super) fn failed_to_match_macro<'cx>(
 
     let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
     else {
-        return DummyResult::any(sp);
+        return DummyResult::any(sp, cx.dcx().span_delayed_bug(sp, "failed to match a macro"));
     };
 
     let span = token.span.substitute_dummy(sp);
@@ -106,9 +106,9 @@ pub(super) fn failed_to_match_macro<'cx>(
             }
         }
     }
-    err.emit();
+    let guar = err.emit();
     cx.trace_macros_diag();
-    DummyResult::any(sp)
+    DummyResult::any(sp, guar)
 }
 
 /// The tracker used for the slow error path that collects useful info for diagnostics.
@@ -180,10 +180,10 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
             }
             Error(err_sp, msg) => {
                 let span = err_sp.substitute_dummy(self.root_span);
-                self.cx.dcx().span_err(span, msg.clone());
-                self.result = Some(DummyResult::any(span));
+                let guar = self.cx.dcx().span_err(span, msg.clone());
+                self.result = Some(DummyResult::any(span, guar));
             }
-            ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),
+            ErrorReported(guar) => self.result = Some(DummyResult::any(self.root_span, *guar)),
         }
     }
 
@@ -224,7 +224,7 @@ pub(super) fn emit_frag_parse_err(
     site_span: Span,
     arm_span: Span,
     kind: AstFragmentKind,
-) {
+) -> ErrorGuaranteed {
     // FIXME(davidtwco): avoid depending on the error message text
     if parser.token == token::Eof
         && let DiagnosticMessage::Str(message) = &e.messages[0].0
@@ -282,7 +282,7 @@ pub(super) fn emit_frag_parse_err(
         },
         _ => annotate_err_with_kind(&mut e, kind, site_span),
     };
-    e.emit();
+    e.emit()
 }
 
 pub(crate) fn annotate_err_with_kind(
diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs
index ffc8f782fd3..8174cb03d33 100644
--- a/compiler/rustc_expand/src/mbe/macro_check.rs
+++ b/compiler/rustc_expand/src/mbe/macro_check.rs
@@ -114,7 +114,7 @@ use rustc_errors::{DiagnosticMessage, MultiSpan};
 use rustc_session::lint::builtin::{META_VARIABLE_MISUSE, MISSING_FRAGMENT_SPECIFIER};
 use rustc_session::parse::ParseSess;
 use rustc_span::symbol::kw;
-use rustc_span::{symbol::MacroRulesNormalizedIdent, Span};
+use rustc_span::{symbol::MacroRulesNormalizedIdent, ErrorGuaranteed, Span};
 
 use smallvec::SmallVec;
 
@@ -203,17 +203,17 @@ pub(super) fn check_meta_variables(
     span: Span,
     lhses: &[TokenTree],
     rhses: &[TokenTree],
-) -> bool {
+) -> Result<(), ErrorGuaranteed> {
     if lhses.len() != rhses.len() {
         sess.dcx.span_bug(span, "length mismatch between LHSes and RHSes")
     }
-    let mut valid = true;
+    let mut guar = None;
     for (lhs, rhs) in iter::zip(lhses, rhses) {
         let mut binders = Binders::default();
-        check_binders(sess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut valid);
-        check_occurrences(sess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut valid);
+        check_binders(sess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar);
+        check_occurrences(sess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut guar);
     }
-    valid
+    guar.map_or(Ok(()), Err)
 }
 
 /// Checks `lhs` as part of the LHS of a macro definition, extends `binders` with new binders, and
@@ -226,7 +226,7 @@ pub(super) fn check_meta_variables(
 /// - `macros` is the stack of possible outer macros
 /// - `binders` contains the binders of the LHS
 /// - `ops` is the stack of Kleene operators from the LHS
-/// - `valid` is set in case of errors
+/// - `guar` is set in case of errors
 fn check_binders(
     sess: &ParseSess,
     node_id: NodeId,
@@ -234,7 +234,7 @@ fn check_binders(
     macros: &Stack<'_, MacroState<'_>>,
     binders: &mut Binders,
     ops: &Stack<'_, KleeneToken>,
-    valid: &mut bool,
+    guar: &mut Option<ErrorGuaranteed>,
 ) {
     match *lhs {
         TokenTree::Token(..) => {}
@@ -258,7 +258,7 @@ fn check_binders(
                 binders.insert(name, BinderInfo { span, ops: ops.into() });
             } else {
                 // 3. The meta-variable is bound: This is an occurrence.
-                check_occurrences(sess, node_id, lhs, macros, binders, ops, valid);
+                check_occurrences(sess, node_id, lhs, macros, binders, ops, guar);
             }
         }
         // Similarly, this can only happen when checking a toplevel macro.
@@ -281,8 +281,10 @@ fn check_binders(
             if let Some(prev_info) = get_binder_info(macros, binders, name) {
                 // Duplicate binders at the top-level macro definition are errors. The lint is only
                 // for nested macro definitions.
-                sess.dcx.emit_err(errors::DuplicateMatcherBinding { span, prev: prev_info.span });
-                *valid = false;
+                *guar = Some(
+                    sess.dcx
+                        .emit_err(errors::DuplicateMatcherBinding { span, prev: prev_info.span }),
+                );
             } else {
                 binders.insert(name, BinderInfo { span, ops: ops.into() });
             }
@@ -291,13 +293,13 @@ fn check_binders(
         TokenTree::MetaVarExpr(..) => {}
         TokenTree::Delimited(.., ref del) => {
             for tt in &del.tts {
-                check_binders(sess, node_id, tt, macros, binders, ops, valid);
+                check_binders(sess, node_id, tt, macros, binders, ops, guar);
             }
         }
         TokenTree::Sequence(_, ref seq) => {
             let ops = ops.push(seq.kleene);
             for tt in &seq.tts {
-                check_binders(sess, node_id, tt, macros, binders, &ops, valid);
+                check_binders(sess, node_id, tt, macros, binders, &ops, guar);
             }
         }
     }
@@ -327,7 +329,7 @@ fn get_binder_info<'a>(
 /// - `macros` is the stack of possible outer macros
 /// - `binders` contains the binders of the associated LHS
 /// - `ops` is the stack of Kleene operators from the RHS
-/// - `valid` is set in case of errors
+/// - `guar` is set in case of errors
 fn check_occurrences(
     sess: &ParseSess,
     node_id: NodeId,
@@ -335,7 +337,7 @@ fn check_occurrences(
     macros: &Stack<'_, MacroState<'_>>,
     binders: &Binders,
     ops: &Stack<'_, KleeneToken>,
-    valid: &mut bool,
+    guar: &mut Option<ErrorGuaranteed>,
 ) {
     match *rhs {
         TokenTree::Token(..) => {}
@@ -353,11 +355,11 @@ fn check_occurrences(
             check_ops_is_prefix(sess, node_id, macros, binders, ops, dl.entire(), name);
         }
         TokenTree::Delimited(.., ref del) => {
-            check_nested_occurrences(sess, node_id, &del.tts, macros, binders, ops, valid);
+            check_nested_occurrences(sess, node_id, &del.tts, macros, binders, ops, guar);
         }
         TokenTree::Sequence(_, ref seq) => {
             let ops = ops.push(seq.kleene);
-            check_nested_occurrences(sess, node_id, &seq.tts, macros, binders, &ops, valid);
+            check_nested_occurrences(sess, node_id, &seq.tts, macros, binders, &ops, guar);
         }
     }
 }
@@ -392,7 +394,7 @@ enum NestedMacroState {
 /// - `macros` is the stack of possible outer macros
 /// - `binders` contains the binders of the associated LHS
 /// - `ops` is the stack of Kleene operators from the RHS
-/// - `valid` is set in case of errors
+/// - `guar` is set in case of errors
 fn check_nested_occurrences(
     sess: &ParseSess,
     node_id: NodeId,
@@ -400,7 +402,7 @@ fn check_nested_occurrences(
     macros: &Stack<'_, MacroState<'_>>,
     binders: &Binders,
     ops: &Stack<'_, KleeneToken>,
-    valid: &mut bool,
+    guar: &mut Option<ErrorGuaranteed>,
 ) {
     let mut state = NestedMacroState::Empty;
     let nested_macros = macros.push(MacroState { binders, ops: ops.into() });
@@ -432,7 +434,7 @@ fn check_nested_occurrences(
             (NestedMacroState::MacroRulesNot, &TokenTree::MetaVar(..)) => {
                 state = NestedMacroState::MacroRulesNotName;
                 // We check that the meta-variable is correctly used.
-                check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
+                check_occurrences(sess, node_id, tt, macros, binders, ops, guar);
             }
             (NestedMacroState::MacroRulesNotName, TokenTree::Delimited(.., del))
             | (NestedMacroState::MacroName, TokenTree::Delimited(.., del))
@@ -441,7 +443,7 @@ fn check_nested_occurrences(
                 let macro_rules = state == NestedMacroState::MacroRulesNotName;
                 state = NestedMacroState::Empty;
                 let rest =
-                    check_nested_macro(sess, node_id, macro_rules, &del.tts, &nested_macros, valid);
+                    check_nested_macro(sess, node_id, macro_rules, &del.tts, &nested_macros, guar);
                 // If we did not check the whole macro definition, then check the rest as if outside
                 // the macro definition.
                 check_nested_occurrences(
@@ -451,7 +453,7 @@ fn check_nested_occurrences(
                     macros,
                     binders,
                     ops,
-                    valid,
+                    guar,
                 );
             }
             (
@@ -463,7 +465,7 @@ fn check_nested_occurrences(
             (NestedMacroState::Macro, &TokenTree::MetaVar(..)) => {
                 state = NestedMacroState::MacroName;
                 // We check that the meta-variable is correctly used.
-                check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
+                check_occurrences(sess, node_id, tt, macros, binders, ops, guar);
             }
             (NestedMacroState::MacroName, TokenTree::Delimited(.., del))
                 if del.delim == Delimiter::Parenthesis =>
@@ -477,7 +479,7 @@ fn check_nested_occurrences(
                     &nested_macros,
                     &mut nested_binders,
                     &Stack::Empty,
-                    valid,
+                    guar,
                 );
             }
             (NestedMacroState::MacroNameParen, TokenTree::Delimited(.., del))
@@ -491,12 +493,12 @@ fn check_nested_occurrences(
                     &nested_macros,
                     &nested_binders,
                     &Stack::Empty,
-                    valid,
+                    guar,
                 );
             }
             (_, tt) => {
                 state = NestedMacroState::Empty;
-                check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
+                check_occurrences(sess, node_id, tt, macros, binders, ops, guar);
             }
         }
     }
@@ -515,14 +517,14 @@ fn check_nested_occurrences(
 /// - `macro_rules` specifies whether the macro is `macro_rules`
 /// - `tts` is checked as a list of (LHS) => {RHS}
 /// - `macros` is the stack of outer macros
-/// - `valid` is set in case of errors
+/// - `guar` is set in case of errors
 fn check_nested_macro(
     sess: &ParseSess,
     node_id: NodeId,
     macro_rules: bool,
     tts: &[TokenTree],
     macros: &Stack<'_, MacroState<'_>>,
-    valid: &mut bool,
+    guar: &mut Option<ErrorGuaranteed>,
 ) -> usize {
     let n = tts.len();
     let mut i = 0;
@@ -539,8 +541,8 @@ fn check_nested_macro(
         let lhs = &tts[i];
         let rhs = &tts[i + 2];
         let mut binders = Binders::default();
-        check_binders(sess, node_id, lhs, macros, &mut binders, &Stack::Empty, valid);
-        check_occurrences(sess, node_id, rhs, macros, &binders, &Stack::Empty, valid);
+        check_binders(sess, node_id, lhs, macros, &mut binders, &Stack::Empty, guar);
+        check_occurrences(sess, node_id, rhs, macros, &binders, &Stack::Empty, guar);
         // Since the last semicolon is optional for `macro_rules` macros and decl_macro are not terminated,
         // we increment our checked position by how many token trees we already checked (the 3
         // above) before checking for the separator.
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index bf99e9e6d5c..c11d538048a 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -66,8 +66,10 @@ impl<'a> ParserAnyMacro<'a> {
         let fragment = match parse_ast_fragment(parser, kind) {
             Ok(f) => f,
             Err(err) => {
-                diagnostics::emit_frag_parse_err(err, parser, snapshot, site_span, arm_span, kind);
-                return kind.dummy(site_span);
+                let guar = diagnostics::emit_frag_parse_err(
+                    err, parser, snapshot, site_span, arm_span, kind,
+                );
+                return kind.dummy(site_span, guar);
             }
         };
 
@@ -101,7 +103,6 @@ struct MacroRulesMacroExpander {
     transparency: Transparency,
     lhses: Vec<Vec<MatcherLoc>>,
     rhses: Vec<mbe::TokenTree>,
-    valid: bool,
 }
 
 impl TTMacroExpander for MacroRulesMacroExpander {
@@ -111,9 +112,6 @@ impl TTMacroExpander for MacroRulesMacroExpander {
         sp: Span,
         input: TokenStream,
     ) -> Box<dyn MacResult + 'cx> {
-        if !self.valid {
-            return DummyResult::any(sp);
-        }
         expand_macro(
             cx,
             sp,
@@ -128,12 +126,17 @@ impl TTMacroExpander for MacroRulesMacroExpander {
     }
 }
 
-fn macro_rules_dummy_expander<'cx>(
-    _: &'cx mut ExtCtxt<'_>,
-    span: Span,
-    _: TokenStream,
-) -> Box<dyn MacResult + 'cx> {
-    DummyResult::any(span)
+struct DummyExpander(ErrorGuaranteed);
+
+impl TTMacroExpander for DummyExpander {
+    fn expand<'cx>(
+        &self,
+        _: &'cx mut ExtCtxt<'_>,
+        span: Span,
+        _: TokenStream,
+    ) -> Box<dyn MacResult + 'cx> {
+        DummyResult::any(span, self.0)
+    }
 }
 
 fn trace_macros_note(cx_expansions: &mut FxIndexMap<Span, Vec<String>>, sp: Span, message: String) {
@@ -217,8 +220,8 @@ fn expand_macro<'cx>(
             let tts = match transcribe(cx, &named_matches, rhs, rhs_span, transparency) {
                 Ok(tts) => tts,
                 Err(err) => {
-                    err.emit();
-                    return DummyResult::any(arm_span);
+                    let guar = err.emit();
+                    return DummyResult::any(arm_span, guar);
                 }
             };
 
@@ -249,9 +252,9 @@ fn expand_macro<'cx>(
                 is_local,
             })
         }
-        Err(CanRetry::No(_)) => {
+        Err(CanRetry::No(guar)) => {
             debug!("Will not retry matching as an error was emitted already");
-            DummyResult::any(sp)
+            DummyResult::any(sp, guar)
         }
         Err(CanRetry::Yes) => {
             // Retry and emit a better error.
@@ -371,7 +374,7 @@ pub fn compile_declarative_macro(
             def.id != DUMMY_NODE_ID,
         )
     };
-    let dummy_syn_ext = || (mk_syn_ext(Box::new(macro_rules_dummy_expander)), Vec::new());
+    let dummy_syn_ext = |guar| (mk_syn_ext(Box::new(DummyExpander(guar))), Vec::new());
 
     let dcx = &sess.parse_sess.dcx;
     let lhs_nm = Ident::new(sym::lhs, def.span);
@@ -456,19 +459,20 @@ pub fn compile_declarative_macro(
                 let mut err = sess.dcx().struct_span_err(sp, s);
                 err.span_label(sp, msg);
                 annotate_doc_comment(sess.dcx(), &mut err, sess.source_map(), sp);
-                err.emit();
-                return dummy_syn_ext();
+                let guar = err.emit();
+                return dummy_syn_ext(guar);
             }
             Error(sp, msg) => {
-                sess.dcx().span_err(sp.substitute_dummy(def.span), msg);
-                return dummy_syn_ext();
+                let guar = sess.dcx().span_err(sp.substitute_dummy(def.span), msg);
+                return dummy_syn_ext(guar);
             }
-            ErrorReported(_) => {
-                return dummy_syn_ext();
+            ErrorReported(guar) => {
+                return dummy_syn_ext(guar);
             }
         };
 
-    let mut valid = true;
+    let mut guar = None;
+    let mut check_emission = |ret: Result<(), ErrorGuaranteed>| guar = guar.or(ret.err());
 
     // Extract the arguments:
     let lhses = match &argument_map[&MacroRulesNormalizedIdent::new(lhs_nm)] {
@@ -488,7 +492,7 @@ pub fn compile_declarative_macro(
                     .unwrap();
                     // We don't handle errors here, the driver will abort
                     // after parsing/expansion. we can report every error in every macro this way.
-                    valid &= check_lhs_nt_follows(sess, def, &tt).is_ok();
+                    check_emission(check_lhs_nt_follows(sess, def, &tt));
                     return tt;
                 }
                 sess.dcx().span_bug(def.span, "wrong-structured lhs")
@@ -520,15 +524,21 @@ pub fn compile_declarative_macro(
     };
 
     for rhs in &rhses {
-        valid &= check_rhs(sess, rhs);
+        check_emission(check_rhs(sess, rhs));
     }
 
     // don't abort iteration early, so that errors for multiple lhses can be reported
     for lhs in &lhses {
-        valid &= check_lhs_no_empty_seq(sess, slice::from_ref(lhs));
+        check_emission(check_lhs_no_empty_seq(sess, slice::from_ref(lhs)));
     }
 
-    valid &= macro_check::check_meta_variables(&sess.parse_sess, def.id, def.span, &lhses, &rhses);
+    check_emission(macro_check::check_meta_variables(
+        &sess.parse_sess,
+        def.id,
+        def.span,
+        &lhses,
+        &rhses,
+    ));
 
     let (transparency, transparency_error) = attr::find_transparency(&def.attrs, macro_rules);
     match transparency_error {
@@ -541,11 +551,15 @@ pub fn compile_declarative_macro(
         None => {}
     }
 
+    if let Some(guar) = guar {
+        // To avoid warning noise, only consider the rules of this
+        // macro for the lint, if all rules are valid.
+        return dummy_syn_ext(guar);
+    }
+
     // Compute the spans of the macro rules for unused rule linting.
-    // To avoid warning noise, only consider the rules of this
-    // macro for the lint, if all rules are valid.
     // Also, we are only interested in non-foreign macros.
-    let rule_spans = if valid && def.id != DUMMY_NODE_ID {
+    let rule_spans = if def.id != DUMMY_NODE_ID {
         lhses
             .iter()
             .zip(rhses.iter())
@@ -562,23 +576,19 @@ pub fn compile_declarative_macro(
     };
 
     // Convert the lhses into `MatcherLoc` form, which is better for doing the
-    // actual matching. Unless the matcher is invalid.
-    let lhses = if valid {
-        lhses
-            .iter()
-            .map(|lhs| {
-                // Ignore the delimiters around the matcher.
-                match lhs {
-                    mbe::TokenTree::Delimited(.., delimited) => {
-                        mbe::macro_parser::compute_locs(&delimited.tts)
-                    }
-                    _ => sess.dcx().span_bug(def.span, "malformed macro lhs"),
+    // actual matching.
+    let lhses = lhses
+        .iter()
+        .map(|lhs| {
+            // Ignore the delimiters around the matcher.
+            match lhs {
+                mbe::TokenTree::Delimited(.., delimited) => {
+                    mbe::macro_parser::compute_locs(&delimited.tts)
                 }
-            })
-            .collect()
-    } else {
-        vec![]
-    };
+                _ => sess.dcx().span_bug(def.span, "malformed macro lhs"),
+            }
+        })
+        .collect();
 
     let expander = Box::new(MacroRulesMacroExpander {
         name: def.ident,
@@ -587,7 +597,6 @@ pub fn compile_declarative_macro(
         transparency,
         lhses,
         rhses,
-        valid,
     });
     (mk_syn_ext(expander), rule_spans)
 }
@@ -640,7 +649,7 @@ fn is_empty_token_tree(sess: &Session, seq: &mbe::SequenceRepetition) -> bool {
 
 /// Checks that the lhs contains no repetition which could match an empty token
 /// tree, because then the matcher would hang indefinitely.
-fn check_lhs_no_empty_seq(sess: &Session, tts: &[mbe::TokenTree]) -> bool {
+fn check_lhs_no_empty_seq(sess: &Session, tts: &[mbe::TokenTree]) -> Result<(), ErrorGuaranteed> {
     use mbe::TokenTree;
     for tt in tts {
         match tt {
@@ -648,35 +657,26 @@ fn check_lhs_no_empty_seq(sess: &Session, tts: &[mbe::TokenTree]) -> bool {
             | TokenTree::MetaVar(..)
             | TokenTree::MetaVarDecl(..)
             | TokenTree::MetaVarExpr(..) => (),
-            TokenTree::Delimited(.., del) => {
-                if !check_lhs_no_empty_seq(sess, &del.tts) {
-                    return false;
-                }
-            }
+            TokenTree::Delimited(.., del) => check_lhs_no_empty_seq(sess, &del.tts)?,
             TokenTree::Sequence(span, seq) => {
                 if is_empty_token_tree(sess, seq) {
                     let sp = span.entire();
-                    sess.dcx().span_err(sp, "repetition matches empty token tree");
-                    return false;
-                }
-                if !check_lhs_no_empty_seq(sess, &seq.tts) {
-                    return false;
+                    let guar = sess.dcx().span_err(sp, "repetition matches empty token tree");
+                    return Err(guar);
                 }
+                check_lhs_no_empty_seq(sess, &seq.tts)?
             }
         }
     }
 
-    true
+    Ok(())
 }
 
-fn check_rhs(sess: &Session, rhs: &mbe::TokenTree) -> bool {
+fn check_rhs(sess: &Session, rhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> {
     match *rhs {
-        mbe::TokenTree::Delimited(..) => return true,
-        _ => {
-            sess.dcx().span_err(rhs.span(), "macro rhs must be delimited");
-        }
+        mbe::TokenTree::Delimited(..) => Ok(()),
+        _ => Err(sess.dcx().span_err(rhs.span(), "macro rhs must be delimited")),
     }
-    false
 }
 
 fn check_matcher(
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index 35ee0c53046..3481c66da70 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -758,7 +758,7 @@ trait UnusedDelimLint {
         }
         impl<'ast> Visitor<'ast> for ErrExprVisitor {
             fn visit_expr(&mut self, expr: &'ast ast::Expr) {
-                if let ExprKind::Err = expr.kind {
+                if let ExprKind::Err(_) = expr.kind {
                     self.has_error = true;
                     return;
                 }
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 517e3d82787..cc1f7c8ac7d 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -744,7 +744,8 @@ impl<'a> Parser<'a> {
         Err(err)
     }
 
-    pub(super) fn attr_on_non_tail_expr(&self, expr: &Expr) {
+    /// The user has written `#[attr] expr` which is unsupported. (#106020)
+    pub(super) fn attr_on_non_tail_expr(&self, expr: &Expr) -> ErrorGuaranteed {
         // Missing semicolon typo error.
         let span = self.prev_token.span.shrink_to_hi();
         let mut err = self.dcx().create_err(ExpectedSemi {
@@ -787,6 +788,8 @@ impl<'a> Parser<'a> {
                 ],
                 Applicability::MachineApplicable,
             );
+
+            // Special handling for `#[cfg(...)]` chains
             let mut snapshot = self.create_snapshot_for_diagnostic();
             if let [attr] = &expr.attrs[..]
                 && let ast::AttrKind::Normal(attr_kind) = &attr.kind
@@ -799,7 +802,7 @@ impl<'a> Parser<'a> {
                     Err(inner_err) => {
                         err.cancel();
                         inner_err.cancel();
-                        return;
+                        return self.dcx().span_delayed_bug(expr.span, "not a tail expression");
                     }
                 }
                 && let ast::AttrKind::Normal(next_attr_kind) = next_attr.kind
@@ -812,7 +815,7 @@ impl<'a> Parser<'a> {
                     Err(inner_err) => {
                         err.cancel();
                         inner_err.cancel();
-                        return;
+                        return self.dcx().span_delayed_bug(expr.span, "not a tail expression");
                     }
                 };
                 // We have for sure
@@ -845,7 +848,7 @@ impl<'a> Parser<'a> {
                 );
             }
         }
-        err.emit();
+        err.emit()
     }
 
     fn check_too_many_raw_str_terminators(&mut self, err: &mut DiagnosticBuilder<'_>) -> bool {
@@ -921,10 +924,10 @@ impl<'a> Parser<'a> {
                     // fn foo() -> Foo { Path {
                     //     field: value,
                     // } }
-                    err.delay_as_bug();
+                    let guar = err.delay_as_bug();
                     self.restore_snapshot(snapshot);
                     let mut tail = self.mk_block(
-                        thin_vec![self.mk_stmt_err(expr.span)],
+                        thin_vec![self.mk_stmt_err(expr.span, guar)],
                         s,
                         lo.to(self.prev_token.span),
                     );
@@ -990,7 +993,7 @@ impl<'a> Parser<'a> {
         decl_hi: Span,
     ) -> PResult<'a, P<Expr>> {
         err.span_label(lo.to(decl_hi), "while parsing the body of this closure");
-        match before.kind {
+        let guar = match before.kind {
             token::OpenDelim(Delimiter::Brace)
                 if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) =>
             {
@@ -1004,8 +1007,9 @@ impl<'a> Parser<'a> {
                     ],
                     Applicability::MaybeIncorrect,
                 );
-                err.emit();
+                let guar = err.emit();
                 self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Brace)]);
+                guar
             }
             token::OpenDelim(Delimiter::Parenthesis)
                 if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) =>
@@ -1022,7 +1026,7 @@ impl<'a> Parser<'a> {
                     ],
                     Applicability::MaybeIncorrect,
                 );
-                err.emit();
+                err.emit()
             }
             _ if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) => {
                 // We don't have a heuristic to correctly identify where the block
@@ -1035,8 +1039,8 @@ impl<'a> Parser<'a> {
                 return Err(err);
             }
             _ => return Err(err),
-        }
-        Ok(self.mk_expr_err(lo.to(self.token.span)))
+        };
+        Ok(self.mk_expr_err(lo.to(self.token.span), guar))
     }
 
     /// Eats and discards tokens until one of `kets` is encountered. Respects token trees,
@@ -1214,7 +1218,7 @@ impl<'a> Parser<'a> {
         &mut self,
         mut e: DiagnosticBuilder<'a>,
         expr: &mut P<Expr>,
-    ) -> PResult<'a, ()> {
+    ) -> PResult<'a, ErrorGuaranteed> {
         if let ExprKind::Binary(binop, _, _) = &expr.kind
             && let ast::BinOpKind::Lt = binop.node
             && self.eat(&token::Comma)
@@ -1239,9 +1243,9 @@ impl<'a> Parser<'a> {
                                 // The subsequent expression is valid. Mark
                                 // `expr` as erroneous and emit `e` now, but
                                 // return `Ok` so parsing can continue.
-                                e.emit();
-                                *expr = self.mk_expr_err(expr.span.to(self.prev_token.span));
-                                return Ok(());
+                                let guar = e.emit();
+                                *expr = self.mk_expr_err(expr.span.to(self.prev_token.span), guar);
+                                return Ok(guar);
                             }
                             Err(err) => {
                                 err.cancel();
@@ -1393,7 +1397,8 @@ impl<'a> Parser<'a> {
             outer_op.node,
         );
 
-        let mk_err_expr = |this: &Self, span| Ok(Some(this.mk_expr(span, ExprKind::Err)));
+        let mk_err_expr =
+            |this: &Self, span, guar| Ok(Some(this.mk_expr(span, ExprKind::Err(guar))));
 
         match &inner_op.kind {
             ExprKind::Binary(op, l1, r1) if op.node.is_comparison() => {
@@ -1443,11 +1448,11 @@ impl<'a> Parser<'a> {
                         match self.parse_expr() {
                             Ok(_) => {
                                 // 99% certain that the suggestion is correct, continue parsing.
-                                self.dcx().emit_err(err);
+                                let guar = self.dcx().emit_err(err);
                                 // FIXME: actually check that the two expressions in the binop are
                                 // paths and resynthesize new fn call expression instead of using
                                 // `ExprKind::Err` placeholder.
-                                mk_err_expr(self, inner_op.span.to(self.prev_token.span))
+                                mk_err_expr(self, inner_op.span.to(self.prev_token.span), guar)
                             }
                             Err(expr_err) => {
                                 expr_err.cancel();
@@ -1471,11 +1476,11 @@ impl<'a> Parser<'a> {
                         match self.consume_fn_args() {
                             Err(()) => Err(self.dcx().create_err(err)),
                             Ok(()) => {
-                                self.dcx().emit_err(err);
+                                let guar = self.dcx().emit_err(err);
                                 // FIXME: actually check that the two expressions in the binop are
                                 // paths and resynthesize new fn call expression instead of using
                                 // `ExprKind::Err` placeholder.
-                                mk_err_expr(self, inner_op.span.to(self.prev_token.span))
+                                mk_err_expr(self, inner_op.span.to(self.prev_token.span), guar)
                             }
                         }
                     } else {
@@ -1492,8 +1497,8 @@ impl<'a> Parser<'a> {
                         let recovered = self
                             .attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op);
                         if matches!(recovered, Recovered::Yes) {
-                            self.dcx().emit_err(err);
-                            mk_err_expr(self, inner_op.span.to(self.prev_token.span))
+                            let guar = self.dcx().emit_err(err);
+                            mk_err_expr(self, inner_op.span.to(self.prev_token.span), guar)
                         } else {
                             // These cases cause too many knock-down errors, bail out (#61329).
                             Err(self.dcx().create_err(err))
@@ -1502,9 +1507,9 @@ impl<'a> Parser<'a> {
                 }
                 let recover =
                     self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op);
-                self.dcx().emit_err(err);
+                let guar = self.dcx().emit_err(err);
                 if matches!(recover, Recovered::Yes) {
-                    return mk_err_expr(self, inner_op.span.to(self.prev_token.span));
+                    return mk_err_expr(self, inner_op.span.to(self.prev_token.span), guar);
                 }
             }
             _ => {}
@@ -1925,8 +1930,8 @@ impl<'a> Parser<'a> {
         } else {
             self.recover_await_prefix(await_sp)?
         };
-        let sp = self.error_on_incorrect_await(lo, hi, &expr, is_question);
-        let expr = self.mk_expr(lo.to(sp), ExprKind::Err);
+        let (sp, guar) = self.error_on_incorrect_await(lo, hi, &expr, is_question);
+        let expr = self.mk_expr_err(lo.to(sp), guar);
         self.maybe_recover_from_bad_qpath(expr)
     }
 
@@ -1955,21 +1960,27 @@ impl<'a> Parser<'a> {
         Ok((expr.span, expr, is_question))
     }
 
-    fn error_on_incorrect_await(&self, lo: Span, hi: Span, expr: &Expr, is_question: bool) -> Span {
+    fn error_on_incorrect_await(
+        &self,
+        lo: Span,
+        hi: Span,
+        expr: &Expr,
+        is_question: bool,
+    ) -> (Span, ErrorGuaranteed) {
         let span = lo.to(hi);
         let applicability = match expr.kind {
             ExprKind::Try(_) => Applicability::MaybeIncorrect, // `await <expr>?`
             _ => Applicability::MachineApplicable,
         };
 
-        self.dcx().emit_err(IncorrectAwait {
+        let guar = self.dcx().emit_err(IncorrectAwait {
             span,
             sugg_span: (span, applicability),
             expr: self.span_to_snippet(expr.span).unwrap_or_else(|_| pprust::expr_to_string(expr)),
             question_mark: if is_question { "?" } else { "" },
         });
 
-        span
+        (span, guar)
     }
 
     /// If encountering `future.await()`, consumes and emits an error.
@@ -2013,8 +2024,8 @@ impl<'a> Parser<'a> {
                 );
             }
             err.span_suggestion(lo.shrink_to_lo(), format!("{prefix}you can still access the deprecated `try!()` macro using the \"raw identifier\" syntax"), "r#", Applicability::MachineApplicable);
-            err.emit();
-            Ok(self.mk_expr_err(lo.to(hi)))
+            let guar = err.emit();
+            Ok(self.mk_expr_err(lo.to(hi), guar))
         } else {
             Err(self.expected_expression_found()) // The user isn't trying to invoke the try! macro
         }
@@ -2059,10 +2070,10 @@ impl<'a> Parser<'a> {
         lo: Span,
         err: PErr<'a>,
     ) -> P<Expr> {
-        err.emit();
+        let guar = err.emit();
         // Recover from parse error, callers expect the closing delim to be consumed.
         self.consume_block(delim, ConsumeClosingDelim::Yes);
-        self.mk_expr(lo.to(self.prev_token.span), ExprKind::Err)
+        self.mk_expr(lo.to(self.prev_token.span), ExprKind::Err(guar))
     }
 
     /// Eats tokens until we can be relatively sure we reached the end of the
@@ -2549,9 +2560,10 @@ impl<'a> Parser<'a> {
             }
             _ => None,
         };
-        self.dcx().emit_err(UnexpectedConstParamDeclaration { span: param.span(), sugg });
+        let guar =
+            self.dcx().emit_err(UnexpectedConstParamDeclaration { span: param.span(), sugg });
 
-        let value = self.mk_expr_err(param.span());
+        let value = self.mk_expr_err(param.span(), guar);
         Some(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }))
     }
 
@@ -2630,8 +2642,8 @@ impl<'a> Parser<'a> {
                         "=",
                         Applicability::MaybeIncorrect,
                     );
-                    let value = self.mk_expr_err(start.to(expr.span));
-                    err.emit();
+                    let guar = err.emit();
+                    let value = self.mk_expr_err(start.to(expr.span), guar);
                     return Ok(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }));
                 } else if token::Colon == snapshot.token.kind
                     && expr.span.lo() == snapshot.token.span.hi()
@@ -2701,8 +2713,8 @@ impl<'a> Parser<'a> {
             vec![(span.shrink_to_lo(), "{ ".to_string()), (span.shrink_to_hi(), " }".to_string())],
             Applicability::MaybeIncorrect,
         );
-        let value = self.mk_expr_err(span);
-        err.emit();
+        let guar = err.emit();
+        let value = self.mk_expr_err(span, guar);
         GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value })
     }
 
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 89f28777bff..e1a5e17004f 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -34,7 +34,7 @@ use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
 use rustc_session::lint::BuiltinLintDiagnostics;
 use rustc_span::source_map::{self, Spanned};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{BytePos, Pos, Span};
+use rustc_span::{BytePos, ErrorGuaranteed, Pos, Span};
 use thin_vec::{thin_vec, ThinVec};
 
 /// Possibly accepts an `token::Interpolated` expression (a pre-parsed expression
@@ -131,9 +131,9 @@ impl<'a> Parser<'a> {
                     if self.may_recover() && self.look_ahead(1, |t| t == &token::Comma) =>
                 {
                     // Special-case handling of `foo(_, _, _)`
-                    err.emit();
+                    let guar = err.emit();
                     self.bump();
-                    Ok(self.mk_expr(self.prev_token.span, ExprKind::Err))
+                    Ok(self.mk_expr(self.prev_token.span, ExprKind::Err(guar)))
                 }
                 _ => Err(err),
             },
@@ -667,8 +667,8 @@ impl<'a> Parser<'a> {
         let (span, _) = self.parse_expr_prefix_common(box_kw)?;
         let inner_span = span.with_lo(box_kw.hi());
         let code = self.sess.source_map().span_to_snippet(inner_span).unwrap();
-        self.dcx().emit_err(errors::BoxSyntaxRemoved { span: span, code: code.trim() });
-        Ok((span, ExprKind::Err))
+        let guar = self.dcx().emit_err(errors::BoxSyntaxRemoved { span: span, code: code.trim() });
+        Ok((span, ExprKind::Err(guar)))
     }
 
     fn is_mistaken_not_ident_negation(&self) -> bool {
@@ -860,7 +860,7 @@ impl<'a> Parser<'a> {
                     ExprKind::MethodCall(_) => "a method call",
                     ExprKind::Call(_, _) => "a function call",
                     ExprKind::Await(_, _) => "`.await`",
-                    ExprKind::Err => return Ok(with_postfix),
+                    ExprKind::Err(_) => return Ok(with_postfix),
                     _ => unreachable!("parse_dot_or_call_expr_with_ shouldn't produce this"),
                 }
             );
@@ -1315,7 +1315,7 @@ impl<'a> Parser<'a> {
                         let fields: Vec<_> =
                             fields.into_iter().filter(|field| !field.is_shorthand).collect();
 
-                        if !fields.is_empty() &&
+                        let guar = if !fields.is_empty() &&
                             // `token.kind` should not be compared here.
                             // This is because the `snapshot.token.kind` is treated as the same as
                             // that of the open delim in `TokenTreesReader::parse_token_tree`, even
@@ -1338,11 +1338,11 @@ impl<'a> Parser<'a> {
                                             .collect(),
                                     },
                                 })
-                                .emit();
+                                .emit()
                         } else {
-                            err.emit();
-                        }
-                        Ok(self.mk_expr_err(span))
+                            err.emit()
+                        };
+                        Ok(self.mk_expr_err(span, guar))
                     }
                     Ok(_) => Err(err),
                     Err(err2) => {
@@ -1684,13 +1684,13 @@ impl<'a> Parser<'a> {
             && (self.check_noexpect(&TokenKind::Comma) || self.check_noexpect(&TokenKind::Gt))
         {
             // We're probably inside of a `Path<'a>` that needs a turbofish
-            self.dcx().emit_err(errors::UnexpectedTokenAfterLabel {
+            let guar = self.dcx().emit_err(errors::UnexpectedTokenAfterLabel {
                 span: self.token.span,
                 remove_label: None,
                 enclose_in_block: None,
             });
             consume_colon = false;
-            Ok(self.mk_expr_err(lo))
+            Ok(self.mk_expr_err(lo, guar))
         } else {
             let mut err = errors::UnexpectedTokenAfterLabel {
                 span: self.token.span,
@@ -2039,7 +2039,7 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, L> {
         if let token::Interpolated(nt) = &self.token.kind
             && let token::NtExpr(e) | token::NtLiteral(e) = &nt.0
-            && matches!(e.kind, ExprKind::Err)
+            && matches!(e.kind, ExprKind::Err(_))
         {
             let mut err = self
                 .dcx()
@@ -2207,7 +2207,7 @@ impl<'a> Parser<'a> {
         let mut snapshot = self.create_snapshot_for_diagnostic();
         match snapshot.parse_expr_array_or_repeat(Delimiter::Brace) {
             Ok(arr) => {
-                self.dcx().emit_err(errors::ArrayBracketsInsteadOfSpaces {
+                let guar = self.dcx().emit_err(errors::ArrayBracketsInsteadOfSpaces {
                     span: arr.span,
                     sub: errors::ArrayBracketsInsteadOfSpacesSugg {
                         left: lo,
@@ -2216,7 +2216,7 @@ impl<'a> Parser<'a> {
                 });
 
                 self.restore_snapshot(snapshot);
-                Some(self.mk_expr_err(arr.span))
+                Some(self.mk_expr_err(arr.span, guar))
             }
             Err(e) => {
                 e.cancel();
@@ -2370,7 +2370,10 @@ impl<'a> Parser<'a> {
             // It is likely that the closure body is a block but where the
             // braces have been removed. We will recover and eat the next
             // statements later in the parsing process.
-            body = self.mk_expr_err(body.span);
+            body = self.mk_expr_err(
+                body.span,
+                self.dcx().span_delayed_bug(body.span, "recovered a closure body as a block"),
+            );
         }
 
         let body_span = body.span;
@@ -2485,7 +2488,7 @@ impl<'a> Parser<'a> {
                 ExprKind::Binary(Spanned { span: binop_span, .. }, _, right)
                     if let ExprKind::Block(_, None) = right.kind =>
                 {
-                    this.dcx().emit_err(errors::IfExpressionMissingThenBlock {
+                    let guar = this.dcx().emit_err(errors::IfExpressionMissingThenBlock {
                         if_span: lo,
                         missing_then_block_sub:
                             errors::IfExpressionMissingThenBlockSub::UnfinishedCondition(
@@ -2493,14 +2496,14 @@ impl<'a> Parser<'a> {
                             ),
                         let_else_sub: None,
                     });
-                    std::mem::replace(right, this.mk_expr_err(binop_span.shrink_to_hi()))
+                    std::mem::replace(right, this.mk_expr_err(binop_span.shrink_to_hi(), guar))
                 }
                 ExprKind::Block(_, None) => {
-                    this.dcx().emit_err(errors::IfExpressionMissingCondition {
+                    let guar = this.dcx().emit_err(errors::IfExpressionMissingCondition {
                         if_span: lo.with_neighbor(cond.span).shrink_to_hi(),
                         block_span: self.sess.source_map().start_point(cond_span),
                     });
-                    std::mem::replace(&mut cond, this.mk_expr_err(cond_span.shrink_to_hi()))
+                    std::mem::replace(&mut cond, this.mk_expr_err(cond_span.shrink_to_hi(), guar))
                 }
                 _ => {
                     return None;
@@ -2520,14 +2523,14 @@ impl<'a> Parser<'a> {
                 let let_else_sub = matches!(cond.kind, ExprKind::Let(..))
                     .then(|| errors::IfExpressionLetSomeSub { if_span: lo.until(cond_span) });
 
-                self.dcx().emit_err(errors::IfExpressionMissingThenBlock {
+                let guar = self.dcx().emit_err(errors::IfExpressionMissingThenBlock {
                     if_span: lo,
                     missing_then_block_sub: errors::IfExpressionMissingThenBlockSub::AddThenBlock(
                         cond_span.shrink_to_hi(),
                     ),
                     let_else_sub,
                 });
-                self.mk_block_err(cond_span.shrink_to_hi())
+                self.mk_block_err(cond_span.shrink_to_hi(), guar)
             }
         } else {
             let attrs = self.parse_outer_attributes()?; // For recovery.
@@ -2797,9 +2800,10 @@ impl<'a> Parser<'a> {
             && !matches!(self.token.kind, token::OpenDelim(Delimiter::Brace))
             && self.may_recover()
         {
-            self.dcx()
+            let guar = self
+                .dcx()
                 .emit_err(errors::MissingExpressionInForLoop { span: expr.span.shrink_to_lo() });
-            let err_expr = self.mk_expr(expr.span, ExprKind::Err);
+            let err_expr = self.mk_expr(expr.span, ExprKind::Err(guar));
             let block = self.mk_block(thin_vec![], BlockCheckMode::Default, self.prev_token.span);
             return Ok(self.mk_expr(
                 lo.to(self.prev_token.span),
@@ -2924,7 +2928,7 @@ impl<'a> Parser<'a> {
                         attrs: Default::default(),
                         pat: self.mk_pat(span, ast::PatKind::Err(guar)),
                         guard: None,
-                        body: Some(self.mk_expr_err(span)),
+                        body: Some(self.mk_expr_err(span, guar)),
                         span,
                         id: DUMMY_NODE_ID,
                         is_placeholder: false,
@@ -2959,7 +2963,7 @@ impl<'a> Parser<'a> {
         let err = |this: &Parser<'_>, stmts: Vec<ast::Stmt>| {
             let span = stmts[0].span.to(stmts[stmts.len() - 1].span);
 
-            this.dcx().emit_err(errors::MatchArmBodyWithoutBraces {
+            let guar = this.dcx().emit_err(errors::MatchArmBodyWithoutBraces {
                 statements: span,
                 arrow: arrow_span,
                 num_statements: stmts.len(),
@@ -2972,7 +2976,7 @@ impl<'a> Parser<'a> {
                     errors::MatchArmBodyWithoutBracesSugg::UseComma { semicolon: semi_sp }
                 },
             });
-            this.mk_expr_err(span)
+            this.mk_expr_err(span, guar)
         };
         // We might have either a `,` -> `;` typo, or a block without braces. We need
         // a more subtle parsing strategy.
@@ -3433,14 +3437,20 @@ impl<'a> Parser<'a> {
         pth: ast::Path,
         recover: bool,
         close_delim: Delimiter,
-    ) -> PResult<'a, (ThinVec<ExprField>, ast::StructRest, bool)> {
+    ) -> PResult<
+        'a,
+        (
+            ThinVec<ExprField>,
+            ast::StructRest,
+            Option<ErrorGuaranteed>, /* async blocks are forbidden in Rust 2015 */
+        ),
+    > {
         let mut fields = ThinVec::new();
         let mut base = ast::StructRest::None;
-        let mut recover_async = false;
+        let mut recovered_async = None;
         let in_if_guard = self.restrictions.contains(Restrictions::IN_IF_GUARD);
 
-        let mut async_block_err = |e: &mut DiagnosticBuilder<'_>, span: Span| {
-            recover_async = true;
+        let async_block_err = |e: &mut DiagnosticBuilder<'_>, span: Span| {
             errors::AsyncBlockIn2015 { span }.add_to_diagnostic(e);
             errors::HelpUseLatestEdition::new().add_to_diagnostic(e);
         };
@@ -3465,9 +3475,34 @@ impl<'a> Parser<'a> {
                 break;
             }
 
-            let recovery_field = self.find_struct_error_after_field_looking_code();
+            // Peek the field's ident before parsing its expr in order to emit better diagnostics.
+            let peek = self
+                .token
+                .ident()
+                .filter(|(ident, is_raw)| {
+                    (!ident.is_reserved() || matches!(is_raw, IdentIsRaw::Yes))
+                        && self.look_ahead(1, |tok| *tok == token::Colon)
+                })
+                .map(|(ident, _)| ident);
+
+            // We still want a field even if its expr didn't parse.
+            let field_ident = |this: &Self, guar: ErrorGuaranteed| {
+                peek.map(|ident| {
+                    let span = ident.span;
+                    ExprField {
+                        ident,
+                        span,
+                        expr: this.mk_expr_err(span, guar),
+                        is_shorthand: false,
+                        attrs: AttrVec::new(),
+                        id: DUMMY_NODE_ID,
+                        is_placeholder: false,
+                    }
+                })
+            };
+
             let parsed_field = match self.parse_expr_field() {
-                Ok(f) => Some(f),
+                Ok(f) => Ok(f),
                 Err(mut e) => {
                     if pth == kw::Async {
                         async_block_err(&mut e, pth.span);
@@ -3499,7 +3534,10 @@ impl<'a> Parser<'a> {
                         return Err(e);
                     }
 
-                    e.emit();
+                    let guar = e.emit();
+                    if pth == kw::Async {
+                        recovered_async = Some(guar);
+                    }
 
                     // If the next token is a comma, then try to parse
                     // what comes next as additional fields, rather than
@@ -3511,18 +3549,20 @@ impl<'a> Parser<'a> {
                         }
                     }
 
-                    None
+                    Err(guar)
                 }
             };
 
-            let is_shorthand = parsed_field.as_ref().is_some_and(|f| f.is_shorthand);
+            let is_shorthand = parsed_field.as_ref().is_ok_and(|f| f.is_shorthand);
             // A shorthand field can be turned into a full field with `:`.
             // We should point this out.
             self.check_or_expected(!is_shorthand, TokenType::Token(token::Colon));
 
             match self.expect_one_of(&[token::Comma], &[token::CloseDelim(close_delim)]) {
                 Ok(_) => {
-                    if let Some(f) = parsed_field.or(recovery_field) {
+                    if let Some(f) =
+                        parsed_field.or_else(|guar| field_ident(self, guar).ok_or(guar)).ok()
+                    {
                         // Only include the field if there's no parse error for the field name.
                         fields.push(f);
                     }
@@ -3532,8 +3572,7 @@ impl<'a> Parser<'a> {
                         async_block_err(&mut e, pth.span);
                     } else {
                         e.span_label(pth.span, "while parsing this struct");
-                        if let Some(f) = recovery_field {
-                            fields.push(f);
+                        if peek.is_some() {
                             e.span_suggestion(
                                 self.prev_token.span.shrink_to_hi(),
                                 "try adding a comma",
@@ -3545,13 +3584,18 @@ impl<'a> Parser<'a> {
                     if !recover {
                         return Err(e);
                     }
-                    e.emit();
+                    let guar = e.emit();
+                    if pth == kw::Async {
+                        recovered_async = Some(guar);
+                    } else if let Some(f) = field_ident(self, guar) {
+                        fields.push(f);
+                    }
                     self.recover_stmt_(SemiColonMode::Comma, BlockMode::Ignore);
                     self.eat(&token::Comma);
                 }
             }
         }
-        Ok((fields, base, recover_async))
+        Ok((fields, base, recovered_async))
     }
 
     /// Precondition: already parsed the '{'.
@@ -3562,39 +3606,18 @@ impl<'a> Parser<'a> {
         recover: bool,
     ) -> PResult<'a, P<Expr>> {
         let lo = pth.span;
-        let (fields, base, recover_async) =
+        let (fields, base, recovered_async) =
             self.parse_struct_fields(pth.clone(), recover, Delimiter::Brace)?;
         let span = lo.to(self.token.span);
         self.expect(&token::CloseDelim(Delimiter::Brace))?;
-        let expr = if recover_async {
-            ExprKind::Err
+        let expr = if let Some(guar) = recovered_async {
+            ExprKind::Err(guar)
         } else {
             ExprKind::Struct(P(ast::StructExpr { qself, path: pth, fields, rest: base }))
         };
         Ok(self.mk_expr(span, expr))
     }
 
-    /// Use in case of error after field-looking code: `S { foo: () with a }`.
-    fn find_struct_error_after_field_looking_code(&self) -> Option<ExprField> {
-        match self.token.ident() {
-            Some((ident, is_raw))
-                if (matches!(is_raw, IdentIsRaw::Yes) || !ident.is_reserved())
-                    && self.look_ahead(1, |t| *t == token::Colon) =>
-            {
-                Some(ast::ExprField {
-                    ident,
-                    span: self.token.span,
-                    expr: self.mk_expr_err(self.token.span),
-                    is_shorthand: false,
-                    attrs: AttrVec::new(),
-                    id: DUMMY_NODE_ID,
-                    is_placeholder: false,
-                })
-            }
-            _ => None,
-        }
-    }
-
     fn recover_struct_comma_after_dotdot(&mut self, span: Span) {
         if self.token != token::Comma {
             return;
@@ -3718,8 +3741,8 @@ impl<'a> Parser<'a> {
         limits: RangeLimits,
     ) -> ExprKind {
         if end.is_none() && limits == RangeLimits::Closed {
-            self.inclusive_range_with_incorrect_end();
-            ExprKind::Err
+            let guar = self.inclusive_range_with_incorrect_end();
+            ExprKind::Err(guar)
         } else {
             ExprKind::Range(start, end, limits)
         }
@@ -3756,8 +3779,8 @@ impl<'a> Parser<'a> {
         self.mk_expr_with_attrs(span, kind, AttrVec::new())
     }
 
-    pub(super) fn mk_expr_err(&self, span: Span) -> P<Expr> {
-        self.mk_expr(span, ExprKind::Err)
+    pub(super) fn mk_expr_err(&self, span: Span, guar: ErrorGuaranteed) -> P<Expr> {
+        self.mk_expr(span, ExprKind::Err(guar))
     }
 
     /// Create expression span ensuring the span of the parent node
@@ -3949,7 +3972,7 @@ impl MutVisitor for CondChecker<'_> {
             | ExprKind::Become(_)
             | ExprKind::IncludedBytes(_)
             | ExprKind::FormatArgs(_)
-            | ExprKind::Err
+            | ExprKind::Err(_)
             | ExprKind::Dummy => {
                 // These would forbid any let expressions they contain already.
             }
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index 2e049ca908f..a678194372e 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -2328,11 +2328,11 @@ impl<'a> Parser<'a> {
             let _ = self.parse_expr()?;
             self.expect_semi()?; // `;`
             let span = eq_sp.to(self.prev_token.span);
-            self.dcx().emit_err(errors::FunctionBodyEqualsExpr {
+            let guar = self.dcx().emit_err(errors::FunctionBodyEqualsExpr {
                 span,
                 sugg: errors::FunctionBodyEqualsExprSugg { eq: eq_sp, semi: self.prev_token.span },
             });
-            (AttrVec::new(), Some(self.mk_block_err(span)))
+            (AttrVec::new(), Some(self.mk_block_err(span, guar)))
         } else {
             let expected = if req_body {
                 &[token::OpenDelim(Delimiter::Brace)][..]
diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index c82b44ac6e1..9bff5b93092 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -566,7 +566,7 @@ impl<'a> Parser<'a> {
             match self.parse_literal_maybe_minus() {
                 Ok(begin) => {
                     let begin = match self.maybe_recover_trailing_expr(begin.span, false) {
-                        Some(_) => self.mk_expr_err(begin.span),
+                        Some(guar) => self.mk_expr_err(begin.span, guar),
                         None => begin,
                     };
 
@@ -719,7 +719,7 @@ impl<'a> Parser<'a> {
                     self.parse_pat_range_begin_with(begin.clone(), form)
                 }
                 // recover ranges with parentheses around the `(start)..`
-                PatKind::Err(_)
+                PatKind::Err(guar)
                     if self.may_recover()
                         && let Some(form) = self.parse_range_end() =>
                 {
@@ -731,7 +731,7 @@ impl<'a> Parser<'a> {
                         },
                     });
 
-                    self.parse_pat_range_begin_with(self.mk_expr(pat.span, ExprKind::Err), form)
+                    self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)
                 }
 
                 // (pat) with optional parentheses
@@ -886,7 +886,7 @@ impl<'a> Parser<'a> {
         Ok(PatKind::Range(Some(begin), end, re))
     }
 
-    pub(super) fn inclusive_range_with_incorrect_end(&mut self) {
+    pub(super) fn inclusive_range_with_incorrect_end(&mut self) -> ErrorGuaranteed {
         let tok = &self.token;
         let span = self.prev_token.span;
         // If the user typed "..==" instead of "..=", we want to give them
@@ -905,15 +905,13 @@ impl<'a> Parser<'a> {
                     let _ = self.parse_pat_range_end().map_err(|e| e.cancel());
                 }
 
-                self.dcx().emit_err(InclusiveRangeExtraEquals { span: span_with_eq });
+                self.dcx().emit_err(InclusiveRangeExtraEquals { span: span_with_eq })
             }
             token::Gt if no_space => {
                 let after_pat = span.with_hi(span.hi() - rustc_span::BytePos(1)).shrink_to_hi();
-                self.dcx().emit_err(InclusiveRangeMatchArrow { span, arrow: tok.span, after_pat });
-            }
-            _ => {
-                self.dcx().emit_err(InclusiveRangeNoEnd { span });
+                self.dcx().emit_err(InclusiveRangeMatchArrow { span, arrow: tok.span, after_pat })
             }
+            _ => self.dcx().emit_err(InclusiveRangeNoEnd { span }),
         }
     }
 
@@ -987,7 +985,7 @@ impl<'a> Parser<'a> {
         }
 
         Ok(match recovered {
-            Some(_) => self.mk_expr_err(bound.span),
+            Some(guar) => self.mk_expr_err(bound.span, guar),
             None => bound,
         })
     }
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index ee02b69c614..15f8124823f 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -22,7 +22,7 @@ use rustc_ast::{Block, BlockCheckMode, Expr, ExprKind, HasAttrs, Local, Stmt};
 use rustc_ast::{StmtKind, DUMMY_NODE_ID};
 use rustc_errors::{Applicability, DiagnosticBuilder, PResult};
 use rustc_span::symbol::{kw, sym, Ident};
-use rustc_span::{BytePos, Span};
+use rustc_span::{BytePos, ErrorGuaranteed, Span};
 
 use std::borrow::Cow;
 use std::mem;
@@ -610,9 +610,9 @@ impl<'a> Parser<'a> {
                         }
                     }
 
-                    err.emit();
+                    let guar = err.emit();
                     self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
-                    Some(self.mk_stmt_err(self.token.span))
+                    Some(self.mk_stmt_err(self.token.span, guar))
                 }
                 Ok(stmt) => stmt,
                 Err(err) => return Err(err),
@@ -651,10 +651,10 @@ impl<'a> Parser<'a> {
                         .contains(&self.token.kind) =>
             {
                 // The user has written `#[attr] expr` which is unsupported. (#106020)
-                self.attr_on_non_tail_expr(&expr);
+                let guar = self.attr_on_non_tail_expr(&expr);
                 // We already emitted an error, so don't emit another type error
                 let sp = expr.span.to(self.prev_token.span);
-                *expr = self.mk_expr_err(sp);
+                *expr = self.mk_expr_err(sp, guar);
             }
 
             // Expression without semicolon.
@@ -666,10 +666,18 @@ impl<'a> Parser<'a> {
                 let expect_result =
                     self.expect_one_of(&[], &[token::Semi, token::CloseDelim(Delimiter::Brace)]);
 
+                // Try to both emit a better diagnostic, and avoid further errors by replacing
+                // the `expr` with `ExprKind::Err`.
                 let replace_with_err = 'break_recover: {
                     match expect_result {
-                        // Recover from parser, skip type error to avoid extra errors.
-                        Ok(Recovered::Yes) => true,
+                        Ok(Recovered::No) => None,
+                        Ok(Recovered::Yes) => {
+                            // Skip type error to avoid extra errors.
+                            let guar = self
+                                .dcx()
+                                .span_delayed_bug(self.prev_token.span, "expected `;` or `}`");
+                            Some(guar)
+                        }
                         Err(e) => {
                             if self.recover_colon_as_semi() {
                                 // recover_colon_as_semi has already emitted a nicer error.
@@ -677,7 +685,7 @@ impl<'a> Parser<'a> {
                                 add_semi_to_stmt = true;
                                 eat_semi = false;
 
-                                break 'break_recover false;
+                                break 'break_recover None;
                             }
 
                             match &expr.kind {
@@ -705,13 +713,13 @@ impl<'a> Parser<'a> {
                                         };
                                         match self.parse_expr_labeled(label, false) {
                                             Ok(labeled_expr) => {
-                                                e.delay_as_bug();
+                                                e.cancel();
                                                 self.dcx().emit_err(MalformedLoopLabel {
                                                     span: label.ident.span,
                                                     correct_label: label.ident,
                                                 });
                                                 *expr = labeled_expr;
-                                                break 'break_recover false;
+                                                break 'break_recover None;
                                             }
                                             Err(err) => {
                                                 err.cancel();
@@ -723,26 +731,26 @@ impl<'a> Parser<'a> {
                                 _ => {}
                             }
 
-                            if let Err(e) =
-                                self.check_mistyped_turbofish_with_multiple_type_params(e, expr)
-                            {
-                                if recover.no() {
-                                    return Err(e);
-                                }
-                                e.emit();
-                                self.recover_stmt();
-                            }
-
-                            true
+                            let res =
+                                self.check_mistyped_turbofish_with_multiple_type_params(e, expr);
+
+                            Some(if recover.no() {
+                                res?
+                            } else {
+                                res.unwrap_or_else(|e| {
+                                    let guar = e.emit();
+                                    self.recover_stmt();
+                                    guar
+                                })
+                            })
                         }
-                        Ok(Recovered::No) => false,
                     }
                 };
 
-                if replace_with_err {
+                if let Some(guar) = replace_with_err {
                     // We already emitted an error, so don't emit another type error
                     let sp = expr.span.to(self.prev_token.span);
-                    *expr = self.mk_expr_err(sp);
+                    *expr = self.mk_expr_err(sp, guar);
                 }
             }
             StmtKind::Expr(_) | StmtKind::MacCall(_) => {}
@@ -791,11 +799,11 @@ impl<'a> Parser<'a> {
         Stmt { id: DUMMY_NODE_ID, kind, span }
     }
 
-    pub(super) fn mk_stmt_err(&self, span: Span) -> Stmt {
-        self.mk_stmt(span, StmtKind::Expr(self.mk_expr_err(span)))
+    pub(super) fn mk_stmt_err(&self, span: Span, guar: ErrorGuaranteed) -> Stmt {
+        self.mk_stmt(span, StmtKind::Expr(self.mk_expr_err(span, guar)))
     }
 
-    pub(super) fn mk_block_err(&self, span: Span) -> P<Block> {
-        self.mk_block(thin_vec![self.mk_stmt_err(span)], BlockCheckMode::Default, span)
+    pub(super) fn mk_block_err(&self, span: Span, guar: ErrorGuaranteed) -> P<Block> {
+        self.mk_block(thin_vec![self.mk_stmt_err(span, guar)], BlockCheckMode::Default, span)
     }
 }
diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs
index 5d46581f646..73c92708eb3 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -90,7 +90,7 @@ pub fn parse_meta<'a>(sess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Meta
                     //   been reported.
                     let msg = "attribute value must be a literal";
                     let mut err = sess.dcx.struct_span_err(expr.span, msg);
-                    if let ast::ExprKind::Err = expr.kind {
+                    if let ast::ExprKind::Err(_) = expr.kind {
                         err.downgrade_to_delayed_bug();
                     }
                     return Err(err);
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index 81a26a12009..c52a4e0d8b7 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -143,7 +143,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
     match (&l.kind, &r.kind) {
         (Paren(l), _) => eq_expr(l, r),
         (_, Paren(r)) => eq_expr(l, r),
-        (Err, Err) => true,
+        (Err(_), Err(_)) => true,
         (Dummy, _) | (_, Dummy) => unreachable!("comparing `ExprKind::Dummy`"),
         (Try(l), Try(r)) | (Await(l, _), Await(r, _)) => eq_expr(l, r),
         (Array(l), Array(r)) => over(l, r, |l, r| eq_expr(l, r)),
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index 7b1b0388b29..1f6446b8746 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -222,7 +222,7 @@ impl<'a> Sugg<'a> {
             | ast::ExprKind::Array(..)
             | ast::ExprKind::While(..)
             | ast::ExprKind::Await(..)
-            | ast::ExprKind::Err
+            | ast::ExprKind::Err(_)
             | ast::ExprKind::Dummy => Sugg::NonParen(snippet_with_context(cx, expr.span, ctxt, default, app).0),
             ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp(
                 AssocOp::DotDot,
diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs
index d46d7c53bdb..c500b30b998 100644
--- a/src/tools/rustfmt/src/expr.rs
+++ b/src/tools/rustfmt/src/expr.rs
@@ -404,7 +404,7 @@ pub(crate) fn format_expr(
             // These do not occur in the AST because macros aren't expanded.
             unreachable!()
         }
-        ast::ExprKind::Err | ast::ExprKind::Dummy => None,
+        ast::ExprKind::Err(_) | ast::ExprKind::Dummy => None,
     };
 
     expr_rw
diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs
index b6b37e492e9..d4218cff75a 100644
--- a/src/tools/rustfmt/src/utils.rs
+++ b/src/tools/rustfmt/src/utils.rs
@@ -498,7 +498,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr
         | ast::ExprKind::Cast(..)
         | ast::ExprKind::Continue(..)
         | ast::ExprKind::Dummy
-        | ast::ExprKind::Err
+        | ast::ExprKind::Err(_)
         | ast::ExprKind::Field(..)
         | ast::ExprKind::IncludedBytes(..)
         | ast::ExprKind::InlineAsm(..)