diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 01f36ad6a7866..207c616cefc02 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -1289,11 +1289,9 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         drop(self);
     }
 
-    /// Stashes diagnostic for possible later improvement in a different,
-    /// later stage of the compiler. The diagnostic can be accessed with
-    /// the provided `span` and `key` through [`DiagCtxt::steal_diagnostic()`].
-    pub fn stash(mut self, span: Span, key: StashKey) {
-        self.dcx.stash_diagnostic(span, key, self.take_diag());
+    /// See `DiagCtxt::stash_diagnostic` for details.
+    pub fn stash(mut self, span: Span, key: StashKey) -> Option<ErrorGuaranteed> {
+        self.dcx.stash_diagnostic(span, key, self.take_diag())
     }
 
     /// Delay emission of this diagnostic as a bug.
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index bc338b01d8bb6..186ec41f7cd1b 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -434,10 +434,6 @@ struct DiagCtxtInner {
     /// The delayed bugs and their error guarantees.
     delayed_bugs: Vec<(DelayedDiagInner, ErrorGuaranteed)>,
 
-    /// The number of stashed errors. Unlike the other counts, this can go up
-    /// and down, so it doesn't guarantee anything.
-    stashed_err_count: usize,
-
     /// The error count shown to the user at the end.
     deduplicated_err_count: usize,
     /// The warning count shown to the user at the end.
@@ -475,7 +471,7 @@ struct DiagCtxtInner {
     /// add more information). All stashed diagnostics must be emitted with
     /// `emit_stashed_diagnostics` by the time the `DiagCtxtInner` is dropped,
     /// otherwise an assertion failure will occur.
-    stashed_diagnostics: FxIndexMap<(Span, StashKey), DiagInner>,
+    stashed_diagnostics: FxIndexMap<(Span, StashKey), (DiagInner, Option<ErrorGuaranteed>)>,
 
     future_breakage_diagnostics: Vec<DiagInner>,
 
@@ -559,10 +555,18 @@ pub struct DiagCtxtFlags {
 
 impl Drop for DiagCtxtInner {
     fn drop(&mut self) {
-        // Any stashed diagnostics should have been handled by
-        // `emit_stashed_diagnostics` by now.
-        assert!(self.stashed_diagnostics.is_empty());
+        // For tools using `interface::run_compiler` (e.g. rustc, rustdoc)
+        // stashed diagnostics will have already been emitted. But for others
+        // that don't use `interface::run_compiler` (e.g. rustfmt, some clippy
+        // lints) this fallback is necessary.
+        //
+        // Important: it is sound to produce an `ErrorGuaranteed` when stashing
+        // errors because they are guaranteed to be emitted here or earlier.
+        self.emit_stashed_diagnostics();
 
+        // Important: it is sound to produce an `ErrorGuaranteed` when emitting
+        // delayed bugs because they are guaranteed to be emitted here if
+        // necessary.
         if self.err_guars.is_empty() {
             self.flush_delayed()
         }
@@ -615,7 +619,6 @@ impl DiagCtxt {
                 err_guars: Vec::new(),
                 lint_err_guars: Vec::new(),
                 delayed_bugs: Vec::new(),
-                stashed_err_count: 0,
                 deduplicated_err_count: 0,
                 deduplicated_warn_count: 0,
                 emitter,
@@ -676,7 +679,6 @@ impl DiagCtxt {
             err_guars,
             lint_err_guars,
             delayed_bugs,
-            stashed_err_count,
             deduplicated_err_count,
             deduplicated_warn_count,
             emitter: _,
@@ -699,7 +701,6 @@ impl DiagCtxt {
         *err_guars = Default::default();
         *lint_err_guars = Default::default();
         *delayed_bugs = Default::default();
-        *stashed_err_count = 0;
         *deduplicated_err_count = 0;
         *deduplicated_warn_count = 0;
         *must_produce_diag = false;
@@ -715,39 +716,111 @@ impl DiagCtxt {
         *fulfilled_expectations = Default::default();
     }
 
-    /// Stash a given diagnostic with the given `Span` and [`StashKey`] as the key.
-    /// Retrieve a stashed diagnostic with `steal_diagnostic`.
-    pub fn stash_diagnostic(&self, span: Span, key: StashKey, diag: DiagInner) {
-        let mut inner = self.inner.borrow_mut();
-
-        let key = (span.with_parent(None), key);
-
-        if diag.is_error() {
-            if diag.is_lint.is_none() {
-                inner.stashed_err_count += 1;
-            }
-        }
+    /// Stashes a diagnostic for possible later improvement in a different,
+    /// later stage of the compiler. Possible actions depend on the diagnostic
+    /// level:
+    /// - Level::Error: immediately counted as an error that has occurred, because it
+    ///   is guaranteed to be emitted eventually. Can be later accessed with the
+    ///   provided `span` and `key` through
+    ///   [`DiagCtxt::try_steal_modify_and_emit_err`] or
+    ///   [`DiagCtxt::try_steal_replace_and_emit_err`]. These do not allow
+    ///   cancellation or downgrading of the error. Returns
+    ///   `Some(ErrorGuaranteed)`.
+    /// - Level::Warning and lower (i.e. !is_error()): can be accessed with the
+    ///   provided `span` and `key` through [`DiagCtxt::steal_non_err()`]. This
+    ///   allows cancelling and downgrading of the diagnostic. Returns `None`.
+    /// - Others: not allowed, will trigger a panic.
+    pub fn stash_diagnostic(
+        &self,
+        span: Span,
+        key: StashKey,
+        diag: DiagInner,
+    ) -> Option<ErrorGuaranteed> {
+        let guar = if diag.level() == Level::Error {
+            // This `unchecked_error_guaranteed` is valid. It is where the
+            // `ErrorGuaranteed` for stashed errors originates. See
+            // `DiagCtxtInner::drop`.
+            #[allow(deprecated)]
+            Some(ErrorGuaranteed::unchecked_error_guaranteed())
+        } else if !diag.is_error() {
+            None
+        } else {
+            self.span_bug(span, format!("invalid level in `stash_diagnostic`: {}", diag.level));
+        };
 
         // FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic
         // if/when we have a more robust macro-friendly replacement for `(span, key)` as a key.
         // See the PR for a discussion.
-        inner.stashed_diagnostics.insert(key, diag);
+        let key = (span.with_parent(None), key);
+        self.inner.borrow_mut().stashed_diagnostics.insert(key, (diag, guar));
+
+        guar
     }
 
-    /// Steal a previously stashed diagnostic with the given `Span` and [`StashKey`] as the key.
-    pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option<Diag<'_, ()>> {
-        let mut inner = self.inner.borrow_mut();
+    /// Steal a previously stashed non-error diagnostic with the given `Span`
+    /// and [`StashKey`] as the key. Panics if the found diagnostic is an
+    /// error.
+    pub fn steal_non_err(&self, span: Span, key: StashKey) -> Option<Diag<'_, ()>> {
         let key = (span.with_parent(None), key);
         // FIXME(#120456) - is `swap_remove` correct?
-        let diag = inner.stashed_diagnostics.swap_remove(&key)?;
-        if diag.is_error() {
-            if diag.is_lint.is_none() {
-                inner.stashed_err_count -= 1;
-            }
-        }
+        let (diag, guar) = self.inner.borrow_mut().stashed_diagnostics.swap_remove(&key)?;
+        assert!(!diag.is_error());
+        assert!(guar.is_none());
         Some(Diag::new_diagnostic(self, diag))
     }
 
+    /// Steals a previously stashed error with the given `Span` and
+    /// [`StashKey`] as the key, modifies it, and emits it. Returns `None` if
+    /// no matching diagnostic is found. Panics if the found diagnostic's level
+    /// isn't `Level::Error`.
+    pub fn try_steal_modify_and_emit_err<F>(
+        &self,
+        span: Span,
+        key: StashKey,
+        mut modify_err: F,
+    ) -> Option<ErrorGuaranteed>
+    where
+        F: FnMut(&mut Diag<'_>),
+    {
+        let key = (span.with_parent(None), key);
+        // FIXME(#120456) - is `swap_remove` correct?
+        let err = self.inner.borrow_mut().stashed_diagnostics.swap_remove(&key);
+        err.map(|(err, guar)| {
+            // The use of `::<ErrorGuaranteed>` is safe because level is `Level::Error`.
+            assert_eq!(err.level, Level::Error);
+            assert!(guar.is_some());
+            let mut err = Diag::<ErrorGuaranteed>::new_diagnostic(self, err);
+            modify_err(&mut err);
+            assert_eq!(err.level, Level::Error);
+            err.emit()
+        })
+    }
+
+    /// Steals a previously stashed error with the given `Span` and
+    /// [`StashKey`] as the key, cancels it if found, and emits `new_err`.
+    /// Panics if the found diagnostic's level isn't `Level::Error`.
+    pub fn try_steal_replace_and_emit_err(
+        &self,
+        span: Span,
+        key: StashKey,
+        new_err: Diag<'_>,
+    ) -> ErrorGuaranteed {
+        let key = (span.with_parent(None), key);
+        // FIXME(#120456) - is `swap_remove` correct?
+        let old_err = self.inner.borrow_mut().stashed_diagnostics.swap_remove(&key);
+        match old_err {
+            Some((old_err, guar)) => {
+                assert_eq!(old_err.level, Level::Error);
+                assert!(guar.is_some());
+                // Because `old_err` has already been counted, it can only be
+                // safely cancelled because the `new_err` supplants it.
+                Diag::<ErrorGuaranteed>::new_diagnostic(self, old_err).cancel();
+            }
+            None => {}
+        };
+        new_err.emit()
+    }
+
     pub fn has_stashed_diagnostic(&self, span: Span, key: StashKey) -> bool {
         self.inner.borrow().stashed_diagnostics.get(&(span.with_parent(None), key)).is_some()
     }
@@ -757,41 +830,40 @@ impl DiagCtxt {
         self.inner.borrow_mut().emit_stashed_diagnostics()
     }
 
-    /// This excludes lint errors, delayed bugs and stashed errors.
+    /// This excludes lint errors, and delayed bugs.
     #[inline]
     pub fn err_count_excluding_lint_errs(&self) -> usize {
-        self.inner.borrow().err_guars.len()
+        let inner = self.inner.borrow();
+        inner.err_guars.len()
+            + inner
+                .stashed_diagnostics
+                .values()
+                .filter(|(diag, guar)| guar.is_some() && diag.is_lint.is_none())
+                .count()
     }
 
-    /// This excludes delayed bugs and stashed errors.
+    /// This excludes delayed bugs.
     #[inline]
     pub fn err_count(&self) -> usize {
         let inner = self.inner.borrow();
-        inner.err_guars.len() + inner.lint_err_guars.len()
+        inner.err_guars.len()
+            + inner.lint_err_guars.len()
+            + inner.stashed_diagnostics.values().filter(|(_diag, guar)| guar.is_some()).count()
     }
 
-    /// This excludes normal errors, lint errors, and delayed bugs. Unless
-    /// absolutely necessary, avoid using this. It's dubious because stashed
-    /// errors can later be cancelled, so the presence of a stashed error at
-    /// some point of time doesn't guarantee anything -- there are no
-    /// `ErrorGuaranteed`s here.
-    pub fn stashed_err_count(&self) -> usize {
-        self.inner.borrow().stashed_err_count
-    }
-
-    /// This excludes lint errors, delayed bugs, and stashed errors. Unless
-    /// absolutely necessary, prefer `has_errors` to this method.
+    /// This excludes lint errors and delayed bugs. Unless absolutely
+    /// necessary, prefer `has_errors` to this method.
     pub fn has_errors_excluding_lint_errors(&self) -> Option<ErrorGuaranteed> {
         self.inner.borrow().has_errors_excluding_lint_errors()
     }
 
-    /// This excludes delayed bugs and stashed errors.
+    /// This excludes delayed bugs.
     pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
         self.inner.borrow().has_errors()
     }
 
-    /// This excludes stashed errors. Unless absolutely necessary, prefer
-    /// `has_errors` to this method.
+    /// This excludes nothing. Unless absolutely necessary, prefer `has_errors`
+    /// to this method.
     pub fn has_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
         self.inner.borrow().has_errors_or_delayed_bugs()
     }
@@ -876,10 +948,10 @@ impl DiagCtxt {
         }
     }
 
-    /// This excludes delayed bugs and stashed errors. Used for early aborts
-    /// after errors occurred -- e.g. because continuing in the face of errors is
-    /// likely to lead to bad results, such as spurious/uninteresting
-    /// additional errors -- when returning an error `Result` is difficult.
+    /// This excludes delayed bugs. Used for early aborts after errors occurred
+    /// -- e.g. because continuing in the face of errors is likely to lead to
+    /// bad results, such as spurious/uninteresting additional errors -- when
+    /// returning an error `Result` is difficult.
     pub fn abort_if_errors(&self) {
         if self.has_errors().is_some() {
             FatalError.raise();
@@ -963,7 +1035,7 @@ impl DiagCtxt {
         inner
             .stashed_diagnostics
             .values_mut()
-            .for_each(|diag| diag.update_unstable_expectation_id(unstable_to_stable));
+            .for_each(|(diag, _guar)| diag.update_unstable_expectation_id(unstable_to_stable));
         inner
             .future_breakage_diagnostics
             .iter_mut()
@@ -1270,12 +1342,8 @@ impl DiagCtxtInner {
     fn emit_stashed_diagnostics(&mut self) -> Option<ErrorGuaranteed> {
         let mut guar = None;
         let has_errors = !self.err_guars.is_empty();
-        for (_, diag) in std::mem::take(&mut self.stashed_diagnostics).into_iter() {
-            if diag.is_error() {
-                if diag.is_lint.is_none() {
-                    self.stashed_err_count -= 1;
-                }
-            } else {
+        for (_, (diag, _guar)) in std::mem::take(&mut self.stashed_diagnostics).into_iter() {
+            if !diag.is_error() {
                 // Unless they're forced, don't flush stashed warnings when
                 // there are errors, to avoid causing warning overload. The
                 // stash would've been stolen already if it were important.
@@ -1334,7 +1402,8 @@ impl DiagCtxtInner {
                 } else {
                     let backtrace = std::backtrace::Backtrace::capture();
                     // This `unchecked_error_guaranteed` is valid. It is where the
-                    // `ErrorGuaranteed` for delayed bugs originates.
+                    // `ErrorGuaranteed` for delayed bugs originates. See
+                    // `DiagCtxtInner::drop`.
                     #[allow(deprecated)]
                     let guar = ErrorGuaranteed::unchecked_error_guaranteed();
                     self.delayed_bugs
@@ -1446,11 +1515,31 @@ impl DiagCtxtInner {
     }
 
     fn has_errors_excluding_lint_errors(&self) -> Option<ErrorGuaranteed> {
-        self.err_guars.get(0).copied()
+        self.err_guars.get(0).copied().or_else(|| {
+            if let Some((_diag, guar)) = self
+                .stashed_diagnostics
+                .values()
+                .find(|(diag, guar)| guar.is_some() && diag.is_lint.is_none())
+            {
+                *guar
+            } else {
+                None
+            }
+        })
     }
 
     fn has_errors(&self) -> Option<ErrorGuaranteed> {
-        self.has_errors_excluding_lint_errors().or_else(|| self.lint_err_guars.get(0).copied())
+        self.err_guars.get(0).copied().or_else(|| self.lint_err_guars.get(0).copied()).or_else(
+            || {
+                if let Some((_diag, guar)) =
+                    self.stashed_diagnostics.values().find(|(_diag, guar)| guar.is_some())
+                {
+                    *guar
+                } else {
+                    None
+                }
+            },
+        )
     }
 
     fn has_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
diff --git a/compiler/rustc_hir_analysis/src/astconv/lint.rs b/compiler/rustc_hir_analysis/src/astconv/lint.rs
index fb5f3426cea6a..227254b4cc8b3 100644
--- a/compiler/rustc_hir_analysis/src/astconv/lint.rs
+++ b/compiler/rustc_hir_analysis/src/astconv/lint.rs
@@ -1,5 +1,5 @@
 use rustc_ast::TraitObjectSyntax;
-use rustc_errors::{codes::*, Diag, EmissionGuarantee, StashKey};
+use rustc_errors::{codes::*, Diag, EmissionGuarantee, Level, StashKey};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_lint_defs::{builtin::BARE_TRAIT_OBJECTS, Applicability};
@@ -237,7 +237,15 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                 }
                 // check if the impl trait that we are considering is a impl of a local trait
                 self.maybe_lint_blanket_trait_impl(self_ty, &mut diag);
-                diag.stash(self_ty.span, StashKey::TraitMissingMethod);
+                match diag.level() {
+                    Level::Error => {
+                        diag.stash(self_ty.span, StashKey::TraitMissingMethod);
+                    }
+                    Level::DelayedBug => {
+                        diag.emit();
+                    }
+                    _ => unreachable!(),
+                }
             } else {
                 let msg = "trait objects without an explicit `dyn` are deprecated";
                 tcx.node_span_lint(BARE_TRAIT_OBJECTS, self_ty.hir_id, self_ty.span, msg, |lint| {
diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs
index 81e84860b8227..96bebda582815 100644
--- a/compiler/rustc_hir_analysis/src/check/check.rs
+++ b/compiler/rustc_hir_analysis/src/check/check.rs
@@ -1350,8 +1350,7 @@ fn check_type_alias_type_params_are_used<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalD
     let ty = tcx.type_of(def_id).instantiate_identity();
     if ty.references_error() {
         // If there is already another error, do not emit an error for not using a type parameter.
-        // Without the `stashed_err_count` part this can fail (#120856).
-        assert!(tcx.dcx().has_errors().is_some() || tcx.dcx().stashed_err_count() > 0);
+        assert!(tcx.dcx().has_errors().is_some());
         return;
     }
 
diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs
index c0128afe2bf6c..417f0fceaa81f 100644
--- a/compiler/rustc_hir_analysis/src/collect/type_of.rs
+++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs
@@ -596,10 +596,11 @@ fn infer_placeholder_type<'a>(
     // then the user may have written e.g. `const A = 42;`.
     // In this case, the parser has stashed a diagnostic for
     // us to improve in typeck so we do that now.
-    match tcx.dcx().steal_diagnostic(span, StashKey::ItemNoType) {
-        Some(mut err) => {
+    let guar = tcx
+        .dcx()
+        .try_steal_modify_and_emit_err(span, StashKey::ItemNoType, |err| {
             if !ty.references_error() {
-                // Only suggest adding `:` if it was missing (and suggested by parsing diagnostic)
+                // Only suggest adding `:` if it was missing (and suggested by parsing diagnostic).
                 let colon = if span == item_ident.span.shrink_to_hi() { ":" } else { "" };
 
                 // The parser provided a sub-optimal `HasPlaceholders` suggestion for the type.
@@ -622,12 +623,8 @@ fn infer_placeholder_type<'a>(
                     ));
                 }
             }
-
-            err.emit();
-            // diagnostic stashing loses the information of whether something is a hard error.
-            Ty::new_error_with_message(tcx, span, "ItemNoType is a hard error")
-        }
-        None => {
+        })
+        .unwrap_or_else(|| {
             let mut diag = bad_placeholder(tcx, vec![span], kind);
 
             if !ty.references_error() {
@@ -645,10 +642,9 @@ fn infer_placeholder_type<'a>(
                     ));
                 }
             }
-
-            Ty::new_error(tcx, diag.emit())
-        }
-    }
+            diag.emit()
+        });
+    Ty::new_error(tcx, guar)
 }
 
 fn check_feature_inherent_assoc_ty(tcx: TyCtxt<'_>, span: Span) {
diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs
index bb6d1ecae02ab..b87d71e353389 100644
--- a/compiler/rustc_hir_typeck/src/callee.rs
+++ b/compiler/rustc_hir_typeck/src/callee.rs
@@ -484,12 +484,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
                 if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = &callee_expr.kind
                     && let [segment] = path.segments
-                    && let Some(mut diag) =
-                        self.dcx().steal_diagnostic(segment.ident.span, StashKey::CallIntoMethod)
                 {
-                    // Try suggesting `foo(a)` -> `a.foo()` if possible.
-                    self.suggest_call_as_method(&mut diag, segment, arg_exprs, call_expr, expected);
-                    diag.emit();
+                    self.dcx().try_steal_modify_and_emit_err(
+                        segment.ident.span,
+                        StashKey::CallIntoMethod,
+                        |err| {
+                            // Try suggesting `foo(a)` -> `a.foo()` if possible.
+                            self.suggest_call_as_method(
+                                err, segment, arg_exprs, call_expr, expected,
+                            );
+                        },
+                    );
                 }
 
                 let err = self.report_invalid_callee(call_expr, callee_expr, callee_ty, arg_exprs);
@@ -601,7 +606,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     /// and suggesting the fix if the method probe is successful.
     fn suggest_call_as_method(
         &self,
-        diag: &mut Diag<'_, ()>,
+        diag: &mut Diag<'_>,
         segment: &'tcx hir::PathSegment<'tcx>,
         arg_exprs: &'tcx [hir::Expr<'tcx>],
         call_expr: &'tcx hir::Expr<'tcx>,
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 2b7bce3f48575..054be89a7c4c5 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -1460,18 +1460,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             && let hir::ArrayLen::Body(hir::AnonConst { hir_id, .. }) = length
         {
             let span = self.tcx.hir().span(hir_id);
-            match self.dcx().steal_diagnostic(span, StashKey::UnderscoreForArrayLengths) {
-                Some(mut err) => {
+            self.dcx().try_steal_modify_and_emit_err(
+                span,
+                StashKey::UnderscoreForArrayLengths,
+                |err| {
                     err.span_suggestion(
                         span,
                         "consider specifying the array length",
                         array_len,
                         Applicability::MaybeIncorrect,
                     );
-                    err.emit();
-                }
-                None => (),
-            }
+                },
+            );
         }
     }
 
@@ -1751,11 +1751,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             let (_, diag) =
                 self.demand_coerce_diag(field.expr, ty, field_type, None, AllowTwoPhase::No);
 
-            if let Some(mut diag) = diag {
+            if let Some(diag) = diag {
                 if idx == ast_fields.len() - 1 {
                     if remaining_fields.is_empty() {
-                        self.suggest_fru_from_range(field, variant, args, &mut diag);
-                        diag.emit();
+                        self.suggest_fru_from_range_and_emit(field, variant, args, diag);
                     } else {
                         diag.stash(field.span, StashKey::MaybeFruTypo);
                     }
@@ -1986,20 +1985,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         err.span_label(span, format!("missing {remaining_fields_names}{truncated_fields_error}"));
 
         if let Some(last) = ast_fields.last() {
-            self.suggest_fru_from_range(last, variant, args, &mut err);
+            self.suggest_fru_from_range_and_emit(last, variant, args, err);
+        } else {
+            err.emit();
         }
-
-        err.emit();
     }
 
     /// If the last field is a range literal, but it isn't supposed to be, then they probably
     /// meant to use functional update syntax.
-    fn suggest_fru_from_range(
+    fn suggest_fru_from_range_and_emit(
         &self,
         last_expr_field: &hir::ExprField<'tcx>,
         variant: &ty::VariantDef,
         args: GenericArgsRef<'tcx>,
-        err: &mut Diag<'_>,
+        mut err: Diag<'_>,
     ) {
         // I don't use 'is_range_literal' because only double-sided, half-open ranges count.
         if let ExprKind::Struct(QPath::LangItem(LangItem::Range, ..), [range_start, range_end], _) =
@@ -2012,16 +2011,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 .map(|adt| adt.did())
                 != range_def_id
         {
-            // Suppress any range expr type mismatches
-            if let Some(diag) =
-                self.dcx().steal_diagnostic(last_expr_field.span, StashKey::MaybeFruTypo)
-            {
-                diag.delay_as_bug();
-            }
-
             // Use a (somewhat arbitrary) filtering heuristic to avoid printing
             // expressions that are either too long, or have control character
-            //such as newlines in them.
+            // such as newlines in them.
             let expr = self
                 .tcx
                 .sess
@@ -2043,6 +2035,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 self.dcx(),
                 TypeMismatchFruTypo { expr_span: range_start.span, fru_span, expr },
             );
+
+            // Suppress any range expr type mismatches
+            self.dcx().try_steal_replace_and_emit_err(
+                last_expr_field.span,
+                StashKey::MaybeFruTypo,
+                err,
+            );
+        } else {
+            err.emit();
         }
     }
 
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
index a5892dea1a51b..dd44fdd889328 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
@@ -848,11 +848,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             .resolve_fully_qualified_call(span, item_name, ty.normalized, qself.span, hir_id)
             .map(|r| {
                 // lint bare trait if the method is found in the trait
-                if span.edition().at_least_rust_2021()
-                    && let Some(diag) =
-                        self.dcx().steal_diagnostic(qself.span, StashKey::TraitMissingMethod)
-                {
-                    diag.emit();
+                if span.edition().at_least_rust_2021() {
+                    self.dcx().try_steal_modify_and_emit_err(
+                        qself.span,
+                        StashKey::TraitMissingMethod,
+                        |_err| {},
+                    );
                 }
                 r
             })
@@ -879,17 +880,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     );
                 }
 
-                // emit or cancel the diagnostic for bare traits
-                if span.edition().at_least_rust_2021()
-                    && let Some(diag) =
-                        self.dcx().steal_diagnostic(qself.span, StashKey::TraitMissingMethod)
-                {
-                    if trait_missing_method {
-                        // cancel the diag for bare traits when meeting `MyTrait::missing_method`
-                        diag.cancel();
-                    } else {
-                        diag.emit();
-                    }
+                // Emit the diagnostic for bare traits. (We used to cancel for slightly better
+                // error messages, but cancelling stashed diagnostics is no longer allowed because
+                // it causes problems when tracking whether errors have actually occurred.)
+                if span.edition().at_least_rust_2021() {
+                    self.dcx().try_steal_modify_and_emit_err(
+                        qself.span,
+                        StashKey::TraitMissingMethod,
+                        |_err| {},
+                    );
                 }
 
                 if item_name.name != kw::Empty {
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
index 61c52422d195d..1311cc8968af0 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
@@ -1941,53 +1941,49 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         errors_causecode: Vec<(Span, ObligationCauseCode<'tcx>)>,
     ) {
         for (span, code) in errors_causecode {
-            let Some(mut diag) = self.dcx().steal_diagnostic(span, StashKey::MaybeForgetReturn)
-            else {
-                continue;
-            };
-
-            if let Some(fn_sig) = self.body_fn_sig()
-                && let ExprBindingObligation(_, _, hir_id, ..) = code
-                && !fn_sig.output().is_unit()
-            {
-                let mut block_num = 0;
-                let mut found_semi = false;
-                for (_, node) in self.tcx.hir().parent_iter(hir_id) {
-                    match node {
-                        hir::Node::Stmt(stmt) => {
-                            if let hir::StmtKind::Semi(expr) = stmt.kind {
-                                let expr_ty = self.typeck_results.borrow().expr_ty(expr);
-                                let return_ty = fn_sig.output();
-                                if !matches!(expr.kind, hir::ExprKind::Ret(..))
-                                    && self.can_coerce(expr_ty, return_ty)
-                                {
-                                    found_semi = true;
+            self.dcx().try_steal_modify_and_emit_err(span, StashKey::MaybeForgetReturn, |err| {
+                if let Some(fn_sig) = self.body_fn_sig()
+                    && let ExprBindingObligation(_, _, hir_id, ..) = code
+                    && !fn_sig.output().is_unit()
+                {
+                    let mut block_num = 0;
+                    let mut found_semi = false;
+                    for (_, node) in self.tcx.hir().parent_iter(hir_id) {
+                        match node {
+                            hir::Node::Stmt(stmt) => {
+                                if let hir::StmtKind::Semi(expr) = stmt.kind {
+                                    let expr_ty = self.typeck_results.borrow().expr_ty(expr);
+                                    let return_ty = fn_sig.output();
+                                    if !matches!(expr.kind, hir::ExprKind::Ret(..))
+                                        && self.can_coerce(expr_ty, return_ty)
+                                    {
+                                        found_semi = true;
+                                    }
                                 }
                             }
-                        }
-                        hir::Node::Block(_block) => {
-                            if found_semi {
-                                block_num += 1;
+                            hir::Node::Block(_block) => {
+                                if found_semi {
+                                    block_num += 1;
+                                }
                             }
-                        }
-                        hir::Node::Item(item) => {
-                            if let hir::ItemKind::Fn(..) = item.kind {
-                                break;
+                            hir::Node::Item(item) => {
+                                if let hir::ItemKind::Fn(..) = item.kind {
+                                    break;
+                                }
                             }
+                            _ => {}
                         }
-                        _ => {}
+                    }
+                    if block_num > 1 && found_semi {
+                        err.span_suggestion_verbose(
+                            span.shrink_to_lo(),
+                            "you might have meant to return this to infer its type parameters",
+                            "return ",
+                            Applicability::MaybeIncorrect,
+                        );
                     }
                 }
-                if block_num > 1 && found_semi {
-                    diag.span_suggestion_verbose(
-                        span.shrink_to_lo(),
-                        "you might have meant to return this to infer its type parameters",
-                        "return ",
-                        Applicability::MaybeIncorrect,
-                    );
-                }
-            }
-            diag.emit();
+            });
         }
     }
 
diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs
index 28771ae40f538..e5b0d0ae0da3a 100644
--- a/compiler/rustc_hir_typeck/src/method/suggest.rs
+++ b/compiler/rustc_hir_typeck/src/method/suggest.rs
@@ -2194,59 +2194,59 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let [seg1, seg2] = segs else {
             return;
         };
-        let Some(mut diag) =
-            self.dcx().steal_diagnostic(seg1.ident.span, StashKey::CallAssocMethod)
-        else {
-            return;
-        };
-
-        let map = self.infcx.tcx.hir();
-        let body_id = self.tcx.hir().body_owned_by(self.body_id);
-        let body = map.body(body_id);
-        struct LetVisitor<'a> {
-            result: Option<&'a hir::Expr<'a>>,
-            ident_name: Symbol,
-        }
+        self.dcx().try_steal_modify_and_emit_err(
+            seg1.ident.span,
+            StashKey::CallAssocMethod,
+            |err| {
+                let map = self.infcx.tcx.hir();
+                let body_id = self.tcx.hir().body_owned_by(self.body_id);
+                let body = map.body(body_id);
+                struct LetVisitor<'a> {
+                    result: Option<&'a hir::Expr<'a>>,
+                    ident_name: Symbol,
+                }
 
-        // FIXME: This really should be taking scoping, etc into account.
-        impl<'v> Visitor<'v> for LetVisitor<'v> {
-            fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
-                if let hir::StmtKind::Local(hir::Local { pat, init, .. }) = &ex.kind
-                    && let Binding(_, _, ident, ..) = pat.kind
-                    && ident.name == self.ident_name
-                {
-                    self.result = *init;
-                } else {
-                    hir::intravisit::walk_stmt(self, ex);
+                // FIXME: This really should be taking scoping, etc into account.
+                impl<'v> Visitor<'v> for LetVisitor<'v> {
+                    fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
+                        if let hir::StmtKind::Local(hir::Local { pat, init, .. }) = &ex.kind
+                            && let Binding(_, _, ident, ..) = pat.kind
+                            && ident.name == self.ident_name
+                        {
+                            self.result = *init;
+                        } else {
+                            hir::intravisit::walk_stmt(self, ex);
+                        }
+                    }
                 }
-            }
-        }
 
-        let mut visitor = LetVisitor { result: None, ident_name: seg1.ident.name };
-        visitor.visit_body(body);
+                let mut visitor = LetVisitor { result: None, ident_name: seg1.ident.name };
+                visitor.visit_body(body);
 
-        if let Node::Expr(call_expr) = self.tcx.parent_hir_node(seg1.hir_id)
-            && let Some(expr) = visitor.result
-            && let Some(self_ty) = self.node_ty_opt(expr.hir_id)
-        {
-            let probe = self.lookup_probe_for_diagnostic(
-                seg2.ident,
-                self_ty,
-                call_expr,
-                ProbeScope::TraitsInScope,
-                None,
-            );
-            if probe.is_ok() {
-                let sm = self.infcx.tcx.sess.source_map();
-                diag.span_suggestion_verbose(
-                    sm.span_extend_while(seg1.ident.span.shrink_to_hi(), |c| c == ':').unwrap(),
-                    "you may have meant to call an instance method",
-                    ".",
-                    Applicability::MaybeIncorrect,
-                );
-            }
-        }
-        diag.emit();
+                if let Node::Expr(call_expr) = self.tcx.parent_hir_node(seg1.hir_id)
+                    && let Some(expr) = visitor.result
+                    && let Some(self_ty) = self.node_ty_opt(expr.hir_id)
+                {
+                    let probe = self.lookup_probe_for_diagnostic(
+                        seg2.ident,
+                        self_ty,
+                        call_expr,
+                        ProbeScope::TraitsInScope,
+                        None,
+                    );
+                    if probe.is_ok() {
+                        let sm = self.infcx.tcx.sess.source_map();
+                        err.span_suggestion_verbose(
+                            sm.span_extend_while(seg1.ident.span.shrink_to_hi(), |c| c == ':')
+                                .unwrap(),
+                            "you may have meant to call an instance method",
+                            ".",
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                }
+            },
+        );
     }
 
     /// Suggest calling a method on a field i.e. `a.field.bar()` instead of `a.bar()`
diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs
index ed102a7fac0be..0740a3fd3e970 100644
--- a/compiler/rustc_hir_typeck/src/writeback.rs
+++ b/compiler/rustc_hir_typeck/src/writeback.rs
@@ -772,10 +772,6 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
     fn report_error(&self, p: impl Into<ty::GenericArg<'tcx>>) -> ErrorGuaranteed {
         if let Some(guar) = self.fcx.dcx().has_errors() {
             guar
-        } else if self.fcx.dcx().stashed_err_count() > 0 {
-            // Without this case we sometimes get uninteresting and extraneous
-            // "type annotations needed" errors.
-            self.fcx.dcx().delayed_bug("error in Resolver")
         } else {
             self.fcx
                 .err_ctxt()
diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs
index cc09a09468869..a086d89b93335 100644
--- a/compiler/rustc_infer/src/infer/at.rs
+++ b/compiler/rustc_infer/src/infer/at.rs
@@ -87,7 +87,6 @@ impl<'tcx> InferCtxt<'tcx> {
             reported_signature_mismatch: self.reported_signature_mismatch.clone(),
             tainted_by_errors: self.tainted_by_errors.clone(),
             err_count_on_creation: self.err_count_on_creation,
-            stashed_err_count_on_creation: self.stashed_err_count_on_creation,
             universe: self.universe.clone(),
             intercrate,
             next_trait_solver: self.next_trait_solver,
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index d7e16488508b5..6f52ded355194 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -306,12 +306,6 @@ pub struct InferCtxt<'tcx> {
     // FIXME(matthewjasper) Merge into `tainted_by_errors`
     err_count_on_creation: usize,
 
-    /// Track how many errors were stashed when this infcx is created.
-    /// Used for the same purpose as `err_count_on_creation`, even
-    /// though it's weaker because the count can go up and down.
-    // FIXME(matthewjasper) Merge into `tainted_by_errors`
-    stashed_err_count_on_creation: usize,
-
     /// What is the innermost universe we have created? Starts out as
     /// `UniverseIndex::root()` but grows from there as we enter
     /// universal quantifiers.
@@ -717,7 +711,6 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
             reported_signature_mismatch: Default::default(),
             tainted_by_errors: Cell::new(None),
             err_count_on_creation: tcx.dcx().err_count_excluding_lint_errs(),
-            stashed_err_count_on_creation: tcx.dcx().stashed_err_count(),
             universe: Cell::new(ty::UniverseIndex::ROOT),
             intercrate,
             next_trait_solver,
@@ -1274,14 +1267,6 @@ impl<'tcx> InferCtxt<'tcx> {
             let guar = self.dcx().has_errors().unwrap();
             self.set_tainted_by_errors(guar);
             Some(guar)
-        } else if self.dcx().stashed_err_count() > self.stashed_err_count_on_creation {
-            // Errors stashed since this infcx was made. Not entirely reliable
-            // because the count of stashed errors can go down. But without
-            // this case we get a moderate number of uninteresting and
-            // extraneous "type annotations needed" errors.
-            let guar = self.dcx().delayed_bug("tainted_by_errors: stashed bug awaiting emission");
-            self.set_tainted_by_errors(guar);
-            Some(guar)
         } else {
             None
         }
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs
index 993b1d739a13d..3a5c585366a31 100644
--- a/compiler/rustc_lint/src/array_into_iter.rs
+++ b/compiler/rustc_lint/src/array_into_iter.rs
@@ -70,15 +70,11 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
 
             // Check if the method call actually calls the libcore
             // `IntoIterator::into_iter`.
-            let trait_id = cx
-                .typeck_results()
-                .type_dependent_def_id(expr.hir_id)
-                .and_then(|did| cx.tcx.trait_of_item(did));
-            if trait_id.is_none()
-                || !cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id.unwrap())
-            {
-                return;
-            }
+            let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+            match cx.tcx.trait_of_item(def_id) {
+                Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {}
+                _ => return,
+            };
 
             // As this is a method call expression, we have at least one argument.
             let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index d97f0e4c32196..6fdb03c0babb5 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -847,13 +847,15 @@ impl<'tcx> OpaqueHiddenType<'tcx> {
         opaque_def_id: LocalDefId,
         tcx: TyCtxt<'tcx>,
     ) -> Result<Diag<'tcx>, ErrorGuaranteed> {
-        if let Some(diag) = tcx
-            .sess
-            .dcx()
-            .steal_diagnostic(tcx.def_span(opaque_def_id), StashKey::OpaqueHiddenTypeMismatch)
-        {
-            diag.cancel();
-        }
+        // We used to cancel here for slightly better error messages, but
+        // cancelling stashed diagnostics is no longer allowed because it
+        // causes problems when tracking whether errors have actually
+        // occurred.
+        tcx.sess.dcx().try_steal_modify_and_emit_err(
+            tcx.def_span(opaque_def_id),
+            StashKey::OpaqueHiddenTypeMismatch,
+            |_err| {},
+        );
         (self.ty, other.ty).error_reported()?;
         // Found different concrete types for the opaque type.
         let sub_diag = if self.span == other.span {
diff --git a/compiler/rustc_middle/src/ty/region.rs b/compiler/rustc_middle/src/ty/region.rs
index b206727f0514e..51a4a9f411c02 100644
--- a/compiler/rustc_middle/src/ty/region.rs
+++ b/compiler/rustc_middle/src/ty/region.rs
@@ -91,8 +91,8 @@ impl<'tcx> Region<'tcx> {
 
     /// Constructs a `RegionKind::ReError` region.
     #[track_caller]
-    pub fn new_error(tcx: TyCtxt<'tcx>, reported: ErrorGuaranteed) -> Region<'tcx> {
-        tcx.intern_region(ty::ReError(reported))
+    pub fn new_error(tcx: TyCtxt<'tcx>, guar: ErrorGuaranteed) -> Region<'tcx> {
+        tcx.intern_region(ty::ReError(guar))
     }
 
     /// Constructs a `RegionKind::ReError` region and registers a delayed bug to ensure it gets
diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs
index 27c78d18d1922..06be8191dc4dd 100644
--- a/compiler/rustc_middle/src/ty/sty.rs
+++ b/compiler/rustc_middle/src/ty/sty.rs
@@ -1528,8 +1528,8 @@ impl<'tcx> Ty<'tcx> {
     }
 
     /// Constructs a `TyKind::Error` type with current `ErrorGuaranteed`
-    pub fn new_error(tcx: TyCtxt<'tcx>, reported: ErrorGuaranteed) -> Ty<'tcx> {
-        Ty::new(tcx, Error(reported))
+    pub fn new_error(tcx: TyCtxt<'tcx>, guar: ErrorGuaranteed) -> Ty<'tcx> {
+        Ty::new(tcx, Error(guar))
     }
 
     /// Constructs a `TyKind::Error` type and registers a `span_delayed_bug` to ensure it gets used.
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index f5a7bfd42ff42..316a9c4f8dfc2 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -1762,24 +1762,25 @@ impl<'a> Parser<'a> {
         err: impl FnOnce(&Self) -> Diag<'a>,
     ) -> L {
         assert!(could_be_unclosed_char_literal(ident));
-        if let Some(diag) = self.dcx().steal_diagnostic(ident.span, StashKey::LifetimeIsChar) {
-            diag.with_span_suggestion_verbose(
-                ident.span.shrink_to_hi(),
-                "add `'` to close the char literal",
-                "'",
-                Applicability::MaybeIncorrect,
-            )
-            .emit();
-        } else {
-            err(self)
-                .with_span_suggestion_verbose(
+        self.dcx()
+            .try_steal_modify_and_emit_err(ident.span, StashKey::LifetimeIsChar, |err| {
+                err.span_suggestion_verbose(
                     ident.span.shrink_to_hi(),
                     "add `'` to close the char literal",
                     "'",
                     Applicability::MaybeIncorrect,
-                )
-                .emit();
-        }
+                );
+            })
+            .unwrap_or_else(|| {
+                err(self)
+                    .with_span_suggestion_verbose(
+                        ident.span.shrink_to_hi(),
+                        "add `'` to close the char literal",
+                        "'",
+                        Applicability::MaybeIncorrect,
+                    )
+                    .emit()
+            });
         let name = ident.without_first_quote().name;
         mk_lit_char(name, ident.span)
     }
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index c12d35ec73c99..04c0cf7436e2b 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -2520,14 +2520,6 @@ fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
         if attr.style == AttrStyle::Inner {
             for attr_to_check in ATTRS_TO_CHECK {
                 if attr.has_name(*attr_to_check) {
-                    if let AttrKind::Normal(ref p) = attr.kind
-                        && let Some(diag) = tcx.dcx().steal_diagnostic(
-                            p.item.path.span,
-                            StashKey::UndeterminedMacroResolution,
-                        )
-                    {
-                        diag.cancel();
-                    }
                     let item = tcx
                         .hir()
                         .items()
@@ -2537,7 +2529,7 @@ fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
                             span: item.ident.span,
                             kind: item.kind.descr(),
                         });
-                    tcx.dcx().emit_err(errors::InvalidAttrAtCrateLevel {
+                    let err = tcx.dcx().create_err(errors::InvalidAttrAtCrateLevel {
                         span: attr.span,
                         sugg_span: tcx
                             .sess
@@ -2553,6 +2545,16 @@ fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
                         name: *attr_to_check,
                         item,
                     });
+
+                    if let AttrKind::Normal(ref p) = attr.kind {
+                        tcx.dcx().try_steal_replace_and_emit_err(
+                            p.item.path.span,
+                            StashKey::UndeterminedMacroResolution,
+                            err,
+                        );
+                    } else {
+                        err.emit();
+                    }
                 }
             }
         }
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index 1c6bd88712821..9d8a9f5fce3e4 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -988,10 +988,7 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> {
     fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
         if let hir::ExprKind::Struct(qpath, fields, ref base) = expr.kind {
             let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
-            let Some(adt) = self.typeck_results().expr_ty(expr).ty_adt_def() else {
-                self.tcx.dcx().span_delayed_bug(expr.span, "no adt_def for expression");
-                return;
-            };
+            let adt = self.typeck_results().expr_ty(expr).ty_adt_def().unwrap();
             let variant = adt.variant_of_res(res);
             if let Some(base) = *base {
                 // If the expression uses FRU we need to make sure all the unmentioned fields
diff --git a/compiler/rustc_query_system/src/query/plumbing.rs b/compiler/rustc_query_system/src/query/plumbing.rs
index e4c596b10b875..40517407ee6a1 100644
--- a/compiler/rustc_query_system/src/query/plumbing.rs
+++ b/compiler/rustc_query_system/src/query/plumbing.rs
@@ -149,8 +149,7 @@ where
             let guar = if let Some(root) = cycle_error.cycle.first()
                 && let Some(span) = root.query.span
             {
-                error.stash(span, StashKey::Cycle);
-                qcx.dep_context().sess().dcx().span_delayed_bug(span, "delayed cycle error")
+                error.stash(span, StashKey::Cycle).unwrap()
             } else {
                 error.emit()
             };
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index 752bb05f3d79d..1b79786c1b832 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -105,8 +105,7 @@ pub fn feature_err_issue(
 
     // Cancel an earlier warning for this same error, if it exists.
     if let Some(span) = span.primary_span() {
-        if let Some(err) = sess.parse_sess.dcx.steal_diagnostic(span, StashKey::EarlySyntaxWarning)
-        {
+        if let Some(err) = sess.parse_sess.dcx.steal_non_err(span, StashKey::EarlySyntaxWarning) {
             err.cancel()
         }
     }
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs
index dcbb63f00f78b..d9e49f1eb0ab4 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs
@@ -889,7 +889,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                 }
             }
 
-            SelectionError::OpaqueTypeAutoTraitLeakageUnknown(def_id) => self.report_opaque_type_auto_trait_leakage(
+            SelectionError::OpaqueTypeAutoTraitLeakageUnknown(def_id) => return self.report_opaque_type_auto_trait_leakage(
                 &obligation,
                 def_id,
             ),
@@ -2252,8 +2252,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                                 ErrorCode::E0282,
                                 false,
                             );
-                            err.stash(span, StashKey::MaybeForgetReturn);
-                            return self.dcx().delayed_bug("stashed error never reported");
+                            return err.stash(span, StashKey::MaybeForgetReturn).unwrap();
                         }
                         Some(e) => return e,
                     }
@@ -2766,7 +2765,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             self.suggest_unsized_bound_if_applicable(err, obligation);
             if let Some(span) = err.span.primary_span()
                 && let Some(mut diag) =
-                    self.tcx.dcx().steal_diagnostic(span, StashKey::AssociatedTypeSuggestion)
+                    self.tcx.dcx().steal_non_err(span, StashKey::AssociatedTypeSuggestion)
                 && let Ok(ref mut s1) = err.suggestions
                 && let Ok(ref mut s2) = diag.suggestions
             {
@@ -3291,7 +3290,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         &self,
         obligation: &PredicateObligation<'tcx>,
         def_id: DefId,
-    ) -> Diag<'tcx> {
+    ) -> ErrorGuaranteed {
         let name = match self.tcx.opaque_type_origin(def_id.expect_local()) {
             hir::OpaqueTyOrigin::FnReturn(_) | hir::OpaqueTyOrigin::AsyncFn(_) => {
                 "opaque type".to_string()
@@ -3318,12 +3317,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             }
         };
 
-        if let Some(diag) = self.dcx().steal_diagnostic(self.tcx.def_span(def_id), StashKey::Cycle)
-        {
-            diag.cancel();
-        }
-
-        err
+        self.note_obligation_cause(&mut err, &obligation);
+        self.point_at_returns_when_relevant(&mut err, &obligation);
+        self.dcx().try_steal_replace_and_emit_err(self.tcx.def_span(def_id), StashKey::Cycle, err)
     }
 
     fn report_signature_mismatch_error(
diff --git a/src/tools/clippy/tests/ui/crashes/unreachable-array-or-slice.rs b/src/tools/clippy/tests/ui/crashes/unreachable-array-or-slice.rs
new file mode 100644
index 0000000000000..b56abccbd4112
--- /dev/null
+++ b/src/tools/clippy/tests/ui/crashes/unreachable-array-or-slice.rs
@@ -0,0 +1,8 @@
+struct Foo(isize, isize, isize, isize);
+
+pub fn main() {
+    let Self::anything_here_kills_it(a, b, ..) = Foo(5, 5, 5, 5);
+    match [5, 5, 5, 5] {
+        [..] => { }
+    }
+}
diff --git a/src/tools/clippy/tests/ui/crashes/unreachable-array-or-slice.stderr b/src/tools/clippy/tests/ui/crashes/unreachable-array-or-slice.stderr
new file mode 100644
index 0000000000000..9e0d3b934b80f
--- /dev/null
+++ b/src/tools/clippy/tests/ui/crashes/unreachable-array-or-slice.stderr
@@ -0,0 +1,9 @@
+error[E0433]: failed to resolve: `Self` is only available in impls, traits, and type definitions
+  --> tests/ui/crashes/unreachable-array-or-slice.rs:4:9
+   |
+LL |     let Self::anything_here_kills_it(a, b, ..) = Foo(5, 5, 5, 5);
+   |         ^^^^ `Self` is only available in impls, traits, and type definitions
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0433`.
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 8a7133fea438d..281a32b77c5b5 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -68,7 +68,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
         queries: &'tcx rustc_interface::Queries<'tcx>,
     ) -> Compilation {
         queries.global_ctxt().unwrap().enter(|tcx| {
-            if tcx.sess.dcx().has_errors().is_some() {
+            if tcx.sess.dcx().has_errors_or_delayed_bugs().is_some() {
                 tcx.dcx().fatal("miri cannot be run on programs that fail compilation");
             }
 
diff --git a/src/tools/rustfmt/src/formatting.rs b/src/tools/rustfmt/src/formatting.rs
index 323ae83fe6ede..1c64921b1a68d 100644
--- a/src/tools/rustfmt/src/formatting.rs
+++ b/src/tools/rustfmt/src/formatting.rs
@@ -109,7 +109,7 @@ fn format_project<T: FormatHandler>(
     let main_file = input.file_name();
     let input_is_stdin = main_file == FileName::Stdin;
 
-    let mut parse_session = ParseSess::new(config)?;
+    let parse_session = ParseSess::new(config)?;
     if config.skip_children() && parse_session.ignore_file(&main_file) {
         return Ok(FormatReport::new());
     }
@@ -118,20 +118,11 @@ fn format_project<T: FormatHandler>(
     let mut report = FormatReport::new();
     let directory_ownership = input.to_directory_ownership();
 
-    // rustfmt doesn't use `run_compiler` like other tools, so it must emit any
-    // stashed diagnostics itself, otherwise the `DiagCtxt` will assert when
-    // dropped. The final result here combines the parsing result and the
-    // `emit_stashed_diagnostics` result.
-    let parse_res = Parser::parse_crate(input, &parse_session);
-    let stashed_res = parse_session.emit_stashed_diagnostics();
-    let krate = match (parse_res, stashed_res) {
-        (Ok(krate), None) => krate,
-        (parse_res, _) => {
-            // Surface parse error via Session (errors are merged there from report).
-            let forbid_verbose = match parse_res {
-                Err(e) if e != ParserError::ParsePanicError => true,
-                _ => input_is_stdin,
-            };
+    let krate = match Parser::parse_crate(input, &parse_session) {
+        Ok(krate) => krate,
+        // Surface parse error via Session (errors are merged there from report)
+        Err(e) => {
+            let forbid_verbose = input_is_stdin || e != ParserError::ParsePanicError;
             should_emit_verbose(forbid_verbose, config, || {
                 eprintln!("The Rust parser panicked");
             });
diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs
index e33f1ca755ca2..f5defe63c13ba 100644
--- a/src/tools/rustfmt/src/parse/session.rs
+++ b/src/tools/rustfmt/src/parse/session.rs
@@ -5,9 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
 use rustc_data_structures::sync::{IntoDynSyncSend, Lrc};
 use rustc_errors::emitter::{DynEmitter, Emitter, HumanEmitter};
 use rustc_errors::translation::Translate;
-use rustc_errors::{
-    ColorConfig, Diag, DiagCtxt, DiagInner, ErrorGuaranteed, Level as DiagnosticLevel,
-};
+use rustc_errors::{ColorConfig, Diag, DiagCtxt, DiagInner, Level as DiagnosticLevel};
 use rustc_session::parse::ParseSess as RawParseSess;
 use rustc_span::{
     source_map::{FilePathMapping, SourceMap},
@@ -230,10 +228,6 @@ impl ParseSess {
         self.ignore_path_set.as_ref().is_match(path)
     }
 
-    pub(crate) fn emit_stashed_diagnostics(&mut self) -> Option<ErrorGuaranteed> {
-        self.parse_sess.dcx.emit_stashed_diagnostics()
-    }
-
     pub(crate) fn set_silent_emitter(&mut self) {
         self.parse_sess.dcx = DiagCtxt::with_emitter(silent_emitter());
     }
diff --git a/tests/ui/foreign/stashed-issue-121451.rs b/tests/ui/foreign/stashed-issue-121451.rs
new file mode 100644
index 0000000000000..97a4af374758d
--- /dev/null
+++ b/tests/ui/foreign/stashed-issue-121451.rs
@@ -0,0 +1,4 @@
+extern "C" fn _f() -> libc::uintptr_t {}
+//~^ ERROR failed to resolve: use of undeclared crate or module `libc`
+
+fn main() {}
diff --git a/tests/ui/foreign/stashed-issue-121451.stderr b/tests/ui/foreign/stashed-issue-121451.stderr
new file mode 100644
index 0000000000000..440d98d6f460d
--- /dev/null
+++ b/tests/ui/foreign/stashed-issue-121451.stderr
@@ -0,0 +1,9 @@
+error[E0433]: failed to resolve: use of undeclared crate or module `libc`
+  --> $DIR/stashed-issue-121451.rs:1:23
+   |
+LL | extern "C" fn _f() -> libc::uintptr_t {}
+   |                       ^^^^ use of undeclared crate or module `libc`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0433`.
diff --git a/tests/ui/impl-trait/stashed-diag-issue-121504.rs b/tests/ui/impl-trait/stashed-diag-issue-121504.rs
new file mode 100644
index 0000000000000..4ac8ffe8931c6
--- /dev/null
+++ b/tests/ui/impl-trait/stashed-diag-issue-121504.rs
@@ -0,0 +1,13 @@
+//@ edition: 2021
+
+trait MyTrait {
+    async fn foo(self) -> (Self, i32);
+}
+
+impl MyTrait for xyz::T { //~ ERROR failed to resolve: use of undeclared crate or module `xyz`
+    async fn foo(self, key: i32) -> (u32, i32) {
+        (self, key)
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/impl-trait/stashed-diag-issue-121504.stderr b/tests/ui/impl-trait/stashed-diag-issue-121504.stderr
new file mode 100644
index 0000000000000..6a881dc7f9fbd
--- /dev/null
+++ b/tests/ui/impl-trait/stashed-diag-issue-121504.stderr
@@ -0,0 +1,9 @@
+error[E0433]: failed to resolve: use of undeclared crate or module `xyz`
+  --> $DIR/stashed-diag-issue-121504.rs:7:18
+   |
+LL | impl MyTrait for xyz::T {
+   |                  ^^^ use of undeclared crate or module `xyz`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0433`.
diff --git a/tests/ui/lowering/issue-121108.rs b/tests/ui/lowering/issue-121108.rs
index 6b2dd24e4a842..6dfb7e0082149 100644
--- a/tests/ui/lowering/issue-121108.rs
+++ b/tests/ui/lowering/issue-121108.rs
@@ -3,7 +3,7 @@
 use std::ptr::addr_of;
 
 const UNINHABITED_VARIANT: () = unsafe {
-    let v = *addr_of!(data).cast(); //~ ERROR cannot determine resolution for the macro `addr_of`
+    let v = *addr_of!(data).cast();
 };
 
 fn main() {}
diff --git a/tests/ui/lowering/issue-121108.stderr b/tests/ui/lowering/issue-121108.stderr
index c2c5746d6f142..e4942e8cb0796 100644
--- a/tests/ui/lowering/issue-121108.stderr
+++ b/tests/ui/lowering/issue-121108.stderr
@@ -13,13 +13,5 @@ LL - #![derive(Clone, Copy)]
 LL + #[derive(Clone, Copy)]
    |
 
-error: cannot determine resolution for the macro `addr_of`
-  --> $DIR/issue-121108.rs:6:14
-   |
-LL |     let v = *addr_of!(data).cast();
-   |              ^^^^^^^
-   |
-   = note: import resolution is stuck, try simplifying macro imports
-
-error: aborting due to 2 previous errors
+error: aborting due to 1 previous error
 
diff --git a/tests/ui/resolve/issue-111312.rs b/tests/ui/resolve/issue-111312.rs
index 68fc8573dde3e..79c6f67dadd39 100644
--- a/tests/ui/resolve/issue-111312.rs
+++ b/tests/ui/resolve/issue-111312.rs
@@ -7,5 +7,7 @@ trait Has {
 trait HasNot {}
 
 fn main() {
-    HasNot::has(); //~ ERROR
+    HasNot::has();
+    //~^ ERROR trait objects must include the `dyn` keyword
+    //~| ERROR no function or associated item named `has` found for trait `HasNot`
 }
diff --git a/tests/ui/resolve/issue-111312.stderr b/tests/ui/resolve/issue-111312.stderr
index 7e7ef22ae6195..431802ead30c4 100644
--- a/tests/ui/resolve/issue-111312.stderr
+++ b/tests/ui/resolve/issue-111312.stderr
@@ -1,3 +1,14 @@
+error[E0782]: trait objects must include the `dyn` keyword
+  --> $DIR/issue-111312.rs:10:5
+   |
+LL |     HasNot::has();
+   |     ^^^^^^
+   |
+help: add `dyn` keyword before this trait
+   |
+LL |     <dyn HasNot>::has();
+   |     ++++       +
+
 error[E0599]: no function or associated item named `has` found for trait `HasNot`
   --> $DIR/issue-111312.rs:10:13
    |
@@ -10,6 +21,7 @@ note: `Has` defines an item `has`
 LL | trait Has {
    | ^^^^^^^^^
 
-error: aborting due to 1 previous error
+error: aborting due to 2 previous errors
 
-For more information about this error, try `rustc --explain E0599`.
+Some errors have detailed explanations: E0599, E0782.
+For more information about an error, try `rustc --explain E0599`.
diff --git a/tests/ui/resolve/issue-111727.rs b/tests/ui/resolve/issue-111727.rs
index 740037fe43425..fcab924b80977 100644
--- a/tests/ui/resolve/issue-111727.rs
+++ b/tests/ui/resolve/issue-111727.rs
@@ -1,5 +1,7 @@
 //@ edition: 2021
 
 fn main() {
-    std::any::Any::create(); //~ ERROR
+    std::any::Any::create();
+    //~^ ERROR trait objects must include the `dyn` keyword
+    //~| ERROR no function or associated item named `create` found for trait `Any`
 }
diff --git a/tests/ui/resolve/issue-111727.stderr b/tests/ui/resolve/issue-111727.stderr
index b58168d0e7581..1ef5a1a1d5efb 100644
--- a/tests/ui/resolve/issue-111727.stderr
+++ b/tests/ui/resolve/issue-111727.stderr
@@ -1,9 +1,21 @@
+error[E0782]: trait objects must include the `dyn` keyword
+  --> $DIR/issue-111727.rs:4:5
+   |
+LL |     std::any::Any::create();
+   |     ^^^^^^^^^^^^^
+   |
+help: add `dyn` keyword before this trait
+   |
+LL |     <dyn std::any::Any>::create();
+   |     ++++              +
+
 error[E0599]: no function or associated item named `create` found for trait `Any`
   --> $DIR/issue-111727.rs:4:20
    |
 LL |     std::any::Any::create();
    |                    ^^^^^^ function or associated item not found in `Any`
 
-error: aborting due to 1 previous error
+error: aborting due to 2 previous errors
 
-For more information about this error, try `rustc --explain E0599`.
+Some errors have detailed explanations: E0599, E0782.
+For more information about an error, try `rustc --explain E0599`.
diff --git a/tests/ui/type-alias-impl-trait/different_defining_uses_never_type-2.rs b/tests/ui/type-alias-impl-trait/different_defining_uses_never_type-2.rs
index b2842df150a3f..4b5f455e38150 100644
--- a/tests/ui/type-alias-impl-trait/different_defining_uses_never_type-2.rs
+++ b/tests/ui/type-alias-impl-trait/different_defining_uses_never_type-2.rs
@@ -11,6 +11,7 @@ fn foo<'a, 'b>() -> Tait<'a> {
     }
     let x: Tait<'a> = ();
     x
+    //~^ ERROR concrete type differs from previous defining opaque type use
 }
 
 fn main() {}
diff --git a/tests/ui/type-alias-impl-trait/different_defining_uses_never_type-2.stderr b/tests/ui/type-alias-impl-trait/different_defining_uses_never_type-2.stderr
index e5cee49cf2961..6f5be5467f704 100644
--- a/tests/ui/type-alias-impl-trait/different_defining_uses_never_type-2.stderr
+++ b/tests/ui/type-alias-impl-trait/different_defining_uses_never_type-2.stderr
@@ -1,3 +1,15 @@
+error: concrete type differs from previous defining opaque type use
+  --> $DIR/different_defining_uses_never_type-2.rs:13:5
+   |
+LL |     x
+   |     ^ expected `i32`, got `()`
+   |
+note: previous use here
+  --> $DIR/different_defining_uses_never_type-2.rs:8:31
+   |
+LL |             let y: Tait<'b> = 1i32;
+   |                               ^^^^
+
 error: concrete type differs from previous defining opaque type use
   --> $DIR/different_defining_uses_never_type-2.rs:8:31
    |
@@ -10,5 +22,5 @@ note: previous use here
 LL |         if { return } {
    |              ^^^^^^
 
-error: aborting due to 1 previous error
+error: aborting due to 2 previous errors