about summary refs log tree commit diff
path: root/compiler/rustc_errors/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_errors/src')
-rw-r--r--compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs18
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs199
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs784
-rw-r--r--compiler/rustc_errors/src/diagnostic_impls.rs90
-rw-r--r--compiler/rustc_errors/src/emitter.rs123
-rw-r--r--compiler/rustc_errors/src/json.rs75
-rw-r--r--compiler/rustc_errors/src/json/tests.rs13
-rw-r--r--compiler/rustc_errors/src/lib.rs1045
-rw-r--r--compiler/rustc_errors/src/markdown/parse.rs2
-rw-r--r--compiler/rustc_errors/src/markdown/tests/term.rs1
10 files changed, 869 insertions, 1481 deletions
diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
index 203e529120b..5c0e210f147 100644
--- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
+++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
@@ -20,7 +20,7 @@ use rustc_span::source_map::SourceMap;
 use rustc_span::SourceFile;
 
 /// Generates diagnostics using annotate-snippet
-pub struct AnnotateSnippetEmitterWriter {
+pub struct AnnotateSnippetEmitter {
     source_map: Option<Lrc<SourceMap>>,
     fluent_bundle: Option<Lrc<FluentBundle>>,
     fallback_bundle: LazyFallbackBundle,
@@ -33,7 +33,7 @@ pub struct AnnotateSnippetEmitterWriter {
     macro_backtrace: bool,
 }
 
-impl Translate for AnnotateSnippetEmitterWriter {
+impl Translate for AnnotateSnippetEmitter {
     fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
         self.fluent_bundle.as_ref()
     }
@@ -43,7 +43,7 @@ impl Translate for AnnotateSnippetEmitterWriter {
     }
 }
 
-impl Emitter for AnnotateSnippetEmitterWriter {
+impl Emitter for AnnotateSnippetEmitter {
     /// The entry point for the diagnostics generation
     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
         let fluent_args = to_fluent_args(diag.args());
@@ -60,7 +60,7 @@ impl Emitter for AnnotateSnippetEmitterWriter {
 
         self.emit_messages_default(
             &diag.level,
-            &diag.message,
+            &diag.messages,
             &fluent_args,
             &diag.code,
             &primary_span,
@@ -86,9 +86,7 @@ fn source_string(file: Lrc<SourceFile>, line: &Line) -> String {
 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
 fn annotation_type_for_level(level: Level) -> AnnotationType {
     match level {
-        Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error { .. } => {
-            AnnotationType::Error
-        }
+        Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error => AnnotationType::Error,
         Level::Warning(_) => AnnotationType::Warning,
         Level::Note | Level::OnceNote => AnnotationType::Note,
         Level::Help | Level::OnceHelp => AnnotationType::Help,
@@ -99,7 +97,7 @@ fn annotation_type_for_level(level: Level) -> AnnotationType {
     }
 }
 
-impl AnnotateSnippetEmitterWriter {
+impl AnnotateSnippetEmitter {
     pub fn new(
         source_map: Option<Lrc<SourceMap>>,
         fluent_bundle: Option<Lrc<FluentBundle>>,
@@ -138,7 +136,7 @@ impl AnnotateSnippetEmitterWriter {
         let message = self.translate_messages(messages, args);
         if let Some(source_map) = &self.source_map {
             // Make sure our primary file comes first
-            let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() {
+            let primary_lo = if let Some(primary_span) = msp.primary_span().as_ref() {
                 if primary_span.is_dummy() {
                     // FIXME(#59346): Not sure when this is the case and what
                     // should be done if it happens
@@ -203,7 +201,7 @@ impl AnnotateSnippetEmitterWriter {
                         Slice {
                             source,
                             line_start: *line_index,
-                            origin: Some(&file_name),
+                            origin: Some(file_name),
                             // FIXME(#59346): Not really sure when `fold` should be true or false
                             fold: false,
                             annotations: annotations
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 470f318eb33..701c1c02ab0 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -3,7 +3,7 @@ use crate::{
     CodeSuggestion, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Level, MultiSpan,
     SubdiagnosticMessage, Substitution, SubstitutionPart, SuggestionStyle,
 };
-use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
 use rustc_error_messages::fluent_value_from_str_list_sep_by_and;
 use rustc_error_messages::FluentValue;
 use rustc_lint_defs::{Applicability, LintExpectationId};
@@ -91,10 +91,7 @@ where
 #[rustc_diagnostic_item = "DecorateLint"]
 pub trait DecorateLint<'a, G: EmissionGuarantee> {
     /// Decorate and emit a lint.
-    fn decorate_lint<'b>(
-        self,
-        diag: &'b mut DiagnosticBuilder<'a, G>,
-    ) -> &'b mut DiagnosticBuilder<'a, G>;
+    fn decorate_lint<'b>(self, diag: &'b mut DiagnosticBuilder<'a, G>);
 
     fn msg(&self) -> DiagnosticMessage;
 }
@@ -106,7 +103,7 @@ pub struct Diagnostic {
     // outside of what methods in this crate themselves allow.
     pub(crate) level: Level,
 
-    pub message: Vec<(DiagnosticMessage, Style)>,
+    pub messages: Vec<(DiagnosticMessage, Style)>,
     pub code: Option<DiagnosticId>,
     pub span: MultiSpan,
     pub children: Vec<SubDiagnostic>,
@@ -164,9 +161,8 @@ pub enum DiagnosticId {
 #[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
 pub struct SubDiagnostic {
     pub level: Level,
-    pub message: Vec<(DiagnosticMessage, Style)>,
+    pub messages: Vec<(DiagnosticMessage, Style)>,
     pub span: MultiSpan,
-    pub render_span: Option<MultiSpan>,
 }
 
 #[derive(Debug, PartialEq, Eq)]
@@ -216,17 +212,20 @@ impl StringPart {
     }
 }
 
+// Note: most of these methods are setters that return `&mut Self`. The small
+// number of simple getter functions all have `get_` prefixes to distinguish
+// them from the setters.
 impl Diagnostic {
     #[track_caller]
     pub fn new<M: Into<DiagnosticMessage>>(level: Level, message: M) -> Self {
-        Diagnostic::new_with_code(level, None, message)
+        Diagnostic::new_with_messages(level, vec![(message.into(), Style::NoStyle)])
     }
 
     #[track_caller]
     pub fn new_with_messages(level: Level, messages: Vec<(DiagnosticMessage, Style)>) -> Self {
         Diagnostic {
             level,
-            message: messages,
+            messages,
             code: None,
             span: MultiSpan::new(),
             children: vec![],
@@ -238,26 +237,6 @@ impl Diagnostic {
         }
     }
 
-    #[track_caller]
-    pub fn new_with_code<M: Into<DiagnosticMessage>>(
-        level: Level,
-        code: Option<DiagnosticId>,
-        message: M,
-    ) -> Self {
-        Diagnostic {
-            level,
-            message: vec![(message.into(), Style::NoStyle)],
-            code,
-            span: MultiSpan::new(),
-            children: vec![],
-            suggestions: Ok(vec![]),
-            args: Default::default(),
-            sort_span: DUMMY_SP,
-            is_lint: false,
-            emitted_at: DiagnosticLocation::caller(),
-        }
-    }
-
     #[inline(always)]
     pub fn level(&self) -> Level {
         self.level
@@ -265,11 +244,9 @@ impl Diagnostic {
 
     pub fn is_error(&self) -> bool {
         match self.level {
-            Level::Bug
-            | Level::DelayedBug
-            | Level::Fatal
-            | Level::Error { .. }
-            | Level::FailureNote => true,
+            Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error | Level::FailureNote => {
+                true
+            }
 
             Level::Warning(_)
             | Level::Note
@@ -281,9 +258,9 @@ impl Diagnostic {
         }
     }
 
-    pub fn update_unstable_expectation_id(
+    pub(crate) fn update_unstable_expectation_id(
         &mut self,
-        unstable_to_stable: &FxHashMap<LintExpectationId, LintExpectationId>,
+        unstable_to_stable: &FxIndexMap<LintExpectationId, LintExpectationId>,
     ) {
         if let Level::Expect(expectation_id) | Level::Warning(Some(expectation_id)) =
             &mut self.level
@@ -307,14 +284,14 @@ impl Diagnostic {
     }
 
     /// Indicates whether this diagnostic should show up in cargo's future breakage report.
-    pub fn has_future_breakage(&self) -> bool {
+    pub(crate) fn has_future_breakage(&self) -> bool {
         match self.code {
             Some(DiagnosticId::Lint { has_future_breakage, .. }) => has_future_breakage,
             _ => false,
         }
     }
 
-    pub fn is_force_warn(&self) -> bool {
+    pub(crate) fn is_force_warn(&self) -> bool {
         match self.code {
             Some(DiagnosticId::Lint { is_force_warn, .. }) => is_force_warn,
             _ => false,
@@ -332,25 +309,27 @@ impl Diagnostic {
     /// In the meantime, though, callsites are required to deal with the "bug"
     /// locally in whichever way makes the most sense.
     #[track_caller]
-    pub fn downgrade_to_delayed_bug(&mut self) -> &mut Self {
+    pub fn downgrade_to_delayed_bug(&mut self) {
         assert!(
             self.is_error(),
             "downgrade_to_delayed_bug: cannot downgrade {:?} to DelayedBug: not an error",
             self.level
         );
         self.level = Level::DelayedBug;
-
-        self
     }
 
-    /// Adds a span/label to be included in the resulting snippet.
+    /// Appends a labeled span to the diagnostic.
     ///
-    /// This is pushed onto the [`MultiSpan`] that was created when the diagnostic
-    /// was first built. That means it will be shown together with the original
-    /// span/label, *not* a span added by one of the `span_{note,warn,help,suggestions}` methods.
+    /// Labels are used to convey additional context for the diagnostic's primary span. They will
+    /// be shown together with the original diagnostic's span, *not* with spans added by
+    /// `span_note`, `span_help`, etc. Therefore, if the primary span is not displayable (because
+    /// the span is `DUMMY_SP` or the source code isn't found), labels will not be displayed
+    /// either.
     ///
-    /// This span is *not* considered a ["primary span"][`MultiSpan`]; only
-    /// the `Span` supplied when creating the diagnostic is primary.
+    /// Implementation-wise, the label span is pushed onto the [`MultiSpan`] that was created when
+    /// the diagnostic was constructed. However, the label span is *not* considered a
+    /// ["primary span"][`MultiSpan`]; only the `Span` supplied when creating the diagnostic is
+    /// primary.
     #[rustc_lint_diagnostics]
     pub fn span_label(&mut self, span: Span, label: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.span.push_span_label(span, self.subdiagnostic_message_to_diagnostic_message(label));
@@ -368,7 +347,7 @@ impl Diagnostic {
 
     pub fn replace_span_with(&mut self, after: Span, keep_label: bool) -> &mut Self {
         let before = self.span.clone();
-        self.set_span(after);
+        self.span(after);
         for span_label in before.span_labels() {
             if let Some(label) = span_label.label {
                 if span_label.is_primary && keep_label {
@@ -391,29 +370,6 @@ impl Diagnostic {
         self.note_expected_found_extra(expected_label, expected, found_label, found, &"", &"")
     }
 
-    pub fn note_unsuccessful_coercion(
-        &mut self,
-        expected: DiagnosticStyledString,
-        found: DiagnosticStyledString,
-    ) -> &mut Self {
-        let mut msg: Vec<_> =
-            vec![(Cow::from("required when trying to coerce from type `"), Style::NoStyle)];
-        msg.extend(expected.0.iter().map(|x| match *x {
-            StringPart::Normal(ref s) => (Cow::from(s.clone()), Style::NoStyle),
-            StringPart::Highlighted(ref s) => (Cow::from(s.clone()), Style::Highlight),
-        }));
-        msg.push((Cow::from("` to type '"), Style::NoStyle));
-        msg.extend(found.0.iter().map(|x| match *x {
-            StringPart::Normal(ref s) => (Cow::from(s.clone()), Style::NoStyle),
-            StringPart::Highlighted(ref s) => (Cow::from(s.clone()), Style::Highlight),
-        }));
-        msg.push((Cow::from("`"), Style::NoStyle));
-
-        // For now, just attach these as notes
-        self.highlighted_note(msg);
-        self
-    }
-
     pub fn note_expected_found_extra(
         &mut self,
         expected_label: &dyn fmt::Display,
@@ -471,22 +427,22 @@ impl Diagnostic {
     /// Add a note attached to this diagnostic.
     #[rustc_lint_diagnostics]
     pub fn note(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
-        self.sub(Level::Note, msg, MultiSpan::new(), None);
+        self.sub(Level::Note, msg, MultiSpan::new());
         self
     }
 
-    pub fn highlighted_note<M: Into<SubdiagnosticMessage>>(
+    fn highlighted_note<M: Into<SubdiagnosticMessage>>(
         &mut self,
         msg: Vec<(M, Style)>,
     ) -> &mut Self {
-        self.sub_with_highlights(Level::Note, msg, MultiSpan::new(), None);
+        self.sub_with_highlights(Level::Note, msg, MultiSpan::new());
         self
     }
 
     /// Prints the span with a note above it.
     /// This is like [`Diagnostic::note()`], but it gets its own span.
     pub fn note_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
-        self.sub(Level::OnceNote, msg, MultiSpan::new(), None);
+        self.sub(Level::OnceNote, msg, MultiSpan::new());
         self
     }
 
@@ -498,7 +454,7 @@ impl Diagnostic {
         sp: S,
         msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self {
-        self.sub(Level::Note, msg, sp.into(), None);
+        self.sub(Level::Note, msg, sp.into());
         self
     }
 
@@ -509,14 +465,14 @@ impl Diagnostic {
         sp: S,
         msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self {
-        self.sub(Level::OnceNote, msg, sp.into(), None);
+        self.sub(Level::OnceNote, msg, sp.into());
         self
     }
 
     /// Add a warning attached to this diagnostic.
     #[rustc_lint_diagnostics]
     pub fn warn(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
-        self.sub(Level::Warning(None), msg, MultiSpan::new(), None);
+        self.sub(Level::Warning(None), msg, MultiSpan::new());
         self
     }
 
@@ -528,27 +484,27 @@ impl Diagnostic {
         sp: S,
         msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self {
-        self.sub(Level::Warning(None), msg, sp.into(), None);
+        self.sub(Level::Warning(None), msg, sp.into());
         self
     }
 
     /// Add a help message attached to this diagnostic.
     #[rustc_lint_diagnostics]
     pub fn help(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
-        self.sub(Level::Help, msg, MultiSpan::new(), None);
+        self.sub(Level::Help, msg, MultiSpan::new());
         self
     }
 
     /// Prints the span with a help above it.
     /// This is like [`Diagnostic::help()`], but it gets its own span.
     pub fn help_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
-        self.sub(Level::OnceHelp, msg, MultiSpan::new(), None);
+        self.sub(Level::OnceHelp, msg, MultiSpan::new());
         self
     }
 
     /// Add a help message attached to this diagnostic with a customizable highlighted message.
     pub fn highlighted_help(&mut self, msg: Vec<(String, Style)>) -> &mut Self {
-        self.sub_with_highlights(Level::Help, msg, MultiSpan::new(), None);
+        self.sub_with_highlights(Level::Help, msg, MultiSpan::new());
         self
     }
 
@@ -560,7 +516,7 @@ impl Diagnostic {
         sp: S,
         msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self {
-        self.sub(Level::Help, msg, sp.into(), None);
+        self.sub(Level::Help, msg, sp.into());
         self
     }
 
@@ -572,14 +528,6 @@ impl Diagnostic {
         self
     }
 
-    /// Clear any existing suggestions.
-    pub fn clear_suggestions(&mut self) -> &mut Self {
-        if let Ok(suggestions) = &mut self.suggestions {
-            suggestions.clear();
-        }
-        self
-    }
-
     /// Helper for pushing to `self.suggestions`, if available (not disable).
     fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
         if let Ok(suggestions) = &mut self.suggestions {
@@ -622,17 +570,18 @@ impl Diagnostic {
     pub fn multipart_suggestion_with_style(
         &mut self,
         msg: impl Into<SubdiagnosticMessage>,
-        suggestion: Vec<(Span, String)>,
+        mut suggestion: Vec<(Span, String)>,
         applicability: Applicability,
         style: SuggestionStyle,
     ) -> &mut Self {
-        let mut parts = suggestion
+        suggestion.sort_unstable();
+        suggestion.dedup();
+
+        let parts = suggestion
             .into_iter()
             .map(|(span, snippet)| SubstitutionPart { snippet, span })
             .collect::<Vec<_>>();
 
-        parts.sort_unstable_by_key(|part| part.span);
-
         assert!(!parts.is_empty());
         debug_assert_eq!(
             parts.iter().find(|part| part.span.is_empty() && part.snippet.is_empty()),
@@ -777,17 +726,15 @@ impl Diagnostic {
         applicability: Applicability,
         style: SuggestionStyle,
     ) -> &mut Self {
-        let mut suggestions: Vec<_> = suggestions.into_iter().collect();
-        suggestions.sort();
-
-        debug_assert!(
-            !(sp.is_empty() && suggestions.iter().any(|suggestion| suggestion.is_empty())),
-            "Span must not be empty and have no suggestion"
-        );
-
         let substitutions = suggestions
             .into_iter()
-            .map(|snippet| Substitution { parts: vec![SubstitutionPart { snippet, span: sp }] })
+            .map(|snippet| {
+                debug_assert!(
+                    !(sp.is_empty() && snippet.is_empty()),
+                    "Span must not be empty and have no suggestion"
+                );
+                Substitution { parts: vec![SubstitutionPart { snippet, span: sp }] }
+            })
             .collect();
         self.push_suggestion(CodeSuggestion {
             substitutions,
@@ -921,18 +868,18 @@ impl Diagnostic {
     /// interpolated variables).
     pub fn eager_subdiagnostic(
         &mut self,
-        handler: &crate::Handler,
+        dcx: &crate::DiagCtxt,
         subdiagnostic: impl AddToDiagnostic,
     ) -> &mut Self {
         subdiagnostic.add_to_diagnostic_with(self, |diag, msg| {
             let args = diag.args();
             let msg = diag.subdiagnostic_message_to_diagnostic_message(msg);
-            handler.eagerly_translate(msg, args)
+            dcx.eagerly_translate(msg, args)
         });
         self
     }
 
-    pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
+    pub fn span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
         self.span = sp.into();
         if let Some(span) = self.span.primary_span() {
             self.sort_span = span;
@@ -940,7 +887,7 @@ impl Diagnostic {
         self
     }
 
-    pub fn set_is_lint(&mut self) -> &mut Self {
+    pub fn is_lint(&mut self) -> &mut Self {
         self.is_lint = true;
         self
     }
@@ -959,8 +906,8 @@ impl Diagnostic {
         self.code.clone()
     }
 
-    pub fn set_primary_message(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
-        self.message[0] = (msg.into(), Style::NoStyle);
+    pub fn primary_message(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
+        self.messages[0] = (msg.into(), Style::NoStyle);
         self
     }
 
@@ -971,7 +918,7 @@ impl Diagnostic {
         self.args.iter()
     }
 
-    pub fn set_arg(
+    pub fn arg(
         &mut self,
         name: impl Into<Cow<'static, str>>,
         arg: impl IntoDiagnosticArg,
@@ -987,19 +934,19 @@ impl Diagnostic {
         self.args = args;
     }
 
-    pub fn styled_message(&self) -> &[(DiagnosticMessage, Style)] {
-        &self.message
+    pub fn messages(&self) -> &[(DiagnosticMessage, Style)] {
+        &self.messages
     }
 
     /// Helper function that takes a `SubdiagnosticMessage` and returns a `DiagnosticMessage` by
     /// combining it with the primary message of the diagnostic (if translatable, otherwise it just
     /// passes the user's string along).
-    pub(crate) fn subdiagnostic_message_to_diagnostic_message(
+    fn subdiagnostic_message_to_diagnostic_message(
         &self,
         attr: impl Into<SubdiagnosticMessage>,
     ) -> DiagnosticMessage {
         let msg =
-            self.message.iter().map(|(msg, _)| msg).next().expect("diagnostic with no messages");
+            self.messages.iter().map(|(msg, _)| msg).next().expect("diagnostic with no messages");
         msg.with_subdiagnostic_message(attr.into())
     }
 
@@ -1007,21 +954,14 @@ impl Diagnostic {
     /// public methods above.
     ///
     /// Used by `proc_macro_server` for implementing `server::Diagnostic`.
-    pub fn sub(
-        &mut self,
-        level: Level,
-        message: impl Into<SubdiagnosticMessage>,
-        span: MultiSpan,
-        render_span: Option<MultiSpan>,
-    ) {
+    pub fn sub(&mut self, level: Level, message: impl Into<SubdiagnosticMessage>, span: MultiSpan) {
         let sub = SubDiagnostic {
             level,
-            message: vec![(
+            messages: vec![(
                 self.subdiagnostic_message_to_diagnostic_message(message),
                 Style::NoStyle,
             )],
             span,
-            render_span,
         };
         self.children.push(sub);
     }
@@ -1031,15 +971,14 @@ impl Diagnostic {
     fn sub_with_highlights<M: Into<SubdiagnosticMessage>>(
         &mut self,
         level: Level,
-        message: Vec<(M, Style)>,
+        messages: Vec<(M, Style)>,
         span: MultiSpan,
-        render_span: Option<MultiSpan>,
     ) {
-        let message = message
+        let messages = messages
             .into_iter()
             .map(|m| (self.subdiagnostic_message_to_diagnostic_message(m.0), m.1))
             .collect();
-        let sub = SubDiagnostic { level, message, span, render_span };
+        let sub = SubDiagnostic { level, messages, span };
         self.children.push(sub);
     }
 
@@ -1057,7 +996,7 @@ impl Diagnostic {
     ) {
         (
             &self.level,
-            &self.message,
+            &self.messages,
             self.args().collect(),
             &self.code,
             &self.span,
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index 85acf8ab5aa..3789cdaf354 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -1,9 +1,9 @@
 use crate::diagnostic::IntoDiagnosticArg;
+use crate::{DiagCtxt, Level, MultiSpan, StashKey};
 use crate::{
     Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed,
     ExplicitBug, SubdiagnosticMessage,
 };
-use crate::{Handler, Level, MultiSpan, StashKey};
 use rustc_lint_defs::Applicability;
 use rustc_span::source_map::Spanned;
 
@@ -15,455 +15,184 @@ use std::ops::{Deref, DerefMut};
 use std::panic;
 use std::thread::panicking;
 
-/// Trait implemented by error types. This should not be implemented manually. Instead, use
+/// Trait implemented by error types. This is rarely implemented manually. Instead, use
 /// `#[derive(Diagnostic)]` -- see [rustc_macros::Diagnostic].
 #[rustc_diagnostic_item = "IntoDiagnostic"]
-pub trait IntoDiagnostic<'a, T: EmissionGuarantee = ErrorGuaranteed> {
-    /// Write out as a diagnostic out of `Handler`.
+pub trait IntoDiagnostic<'a, G: EmissionGuarantee = ErrorGuaranteed> {
+    /// Write out as a diagnostic out of `DiagCtxt`.
     #[must_use]
-    fn into_diagnostic(self, handler: &'a Handler) -> DiagnosticBuilder<'a, T>;
+    fn into_diagnostic(self, dcx: &'a DiagCtxt, level: Level) -> DiagnosticBuilder<'a, G>;
 }
 
-impl<'a, T, E> IntoDiagnostic<'a, E> for Spanned<T>
+impl<'a, T, G> IntoDiagnostic<'a, G> for Spanned<T>
 where
-    T: IntoDiagnostic<'a, E>,
-    E: EmissionGuarantee,
+    T: IntoDiagnostic<'a, G>,
+    G: EmissionGuarantee,
 {
-    fn into_diagnostic(self, handler: &'a Handler) -> DiagnosticBuilder<'a, E> {
-        let mut diag = self.node.into_diagnostic(handler);
-        diag.set_span(self.span);
-        diag
+    fn into_diagnostic(self, dcx: &'a DiagCtxt, level: Level) -> DiagnosticBuilder<'a, G> {
+        self.node.into_diagnostic(dcx, level).span_mv(self.span)
     }
 }
 
 /// Used for emitting structured error messages and other diagnostic information.
+/// Each constructed `DiagnosticBuilder` must be consumed by a function such as
+/// `emit`, `cancel`, `delay_as_bug`, or `into_diagnostic`. A panic occurrs if a
+/// `DiagnosticBuilder` is dropped without being consumed by one of these
+/// functions.
 ///
 /// If there is some state in a downstream crate you would like to
 /// access in the methods of `DiagnosticBuilder` here, consider
-/// extending `HandlerFlags`, accessed via `self.handler.flags`.
+/// extending `DiagCtxtFlags`.
 #[must_use]
-#[derive(Clone)]
-pub struct DiagnosticBuilder<'a, G: EmissionGuarantee> {
-    inner: DiagnosticBuilderInner<'a>,
-    _marker: PhantomData<G>,
-}
+pub struct DiagnosticBuilder<'a, G: EmissionGuarantee = ErrorGuaranteed> {
+    pub dcx: &'a DiagCtxt,
 
-/// This type exists only for `DiagnosticBuilder::forget_guarantee`, because it:
-/// 1. lacks the `G` parameter and therefore `DiagnosticBuilder<G1>` can be
-///    converted into `DiagnosticBuilder<G2>` while reusing the `inner` field
-/// 2. can implement the `Drop` "bomb" instead of `DiagnosticBuilder`, as it
-///    contains all of the data (`state` + `diagnostic`) of `DiagnosticBuilder`
-///
-/// The `diagnostic` field is not `Copy` and can't be moved out of whichever
-/// type implements the `Drop` "bomb", but because of the above two facts, that
-/// never needs to happen - instead, the whole `inner: DiagnosticBuilderInner`
-/// can be moved out of a `DiagnosticBuilder` and into another.
-#[must_use]
-#[derive(Clone)]
-struct DiagnosticBuilderInner<'a> {
-    state: DiagnosticBuilderState<'a>,
-
-    /// `Diagnostic` is a large type, and `DiagnosticBuilder` is often used as a
-    /// return value, especially within the frequently-used `PResult` type.
-    /// In theory, return value optimization (RVO) should avoid unnecessary
-    /// copying. In practice, it does not (at the time of writing).
-    diagnostic: Box<Diagnostic>,
-}
-
-#[derive(Clone)]
-enum DiagnosticBuilderState<'a> {
-    /// Initial state of a `DiagnosticBuilder`, before `.emit()` or `.cancel()`.
+    /// Why the `Option`? It is always `Some` until the `DiagnosticBuilder` is
+    /// consumed via `emit`, `cancel`, etc. At that point it is consumed and
+    /// replaced with `None`. Then `drop` checks that it is `None`; if not, it
+    /// panics because a diagnostic was built but not used.
     ///
-    /// The `Diagnostic` will be emitted through this `Handler`.
-    Emittable(&'a Handler),
+    /// Why the Box? `Diagnostic` is a large type, and `DiagnosticBuilder` is
+    /// often used as a return value, especially within the frequently-used
+    /// `PResult` type. In theory, return value optimization (RVO) should avoid
+    /// unnecessary copying. In practice, it does not (at the time of writing).
+    diag: Option<Box<Diagnostic>>,
 
-    /// State of a `DiagnosticBuilder`, after `.emit()` or *during* `.cancel()`.
-    ///
-    /// The `Diagnostic` will be ignored when calling `.emit()`, and it can be
-    /// assumed that `.emit()` was previously called, to end up in this state.
-    ///
-    /// While this is also used by `.cancel()`, this state is only observed by
-    /// the `Drop` `impl` of `DiagnosticBuilderInner`, as `.cancel()` takes
-    /// `self` by-value specifically to prevent any attempts to `.emit()`.
-    ///
-    // FIXME(eddyb) currently this doesn't prevent extending the `Diagnostic`,
-    // despite that being potentially lossy, if important information is added
-    // *after* the original `.emit()` call.
-    AlreadyEmittedOrDuringCancellation,
+    _marker: PhantomData<G>,
 }
 
-// `DiagnosticBuilderState` should be pointer-sized.
+// Cloning a `DiagnosticBuilder` is a recipe for a diagnostic being emitted
+// twice, which would be bad.
+impl<G> !Clone for DiagnosticBuilder<'_, G> {}
+
 rustc_data_structures::static_assert_size!(
-    DiagnosticBuilderState<'_>,
-    std::mem::size_of::<&Handler>()
+    DiagnosticBuilder<'_, ()>,
+    2 * std::mem::size_of::<usize>()
 );
 
 /// Trait for types that `DiagnosticBuilder::emit` can return as a "guarantee"
 /// (or "proof") token that the emission happened.
 pub trait EmissionGuarantee: Sized {
+    /// This exists so that bugs and fatal errors can both result in `!` (an
+    /// abort) when emitted, but have different aborting behaviour.
+    type EmitResult = Self;
+
     /// Implementation of `DiagnosticBuilder::emit`, fully controlled by each
     /// `impl` of `EmissionGuarantee`, to make it impossible to create a value
-    /// of `Self` without actually performing the emission.
-    #[track_caller]
-    fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self;
-
-    /// Creates a new `DiagnosticBuilder` that will return this type of guarantee.
-    #[track_caller]
-    fn make_diagnostic_builder(
-        handler: &Handler,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, Self>;
-}
-
-impl<'a> DiagnosticBuilder<'a, ErrorGuaranteed> {
-    /// Convenience function for internal use, clients should use one of the
-    /// `struct_*` methods on [`Handler`].
+    /// of `Self::EmitResult` without actually performing the emission.
     #[track_caller]
-    pub(crate) fn new_guaranteeing_error<M: Into<DiagnosticMessage>>(
-        handler: &'a Handler,
-        message: M,
-    ) -> Self {
-        Self {
-            inner: DiagnosticBuilderInner {
-                state: DiagnosticBuilderState::Emittable(handler),
-                diagnostic: Box::new(Diagnostic::new_with_code(
-                    Level::Error { lint: false },
-                    None,
-                    message,
-                )),
-            },
-            _marker: PhantomData,
-        }
-    }
-
-    /// Discard the guarantee `.emit()` would return, in favor of having the
-    /// type `DiagnosticBuilder<'a, ()>`. This may be necessary whenever there
-    /// is a common codepath handling both errors and warnings.
-    pub fn forget_guarantee(self) -> DiagnosticBuilder<'a, ()> {
-        DiagnosticBuilder { inner: self.inner, _marker: PhantomData }
-    }
-}
-
-// FIXME(eddyb) make `ErrorGuaranteed` impossible to create outside `.emit()`.
-impl EmissionGuarantee for ErrorGuaranteed {
-    fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self {
-        match db.inner.state {
-            // First `.emit()` call, the `&Handler` is still available.
-            DiagnosticBuilderState::Emittable(handler) => {
-                db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
-
-                let guar = handler.emit_diagnostic(&mut db.inner.diagnostic);
-
-                // Only allow a guarantee if the `level` wasn't switched to a
-                // non-error - the field isn't `pub`, but the whole `Diagnostic`
-                // can be overwritten with a new one, thanks to `DerefMut`.
-                assert!(
-                    db.inner.diagnostic.is_error(),
-                    "emitted non-error ({:?}) diagnostic \
-                     from `DiagnosticBuilder<ErrorGuaranteed>`",
-                    db.inner.diagnostic.level,
-                );
-                guar.unwrap()
-            }
-            // `.emit()` was previously called, disallowed from repeating it,
-            // but can take advantage of the previous `.emit()`'s guarantee
-            // still being applicable (i.e. as a form of idempotency).
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {
-                // Only allow a guarantee if the `level` wasn't switched to a
-                // non-error - the field isn't `pub`, but the whole `Diagnostic`
-                // can be overwritten with a new one, thanks to `DerefMut`.
-                assert!(
-                    db.inner.diagnostic.is_error(),
-                    "`DiagnosticBuilder<ErrorGuaranteed>`'s diagnostic \
-                     became non-error ({:?}), after original `.emit()`",
-                    db.inner.diagnostic.level,
-                );
-                #[allow(deprecated)]
-                ErrorGuaranteed::unchecked_claim_error_was_emitted()
-            }
-        }
-    }
-
-    #[track_caller]
-    fn make_diagnostic_builder(
-        handler: &Handler,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, Self> {
-        DiagnosticBuilder::new_guaranteeing_error(handler, msg)
-    }
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult;
 }
 
-impl<'a> DiagnosticBuilder<'a, ()> {
-    /// Convenience function for internal use, clients should use one of the
-    /// `struct_*` methods on [`Handler`].
-    #[track_caller]
-    pub(crate) fn new<M: Into<DiagnosticMessage>>(
-        handler: &'a Handler,
-        level: Level,
-        message: M,
-    ) -> Self {
-        let diagnostic = Diagnostic::new_with_code(level, None, message);
-        Self::new_diagnostic(handler, diagnostic)
+impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
+    /// Takes the diagnostic. For use by methods that consume the
+    /// DiagnosticBuilder: `emit`, `cancel`, etc. Afterwards, `drop` is the
+    /// only code that will be run on `self`.
+    fn take_diag(&mut self) -> Diagnostic {
+        Box::into_inner(self.diag.take().unwrap())
     }
 
-    /// Creates a new `DiagnosticBuilder` with an already constructed
-    /// diagnostic.
-    #[track_caller]
-    pub(crate) fn new_diagnostic(handler: &'a Handler, diagnostic: Diagnostic) -> Self {
-        debug!("Created new diagnostic");
-        Self {
-            inner: DiagnosticBuilderInner {
-                state: DiagnosticBuilderState::Emittable(handler),
-                diagnostic: Box::new(diagnostic),
-            },
-            _marker: PhantomData,
-        }
+    /// Most `emit_producing_guarantee` functions use this as a starting point.
+    fn emit_producing_nothing(mut self) {
+        let diag = self.take_diag();
+        self.dcx.emit_diagnostic(diag);
     }
-}
 
-// FIXME(eddyb) should there be a `Option<ErrorGuaranteed>` impl as well?
-impl EmissionGuarantee for () {
-    fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self {
-        match db.inner.state {
-            // First `.emit()` call, the `&Handler` is still available.
-            DiagnosticBuilderState::Emittable(handler) => {
-                db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
+    /// `ErrorGuaranteed::emit_producing_guarantee` uses this.
+    // FIXME(eddyb) make `ErrorGuaranteed` impossible to create outside `.emit()`.
+    fn emit_producing_error_guaranteed(mut self) -> ErrorGuaranteed {
+        let diag = self.take_diag();
 
-                handler.emit_diagnostic(&mut db.inner.diagnostic);
-            }
-            // `.emit()` was previously called, disallowed from repeating it.
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
-        }
-    }
+        // Only allow a guarantee if the `level` wasn't switched to a
+        // non-error. The field isn't `pub`, but the whole `Diagnostic` can be
+        // overwritten with a new one, thanks to `DerefMut`.
+        assert!(
+            diag.is_error(),
+            "emitted non-error ({:?}) diagnostic from `DiagnosticBuilder<ErrorGuaranteed>`",
+            diag.level,
+        );
 
-    fn make_diagnostic_builder(
-        handler: &Handler,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, Self> {
-        DiagnosticBuilder::new(handler, Level::Warning(None), msg)
+        let guar = self.dcx.emit_diagnostic(diag);
+        guar.unwrap()
     }
 }
 
-/// Marker type which enables implementation of `create_note` and `emit_note` functions for
-/// note-without-error struct diagnostics.
-#[derive(Copy, Clone)]
-pub struct Noted;
-
-impl<'a> DiagnosticBuilder<'a, Noted> {
-    /// Convenience function for internal use, clients should use one of the
-    /// `struct_*` methods on [`Handler`].
-    pub(crate) fn new_note(handler: &'a Handler, message: impl Into<DiagnosticMessage>) -> Self {
-        let diagnostic = Diagnostic::new_with_code(Level::Note, None, message);
-        Self::new_diagnostic_note(handler, diagnostic)
-    }
-
-    /// Creates a new `DiagnosticBuilder` with an already constructed
-    /// diagnostic.
-    pub(crate) fn new_diagnostic_note(handler: &'a Handler, diagnostic: Diagnostic) -> Self {
-        debug!("Created new diagnostic");
-        Self {
-            inner: DiagnosticBuilderInner {
-                state: DiagnosticBuilderState::Emittable(handler),
-                diagnostic: Box::new(diagnostic),
-            },
-            _marker: PhantomData,
-        }
+impl EmissionGuarantee for ErrorGuaranteed {
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_error_guaranteed()
     }
 }
 
-impl EmissionGuarantee for Noted {
-    fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self {
-        match db.inner.state {
-            // First `.emit()` call, the `&Handler` is still available.
-            DiagnosticBuilderState::Emittable(handler) => {
-                db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
-                handler.emit_diagnostic(&mut db.inner.diagnostic);
-            }
-            // `.emit()` was previously called, disallowed from repeating it.
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
-        }
-
-        Noted
-    }
-
-    fn make_diagnostic_builder(
-        handler: &Handler,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, Self> {
-        DiagnosticBuilder::new_note(handler, msg)
+impl EmissionGuarantee for () {
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_nothing();
     }
 }
 
 /// Marker type which enables implementation of `create_bug` and `emit_bug` functions for
-/// bug struct diagnostics.
+/// bug diagnostics.
 #[derive(Copy, Clone)]
-pub struct Bug;
-
-impl<'a> DiagnosticBuilder<'a, Bug> {
-    /// Convenience function for internal use, clients should use one of the
-    /// `struct_*` methods on [`Handler`].
-    #[track_caller]
-    pub(crate) fn new_bug(handler: &'a Handler, message: impl Into<DiagnosticMessage>) -> Self {
-        let diagnostic = Diagnostic::new_with_code(Level::Bug, None, message);
-        Self::new_diagnostic_bug(handler, diagnostic)
-    }
+pub struct BugAbort;
 
-    /// Creates a new `DiagnosticBuilder` with an already constructed
-    /// diagnostic.
-    pub(crate) fn new_diagnostic_bug(handler: &'a Handler, diagnostic: Diagnostic) -> Self {
-        debug!("Created new diagnostic bug");
-        Self {
-            inner: DiagnosticBuilderInner {
-                state: DiagnosticBuilderState::Emittable(handler),
-                diagnostic: Box::new(diagnostic),
-            },
-            _marker: PhantomData,
-        }
-    }
-}
-
-impl EmissionGuarantee for Bug {
-    fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self {
-        match db.inner.state {
-            // First `.emit()` call, the `&Handler` is still available.
-            DiagnosticBuilderState::Emittable(handler) => {
-                db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
+impl EmissionGuarantee for BugAbort {
+    type EmitResult = !;
 
-                handler.emit_diagnostic(&mut db.inner.diagnostic);
-            }
-            // `.emit()` was previously called, disallowed from repeating it.
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
-        }
-        // Then panic. No need to return the marker type.
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_nothing();
         panic::panic_any(ExplicitBug);
     }
-
-    fn make_diagnostic_builder(
-        handler: &Handler,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, Self> {
-        DiagnosticBuilder::new_bug(handler, msg)
-    }
 }
 
-impl<'a> DiagnosticBuilder<'a, !> {
-    /// Convenience function for internal use, clients should use one of the
-    /// `struct_*` methods on [`Handler`].
-    #[track_caller]
-    pub(crate) fn new_fatal(handler: &'a Handler, message: impl Into<DiagnosticMessage>) -> Self {
-        let diagnostic = Diagnostic::new_with_code(Level::Fatal, None, message);
-        Self::new_diagnostic_fatal(handler, diagnostic)
-    }
-
-    /// Creates a new `DiagnosticBuilder` with an already constructed
-    /// diagnostic.
-    pub(crate) fn new_diagnostic_fatal(handler: &'a Handler, diagnostic: Diagnostic) -> Self {
-        debug!("Created new diagnostic");
-        Self {
-            inner: DiagnosticBuilderInner {
-                state: DiagnosticBuilderState::Emittable(handler),
-                diagnostic: Box::new(diagnostic),
-            },
-            _marker: PhantomData,
-        }
-    }
-}
+/// Marker type which enables implementation of `create_fatal` and `emit_fatal` functions for
+/// fatal diagnostics.
+#[derive(Copy, Clone)]
+pub struct FatalAbort;
 
-impl EmissionGuarantee for ! {
-    fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self {
-        match db.inner.state {
-            // First `.emit()` call, the `&Handler` is still available.
-            DiagnosticBuilderState::Emittable(handler) => {
-                db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
+impl EmissionGuarantee for FatalAbort {
+    type EmitResult = !;
 
-                handler.emit_diagnostic(&mut db.inner.diagnostic);
-            }
-            // `.emit()` was previously called, disallowed from repeating it.
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
-        }
-        // Then fatally error, returning `!`
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_nothing();
         crate::FatalError.raise()
     }
-
-    fn make_diagnostic_builder(
-        handler: &Handler,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, Self> {
-        DiagnosticBuilder::new_fatal(handler, msg)
-    }
-}
-
-impl<'a> DiagnosticBuilder<'a, rustc_span::fatal_error::FatalError> {
-    /// Convenience function for internal use, clients should use one of the
-    /// `struct_*` methods on [`Handler`].
-    #[track_caller]
-    pub(crate) fn new_almost_fatal(
-        handler: &'a Handler,
-        message: impl Into<DiagnosticMessage>,
-    ) -> Self {
-        let diagnostic = Diagnostic::new_with_code(Level::Fatal, None, message);
-        Self::new_diagnostic_almost_fatal(handler, diagnostic)
-    }
-
-    /// Creates a new `DiagnosticBuilder` with an already constructed
-    /// diagnostic.
-    pub(crate) fn new_diagnostic_almost_fatal(
-        handler: &'a Handler,
-        diagnostic: Diagnostic,
-    ) -> Self {
-        debug!("Created new diagnostic");
-        Self {
-            inner: DiagnosticBuilderInner {
-                state: DiagnosticBuilderState::Emittable(handler),
-                diagnostic: Box::new(diagnostic),
-            },
-            _marker: PhantomData,
-        }
-    }
 }
 
 impl EmissionGuarantee for rustc_span::fatal_error::FatalError {
-    fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self {
-        match db.inner.state {
-            // First `.emit()` call, the `&Handler` is still available.
-            DiagnosticBuilderState::Emittable(handler) => {
-                db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
-
-                handler.emit_diagnostic(&mut db.inner.diagnostic);
-            }
-            // `.emit()` was previously called, disallowed from repeating it.
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
-        }
-        // Then fatally error..
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_nothing();
         rustc_span::fatal_error::FatalError
     }
-
-    fn make_diagnostic_builder(
-        handler: &Handler,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, Self> {
-        DiagnosticBuilder::new_almost_fatal(handler, msg)
-    }
 }
 
-/// In general, the `DiagnosticBuilder` uses deref to allow access to
-/// the fields and methods of the embedded `diagnostic` in a
-/// transparent way. *However,* many of the methods are intended to
-/// be used in a chained way, and hence ought to return `self`. In
-/// that case, we can't just naively forward to the method on the
-/// `diagnostic`, because the return type would be a `&Diagnostic`
-/// instead of a `&DiagnosticBuilder<'a>`. This `forward!` macro makes
-/// it easy to declare such methods on the builder.
+/// `DiagnosticBuilder` impls `DerefMut`, which allows access to the fields and
+/// methods of the embedded `Diagnostic`. However, that doesn't allow method
+/// chaining at the `DiagnosticBuilder` level. Each use of this macro defines
+/// two builder methods at that level, both of which wrap the equivalent method
+/// in `Diagnostic`.
+/// - A `&mut self -> &mut Self` method, with the same name as the underlying
+///   `Diagnostic` method. It is mostly to modify existing diagnostics, either
+///   in a standalone fashion, e.g. `err.code(code)`, or in a chained fashion
+///   to make multiple modifications, e.g. `err.code(code).span(span)`.
+/// - A `self -> Self` method, with `_mv` suffix added (short for "move").
+///   It is mostly used in a chained fashion when producing a new diagnostic,
+///   e.g. `let err = struct_err(msg).code_mv(code)`, or when emitting a new
+///   diagnostic , e.g. `struct_err(msg).code_mv(code).emit()`.
+///
+/// Although the latter method can be used to modify an existing diagnostic,
+/// e.g. `err = err.code_mv(code)`, this should be avoided because the former
+/// method give shorter code, e.g. `err.code(code)`.
 macro_rules! forward {
-    // Forward pattern for &mut self -> &mut Self
     (
-        $(#[$attrs:meta])*
-        pub fn $n:ident(&mut self, $($name:ident: $ty:ty),* $(,)?) -> &mut Self
+        ($n:ident, $n_mv:ident)($($name:ident: $ty:ty),* $(,)?)
     ) => {
-        $(#[$attrs])*
         #[doc = concat!("See [`Diagnostic::", stringify!($n), "()`].")]
         pub fn $n(&mut self, $($name: $ty),*) -> &mut Self {
-            self.inner.diagnostic.$n($($name),*);
+            self.diag.as_mut().unwrap().$n($($name),*);
+            self
+        }
+        #[doc = concat!("See [`Diagnostic::", stringify!($n), "()`].")]
+        pub fn $n_mv(mut self, $($name: $ty),*) -> Self {
+            self.diag.as_mut().unwrap().$n($($name),*);
             self
         }
     };
@@ -473,21 +202,35 @@ impl<G: EmissionGuarantee> Deref for DiagnosticBuilder<'_, G> {
     type Target = Diagnostic;
 
     fn deref(&self) -> &Diagnostic {
-        &self.inner.diagnostic
+        self.diag.as_ref().unwrap()
     }
 }
 
 impl<G: EmissionGuarantee> DerefMut for DiagnosticBuilder<'_, G> {
     fn deref_mut(&mut self) -> &mut Diagnostic {
-        &mut self.inner.diagnostic
+        self.diag.as_mut().unwrap()
     }
 }
 
 impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
-    /// Emit the diagnostic.
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn new<M: Into<DiagnosticMessage>>(dcx: &'a DiagCtxt, level: Level, message: M) -> Self {
+        Self::new_diagnostic(dcx, Diagnostic::new(level, message))
+    }
+
+    /// Creates a new `DiagnosticBuilder` with an already constructed
+    /// diagnostic.
+    #[track_caller]
+    pub(crate) fn new_diagnostic(dcx: &'a DiagCtxt, diag: Diagnostic) -> Self {
+        debug!("Created new diagnostic");
+        Self { dcx, diag: Some(Box::new(diag)), _marker: PhantomData }
+    }
+
+    /// Emit and consume the diagnostic.
     #[track_caller]
-    pub fn emit(&mut self) -> G {
-        G::diagnostic_builder_emit_producing_guarantee(self)
+    pub fn emit(self) -> G::EmitResult {
+        G::emit_producing_guarantee(self)
     }
 
     /// Emit the diagnostic unless `delay` is true,
@@ -495,78 +238,50 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
     ///
     /// See `emit` and `delay_as_bug` for details.
     #[track_caller]
-    pub fn emit_unless(&mut self, delay: bool) -> G {
+    pub fn emit_unless(mut self, delay: bool) -> G::EmitResult {
         if delay {
             self.downgrade_to_delayed_bug();
         }
         self.emit()
     }
 
-    /// Cancel the diagnostic (a structured diagnostic must either be emitted or
+    /// Cancel and consume the diagnostic. (A diagnostic must either be emitted or
     /// cancelled or it will panic when dropped).
-    ///
-    /// This method takes `self` by-value to disallow calling `.emit()` on it,
-    /// which may be expected to *guarantee* the emission of an error, either
-    /// at the time of the call, or through a prior `.emit()` call.
     pub fn cancel(mut self) {
-        self.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
+        self.diag = None;
         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 [`Handler::steal_diagnostic()`].
+    /// the provided `span` and `key` through [`DiagCtxt::steal_diagnostic()`].
     ///
-    /// As with `buffer`, this is unless the handler has disabled such buffering.
+    /// As with `buffer`, this is unless the dcx has disabled such buffering.
     pub fn stash(self, span: Span, key: StashKey) {
-        if let Some((diag, handler)) = self.into_diagnostic() {
-            handler.stash_diagnostic(span, key, diag);
+        if let Some((diag, dcx)) = self.into_diagnostic() {
+            dcx.stash_diagnostic(span, key, diag);
         }
     }
 
     /// Converts the builder to a `Diagnostic` for later emission,
-    /// unless handler has disabled such buffering, or `.emit()` was called.
-    pub fn into_diagnostic(mut self) -> Option<(Diagnostic, &'a Handler)> {
-        let handler = match self.inner.state {
-            // No `.emit()` calls, the `&Handler` is still available.
-            DiagnosticBuilderState::Emittable(handler) => handler,
-            // `.emit()` was previously called, nothing we can do.
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {
-                return None;
-            }
-        };
-
-        if handler.inner.lock().flags.dont_buffer_diagnostics
-            || handler.inner.lock().flags.treat_err_as_bug.is_some()
-        {
+    /// unless dcx has disabled such buffering.
+    pub fn into_diagnostic(mut self) -> Option<(Diagnostic, &'a DiagCtxt)> {
+        if self.dcx.inner.lock().flags.treat_err_as_bug.is_some() {
             self.emit();
             return None;
         }
 
-        // Take the `Diagnostic` by replacing it with a dummy.
-        let dummy = Diagnostic::new(Level::Allow, DiagnosticMessage::from(""));
-        let diagnostic = std::mem::replace(&mut *self.inner.diagnostic, dummy);
-
-        // Disable the ICE on `Drop`.
-        self.cancel();
+        let diag = self.take_diag();
 
         // Logging here is useful to help track down where in logs an error was
         // actually emitted.
-        debug!("buffer: diagnostic={:?}", diagnostic);
-
-        Some((diagnostic, handler))
-    }
+        debug!("buffer: diag={:?}", diag);
 
-    /// Retrieves the [`Handler`] if available
-    pub fn handler(&self) -> Option<&Handler> {
-        match self.inner.state {
-            DiagnosticBuilderState::Emittable(handler) => Some(handler),
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => None,
-        }
+        Some((diag, self.dcx))
     }
 
     /// Buffers the diagnostic for later emission,
-    /// unless handler has disabled such buffering.
+    /// unless dcx has disabled such buffering.
     pub fn buffer(self, buffered_diagnostics: &mut Vec<Diagnostic>) {
         buffered_diagnostics.extend(self.into_diagnostic().map(|(diag, _)| diag));
     }
@@ -582,214 +297,169 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
     /// In the meantime, though, callsites are required to deal with the "bug"
     /// locally in whichever way makes the most sense.
     #[track_caller]
-    pub fn delay_as_bug(&mut self) -> G {
+    pub fn delay_as_bug(mut self) -> G::EmitResult {
         self.downgrade_to_delayed_bug();
         self.emit()
     }
 
-    forward!(
-        #[track_caller]
-        pub fn downgrade_to_delayed_bug(&mut self,) -> &mut Self
-    );
-
-    forward!(
-    /// Appends a labeled span to the diagnostic.
-    ///
-    /// Labels are used to convey additional context for the diagnostic's primary span. They will
-    /// be shown together with the original diagnostic's span, *not* with spans added by
-    /// `span_note`, `span_help`, etc. Therefore, if the primary span is not displayable (because
-    /// the span is `DUMMY_SP` or the source code isn't found), labels will not be displayed
-    /// either.
-    ///
-    /// Implementation-wise, the label span is pushed onto the [`MultiSpan`] that was created when
-    /// the diagnostic was constructed. However, the label span is *not* considered a
-    /// ["primary span"][`MultiSpan`]; only the `Span` supplied when creating the diagnostic is
-    /// primary.
-    pub fn span_label(&mut self, span: Span, label: impl Into<SubdiagnosticMessage>) -> &mut Self);
-
-    forward!(
-    /// Labels all the given spans with the provided label.
-    /// See [`Diagnostic::span_label()`] for more information.
-    pub fn span_labels(
-        &mut self,
+    forward!((span_label, span_label_mv)(
+        span: Span,
+        label: impl Into<SubdiagnosticMessage>,
+    ));
+    forward!((span_labels, span_labels_mv)(
         spans: impl IntoIterator<Item = Span>,
         label: &str,
-    ) -> &mut Self);
-
-    forward!(pub fn note_expected_found(
-        &mut self,
+    ));
+    forward!((note_expected_found, note_expected_found_mv)(
         expected_label: &dyn fmt::Display,
         expected: DiagnosticStyledString,
         found_label: &dyn fmt::Display,
         found: DiagnosticStyledString,
-    ) -> &mut Self);
-
-    forward!(pub fn note_expected_found_extra(
-        &mut self,
+    ));
+    forward!((note_expected_found_extra, note_expected_found_extra_mv)(
         expected_label: &dyn fmt::Display,
         expected: DiagnosticStyledString,
         found_label: &dyn fmt::Display,
         found: DiagnosticStyledString,
         expected_extra: &dyn fmt::Display,
         found_extra: &dyn fmt::Display,
-    ) -> &mut Self);
-
-    forward!(pub fn note_unsuccessful_coercion(
-        &mut self,
-        expected: DiagnosticStyledString,
-        found: DiagnosticStyledString,
-    ) -> &mut Self);
-
-    forward!(pub fn note(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
-    forward!(pub fn note_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
-    forward!(pub fn span_note(
-        &mut self,
+    ));
+    forward!((note, note_mv)(
+        msg: impl Into<SubdiagnosticMessage>,
+    ));
+    forward!((note_once, note_once_mv)(
+        msg: impl Into<SubdiagnosticMessage>,
+    ));
+    forward!((span_note, span_note_mv)(
         sp: impl Into<MultiSpan>,
         msg: impl Into<SubdiagnosticMessage>,
-    ) -> &mut Self);
-    forward!(pub fn span_note_once(
-        &mut self,
+    ));
+    forward!((span_note_once, span_note_once_mv)(
         sp: impl Into<MultiSpan>,
         msg: impl Into<SubdiagnosticMessage>,
-    ) -> &mut Self);
-    forward!(pub fn warn(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
-    forward!(pub fn span_warn(
-        &mut self,
+    ));
+    forward!((warn, warn_mv)(
+        msg: impl Into<SubdiagnosticMessage>,
+    ));
+    forward!((span_warn, span_warn_mv)(
         sp: impl Into<MultiSpan>,
         msg: impl Into<SubdiagnosticMessage>,
-    ) -> &mut Self);
-    forward!(pub fn help(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
-    forward!(pub fn help_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
-    forward!(pub fn span_help(
-        &mut self,
+    ));
+    forward!((help, help_mv)(
+        msg: impl Into<SubdiagnosticMessage>,
+    ));
+    forward!((help_once, help_once_mv)(
+        msg: impl Into<SubdiagnosticMessage>,
+    ));
+    forward!((span_help, span_help_once_mv)(
         sp: impl Into<MultiSpan>,
         msg: impl Into<SubdiagnosticMessage>,
-    ) -> &mut Self);
-    forward!(pub fn set_is_lint(&mut self,) -> &mut Self);
-
-    forward!(pub fn disable_suggestions(&mut self,) -> &mut Self);
-    forward!(pub fn clear_suggestions(&mut self,) -> &mut Self);
-
-    forward!(pub fn multipart_suggestion(
-        &mut self,
+    ));
+    forward!((multipart_suggestion, multipart_suggestion_mv)(
         msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn multipart_suggestion_verbose(
-        &mut self,
+    ));
+    forward!((multipart_suggestion_verbose, multipart_suggestion_verbose_mv)(
         msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn tool_only_multipart_suggestion(
-        &mut self,
+    ));
+    forward!((tool_only_multipart_suggestion, tool_only_multipart_suggestion_mv)(
         msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn span_suggestion(
-        &mut self,
+    ));
+    forward!((span_suggestion, span_suggestion_mv)(
         sp: Span,
         msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn span_suggestions(
-        &mut self,
+    ));
+    forward!((span_suggestions, span_suggestions_mv)(
         sp: Span,
         msg: impl Into<SubdiagnosticMessage>,
         suggestions: impl IntoIterator<Item = String>,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn multipart_suggestions(
-        &mut self,
+    ));
+    forward!((multipart_suggestions, multipart_suggestions_mv)(
         msg: impl Into<SubdiagnosticMessage>,
         suggestions: impl IntoIterator<Item = Vec<(Span, String)>>,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn span_suggestion_short(
-        &mut self,
+    ));
+    forward!((span_suggestion_short, span_suggestion_short_mv)(
         sp: Span,
         msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn span_suggestion_verbose(
-        &mut self,
+    ));
+    forward!((span_suggestion_verbose, span_suggestion_verbose_mv)(
         sp: Span,
         msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn span_suggestion_hidden(
-        &mut self,
+    ));
+    forward!((span_suggestion_hidden, span_suggestion_hidden_mv)(
         sp: Span,
         msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
-    ) -> &mut Self);
-    forward!(pub fn tool_only_span_suggestion(
-        &mut self,
+    ));
+    forward!((tool_only_span_suggestion, tool_only_span_suggestion_mv)(
         sp: Span,
         msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
-    ) -> &mut Self);
-
-    forward!(pub fn set_primary_message(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self);
-    forward!(pub fn set_span(&mut self, sp: impl Into<MultiSpan>) -> &mut Self);
-    forward!(pub fn code(&mut self, s: DiagnosticId) -> &mut Self);
-    forward!(pub fn set_arg(
-        &mut self,
-        name: impl Into<Cow<'static, str>>,
-        arg: impl IntoDiagnosticArg,
-    ) -> &mut Self);
-
-    forward!(pub fn subdiagnostic(
-        &mut self,
-        subdiagnostic: impl crate::AddToDiagnostic
-    ) -> &mut Self);
+    ));
+    forward!((primary_message, primary_message_mv)(
+        msg: impl Into<DiagnosticMessage>,
+    ));
+    forward!((span, span_mv)(
+        sp: impl Into<MultiSpan>,
+    ));
+    forward!((code, code_mv)(
+        s: DiagnosticId,
+    ));
+    forward!((arg, arg_mv)(
+        name: impl Into<Cow<'static, str>>, arg: impl IntoDiagnosticArg,
+    ));
+    forward!((subdiagnostic, subdiagnostic_mv)(
+        subdiagnostic: impl crate::AddToDiagnostic,
+    ));
 }
 
 impl<G: EmissionGuarantee> Debug for DiagnosticBuilder<'_, G> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.inner.diagnostic.fmt(f)
+        self.diag.fmt(f)
     }
 }
 
-/// Destructor bomb - a `DiagnosticBuilder` must be either emitted or cancelled
-/// or we emit a bug.
-impl Drop for DiagnosticBuilderInner<'_> {
+/// Destructor bomb: every `DiagnosticBuilder` must be consumed (emitted,
+/// cancelled, etc.) or we emit a bug.
+impl<G: EmissionGuarantee> Drop for DiagnosticBuilder<'_, G> {
     fn drop(&mut self) {
-        match self.state {
-            // No `.emit()` or `.cancel()` calls.
-            DiagnosticBuilderState::Emittable(handler) => {
-                if !panicking() {
-                    handler.emit_diagnostic(&mut Diagnostic::new(
-                        Level::Bug,
-                        DiagnosticMessage::from(
-                            "the following error was constructed but not emitted",
-                        ),
-                    ));
-                    handler.emit_diagnostic(&mut self.diagnostic);
-                    panic!("error was constructed but not emitted");
-                }
+        match self.diag.take() {
+            Some(diag) if !panicking() => {
+                self.dcx.emit_diagnostic(Diagnostic::new(
+                    Level::Bug,
+                    DiagnosticMessage::from("the following error was constructed but not emitted"),
+                ));
+                self.dcx.emit_diagnostic(*diag);
+                panic!("error was constructed but not emitted");
             }
-            // `.emit()` was previously called, or maybe we're during `.cancel()`.
-            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
+            _ => {}
         }
     }
 }
 
 #[macro_export]
 macro_rules! struct_span_err {
-    ($session:expr, $span:expr, $code:ident, $($message:tt)*) => ({
-        $session.struct_span_err_with_code(
+    ($dcx:expr, $span:expr, $code:ident, $($message:tt)*) => ({
+        $dcx.struct_span_err(
             $span,
             format!($($message)*),
-            $crate::error_code!($code),
         )
+        .code_mv($crate::error_code!($code))
     })
 }
 
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index 4f77f09b26e..58d4d2caf2e 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -1,10 +1,12 @@
 use crate::diagnostic::DiagnosticLocation;
 use crate::{fluent_generated as fluent, AddToDiagnostic};
-use crate::{DiagnosticArgValue, DiagnosticBuilder, Handler, IntoDiagnostic, IntoDiagnosticArg};
+use crate::{
+    DiagCtxt, DiagnosticArgValue, DiagnosticBuilder, EmissionGuarantee, IntoDiagnostic,
+    IntoDiagnosticArg, Level,
+};
 use rustc_ast as ast;
 use rustc_ast_pretty::pprust;
 use rustc_hir as hir;
-use rustc_lint_defs::Level;
 use rustc_span::edition::Edition;
 use rustc_span::symbol::{Ident, MacroRulesNormalizedIdent, Symbol};
 use rustc_span::Span;
@@ -216,7 +218,7 @@ impl IntoDiagnosticArg for ast::Visibility {
     }
 }
 
-impl IntoDiagnosticArg for Level {
+impl IntoDiagnosticArg for rustc_lint_defs::Level {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         DiagnosticArgValue::Str(Cow::Borrowed(self.to_cmd_flag()))
     }
@@ -245,53 +247,45 @@ impl<Id> IntoDiagnosticArg for hir::def::Res<Id> {
     }
 }
 
-impl IntoDiagnostic<'_, !> for TargetDataLayoutErrors<'_> {
-    fn into_diagnostic(self, handler: &Handler) -> DiagnosticBuilder<'_, !> {
-        let mut diag;
+impl<G: EmissionGuarantee> IntoDiagnostic<'_, G> for TargetDataLayoutErrors<'_> {
+    fn into_diagnostic(self, dcx: &DiagCtxt, level: Level) -> DiagnosticBuilder<'_, G> {
         match self {
             TargetDataLayoutErrors::InvalidAddressSpace { addr_space, err, cause } => {
-                diag = handler.struct_fatal(fluent::errors_target_invalid_address_space);
-                diag.set_arg("addr_space", addr_space);
-                diag.set_arg("cause", cause);
-                diag.set_arg("err", err);
-                diag
+                DiagnosticBuilder::new(dcx, level, fluent::errors_target_invalid_address_space)
+                    .arg_mv("addr_space", addr_space)
+                    .arg_mv("cause", cause)
+                    .arg_mv("err", err)
             }
             TargetDataLayoutErrors::InvalidBits { kind, bit, cause, err } => {
-                diag = handler.struct_fatal(fluent::errors_target_invalid_bits);
-                diag.set_arg("kind", kind);
-                diag.set_arg("bit", bit);
-                diag.set_arg("cause", cause);
-                diag.set_arg("err", err);
-                diag
+                DiagnosticBuilder::new(dcx, level, fluent::errors_target_invalid_bits)
+                    .arg_mv("kind", kind)
+                    .arg_mv("bit", bit)
+                    .arg_mv("cause", cause)
+                    .arg_mv("err", err)
             }
             TargetDataLayoutErrors::MissingAlignment { cause } => {
-                diag = handler.struct_fatal(fluent::errors_target_missing_alignment);
-                diag.set_arg("cause", cause);
-                diag
+                DiagnosticBuilder::new(dcx, level, fluent::errors_target_missing_alignment)
+                    .arg_mv("cause", cause)
             }
             TargetDataLayoutErrors::InvalidAlignment { cause, err } => {
-                diag = handler.struct_fatal(fluent::errors_target_invalid_alignment);
-                diag.set_arg("cause", cause);
-                diag.set_arg("err_kind", err.diag_ident());
-                diag.set_arg("align", err.align());
-                diag
+                DiagnosticBuilder::new(dcx, level, fluent::errors_target_invalid_alignment)
+                    .arg_mv("cause", cause)
+                    .arg_mv("err_kind", err.diag_ident())
+                    .arg_mv("align", err.align())
             }
             TargetDataLayoutErrors::InconsistentTargetArchitecture { dl, target } => {
-                diag = handler.struct_fatal(fluent::errors_target_inconsistent_architecture);
-                diag.set_arg("dl", dl);
-                diag.set_arg("target", target);
-                diag
+                DiagnosticBuilder::new(dcx, level, fluent::errors_target_inconsistent_architecture)
+                    .arg_mv("dl", dl)
+                    .arg_mv("target", target)
             }
             TargetDataLayoutErrors::InconsistentTargetPointerWidth { pointer_size, target } => {
-                diag = handler.struct_fatal(fluent::errors_target_inconsistent_pointer_width);
-                diag.set_arg("pointer_size", pointer_size);
-                diag.set_arg("target", target);
-                diag
+                DiagnosticBuilder::new(dcx, level, fluent::errors_target_inconsistent_pointer_width)
+                    .arg_mv("pointer_size", pointer_size)
+                    .arg_mv("target", target)
             }
             TargetDataLayoutErrors::InvalidBitsSize { err } => {
-                diag = handler.struct_fatal(fluent::errors_target_invalid_bits_size);
-                diag.set_arg("err", err);
-                diag
+                DiagnosticBuilder::new(dcx, level, fluent::errors_target_invalid_bits_size)
+                    .arg_mv("err", err)
             }
         }
     }
@@ -301,25 +295,13 @@ impl IntoDiagnostic<'_, !> for TargetDataLayoutErrors<'_> {
 pub struct SingleLabelManySpans {
     pub spans: Vec<Span>,
     pub label: &'static str,
-    pub kind: LabelKind,
 }
 impl AddToDiagnostic for SingleLabelManySpans {
     fn add_to_diagnostic_with<F>(self, diag: &mut crate::Diagnostic, _: F) {
-        match self.kind {
-            LabelKind::Note => diag.span_note(self.spans, self.label),
-            LabelKind::Label => diag.span_labels(self.spans, self.label),
-            LabelKind::Help => diag.span_help(self.spans, self.label),
-        };
+        diag.span_labels(self.spans, self.label);
     }
 }
 
-/// The kind of label to attach when using [`SingleLabelManySpans`]
-pub enum LabelKind {
-    Note,
-    Label,
-    Help,
-}
-
 #[derive(Subdiagnostic)]
 #[label(errors_expected_lifetime_parameter)]
 pub struct ExpectedLifetimeParameter {
@@ -362,9 +344,9 @@ impl IntoDiagnosticArg for Backtrace {
 pub struct InvalidFlushedDelayedDiagnosticLevel {
     #[primary_span]
     pub span: Span,
-    pub level: rustc_errors::Level,
+    pub level: Level,
 }
-impl IntoDiagnosticArg for rustc_errors::Level {
+impl IntoDiagnosticArg for Level {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         DiagnosticArgValue::Str(Cow::from(self.to_string()))
     }
@@ -378,3 +360,9 @@ pub struct IndicateAnonymousLifetime {
     pub count: usize,
     pub suggestion: String,
 }
+
+impl IntoDiagnosticArg for type_ir::ClosureKind {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(self.as_str().into())
+    }
+}
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 922846775f6..987832e6937 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -16,14 +16,14 @@ use crate::snippet::{
 use crate::styled_buffer::StyledBuffer;
 use crate::translation::{to_fluent_args, Translate};
 use crate::{
-    diagnostic::DiagnosticLocation, CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage,
-    FluentBundle, Handler, LazyFallbackBundle, Level, MultiSpan, SubDiagnostic,
+    diagnostic::DiagnosticLocation, CodeSuggestion, DiagCtxt, Diagnostic, DiagnosticId,
+    DiagnosticMessage, FluentBundle, LazyFallbackBundle, Level, MultiSpan, SubDiagnostic,
     SubstitutionHighlight, SuggestionStyle, TerminalUrl,
 };
 use rustc_lint_defs::pluralize;
 
 use derive_setters::Setters;
-use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
+use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
 use rustc_data_structures::sync::{DynSend, IntoDynSyncSend, Lrc};
 use rustc_error_messages::{FluentArgs, SpanLabel};
 use rustc_span::hygiene::{ExpnKind, MacroKind};
@@ -61,13 +61,13 @@ impl HumanReadableErrorType {
         self,
         mut dst: Box<dyn WriteColor + Send>,
         fallback_bundle: LazyFallbackBundle,
-    ) -> EmitterWriter {
+    ) -> HumanEmitter {
         let (short, color_config) = self.unzip();
         let color = color_config.suggests_using_colors();
         if !dst.supports_color() && color {
             dst = Box::new(Ansi::new(dst));
         }
-        EmitterWriter::new(dst, fallback_bundle).short_message(short)
+        HumanEmitter::new(dst, fallback_bundle).short_message(short)
     }
 }
 
@@ -196,13 +196,15 @@ pub trait Emitter: Translate {
     fn emit_diagnostic(&mut self, diag: &Diagnostic);
 
     /// Emit a notification that an artifact has been output.
-    /// This is currently only supported for the JSON format,
-    /// other formats can, and will, simply ignore it.
+    /// Currently only supported for the JSON format.
     fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
 
+    /// Emit a report about future breakage.
+    /// Currently only supported for the JSON format.
     fn emit_future_breakage_report(&mut self, _diags: Vec<Diagnostic>) {}
 
-    /// Emit list of unused externs
+    /// Emit list of unused externs.
+    /// Currently only supported for the JSON format.
     fn emit_unused_externs(
         &mut self,
         _lint_level: rustc_lint_defs::Level,
@@ -350,9 +352,8 @@ pub trait Emitter: Translate {
 
                 children.push(SubDiagnostic {
                     level: Level::Note,
-                    message: vec![(DiagnosticMessage::from(msg), Style::NoStyle)],
+                    messages: vec![(DiagnosticMessage::from(msg), Style::NoStyle)],
                     span: MultiSpan::new(),
-                    render_span: None,
                 });
             }
         }
@@ -370,7 +371,7 @@ pub trait Emitter: Translate {
     }
 
     fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
-        let mut new_labels: Vec<(Span, String)> = vec![];
+        let mut new_labels = FxIndexSet::default();
 
         for &sp in span.primary_spans() {
             if sp.is_dummy() {
@@ -387,7 +388,7 @@ pub trait Emitter: Translate {
                 }
 
                 if always_backtrace {
-                    new_labels.push((
+                    new_labels.insert((
                         trace.def_site,
                         format!(
                             "in this expansion of `{}`{}",
@@ -431,7 +432,7 @@ pub trait Emitter: Translate {
                             format!("this {} desugaring", kind.descr()).into()
                         }
                     };
-                    new_labels.push((
+                    new_labels.insert((
                         trace.call_site,
                         format!(
                             "in {}{}",
@@ -502,7 +503,7 @@ pub trait Emitter: Translate {
     }
 }
 
-impl Translate for EmitterWriter {
+impl Translate for HumanEmitter {
     fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
         self.fluent_bundle.as_ref()
     }
@@ -512,7 +513,7 @@ impl Translate for EmitterWriter {
     }
 }
 
-impl Emitter for EmitterWriter {
+impl Emitter for HumanEmitter {
     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
         self.sm.as_ref()
     }
@@ -533,7 +534,7 @@ impl Emitter for EmitterWriter {
 
         self.emit_messages_default(
             &diag.level,
-            &diag.message,
+            &diag.messages,
             &fluent_args,
             &diag.code,
             &primary_span,
@@ -553,10 +554,10 @@ impl Emitter for EmitterWriter {
 }
 
 /// An emitter that does nothing when emitting a non-fatal diagnostic.
-/// Fatal diagnostics are forwarded to `fatal_handler` to avoid silent
+/// Fatal diagnostics are forwarded to `fatal_dcx` to avoid silent
 /// failures of rustc, as witnessed e.g. in issue #89358.
 pub struct SilentEmitter {
-    pub fatal_handler: Handler,
+    pub fatal_dcx: DiagCtxt,
     pub fatal_note: Option<String>,
 }
 
@@ -581,7 +582,7 @@ impl Emitter for SilentEmitter {
             if let Some(ref note) = self.fatal_note {
                 d.note(note.clone());
             }
-            self.fatal_handler.emit_diagnostic(&mut d);
+            self.fatal_dcx.emit_diagnostic(d);
         }
     }
 }
@@ -623,7 +624,7 @@ impl ColorConfig {
 
 /// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short`
 #[derive(Setters)]
-pub struct EmitterWriter {
+pub struct HumanEmitter {
     #[setters(skip)]
     dst: IntoDynSyncSend<Destination>,
     sm: Option<Lrc<SourceMap>>,
@@ -648,14 +649,14 @@ pub struct FileWithAnnotatedLines {
     multiline_depth: usize,
 }
 
-impl EmitterWriter {
-    pub fn stderr(color_config: ColorConfig, fallback_bundle: LazyFallbackBundle) -> EmitterWriter {
+impl HumanEmitter {
+    pub fn stderr(color_config: ColorConfig, fallback_bundle: LazyFallbackBundle) -> HumanEmitter {
         let dst = from_stderr(color_config);
         Self::create(dst, fallback_bundle)
     }
 
-    fn create(dst: Destination, fallback_bundle: LazyFallbackBundle) -> EmitterWriter {
-        EmitterWriter {
+    fn create(dst: Destination, fallback_bundle: LazyFallbackBundle) -> HumanEmitter {
+        HumanEmitter {
             dst: IntoDynSyncSend(dst),
             sm: None,
             fluent_bundle: None,
@@ -674,7 +675,7 @@ impl EmitterWriter {
     pub fn new(
         dst: Box<dyn WriteColor + Send>,
         fallback_bundle: LazyFallbackBundle,
-    ) -> EmitterWriter {
+    ) -> HumanEmitter {
         Self::create(dst, fallback_bundle)
     }
 
@@ -1228,10 +1229,10 @@ impl EmitterWriter {
 
     /// Adds a left margin to every line but the first, given a padding length and the label being
     /// displayed, keeping the provided highlighting.
-    fn msg_to_buffer(
+    fn msgs_to_buffer(
         &self,
         buffer: &mut StyledBuffer,
-        msg: &[(DiagnosticMessage, Style)],
+        msgs: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
         padding: usize,
         label: &str,
@@ -1267,7 +1268,7 @@ impl EmitterWriter {
 
         // Provided the following diagnostic message:
         //
-        //     let msg = vec![
+        //     let msgs = vec![
         //       ("
         //       ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle),
         //       ("looks", Style::Highlight),
@@ -1284,7 +1285,7 @@ impl EmitterWriter {
         //                see how it *looks* with
         //                very *weird* formats
         //                see?
-        for (text, style) in msg.iter() {
+        for (text, style) in msgs.iter() {
             let text = self.translate_message(text, args).map_err(Report::new).unwrap();
             let text = &normalize_whitespace(&text);
             let lines = text.split('\n').collect::<Vec<_>>();
@@ -1297,16 +1298,16 @@ impl EmitterWriter {
                     buffer.append(line_number, line, style_or_override(*style, override_style));
                 }
             } else {
-                buffer.append(line_number, &text, style_or_override(*style, override_style));
+                buffer.append(line_number, text, style_or_override(*style, override_style));
             }
         }
     }
 
     #[instrument(level = "trace", skip(self, args), ret)]
-    fn emit_message_default(
+    fn emit_messages_default_inner(
         &mut self,
         msp: &MultiSpan,
-        msg: &[(DiagnosticMessage, Style)],
+        msgs: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
         code: &Option<DiagnosticId>,
         level: &Level,
@@ -1327,7 +1328,7 @@ impl EmitterWriter {
                 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
                 buffer.append(0, ": ", Style::NoStyle);
             }
-            self.msg_to_buffer(&mut buffer, msg, args, max_line_num_len, "note", None);
+            self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
         } else {
             let mut label_width = 0;
             // The failure note level itself does not provide any useful diagnostic information
@@ -1348,12 +1349,19 @@ impl EmitterWriter {
                 buffer.append(0, "]", Style::Level(*level));
                 label_width += 2 + code.len();
             }
-            let header_style = if is_secondary { Style::HeaderMsg } else { Style::MainHeaderMsg };
+            let header_style = if is_secondary {
+                Style::HeaderMsg
+            } else if self.short_message {
+                // For short messages avoid bolding the message, as it doesn't look great (#63835).
+                Style::NoStyle
+            } else {
+                Style::MainHeaderMsg
+            };
             if *level != Level::FailureNote {
                 buffer.append(0, ": ", header_style);
                 label_width += 2;
             }
-            for (text, _) in msg.iter() {
+            for (text, _) in msgs.iter() {
                 let text = self.translate_message(text, args).map_err(Report::new).unwrap();
                 // Account for newlines to align output to its label.
                 for (line, text) in normalize_whitespace(&text).lines().enumerate() {
@@ -1740,7 +1748,7 @@ impl EmitterWriter {
         buffer.append(0, level.to_str(), Style::Level(*level));
         buffer.append(0, ": ", Style::HeaderMsg);
 
-        self.msg_to_buffer(
+        self.msgs_to_buffer(
             &mut buffer,
             &[(suggestion.msg.to_owned(), Style::NoStyle)],
             args,
@@ -1924,7 +1932,7 @@ impl EmitterWriter {
                 self.draw_code_line(
                     &mut buffer,
                     &mut row_num,
-                    &highlight_parts,
+                    highlight_parts,
                     line_pos + line_start,
                     line,
                     show_code_change,
@@ -2067,7 +2075,7 @@ impl EmitterWriter {
     fn emit_messages_default(
         &mut self,
         level: &Level,
-        message: &[(DiagnosticMessage, Style)],
+        messages: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
         code: &Option<DiagnosticId>,
         span: &MultiSpan,
@@ -2082,9 +2090,9 @@ impl EmitterWriter {
             num_decimal_digits(n)
         };
 
-        match self.emit_message_default(
+        match self.emit_messages_default_inner(
             span,
-            message,
+            messages,
             args,
             code,
             level,
@@ -2111,10 +2119,10 @@ impl EmitterWriter {
                 }
                 if !self.short_message {
                     for child in children {
-                        let span = child.render_span.as_ref().unwrap_or(&child.span);
-                        if let Err(err) = self.emit_message_default(
+                        let span = &child.span;
+                        if let Err(err) = self.emit_messages_default_inner(
                             span,
-                            &child.message,
+                            &child.messages,
                             args,
                             &None,
                             &child.level,
@@ -2131,7 +2139,7 @@ impl EmitterWriter {
                                 // do not display this suggestion, it is meant only for tools
                             }
                             SuggestionStyle::HideCodeAlways => {
-                                if let Err(e) = self.emit_message_default(
+                                if let Err(e) = self.emit_messages_default_inner(
                                     &MultiSpan::new(),
                                     &[(sugg.msg.to_owned(), Style::HeaderMsg)],
                                     args,
@@ -2331,7 +2339,7 @@ impl FileWithAnnotatedLines {
         let mut output = vec![];
         let mut multiline_annotations = vec![];
 
-        if let Some(ref sm) = emitter.source_map() {
+        if let Some(sm) = emitter.source_map() {
             for SpanLabel { span, is_primary, label } in msp.span_labels() {
                 // If we don't have a useful span, pick the primary span if that exists.
                 // Worst case we'll just print an error at the top of the main file.
@@ -2355,11 +2363,7 @@ impl FileWithAnnotatedLines {
 
                 let label = label.as_ref().map(|m| {
                     normalize_whitespace(
-                        &emitter
-                            .translate_message(m, &args)
-                            .map_err(Report::new)
-                            .unwrap()
-                            .to_string(),
+                        &emitter.translate_message(m, args).map_err(Report::new).unwrap(),
                     )
                 });
 
@@ -2671,6 +2675,11 @@ fn from_stderr(color: ColorConfig) -> Destination {
     }
 }
 
+/// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
+///
+/// See #36178.
+const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue };
+
 impl Style {
     fn color_spec(&self, lvl: Level) -> ColorSpec {
         let mut spec = ColorSpec::new();
@@ -2685,11 +2694,7 @@ impl Style {
             Style::LineNumber => {
                 spec.set_bold(true);
                 spec.set_intense(true);
-                if cfg!(windows) {
-                    spec.set_fg(Some(Color::Cyan));
-                } else {
-                    spec.set_fg(Some(Color::Blue));
-                }
+                spec.set_fg(Some(BRIGHT_BLUE));
             }
             Style::Quotation => {}
             Style::MainHeaderMsg => {
@@ -2704,11 +2709,7 @@ impl Style {
             }
             Style::UnderlineSecondary | Style::LabelSecondary => {
                 spec.set_bold(true).set_intense(true);
-                if cfg!(windows) {
-                    spec.set_fg(Some(Color::Cyan));
-                } else {
-                    spec.set_fg(Some(Color::Blue));
-                }
+                spec.set_fg(Some(BRIGHT_BLUE));
             }
             Style::HeaderMsg | Style::NoStyle => {}
             Style::Level(lvl) => {
@@ -2716,7 +2717,7 @@ impl Style {
                 spec.set_bold(true);
             }
             Style::Highlight => {
-                spec.set_bold(true);
+                spec.set_bold(true).set_fg(Some(Color::Magenta));
             }
         }
         spec
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index 0cb75c71b73..52fcb50e9fb 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -145,6 +145,25 @@ impl JsonEmitter {
     pub fn ignored_directories_in_source_blocks(self, value: Vec<String>) -> Self {
         Self { ignored_directories_in_source_blocks: value, ..self }
     }
+
+    fn emit(&mut self, val: EmitTyped<'_>) -> io::Result<()> {
+        if self.pretty {
+            serde_json::to_writer_pretty(&mut *self.dst, &val)?
+        } else {
+            serde_json::to_writer(&mut *self.dst, &val)?
+        };
+        self.dst.write_all(b"\n")?;
+        self.dst.flush()
+    }
+}
+
+#[derive(Serialize)]
+#[serde(tag = "$message_type", rename_all = "snake_case")]
+enum EmitTyped<'a> {
+    Diagnostic(Diagnostic),
+    Artifact(ArtifactNotification<'a>),
+    FutureIncompat(FutureIncompatReport<'a>),
+    UnusedExtern(UnusedExterns<'a, 'a, 'a>),
 }
 
 impl Translate for JsonEmitter {
@@ -160,12 +179,7 @@ impl Translate for JsonEmitter {
 impl Emitter for JsonEmitter {
     fn emit_diagnostic(&mut self, diag: &crate::Diagnostic) {
         let data = Diagnostic::from_errors_diagnostic(diag, self);
-        let result = if self.pretty {
-            writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&data).unwrap())
-        } else {
-            writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
-        }
-        .and_then(|_| self.dst.flush());
+        let result = self.emit(EmitTyped::Diagnostic(data));
         if let Err(e) = result {
             panic!("failed to print diagnostics: {e:?}");
         }
@@ -173,34 +187,28 @@ impl Emitter for JsonEmitter {
 
     fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
         let data = ArtifactNotification { artifact: path, emit: artifact_type };
-        let result = if self.pretty {
-            writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&data).unwrap())
-        } else {
-            writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
-        }
-        .and_then(|_| self.dst.flush());
+        let result = self.emit(EmitTyped::Artifact(data));
         if let Err(e) = result {
             panic!("failed to print notification: {e:?}");
         }
     }
 
     fn emit_future_breakage_report(&mut self, diags: Vec<crate::Diagnostic>) {
-        let data: Vec<FutureBreakageItem> = diags
+        let data: Vec<FutureBreakageItem<'_>> = diags
             .into_iter()
             .map(|mut diag| {
                 if diag.level == crate::Level::Allow {
                     diag.level = crate::Level::Warning(None);
                 }
-                FutureBreakageItem { diagnostic: Diagnostic::from_errors_diagnostic(&diag, self) }
+                FutureBreakageItem {
+                    diagnostic: EmitTyped::Diagnostic(Diagnostic::from_errors_diagnostic(
+                        &diag, self,
+                    )),
+                }
             })
             .collect();
         let report = FutureIncompatReport { future_incompat_report: data };
-        let result = if self.pretty {
-            writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&report).unwrap())
-        } else {
-            writeln!(&mut self.dst, "{}", serde_json::to_string(&report).unwrap())
-        }
-        .and_then(|_| self.dst.flush());
+        let result = self.emit(EmitTyped::FutureIncompat(report));
         if let Err(e) = result {
             panic!("failed to print future breakage report: {e:?}");
         }
@@ -209,12 +217,7 @@ impl Emitter for JsonEmitter {
     fn emit_unused_externs(&mut self, lint_level: rustc_lint_defs::Level, unused_externs: &[&str]) {
         let lint_level = lint_level.as_str();
         let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
-        let result = if self.pretty {
-            writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&data).unwrap())
-        } else {
-            writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
-        }
-        .and_then(|_| self.dst.flush());
+        let result = self.emit(EmitTyped::UnusedExtern(data));
         if let Err(e) = result {
             panic!("failed to print unused externs: {e:?}");
         }
@@ -313,13 +316,15 @@ struct ArtifactNotification<'a> {
 }
 
 #[derive(Serialize)]
-struct FutureBreakageItem {
-    diagnostic: Diagnostic,
+struct FutureBreakageItem<'a> {
+    // Always EmitTyped::Diagnostic, but we want to make sure it gets serialized
+    // with "$message_type".
+    diagnostic: EmitTyped<'a>,
 }
 
 #[derive(Serialize)]
-struct FutureIncompatReport {
-    future_incompat_report: Vec<FutureBreakageItem>,
+struct FutureIncompatReport<'a> {
+    future_incompat_report: Vec<FutureBreakageItem<'a>>,
 }
 
 // NOTE: Keep this in sync with the equivalent structs in rustdoc's
@@ -393,7 +398,7 @@ impl Diagnostic {
         let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
         let output = String::from_utf8(output).unwrap();
 
-        let translated_message = je.translate_messages(&diag.message, &args);
+        let translated_message = je.translate_messages(&diag.messages, &args);
         Diagnostic {
             message: translated_message.to_string(),
             code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
@@ -414,16 +419,12 @@ impl Diagnostic {
         args: &FluentArgs<'_>,
         je: &JsonEmitter,
     ) -> Diagnostic {
-        let translated_message = je.translate_messages(&diag.message, args);
+        let translated_message = je.translate_messages(&diag.messages, args);
         Diagnostic {
             message: translated_message.to_string(),
             code: None,
             level: diag.level.to_str(),
-            spans: diag
-                .render_span
-                .as_ref()
-                .map(|sp| DiagnosticSpan::from_multispan(sp, args, je))
-                .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, args, je)),
+            spans: DiagnosticSpan::from_multispan(&diag.span, args, je),
             children: vec![],
             rendered: None,
         }
diff --git a/compiler/rustc_errors/src/json/tests.rs b/compiler/rustc_errors/src/json/tests.rs
index 1f9a2981e02..303de0a93f6 100644
--- a/compiler/rustc_errors/src/json/tests.rs
+++ b/compiler/rustc_errors/src/json/tests.rs
@@ -1,11 +1,8 @@
 use super::*;
 
-use crate::json::JsonEmitter;
-use rustc_span::source_map::{FilePathMapping, SourceMap};
-
-use crate::emitter::{ColorConfig, HumanReadableErrorType};
-use crate::{Handler, TerminalUrl};
-use rustc_span::{BytePos, Span};
+use crate::emitter::ColorConfig;
+use crate::DiagCtxt;
+use rustc_span::BytePos;
 
 use std::str;
 
@@ -64,8 +61,8 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
         );
 
         let span = Span::with_root_ctxt(BytePos(span.0), BytePos(span.1));
-        let handler = Handler::with_emitter(Box::new(je));
-        handler.span_err(span, "foo");
+        let dcx = DiagCtxt::with_emitter(Box::new(je));
+        dcx.span_err(span, "foo");
 
         let bytes = output.lock().unwrap();
         let actual_output = str::from_utf8(&bytes).unwrap();
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index da74ee6391a..76b7e0d79a9 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -3,14 +3,16 @@
 //! This module contains the code for creating and emitting diagnostics.
 
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
-#![cfg_attr(not(bootstrap), doc(rust_logo))]
-#![cfg_attr(not(bootstrap), feature(rustdoc_internals))]
+#![doc(rust_logo)]
+#![feature(rustdoc_internals)]
 #![feature(array_windows)]
+#![feature(associated_type_defaults)]
+#![feature(box_into_inner)]
 #![feature(extract_if)]
 #![feature(if_let_guard)]
 #![feature(let_chains)]
+#![feature(negative_impls)]
 #![feature(never_type)]
-#![feature(result_option_inspect)]
 #![feature(rustc_attrs)]
 #![feature(yeet_expr)]
 #![feature(try_blocks)]
@@ -27,27 +29,42 @@ extern crate tracing;
 
 extern crate self as rustc_errors;
 
+pub use diagnostic::{
+    AddToDiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId,
+    DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic,
+};
+pub use diagnostic_builder::{
+    BugAbort, DiagnosticBuilder, EmissionGuarantee, FatalAbort, IntoDiagnostic,
+};
+pub use diagnostic_impls::{
+    DiagnosticArgFromDisplay, DiagnosticSymbolList, ExpectedLifetimeParameter,
+    IndicateAnonymousLifetime, InvalidFlushedDelayedDiagnosticLevel, SingleLabelManySpans,
+};
 pub use emitter::ColorConfig;
+pub use rustc_error_messages::{
+    fallback_fluent_bundle, fluent_bundle, DelayDm, DiagnosticMessage, FluentBundle,
+    LanguageIdentifier, LazyFallbackBundle, MultiSpan, SpanLabel, SubdiagnosticMessage,
+};
+pub use rustc_lint_defs::{pluralize, Applicability};
+pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker};
+pub use rustc_span::ErrorGuaranteed;
+pub use snippet::Style;
 
-use rustc_lint_defs::LintExpectationId;
-use Level::*;
+// Used by external projects such as `rust-gpu`.
+// See https://github.com/rust-lang/rust/pull/115393.
+pub use termcolor::{Color, ColorSpec, WriteColor};
 
-use emitter::{is_case_difference, DynEmitter, Emitter, EmitterWriter};
+use crate::diagnostic_impls::{DelayedAtWithNewline, DelayedAtWithoutNewline};
+use emitter::{is_case_difference, DynEmitter, Emitter, HumanEmitter};
 use registry::Registry;
-use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
+use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
 use rustc_data_structures::stable_hasher::{Hash128, StableHasher};
 use rustc_data_structures::sync::{Lock, Lrc};
 use rustc_data_structures::AtomicRef;
-pub use rustc_error_messages::{
-    fallback_fluent_bundle, fluent_bundle, DelayDm, DiagnosticMessage, FluentBundle,
-    LanguageIdentifier, LazyFallbackBundle, MultiSpan, SpanLabel, SubdiagnosticMessage,
-};
-use rustc_fluent_macro::fluent_messages;
-pub use rustc_lint_defs::{pluralize, Applicability};
+use rustc_lint_defs::LintExpectationId;
 use rustc_span::source_map::SourceMap;
-pub use rustc_span::ErrorGuaranteed;
 use rustc_span::{Loc, Span, DUMMY_SP};
-
+use std::backtrace::{Backtrace, BacktraceStatus};
 use std::borrow::Cow;
 use std::error::Report;
 use std::fmt;
@@ -57,9 +74,7 @@ use std::num::NonZeroUsize;
 use std::panic;
 use std::path::{Path, PathBuf};
 
-// Used by external projects such as `rust-gpu`.
-// See https://github.com/rust-lang/rust/pull/115393.
-pub use termcolor::{Color, ColorSpec, WriteColor};
+use Level::*;
 
 pub mod annotate_snippet_emitter_writer;
 mod diagnostic;
@@ -77,13 +92,10 @@ mod styled_buffer;
 mod tests;
 pub mod translation;
 
-pub use diagnostic_builder::IntoDiagnostic;
-pub use snippet::Style;
-
-pub type PErr<'a> = DiagnosticBuilder<'a, ErrorGuaranteed>;
+pub type PErr<'a> = DiagnosticBuilder<'a>;
 pub type PResult<'a, T> = Result<T, PErr<'a>>;
 
-fluent_messages! { "../messages.ftl" }
+rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
 
 // `PResult` is used a lot. Make sure it doesn't unintentionally get bigger.
 // (See also the comment on `DiagnosticBuilderInner`'s `diagnostic` field.)
@@ -164,7 +176,7 @@ pub struct SubstitutionPart {
 /// Used to translate between `Span`s and byte positions within a single output line in highlighted
 /// code of structured suggestions.
 #[derive(Debug, Clone, Copy)]
-pub struct SubstitutionHighlight {
+pub(crate) struct SubstitutionHighlight {
     start: usize,
     end: usize,
 }
@@ -191,7 +203,7 @@ impl SubstitutionPart {
 impl CodeSuggestion {
     /// Returns the assembled code suggestions, whether they should be shown with an underline
     /// and whether the substitution only differs in capitalization.
-    pub fn splice_lines(
+    pub(crate) fn splice_lines(
         &self,
         sm: &SourceMap,
     ) -> Vec<(String, Vec<SubstitutionPart>, Vec<Vec<SubstitutionHighlight>>, bool)> {
@@ -388,8 +400,6 @@ impl CodeSuggestion {
     }
 }
 
-pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker};
-
 /// Signifies that the compiler died with an explicit call to `.bug`
 /// or `.span_bug` rather than a failed assertion, etc.
 pub struct ExplicitBug;
@@ -398,31 +408,18 @@ pub struct ExplicitBug;
 /// rather than a failed assertion, etc.
 pub struct DelayedBugPanic;
 
-use crate::diagnostic_impls::{DelayedAtWithNewline, DelayedAtWithoutNewline};
-pub use diagnostic::{
-    AddToDiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId,
-    DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic,
-};
-pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee, Noted};
-pub use diagnostic_impls::{
-    DiagnosticArgFromDisplay, DiagnosticSymbolList, ExpectedLifetimeParameter,
-    IndicateAnonymousLifetime, InvalidFlushedDelayedDiagnosticLevel, LabelKind,
-    SingleLabelManySpans,
-};
-use std::backtrace::{Backtrace, BacktraceStatus};
-
-/// A handler deals with errors and other compiler output.
+/// A `DiagCtxt` deals with errors and other compiler output.
 /// Certain errors (fatal, bug, unimpl) may cause immediate exit,
 /// others log errors for later reporting.
-pub struct Handler {
-    inner: Lock<HandlerInner>,
+pub struct DiagCtxt {
+    inner: Lock<DiagCtxtInner>,
 }
 
 /// This inner struct exists to keep it all behind a single lock;
 /// this is done to prevent possible deadlocks in a multi-threaded compiler,
 /// as well as inconsistent state observation.
-struct HandlerInner {
-    flags: HandlerFlags,
+struct DiagCtxtInner {
+    flags: DiagCtxtFlags,
     /// The number of lint errors that have been emitted.
     lint_err_count: usize,
     /// The number of errors that have been emitted, including duplicates.
@@ -433,10 +430,10 @@ struct HandlerInner {
     warn_count: usize,
     deduplicated_err_count: usize,
     emitter: Box<DynEmitter>,
-    delayed_span_bugs: Vec<DelayedDiagnostic>,
-    delayed_good_path_bugs: Vec<DelayedDiagnostic>,
+    span_delayed_bugs: Vec<DelayedDiagnostic>,
+    good_path_delayed_bugs: Vec<DelayedDiagnostic>,
     /// This flag indicates that an expected diagnostic was emitted and suppressed.
-    /// This is used for the `delayed_good_path_bugs` check.
+    /// This is used for the `good_path_delayed_bugs` check.
     suppressed_expected_diag: bool,
 
     /// This set contains the `DiagnosticId` of all emitted diagnostics to avoid
@@ -448,7 +445,7 @@ struct HandlerInner {
     emitted_diagnostic_codes: FxIndexSet<DiagnosticId>,
 
     /// This set contains a hash of every diagnostic that has been emitted by
-    /// this handler. These hashes is used to avoid emitting the same error
+    /// this `DiagCtxt`. These hashes is used to avoid emitting the same error
     /// twice.
     emitted_diagnostics: FxHashSet<Hash128>,
 
@@ -508,29 +505,25 @@ pub enum StashKey {
     TraitMissingMethod,
     OpaqueHiddenTypeMismatch,
     MaybeForgetReturn,
+    /// Query cycle detected, stashing in favor of a better error.
+    Cycle,
 }
 
-fn default_track_diagnostic(d: &mut Diagnostic, f: &mut dyn FnMut(&mut Diagnostic)) {
-    (*f)(d)
+fn default_track_diagnostic(diag: Diagnostic, f: &mut dyn FnMut(Diagnostic)) {
+    (*f)(diag)
 }
 
-pub static TRACK_DIAGNOSTICS: AtomicRef<fn(&mut Diagnostic, &mut dyn FnMut(&mut Diagnostic))> =
+pub static TRACK_DIAGNOSTICS: AtomicRef<fn(Diagnostic, &mut dyn FnMut(Diagnostic))> =
     AtomicRef::new(&(default_track_diagnostic as _));
 
 #[derive(Copy, Clone, Default)]
-pub struct HandlerFlags {
+pub struct DiagCtxtFlags {
     /// If false, warning-level lints are suppressed.
     /// (rustc: see `--allow warnings` and `--cap-lints`)
     pub can_emit_warnings: bool,
     /// If Some, the Nth error-level diagnostic is upgraded to bug-level.
     /// (rustc: see `-Z treat-err-as-bug`)
     pub treat_err_as_bug: Option<NonZeroUsize>,
-    /// If true, immediately emit diagnostics that would otherwise be buffered.
-    /// (rustc: see `-Z dont-buffer-diagnostics` and `-Z treat-err-as-bug`)
-    pub dont_buffer_diagnostics: bool,
-    /// If true, immediately print bugs registered with `delay_span_bug`.
-    /// (rustc: see `-Z report-delayed-bugs`)
-    pub report_delayed_bugs: bool,
     /// Show macro backtraces.
     /// (rustc: see `-Z macro-backtrace`)
     pub macro_backtrace: bool,
@@ -540,25 +533,26 @@ pub struct HandlerFlags {
     pub track_diagnostics: bool,
 }
 
-impl Drop for HandlerInner {
+impl Drop for DiagCtxtInner {
     fn drop(&mut self) {
         self.emit_stashed_diagnostics();
 
         if !self.has_errors() {
-            let bugs = std::mem::replace(&mut self.delayed_span_bugs, Vec::new());
-            self.flush_delayed(bugs, "no errors encountered even though `delay_span_bug` issued");
+            let bugs = std::mem::replace(&mut self.span_delayed_bugs, Vec::new());
+            self.flush_delayed(bugs, "no errors encountered even though `span_delayed_bug` issued");
         }
 
-        // FIXME(eddyb) this explains what `delayed_good_path_bugs` are!
-        // They're `delayed_span_bugs` but for "require some diagnostic happened"
+        // FIXME(eddyb) this explains what `good_path_delayed_bugs` are!
+        // They're `span_delayed_bugs` but for "require some diagnostic happened"
         // instead of "require some error happened". Sadly that isn't ideal, as
         // lints can be `#[allow]`'d, potentially leading to this triggering.
         // Also, "good path" should be replaced with a better naming.
-        if !self.has_any_message() && !self.suppressed_expected_diag {
-            let bugs = std::mem::replace(&mut self.delayed_good_path_bugs, Vec::new());
+        let has_any_message = self.err_count > 0 || self.lint_err_count > 0 || self.warn_count > 0;
+        if !has_any_message && !self.suppressed_expected_diag && !std::thread::panicking() {
+            let bugs = std::mem::replace(&mut self.good_path_delayed_bugs, Vec::new());
             self.flush_delayed(
                 bugs,
-                "no warnings or errors encountered even though `delayed_good_path_bugs` issued",
+                "no warnings or errors encountered even though `good_path_delayed_bugs` issued",
             );
         }
 
@@ -571,12 +565,12 @@ impl Drop for HandlerInner {
     }
 }
 
-impl Handler {
+impl DiagCtxt {
     pub fn with_tty_emitter(
         sm: Option<Lrc<SourceMap>>,
         fallback_bundle: LazyFallbackBundle,
     ) -> Self {
-        let emitter = Box::new(EmitterWriter::stderr(ColorConfig::Auto, fallback_bundle).sm(sm));
+        let emitter = Box::new(HumanEmitter::stderr(ColorConfig::Auto, fallback_bundle).sm(sm));
         Self::with_emitter(emitter)
     }
     pub fn disable_warnings(mut self) -> Self {
@@ -584,12 +578,7 @@ impl Handler {
         self
     }
 
-    pub fn treat_err_as_bug(mut self, treat_err_as_bug: NonZeroUsize) -> Self {
-        self.inner.get_mut().flags.treat_err_as_bug = Some(treat_err_as_bug);
-        self
-    }
-
-    pub fn with_flags(mut self, flags: HandlerFlags) -> Self {
+    pub fn with_flags(mut self, flags: DiagCtxtFlags) -> Self {
         self.inner.get_mut().flags = flags;
         self
     }
@@ -601,16 +590,16 @@ impl Handler {
 
     pub fn with_emitter(emitter: Box<DynEmitter>) -> Self {
         Self {
-            inner: Lock::new(HandlerInner {
-                flags: HandlerFlags { can_emit_warnings: true, ..Default::default() },
+            inner: Lock::new(DiagCtxtInner {
+                flags: DiagCtxtFlags { can_emit_warnings: true, ..Default::default() },
                 lint_err_count: 0,
                 err_count: 0,
                 warn_count: 0,
                 deduplicated_err_count: 0,
                 deduplicated_warn_count: 0,
                 emitter,
-                delayed_span_bugs: Vec::new(),
-                delayed_good_path_bugs: Vec::new(),
+                span_delayed_bugs: Vec::new(),
+                good_path_delayed_bugs: Vec::new(),
                 suppressed_expected_diag: false,
                 taught_diagnostics: Default::default(),
                 emitted_diagnostic_codes: Default::default(),
@@ -648,7 +637,7 @@ impl Handler {
     // This is here to not allow mutation of flags;
     // as of this writing it's only used in tests in librustc_middle.
     pub fn can_emit_warnings(&self) -> bool {
-        self.inner.lock().flags.can_emit_warnings
+        self.inner.borrow_mut().flags.can_emit_warnings
     }
 
     /// Resets the diagnostic error count as well as the cached emitted diagnostics.
@@ -664,8 +653,8 @@ impl Handler {
         inner.deduplicated_warn_count = 0;
 
         // actually free the underlying memory (which `clear` would not do)
-        inner.delayed_span_bugs = Default::default();
-        inner.delayed_good_path_bugs = Default::default();
+        inner.span_delayed_bugs = Default::default();
+        inner.good_path_delayed_bugs = Default::default();
         inner.taught_diagnostics = Default::default();
         inner.emitted_diagnostic_codes = Default::default();
         inner.emitted_diagnostics = Default::default();
@@ -676,15 +665,45 @@ impl Handler {
     /// Retrieve a stashed diagnostic with `steal_diagnostic`.
     pub fn stash_diagnostic(&self, span: Span, key: StashKey, diag: Diagnostic) {
         let mut inner = self.inner.borrow_mut();
-        inner.stash((span.with_parent(None), key), diag);
+
+        let key = (span.with_parent(None), key);
+
+        if diag.is_error() {
+            if diag.level == Error && diag.is_lint {
+                inner.lint_err_count += 1;
+            } else {
+                inner.err_count += 1;
+            }
+        } else {
+            // Warnings are only automatically flushed if they're forced.
+            if diag.is_force_warn() {
+                inner.warn_count += 1;
+            }
+        }
+
+        // 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);
     }
 
     /// Steal a previously stashed diagnostic with the given `Span` and [`StashKey`] as the key.
     pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option<DiagnosticBuilder<'_, ()>> {
         let mut inner = self.inner.borrow_mut();
-        inner
-            .steal((span.with_parent(None), key))
-            .map(|diag| DiagnosticBuilder::new_diagnostic(self, diag))
+        let key = (span.with_parent(None), key);
+        let diag = inner.stashed_diagnostics.remove(&key)?;
+        if diag.is_error() {
+            if diag.level == Error && diag.is_lint {
+                inner.lint_err_count -= 1;
+            } else {
+                inner.err_count -= 1;
+            }
+        } else {
+            if diag.is_force_warn() {
+                inner.warn_count -= 1;
+            }
+        }
+        Some(DiagnosticBuilder::new_diagnostic(self, diag))
     }
 
     pub fn has_stashed_diagnostic(&self, span: Span, key: StashKey) -> bool {
@@ -696,16 +715,6 @@ impl Handler {
         self.inner.borrow_mut().emit_stashed_diagnostics()
     }
 
-    /// Construct a builder with the `msg` at the level appropriate for the specific `EmissionGuarantee`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_diagnostic<G: EmissionGuarantee>(
-        &self,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, G> {
-        G::make_diagnostic_builder(self, msg)
-    }
-
     /// Construct a builder at the `Warning` level at the given `span` and with the `msg`.
     ///
     /// Attempting to `.emit()` the builder will only emit if either:
@@ -718,55 +727,7 @@ impl Handler {
         span: impl Into<MultiSpan>,
         msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ()> {
-        let mut result = self.struct_warn(msg);
-        result.set_span(span);
-        result
-    }
-
-    /// Construct a builder at the `Warning` level at the given `span` and with the `msg`.
-    /// The `id` is used for lint emissions which should also fulfill a lint expectation.
-    ///
-    /// Attempting to `.emit()` the builder will only emit if either:
-    /// * `can_emit_warnings` is `true`
-    /// * `is_force_warn` was set in `DiagnosticId::Lint`
-    #[track_caller]
-    pub fn struct_span_warn_with_expectation(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-        id: LintExpectationId,
-    ) -> DiagnosticBuilder<'_, ()> {
-        let mut result = self.struct_warn_with_expectation(msg, id);
-        result.set_span(span);
-        result
-    }
-
-    /// Construct a builder at the `Allow` level at the given `span` and with the `msg`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_span_allow(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, ()> {
-        let mut result = self.struct_allow(msg);
-        result.set_span(span);
-        result
-    }
-
-    /// Construct a builder at the `Warning` level at the given `span` and with the `msg`.
-    /// Also include a code.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_span_warn_with_code(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-        code: DiagnosticId,
-    ) -> DiagnosticBuilder<'_, ()> {
-        let mut result = self.struct_span_warn(span, msg);
-        result.code(code);
-        result
+        self.struct_warn(msg).span_mv(span)
     }
 
     /// Construct a builder at the `Warning` level with the `msg`.
@@ -777,29 +738,14 @@ impl Handler {
     #[rustc_lint_diagnostics]
     #[track_caller]
     pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Level::Warning(None), msg)
-    }
-
-    /// Construct a builder at the `Warning` level with the `msg`. The `id` is used for
-    /// lint emissions which should also fulfill a lint expectation.
-    ///
-    /// Attempting to `.emit()` the builder will only emit if either:
-    /// * `can_emit_warnings` is `true`
-    /// * `is_force_warn` was set in `DiagnosticId::Lint`
-    #[track_caller]
-    pub fn struct_warn_with_expectation(
-        &self,
-        msg: impl Into<DiagnosticMessage>,
-        id: LintExpectationId,
-    ) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Level::Warning(Some(id)), msg)
+        DiagnosticBuilder::new(self, Warning(None), msg)
     }
 
     /// Construct a builder at the `Allow` level with the `msg`.
     #[rustc_lint_diagnostics]
     #[track_caller]
     pub fn struct_allow(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Level::Allow, msg)
+        DiagnosticBuilder::new(self, Allow, msg)
     }
 
     /// Construct a builder at the `Expect` level with the `msg`.
@@ -810,7 +756,7 @@ impl Handler {
         msg: impl Into<DiagnosticMessage>,
         id: LintExpectationId,
     ) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Level::Expect(id), msg)
+        DiagnosticBuilder::new(self, Expect(id), msg)
     }
 
     /// Construct a builder at the `Error` level at the given `span` and with the `msg`.
@@ -820,68 +766,16 @@ impl Handler {
         &self,
         span: impl Into<MultiSpan>,
         msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
-        let mut result = self.struct_err(msg);
-        result.set_span(span);
-        result
-    }
-
-    /// Construct a builder at the `Error` level at the given `span`, with the `msg`, and `code`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_span_err_with_code(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-        code: DiagnosticId,
-    ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
-        let mut result = self.struct_span_err(span, msg);
-        result.code(code);
-        result
+    ) -> DiagnosticBuilder<'_> {
+        self.struct_err(msg).span_mv(span)
     }
 
     /// Construct a builder at the `Error` level with the `msg`.
     // FIXME: This method should be removed (every error should have an associated error code).
     #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn struct_err(
-        &self,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
-        DiagnosticBuilder::new_guaranteeing_error(self, msg)
-    }
-
-    /// This should only be used by `rustc_middle::lint::struct_lint_level`. Do not use it for hard errors.
-    #[doc(hidden)]
-    #[track_caller]
-    pub fn struct_err_lint(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Level::Error { lint: true }, msg)
-    }
-
-    /// Construct a builder at the `Error` level with the `msg` and the `code`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_err_with_code(
-        &self,
-        msg: impl Into<DiagnosticMessage>,
-        code: DiagnosticId,
-    ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
-        let mut result = self.struct_err(msg);
-        result.code(code);
-        result
-    }
-
-    /// Construct a builder at the `Warn` level with the `msg` and the `code`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_warn_with_code(
-        &self,
-        msg: impl Into<DiagnosticMessage>,
-        code: DiagnosticId,
-    ) -> DiagnosticBuilder<'_, ()> {
-        let mut result = self.struct_warn(msg);
-        result.code(code);
-        result
+    pub fn struct_err(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_> {
+        DiagnosticBuilder::new(self, Error, msg)
     }
 
     /// Construct a builder at the `Fatal` level at the given `span` and with the `msg`.
@@ -891,185 +785,161 @@ impl Handler {
         &self,
         span: impl Into<MultiSpan>,
         msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, !> {
-        let mut result = self.struct_fatal(msg);
-        result.set_span(span);
-        result
+    ) -> DiagnosticBuilder<'_, FatalAbort> {
+        self.struct_fatal(msg).span_mv(span)
     }
 
-    /// Construct a builder at the `Fatal` level at the given `span`, with the `msg`, and `code`.
+    /// Construct a builder at the `Fatal` level with the `msg`.
     #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn struct_span_fatal_with_code(
+    pub fn struct_fatal(
         &self,
-        span: impl Into<MultiSpan>,
         msg: impl Into<DiagnosticMessage>,
-        code: DiagnosticId,
-    ) -> DiagnosticBuilder<'_, !> {
-        let mut result = self.struct_span_fatal(span, msg);
-        result.code(code);
-        result
-    }
-
-    /// Construct a builder at the `Error` level with the `msg`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_fatal(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, !> {
-        DiagnosticBuilder::new_fatal(self, msg)
+    ) -> DiagnosticBuilder<'_, FatalAbort> {
+        DiagnosticBuilder::new(self, Fatal, msg)
     }
 
     /// Construct a builder at the `Help` level with the `msg`.
     #[rustc_lint_diagnostics]
     pub fn struct_help(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Level::Help, msg)
+        DiagnosticBuilder::new(self, Help, msg)
     }
 
     /// Construct a builder at the `Note` level with the `msg`.
     #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn struct_note_without_error(
-        &self,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Level::Note, msg)
+    pub fn struct_note(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
+        DiagnosticBuilder::new(self, Note, msg)
     }
 
+    /// Construct a builder at the `Bug` level with the `msg`.
     #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn span_fatal(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
-        self.emit_diag_at_span(Diagnostic::new(Fatal, msg), span);
-        FatalError.raise()
+    pub fn struct_bug(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, BugAbort> {
+        DiagnosticBuilder::new(self, Bug, msg)
     }
 
+    /// Construct a builder at the `Bug` level at the given `span` with the `msg`.
     #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn span_fatal_with_code(
+    pub fn struct_span_bug(
         &self,
         span: impl Into<MultiSpan>,
         msg: impl Into<DiagnosticMessage>,
-        code: DiagnosticId,
-    ) -> ! {
-        self.emit_diag_at_span(Diagnostic::new_with_code(Fatal, Some(code), msg), span);
-        FatalError.raise()
+    ) -> DiagnosticBuilder<'_, BugAbort> {
+        self.struct_bug(msg).span_mv(span)
     }
 
     #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn span_err(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> ErrorGuaranteed {
-        self.emit_diag_at_span(Diagnostic::new(Error { lint: false }, msg), span).unwrap()
+    pub fn span_fatal(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
+        self.struct_span_fatal(span, msg).emit()
     }
 
     #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn span_err_with_code(
+    pub fn span_err(
         &self,
         span: impl Into<MultiSpan>,
         msg: impl Into<DiagnosticMessage>,
-        code: DiagnosticId,
-    ) {
-        self.emit_diag_at_span(
-            Diagnostic::new_with_code(Error { lint: false }, Some(code), msg),
-            span,
-        );
+    ) -> ErrorGuaranteed {
+        self.struct_span_err(span, msg).emit()
     }
 
     #[rustc_lint_diagnostics]
     #[track_caller]
     pub fn span_warn(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
-        self.emit_diag_at_span(Diagnostic::new(Warning(None), msg), span);
-    }
-
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn span_warn_with_code(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-        code: DiagnosticId,
-    ) {
-        self.emit_diag_at_span(Diagnostic::new_with_code(Warning(None), Some(code), msg), span);
+        self.struct_span_warn(span, msg).emit()
     }
 
-    pub fn span_bug(&self, span: impl Into<MultiSpan>, msg: impl Into<String>) -> ! {
-        self.inner.borrow_mut().span_bug(span, msg)
+    pub fn span_bug(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
+        self.struct_span_bug(span, msg).emit()
     }
 
-    /// For documentation on this, see `Session::delay_span_bug`.
+    /// Ensures that compilation cannot succeed.
+    ///
+    /// If this function has been called but no errors have been emitted and
+    /// compilation succeeds, it will cause an internal compiler error (ICE).
+    ///
+    /// This can be used in code paths that should never run on successful compilations.
+    /// For example, it can be used to create an [`ErrorGuaranteed`]
+    /// (but you should prefer threading through the [`ErrorGuaranteed`] from an error emission
+    /// directly).
+    ///
+    /// If no span is available, use [`DUMMY_SP`].
+    ///
+    /// [`DUMMY_SP`]: rustc_span::DUMMY_SP
+    ///
+    /// Note: this function used to be called `delay_span_bug`. It was renamed
+    /// to match similar functions like `span_err`, `span_warn`, etc.
     #[track_caller]
-    pub fn delay_span_bug(
+    pub fn span_delayed_bug(
         &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<String>,
+        sp: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
     ) -> ErrorGuaranteed {
-        self.inner.borrow_mut().delay_span_bug(span, msg)
+        let treat_next_err_as_bug = self.inner.borrow().treat_next_err_as_bug();
+        if treat_next_err_as_bug {
+            self.span_bug(sp, msg);
+        }
+        let mut diagnostic = Diagnostic::new(DelayedBug, msg);
+        diagnostic.span(sp);
+        self.emit_diagnostic(diagnostic).unwrap()
     }
 
-    // FIXME(eddyb) note the comment inside `impl Drop for HandlerInner`, that's
+    // FIXME(eddyb) note the comment inside `impl Drop for DiagCtxtInner`, that's
     // where the explanation of what "good path" is (also, it should be renamed).
-    pub fn delay_good_path_bug(&self, msg: impl Into<DiagnosticMessage>) {
-        self.inner.borrow_mut().delay_good_path_bug(msg)
-    }
-
-    #[track_caller]
-    pub fn span_bug_no_panic(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
-        self.emit_diag_at_span(Diagnostic::new(Bug, msg), span);
+    pub fn good_path_delayed_bug(&self, msg: impl Into<DiagnosticMessage>) {
+        let mut inner = self.inner.borrow_mut();
+        let diagnostic = Diagnostic::new(DelayedBug, msg);
+        let backtrace = std::backtrace::Backtrace::capture();
+        inner.good_path_delayed_bugs.push(DelayedDiagnostic::with_backtrace(diagnostic, backtrace));
     }
 
     #[track_caller]
     #[rustc_lint_diagnostics]
-    pub fn span_note_without_error(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) {
-        self.emit_diag_at_span(Diagnostic::new(Note, msg), span);
+    pub fn span_note(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
+        self.struct_span_note(span, msg).emit()
     }
 
     #[track_caller]
     #[rustc_lint_diagnostics]
-    pub fn span_note_diag(
+    pub fn struct_span_note(
         &self,
-        span: Span,
+        span: impl Into<MultiSpan>,
         msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ()> {
-        let mut db = DiagnosticBuilder::new(self, Note, msg);
-        db.set_span(span);
-        db
+        DiagnosticBuilder::new(self, Note, msg).span_mv(span)
     }
 
-    // NOTE: intentionally doesn't raise an error so rustc_codegen_ssa only reports fatal errors in the main thread
     #[rustc_lint_diagnostics]
-    pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> FatalError {
-        self.inner.borrow_mut().fatal(msg)
+    pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> ! {
+        self.struct_fatal(msg).emit()
     }
 
     #[rustc_lint_diagnostics]
     pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
-        self.inner.borrow_mut().err(msg)
+        self.struct_err(msg).emit()
     }
 
     #[rustc_lint_diagnostics]
     pub fn warn(&self, msg: impl Into<DiagnosticMessage>) {
-        let mut db = DiagnosticBuilder::new(self, Warning(None), msg);
-        db.emit();
+        self.struct_warn(msg).emit()
     }
 
     #[rustc_lint_diagnostics]
-    pub fn note_without_error(&self, msg: impl Into<DiagnosticMessage>) {
-        DiagnosticBuilder::new(self, Note, msg).emit();
+    pub fn note(&self, msg: impl Into<DiagnosticMessage>) {
+        self.struct_note(msg).emit()
     }
 
+    #[rustc_lint_diagnostics]
     pub fn bug(&self, msg: impl Into<DiagnosticMessage>) -> ! {
-        self.inner.borrow_mut().bug(msg)
+        self.struct_bug(msg).emit()
     }
 
     #[inline]
     pub fn err_count(&self) -> usize {
-        self.inner.borrow().err_count()
+        self.inner.borrow().err_count
     }
 
     pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
@@ -1080,26 +950,100 @@ impl Handler {
     }
 
     pub fn has_errors_or_lint_errors(&self) -> Option<ErrorGuaranteed> {
-        self.inner.borrow().has_errors_or_lint_errors().then(|| {
+        let inner = self.inner.borrow();
+        let has_errors_or_lint_errors = inner.has_errors() || inner.lint_err_count > 0;
+        has_errors_or_lint_errors.then(|| {
             #[allow(deprecated)]
             ErrorGuaranteed::unchecked_claim_error_was_emitted()
         })
     }
-    pub fn has_errors_or_delayed_span_bugs(&self) -> Option<ErrorGuaranteed> {
-        self.inner.borrow().has_errors_or_delayed_span_bugs().then(|| {
+
+    pub fn has_errors_or_span_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
+        let inner = self.inner.borrow();
+        let has_errors_or_span_delayed_bugs =
+            inner.has_errors() || !inner.span_delayed_bugs.is_empty();
+        has_errors_or_span_delayed_bugs.then(|| {
             #[allow(deprecated)]
             ErrorGuaranteed::unchecked_claim_error_was_emitted()
         })
     }
+
     pub fn is_compilation_going_to_fail(&self) -> Option<ErrorGuaranteed> {
-        self.inner.borrow().is_compilation_going_to_fail().then(|| {
+        let inner = self.inner.borrow();
+        let will_fail =
+            inner.has_errors() || inner.lint_err_count > 0 || !inner.span_delayed_bugs.is_empty();
+        will_fail.then(|| {
             #[allow(deprecated)]
             ErrorGuaranteed::unchecked_claim_error_was_emitted()
         })
     }
 
     pub fn print_error_count(&self, registry: &Registry) {
-        self.inner.borrow_mut().print_error_count(registry)
+        let mut inner = self.inner.borrow_mut();
+
+        inner.emit_stashed_diagnostics();
+
+        let warnings = match inner.deduplicated_warn_count {
+            0 => Cow::from(""),
+            1 => Cow::from("1 warning emitted"),
+            count => Cow::from(format!("{count} warnings emitted")),
+        };
+        let errors = match inner.deduplicated_err_count {
+            0 => Cow::from(""),
+            1 => Cow::from("aborting due to 1 previous error"),
+            count => Cow::from(format!("aborting due to {count} previous errors")),
+        };
+        if inner.treat_err_as_bug() {
+            return;
+        }
+
+        match (errors.len(), warnings.len()) {
+            (0, 0) => return,
+            (0, _) => inner
+                .emitter
+                .emit_diagnostic(&Diagnostic::new(Warning(None), DiagnosticMessage::Str(warnings))),
+            (_, 0) => {
+                inner.emit_diagnostic(Diagnostic::new(Fatal, errors));
+            }
+            (_, _) => {
+                inner.emit_diagnostic(Diagnostic::new(Fatal, format!("{errors}; {warnings}")));
+            }
+        }
+
+        let can_show_explain = inner.emitter.should_show_explain();
+        let are_there_diagnostics = !inner.emitted_diagnostic_codes.is_empty();
+        if can_show_explain && are_there_diagnostics {
+            let mut error_codes = inner
+                .emitted_diagnostic_codes
+                .iter()
+                .filter_map(|x| match &x {
+                    DiagnosticId::Error(s) if registry.try_find_description(s).is_ok() => {
+                        Some(s.clone())
+                    }
+                    _ => None,
+                })
+                .collect::<Vec<_>>();
+            if !error_codes.is_empty() {
+                error_codes.sort();
+                if error_codes.len() > 1 {
+                    let limit = if error_codes.len() > 9 { 9 } else { error_codes.len() };
+                    inner.failure_note(format!(
+                        "Some errors have detailed explanations: {}{}",
+                        error_codes[..limit].join(", "),
+                        if error_codes.len() > 9 { "..." } else { "." }
+                    ));
+                    inner.failure_note(format!(
+                        "For more information about an error, try `rustc --explain {}`.",
+                        &error_codes[0]
+                    ));
+                } else {
+                    inner.failure_note(format!(
+                        "For more information about this error, try `rustc --explain {}`.",
+                        &error_codes[0]
+                    ));
+                }
+            }
+        }
     }
 
     pub fn take_future_breakage_diagnostics(&self) -> Vec<Diagnostic> {
@@ -1107,7 +1051,11 @@ impl Handler {
     }
 
     pub fn abort_if_errors(&self) {
-        self.inner.borrow_mut().abort_if_errors()
+        let mut inner = self.inner.borrow_mut();
+        inner.emit_stashed_diagnostics();
+        if inner.has_errors() {
+            FatalError.raise();
+        }
     }
 
     /// `true` if we haven't taught a diagnostic with this code already.
@@ -1116,46 +1064,49 @@ impl Handler {
     /// Used to suppress emitting the same error multiple times with extended explanation when
     /// calling `-Zteach`.
     pub fn must_teach(&self, code: &DiagnosticId) -> bool {
-        self.inner.borrow_mut().must_teach(code)
+        self.inner.borrow_mut().taught_diagnostics.insert(code.clone())
     }
 
     pub fn force_print_diagnostic(&self, db: Diagnostic) {
-        self.inner.borrow_mut().force_print_diagnostic(db)
+        self.inner.borrow_mut().emitter.emit_diagnostic(&db);
     }
 
-    pub fn emit_diagnostic(&self, diagnostic: &mut Diagnostic) -> Option<ErrorGuaranteed> {
+    pub fn emit_diagnostic(&self, diagnostic: Diagnostic) -> Option<ErrorGuaranteed> {
         self.inner.borrow_mut().emit_diagnostic(diagnostic)
     }
 
+    #[track_caller]
     pub fn emit_err<'a>(&'a self, err: impl IntoDiagnostic<'a>) -> ErrorGuaranteed {
         self.create_err(err).emit()
     }
 
-    pub fn create_err<'a>(
-        &'a self,
-        err: impl IntoDiagnostic<'a>,
-    ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
-        err.into_diagnostic(self)
+    #[track_caller]
+    pub fn create_err<'a>(&'a self, err: impl IntoDiagnostic<'a>) -> DiagnosticBuilder<'a> {
+        err.into_diagnostic(self, Error)
     }
 
+    #[track_caller]
     pub fn create_warning<'a>(
         &'a self,
         warning: impl IntoDiagnostic<'a, ()>,
     ) -> DiagnosticBuilder<'a, ()> {
-        warning.into_diagnostic(self)
+        warning.into_diagnostic(self, Warning(None))
     }
 
+    #[track_caller]
     pub fn emit_warning<'a>(&'a self, warning: impl IntoDiagnostic<'a, ()>) {
         self.create_warning(warning).emit()
     }
 
+    #[track_caller]
     pub fn create_almost_fatal<'a>(
         &'a self,
         fatal: impl IntoDiagnostic<'a, FatalError>,
     ) -> DiagnosticBuilder<'a, FatalError> {
-        fatal.into_diagnostic(self)
+        fatal.into_diagnostic(self, Fatal)
     }
 
+    #[track_caller]
     pub fn emit_almost_fatal<'a>(
         &'a self,
         fatal: impl IntoDiagnostic<'a, FatalError>,
@@ -1163,53 +1114,47 @@ impl Handler {
         self.create_almost_fatal(fatal).emit()
     }
 
+    #[track_caller]
     pub fn create_fatal<'a>(
         &'a self,
-        fatal: impl IntoDiagnostic<'a, !>,
-    ) -> DiagnosticBuilder<'a, !> {
-        fatal.into_diagnostic(self)
+        fatal: impl IntoDiagnostic<'a, FatalAbort>,
+    ) -> DiagnosticBuilder<'a, FatalAbort> {
+        fatal.into_diagnostic(self, Fatal)
     }
 
-    pub fn emit_fatal<'a>(&'a self, fatal: impl IntoDiagnostic<'a, !>) -> ! {
+    #[track_caller]
+    pub fn emit_fatal<'a>(&'a self, fatal: impl IntoDiagnostic<'a, FatalAbort>) -> ! {
         self.create_fatal(fatal).emit()
     }
 
+    #[track_caller]
     pub fn create_bug<'a>(
         &'a self,
-        bug: impl IntoDiagnostic<'a, diagnostic_builder::Bug>,
-    ) -> DiagnosticBuilder<'a, diagnostic_builder::Bug> {
-        bug.into_diagnostic(self)
+        bug: impl IntoDiagnostic<'a, BugAbort>,
+    ) -> DiagnosticBuilder<'a, BugAbort> {
+        bug.into_diagnostic(self, Bug)
     }
 
-    pub fn emit_bug<'a>(
-        &'a self,
-        bug: impl IntoDiagnostic<'a, diagnostic_builder::Bug>,
-    ) -> diagnostic_builder::Bug {
+    #[track_caller]
+    pub fn emit_bug<'a>(&'a self, bug: impl IntoDiagnostic<'a, diagnostic_builder::BugAbort>) -> ! {
         self.create_bug(bug).emit()
     }
 
-    pub fn emit_note<'a>(&'a self, note: impl IntoDiagnostic<'a, Noted>) -> Noted {
+    #[track_caller]
+    pub fn emit_note<'a>(&'a self, note: impl IntoDiagnostic<'a, ()>) {
         self.create_note(note).emit()
     }
 
+    #[track_caller]
     pub fn create_note<'a>(
         &'a self,
-        note: impl IntoDiagnostic<'a, Noted>,
-    ) -> DiagnosticBuilder<'a, Noted> {
-        note.into_diagnostic(self)
-    }
-
-    fn emit_diag_at_span(
-        &self,
-        mut diag: Diagnostic,
-        sp: impl Into<MultiSpan>,
-    ) -> Option<ErrorGuaranteed> {
-        let mut inner = self.inner.borrow_mut();
-        inner.emit_diagnostic(diag.set_span(sp))
+        note: impl IntoDiagnostic<'a, ()>,
+    ) -> DiagnosticBuilder<'a, ()> {
+        note.into_diagnostic(self, Note)
     }
 
     pub fn emit_artifact_notification(&self, path: &Path, artifact_type: &str) {
-        self.inner.borrow_mut().emit_artifact_notification(path, artifact_type)
+        self.inner.borrow_mut().emitter.emit_artifact_notification(path, artifact_type);
     }
 
     pub fn emit_future_breakage_report(&self, diags: Vec<Diagnostic>) {
@@ -1228,12 +1173,12 @@ impl Handler {
             inner.bump_err_count();
         }
 
-        inner.emit_unused_externs(lint_level, unused_externs)
+        inner.emitter.emit_unused_externs(lint_level, unused_externs)
     }
 
     pub fn update_unstable_expectation_id(
         &self,
-        unstable_to_stable: &FxHashMap<LintExpectationId, LintExpectationId>,
+        unstable_to_stable: &FxIndexMap<LintExpectationId, LintExpectationId>,
     ) {
         let mut inner = self.inner.borrow_mut();
         let diags = std::mem::take(&mut inner.unstable_expect_diagnostics);
@@ -1247,7 +1192,7 @@ impl Handler {
                 // Here the diagnostic is given back to `emit_diagnostic` where it was first
                 // intercepted. Now it should be processed as usual, since the unstable expectation
                 // id is now stable.
-                inner.emit_diagnostic(&mut diag);
+                inner.emit_diagnostic(diag);
             }
         }
 
@@ -1262,41 +1207,37 @@ impl Handler {
     }
 
     /// This methods steals all [`LintExpectationId`]s that are stored inside
-    /// [`HandlerInner`] and indicate that the linked expectation has been fulfilled.
+    /// [`DiagCtxtInner`] and indicate that the linked expectation has been fulfilled.
     #[must_use]
     pub fn steal_fulfilled_expectation_ids(&self) -> FxHashSet<LintExpectationId> {
         assert!(
             self.inner.borrow().unstable_expect_diagnostics.is_empty(),
-            "`HandlerInner::unstable_expect_diagnostics` should be empty at this point",
+            "`DiagCtxtInner::unstable_expect_diagnostics` should be empty at this point",
         );
         std::mem::take(&mut self.inner.borrow_mut().fulfilled_expectations)
     }
 
     pub fn flush_delayed(&self) {
-        let mut inner = self.inner.lock();
-        let bugs = std::mem::replace(&mut inner.delayed_span_bugs, Vec::new());
-        inner.flush_delayed(bugs, "no errors encountered even though `delay_span_bug` issued");
+        let mut inner = self.inner.borrow_mut();
+        let bugs = std::mem::replace(&mut inner.span_delayed_bugs, Vec::new());
+        inner.flush_delayed(bugs, "no errors encountered even though `span_delayed_bug` issued");
     }
 }
 
-impl HandlerInner {
-    fn must_teach(&mut self, code: &DiagnosticId) -> bool {
-        self.taught_diagnostics.insert(code.clone())
-    }
-
-    fn force_print_diagnostic(&mut self, db: Diagnostic) {
-        self.emitter.emit_diagnostic(&db);
-    }
-
+// Note: we prefer implementing operations on `DiagCtxt`, rather than
+// `DiagCtxtInner`, whenever possible. This minimizes functions where
+// `DiagCtxt::foo()` just borrows `inner` and forwards a call to
+// `HanderInner::foo`.
+impl DiagCtxtInner {
     /// Emit all stashed diagnostics.
     fn emit_stashed_diagnostics(&mut self) -> Option<ErrorGuaranteed> {
         let has_errors = self.has_errors();
         let diags = self.stashed_diagnostics.drain(..).map(|x| x.1).collect::<Vec<_>>();
         let mut reported = None;
-        for mut diag in diags {
+        for diag in diags {
             // Decrement the count tracking the stash; emitting will increment it.
             if diag.is_error() {
-                if matches!(diag.level, Level::Error { lint: true }) {
+                if diag.level == Error && diag.is_lint {
                     self.lint_err_count -= 1;
                 } else {
                     self.err_count -= 1;
@@ -1313,14 +1254,17 @@ impl HandlerInner {
                     }
                 }
             }
-            let reported_this = self.emit_diagnostic(&mut diag);
+            let reported_this = self.emit_diagnostic(diag);
             reported = reported.or(reported_this);
         }
         reported
     }
 
-    // FIXME(eddyb) this should ideally take `diagnostic` by value.
-    fn emit_diagnostic(&mut self, diagnostic: &mut Diagnostic) -> Option<ErrorGuaranteed> {
+    fn emit_diagnostic(&mut self, mut diagnostic: Diagnostic) -> Option<ErrorGuaranteed> {
+        if matches!(diagnostic.level, Error | Fatal) && self.treat_err_as_bug() {
+            diagnostic.level = Bug;
+        }
+
         // The `LintExpectationId` can be stable or unstable depending on when it was created.
         // Diagnostics created before the definition of `HirId`s are unstable and can not yet
         // be stored. Instead, they are buffered until the `LintExpectationId` is replaced by
@@ -1330,23 +1274,21 @@ impl HandlerInner {
             return None;
         }
 
-        if diagnostic.level == Level::DelayedBug {
+        if diagnostic.level == DelayedBug {
             // FIXME(eddyb) this should check for `has_errors` and stop pushing
-            // once *any* errors were emitted (and truncate `delayed_span_bugs`
+            // once *any* errors were emitted (and truncate `span_delayed_bugs`
             // when an error is first emitted, also), but maybe there's a case
             // in which that's not sound? otherwise this is really inefficient.
             let backtrace = std::backtrace::Backtrace::capture();
-            self.delayed_span_bugs
+            self.span_delayed_bugs
                 .push(DelayedDiagnostic::with_backtrace(diagnostic.clone(), backtrace));
 
-            if !self.flags.report_delayed_bugs {
-                #[allow(deprecated)]
-                return Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
-            }
+            #[allow(deprecated)]
+            return Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
         }
 
         if diagnostic.has_future_breakage() {
-            // Future breakages aren't emitted if they're Level::Allowed,
+            // Future breakages aren't emitted if they're Level::Allow,
             // but they still need to be constructed and stashed below,
             // so they'll trigger the good-path bug check.
             self.suppressed_expected_diag = true;
@@ -1368,13 +1310,13 @@ impl HandlerInner {
             return None;
         }
 
-        if matches!(diagnostic.level, Level::Expect(_) | Level::Allow) {
+        if matches!(diagnostic.level, Expect(_) | Allow) {
             (*TRACK_DIAGNOSTICS)(diagnostic, &mut |_| {});
             return None;
         }
 
         let mut guaranteed = None;
-        (*TRACK_DIAGNOSTICS)(diagnostic, &mut |diagnostic| {
+        (*TRACK_DIAGNOSTICS)(diagnostic, &mut |mut diagnostic| {
             if let Some(ref code) = diagnostic.code {
                 self.emitted_diagnostic_codes.insert(code.clone());
             }
@@ -1393,7 +1335,7 @@ impl HandlerInner {
                 debug!(?self.emitted_diagnostics);
                 let already_emitted_sub = |sub: &mut SubDiagnostic| {
                     debug!(?sub);
-                    if sub.level != Level::OnceNote && sub.level != Level::OnceHelp {
+                    if sub.level != OnceNote && sub.level != OnceHelp {
                         return false;
                     }
                     let mut hasher = StableHasher::new();
@@ -1410,7 +1352,7 @@ impl HandlerInner {
                     );
                 }
 
-                self.emitter.emit_diagnostic(diagnostic);
+                self.emitter.emit_diagnostic(&diagnostic);
                 if diagnostic.is_error() {
                     self.deduplicated_err_count += 1;
                 } else if let Warning(_) = diagnostic.level {
@@ -1418,7 +1360,7 @@ impl HandlerInner {
                 }
             }
             if diagnostic.is_error() {
-                if matches!(diagnostic.level, Level::Error { lint: true }) {
+                if diagnostic.level == Error && diagnostic.is_lint {
                     self.bump_lint_err_count();
                 } else {
                     self.bump_err_count();
@@ -1436,238 +1378,43 @@ impl HandlerInner {
         guaranteed
     }
 
-    fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
-        self.emitter.emit_artifact_notification(path, artifact_type);
-    }
-
-    fn emit_unused_externs(&mut self, lint_level: rustc_lint_defs::Level, unused_externs: &[&str]) {
-        self.emitter.emit_unused_externs(lint_level, unused_externs);
-    }
-
     fn treat_err_as_bug(&self) -> bool {
         self.flags.treat_err_as_bug.is_some_and(|c| {
-            self.err_count() + self.lint_err_count + self.delayed_bug_count() >= c.get()
+            self.err_count + self.lint_err_count + self.delayed_bug_count() >= c.get()
         })
     }
 
-    fn delayed_bug_count(&self) -> usize {
-        self.delayed_span_bugs.len() + self.delayed_good_path_bugs.len()
-    }
-
-    fn print_error_count(&mut self, registry: &Registry) {
-        self.emit_stashed_diagnostics();
-
-        let warnings = match self.deduplicated_warn_count {
-            0 => Cow::from(""),
-            1 => Cow::from("1 warning emitted"),
-            count => Cow::from(format!("{count} warnings emitted")),
-        };
-        let errors = match self.deduplicated_err_count {
-            0 => Cow::from(""),
-            1 => Cow::from("aborting due to previous error"),
-            count => Cow::from(format!("aborting due to {count} previous errors")),
-        };
-        if self.treat_err_as_bug() {
-            return;
-        }
-
-        match (errors.len(), warnings.len()) {
-            (0, 0) => return,
-            (0, _) => self.emitter.emit_diagnostic(&Diagnostic::new(
-                Level::Warning(None),
-                DiagnosticMessage::Str(warnings),
-            )),
-            (_, 0) => {
-                let _ = self.fatal(errors);
-            }
-            (_, _) => {
-                let _ = self.fatal(format!("{errors}; {warnings}"));
-            }
-        }
-
-        let can_show_explain = self.emitter.should_show_explain();
-        let are_there_diagnostics = !self.emitted_diagnostic_codes.is_empty();
-        if can_show_explain && are_there_diagnostics {
-            let mut error_codes = self
-                .emitted_diagnostic_codes
-                .iter()
-                .filter_map(|x| match &x {
-                    DiagnosticId::Error(s) if registry.try_find_description(s).is_ok() => {
-                        Some(s.clone())
-                    }
-                    _ => None,
-                })
-                .collect::<Vec<_>>();
-            if !error_codes.is_empty() {
-                error_codes.sort();
-                if error_codes.len() > 1 {
-                    let limit = if error_codes.len() > 9 { 9 } else { error_codes.len() };
-                    self.failure(format!(
-                        "Some errors have detailed explanations: {}{}",
-                        error_codes[..limit].join(", "),
-                        if error_codes.len() > 9 { "..." } else { "." }
-                    ));
-                    self.failure(format!(
-                        "For more information about an error, try \
-                         `rustc --explain {}`.",
-                        &error_codes[0]
-                    ));
-                } else {
-                    self.failure(format!(
-                        "For more information about this error, try \
-                         `rustc --explain {}`.",
-                        &error_codes[0]
-                    ));
-                }
-            }
-        }
-    }
-
-    fn stash(&mut self, key: (Span, StashKey), diagnostic: Diagnostic) {
-        // Track the diagnostic for counts, but don't panic-if-treat-err-as-bug
-        // yet; that happens when we actually emit the diagnostic.
-        if diagnostic.is_error() {
-            if matches!(diagnostic.level, Level::Error { lint: true }) {
-                self.lint_err_count += 1;
-            } else {
-                self.err_count += 1;
-            }
-        } else {
-            // Warnings are only automatically flushed if they're forced.
-            if diagnostic.is_force_warn() {
-                self.warn_count += 1;
-            }
-        }
-
-        // 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.
-        self.stashed_diagnostics.insert(key, diagnostic);
-    }
-
-    fn steal(&mut self, key: (Span, StashKey)) -> Option<Diagnostic> {
-        let diagnostic = self.stashed_diagnostics.remove(&key)?;
-        if diagnostic.is_error() {
-            if matches!(diagnostic.level, Level::Error { lint: true }) {
-                self.lint_err_count -= 1;
-            } else {
-                self.err_count -= 1;
-            }
-        } else {
-            if diagnostic.is_force_warn() {
-                self.warn_count -= 1;
-            }
-        }
-        Some(diagnostic)
+    // Use this one before incrementing `err_count`.
+    fn treat_next_err_as_bug(&self) -> bool {
+        self.flags.treat_err_as_bug.is_some_and(|c| {
+            self.err_count + self.lint_err_count + self.delayed_bug_count() + 1 >= c.get()
+        })
     }
 
-    #[inline]
-    fn err_count(&self) -> usize {
-        self.err_count
+    fn delayed_bug_count(&self) -> usize {
+        self.span_delayed_bugs.len() + self.good_path_delayed_bugs.len()
     }
 
     fn has_errors(&self) -> bool {
-        self.err_count() > 0
-    }
-    fn has_errors_or_lint_errors(&self) -> bool {
-        self.has_errors() || self.lint_err_count > 0
-    }
-    fn has_errors_or_delayed_span_bugs(&self) -> bool {
-        self.has_errors() || !self.delayed_span_bugs.is_empty()
-    }
-    fn has_any_message(&self) -> bool {
-        self.err_count() > 0 || self.lint_err_count > 0 || self.warn_count > 0
-    }
-
-    fn is_compilation_going_to_fail(&self) -> bool {
-        self.has_errors() || self.lint_err_count > 0 || !self.delayed_span_bugs.is_empty()
-    }
-
-    fn abort_if_errors(&mut self) {
-        self.emit_stashed_diagnostics();
-
-        if self.has_errors() {
-            FatalError.raise();
-        }
-    }
-
-    #[track_caller]
-    fn span_bug(&mut self, sp: impl Into<MultiSpan>, msg: impl Into<String>) -> ! {
-        self.emit_diag_at_span(Diagnostic::new(Bug, msg.into()), sp);
-        panic::panic_any(ExplicitBug);
-    }
-
-    fn emit_diag_at_span(&mut self, mut diag: Diagnostic, sp: impl Into<MultiSpan>) {
-        self.emit_diagnostic(diag.set_span(sp));
-    }
-
-    /// For documentation on this, see `Session::delay_span_bug`.
-    #[track_caller]
-    fn delay_span_bug(
-        &mut self,
-        sp: impl Into<MultiSpan>,
-        msg: impl Into<String>,
-    ) -> ErrorGuaranteed {
-        // This is technically `self.treat_err_as_bug()` but `delay_span_bug` is called before
-        // incrementing `err_count` by one, so we need to +1 the comparing.
-        // FIXME: Would be nice to increment err_count in a more coherent way.
-        if self.flags.treat_err_as_bug.is_some_and(|c| {
-            self.err_count() + self.lint_err_count + self.delayed_bug_count() + 1 >= c.get()
-        }) {
-            // FIXME: don't abort here if report_delayed_bugs is off
-            self.span_bug(sp, msg.into());
-        }
-        let mut diagnostic = Diagnostic::new(Level::DelayedBug, msg.into());
-        diagnostic.set_span(sp.into());
-        self.emit_diagnostic(&mut diagnostic).unwrap()
-    }
-
-    // FIXME(eddyb) note the comment inside `impl Drop for HandlerInner`, that's
-    // where the explanation of what "good path" is (also, it should be renamed).
-    fn delay_good_path_bug(&mut self, msg: impl Into<DiagnosticMessage>) {
-        let mut diagnostic = Diagnostic::new(Level::DelayedBug, msg);
-        if self.flags.report_delayed_bugs {
-            self.emit_diagnostic(&mut diagnostic);
-        }
-        let backtrace = std::backtrace::Backtrace::capture();
-        self.delayed_good_path_bugs.push(DelayedDiagnostic::with_backtrace(diagnostic, backtrace));
-    }
-
-    fn failure(&mut self, msg: impl Into<DiagnosticMessage>) {
-        self.emit_diagnostic(&mut Diagnostic::new(FailureNote, msg));
+        self.err_count > 0
     }
 
-    fn fatal(&mut self, msg: impl Into<DiagnosticMessage>) -> FatalError {
-        self.emit(Fatal, msg);
-        FatalError
-    }
-
-    fn err(&mut self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
-        self.emit(Error { lint: false }, msg)
-    }
-
-    /// Emit an error; level should be `Error` or `Fatal`.
-    fn emit(&mut self, level: Level, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
-        if self.treat_err_as_bug() {
-            self.bug(msg);
-        }
-        self.emit_diagnostic(&mut Diagnostic::new(level, msg)).unwrap()
-    }
-
-    fn bug(&mut self, msg: impl Into<DiagnosticMessage>) -> ! {
-        self.emit_diagnostic(&mut Diagnostic::new(Bug, msg));
-        panic::panic_any(ExplicitBug);
+    fn failure_note(&mut self, msg: impl Into<DiagnosticMessage>) {
+        self.emit_diagnostic(Diagnostic::new(FailureNote, msg));
     }
 
     fn flush_delayed(
         &mut self,
-        bugs: impl IntoIterator<Item = DelayedDiagnostic>,
+        bugs: Vec<DelayedDiagnostic>,
         explanation: impl Into<DiagnosticMessage> + Copy,
     ) {
-        let mut no_bugs = true;
+        if bugs.is_empty() {
+            return;
+        }
+
         // If backtraces are enabled, also print the query stack
         let backtrace = std::env::var_os("RUST_BACKTRACE").map_or(true, |x| &x != "0");
-        for bug in bugs {
+        for (i, bug) in bugs.into_iter().enumerate() {
             if let Some(file) = self.ice_file.as_ref()
                 && let Ok(mut out) = std::fs::File::options().create(true).append(true).open(file)
             {
@@ -1675,25 +1422,25 @@ impl HandlerInner {
                     &mut out,
                     "delayed span bug: {}\n{}\n",
                     bug.inner
-                        .styled_message()
+                        .messages()
                         .iter()
                         .filter_map(|(msg, _)| msg.as_str())
                         .collect::<String>(),
                     &bug.note
                 );
             }
-            let mut bug =
-                if backtrace || self.ice_file.is_none() { bug.decorate() } else { bug.inner };
 
-            if no_bugs {
+            if i == 0 {
                 // Put the overall explanation before the `DelayedBug`s, to
                 // frame them better (e.g. separate warnings from them).
-                self.emit_diagnostic(&mut Diagnostic::new(Bug, explanation));
-                no_bugs = false;
+                self.emit_diagnostic(Diagnostic::new(Bug, explanation));
             }
 
+            let mut bug =
+                if backtrace || self.ice_file.is_none() { bug.decorate() } else { bug.inner };
+
             // "Undelay" the `DelayedBug`s (into plain `Bug`s).
-            if bug.level != Level::DelayedBug {
+            if bug.level != DelayedBug {
                 // NOTE(eddyb) not panicking here because we're already producing
                 // an ICE, and the more information the merrier.
                 bug.subdiagnostic(InvalidFlushedDelayedDiagnosticLevel {
@@ -1701,15 +1448,13 @@ impl HandlerInner {
                     level: bug.level,
                 });
             }
-            bug.level = Level::Bug;
+            bug.level = Bug;
 
-            self.emit_diagnostic(&mut bug);
+            self.emit_diagnostic(bug);
         }
 
         // Panic with `DelayedBugPanic` to avoid "unexpected panic" messages.
-        if !no_bugs {
-            panic::panic_any(DelayedBugPanic);
-        }
+        panic::panic_any(DelayedBugPanic);
     }
 
     fn bump_lint_err_count(&mut self) {
@@ -1729,7 +1474,7 @@ impl HandlerInner {
     fn panic_if_treat_err_as_bug(&self) {
         if self.treat_err_as_bug() {
             match (
-                self.err_count() + self.lint_err_count,
+                self.err_count + self.lint_err_count,
                 self.delayed_bug_count(),
                 self.flags.treat_err_as_bug.map(|c| c.get()).unwrap(),
             ) {
@@ -1787,25 +1532,76 @@ impl DelayedDiagnostic {
 
 #[derive(Copy, PartialEq, Eq, Clone, Hash, Debug, Encodable, Decodable)]
 pub enum Level {
+    /// For bugs in the compiler. Manifests as an ICE (internal compiler error) panic.
+    ///
+    /// Its `EmissionGuarantee` is `BugAbort`.
     Bug,
+
+    /// This is a strange one: lets you register an error without emitting it. If compilation ends
+    /// without any other errors occurring, this will be emitted as a bug. Otherwise, it will be
+    /// silently dropped. I.e. "expect other errors are emitted" semantics. Useful on code paths
+    /// that should only be reached when compiling erroneous code.
+    ///
+    /// Its `EmissionGuarantee` is `ErrorGuaranteed`.
     DelayedBug,
+
+    /// An error that causes an immediate abort. Used for things like configuration errors,
+    /// internal overflows, some file operation errors.
+    ///
+    /// Its `EmissionGuarantee` is `FatalAbort`, except in the non-aborting "almost fatal" case
+    /// that is occasionally used, where it is `FatalError`.
     Fatal,
-    Error {
-        /// If this error comes from a lint, don't abort compilation even when abort_if_errors() is called.
-        lint: bool,
-    },
+
+    /// An error in the code being compiled, which prevents compilation from finishing. This is the
+    /// most common case.
+    ///
+    /// Its `EmissionGuarantee` is `ErrorGuaranteed`.
+    Error,
+
+    /// A warning about the code being compiled. Does not prevent compilation from finishing.
+    ///
     /// This [`LintExpectationId`] is used for expected lint diagnostics, which should
     /// also emit a warning due to the `force-warn` flag. In all other cases this should
     /// be `None`.
+    ///
+    /// Its `EmissionGuarantee` is `()`.
     Warning(Option<LintExpectationId>),
+
+    /// A message giving additional context. Rare, because notes are more commonly attached to other
+    /// diagnostics such as errors.
+    ///
+    /// Its `EmissionGuarantee` is `()`.
     Note,
-    /// A note that is only emitted once.
+
+    /// A note that is only emitted once. Rare, mostly used in circumstances relating to lints.
+    ///
+    /// Its `EmissionGuarantee` is `()`.
     OnceNote,
+
+    /// A message suggesting how to fix something. Rare, because help messages are more commonly
+    /// attached to other diagnostics such as errors.
+    ///
+    /// Its `EmissionGuarantee` is `()`.
     Help,
-    /// A help that is only emitted once.
+
+    /// A help that is only emitted once. Rare.
+    ///
+    /// Its `EmissionGuarantee` is `()`.
     OnceHelp,
+
+    /// Similar to `Note`, but used in cases where compilation has failed. Rare.
+    ///
+    /// Its `EmissionGuarantee` is `()`.
     FailureNote,
+
+    /// Only used for lints.
+    ///
+    /// Its `EmissionGuarantee` is `()`.
     Allow,
+
+    /// Only used for lints.
+    ///
+    /// Its `EmissionGuarantee` is `()`.
     Expect(LintExpectationId),
 }
 
@@ -1819,7 +1615,7 @@ impl Level {
     fn color(self) -> ColorSpec {
         let mut spec = ColorSpec::new();
         match self {
-            Bug | DelayedBug | Fatal | Error { .. } => {
+            Bug | DelayedBug | Fatal | Error => {
                 spec.set_fg(Some(Color::Red)).set_intense(true);
             }
             Warning(_) => {
@@ -1840,13 +1636,12 @@ impl Level {
     pub fn to_str(self) -> &'static str {
         match self {
             Bug | DelayedBug => "error: internal compiler error",
-            Fatal | Error { .. } => "error",
+            Fatal | Error => "error",
             Warning(_) => "warning",
             Note | OnceNote => "note",
             Help | OnceHelp => "help",
             FailureNote => "failure-note",
-            Allow => panic!("Shouldn't call on allowed error"),
-            Expect(_) => panic!("Shouldn't call on expected error"),
+            Allow | Expect(_) => unreachable!(),
         }
     }
 
@@ -1856,7 +1651,7 @@ impl Level {
 
     pub fn get_expectation_id(&self) -> Option<LintExpectationId> {
         match self {
-            Level::Expect(id) | Level::Warning(Some(id)) => Some(*id),
+            Expect(id) | Warning(Some(id)) => Some(*id),
             _ => None,
         }
     }
diff --git a/compiler/rustc_errors/src/markdown/parse.rs b/compiler/rustc_errors/src/markdown/parse.rs
index d3a08da6283..67e4963fddf 100644
--- a/compiler/rustc_errors/src/markdown/parse.rs
+++ b/compiler/rustc_errors/src/markdown/parse.rs
@@ -329,7 +329,7 @@ fn parse_with_end_pat<'a>(
     end_sep: &[u8],
     ignore_esc: bool,
 ) -> Option<(&'a [u8], &'a [u8])> {
-    // Find positions that start with the end seperator
+    // Find positions that start with the end separator
     for idx in (0..buf.len()).filter(|idx| buf[*idx..].starts_with(end_sep)) {
         if !ignore_esc && idx > 0 && buf[idx - 1] == b'\\' {
             continue;
diff --git a/compiler/rustc_errors/src/markdown/tests/term.rs b/compiler/rustc_errors/src/markdown/tests/term.rs
index 6f68fb25a58..a0d956bf0cd 100644
--- a/compiler/rustc_errors/src/markdown/tests/term.rs
+++ b/compiler/rustc_errors/src/markdown/tests/term.rs
@@ -3,7 +3,6 @@ use std::path::PathBuf;
 use termcolor::{BufferWriter, ColorChoice};
 
 use super::*;
-use crate::markdown::MdStream;
 
 const INPUT: &str = include_str!("input.md");
 const OUTPUT_PATH: &[&str] = &[env!("CARGO_MANIFEST_DIR"), "src","markdown","tests","output.stdout"];