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.rs25
-rw-r--r--compiler/rustc_errors/src/codes.rs39
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs794
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs450
-rw-r--r--compiler/rustc_errors/src/diagnostic_impls.rs144
-rw-r--r--compiler/rustc_errors/src/emitter.rs109
-rw-r--r--compiler/rustc_errors/src/json.rs52
-rw-r--r--compiler/rustc_errors/src/lib.rs1325
-rw-r--r--compiler/rustc_errors/src/lock.rs6
-rw-r--r--compiler/rustc_errors/src/registry.rs9
-rw-r--r--compiler/rustc_errors/src/snippet.rs2
-rw-r--r--compiler/rustc_errors/src/translation.rs10
12 files changed, 1498 insertions, 1467 deletions
diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
index f0699a56f98..1a34a83c1a4 100644
--- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
+++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
@@ -9,8 +9,8 @@ use crate::emitter::FileWithAnnotatedLines;
 use crate::snippet::Line;
 use crate::translation::{to_fluent_args, Translate};
 use crate::{
-    CodeSuggestion, Diagnostic, DiagnosticMessage, Emitter, FluentBundle, LazyFallbackBundle,
-    Level, MultiSpan, Style, SubDiagnostic,
+    CodeSuggestion, Diagnostic, DiagnosticMessage, Emitter, ErrCode, FluentBundle,
+    LazyFallbackBundle, Level, MultiSpan, Style, SubDiagnostic,
 };
 use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
 use rustc_data_structures::sync::Lrc;
@@ -44,15 +44,15 @@ impl Translate for AnnotateSnippetEmitter {
 
 impl Emitter for AnnotateSnippetEmitter {
     /// The entry point for the diagnostics generation
-    fn emit_diagnostic(&mut self, diag: &Diagnostic) {
+    fn emit_diagnostic(&mut self, mut diag: Diagnostic) {
         let fluent_args = to_fluent_args(diag.args());
 
-        let mut children = diag.children.clone();
-        let (mut primary_span, suggestions) = self.primary_span_formatted(diag, &fluent_args);
+        let mut suggestions = diag.suggestions.unwrap_or(vec![]);
+        self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
 
         self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
-            &mut primary_span,
-            &mut children,
+            &mut diag.span,
+            &mut diag.children,
             &diag.level,
             self.macro_backtrace,
         );
@@ -62,9 +62,9 @@ impl Emitter for AnnotateSnippetEmitter {
             &diag.messages,
             &fluent_args,
             &diag.code,
-            &primary_span,
-            &children,
-            suggestions,
+            &diag.span,
+            &diag.children,
+            &suggestions,
         );
     }
 
@@ -85,7 +85,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::Fatal | Level::Error | Level::DelayedBug => AnnotationType::Error,
         Level::ForceWarning(_) | Level::Warning => AnnotationType::Warning,
         Level::Note | Level::OnceNote => AnnotationType::Note,
         Level::Help | Level::OnceHelp => AnnotationType::Help,
@@ -127,7 +127,7 @@ impl AnnotateSnippetEmitter {
         level: &Level,
         messages: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
-        code: &Option<String>,
+        code: &Option<ErrCode>,
         msp: &MultiSpan,
         _children: &[SubDiagnostic],
         _suggestions: &[CodeSuggestion],
@@ -178,6 +178,7 @@ impl AnnotateSnippetEmitter {
                         .collect::<Vec<Owned>>()
                 })
                 .collect();
+            let code = code.map(|code| code.to_string());
             let snippet = Snippet {
                 title: Some(Annotation {
                     label: Some(&message),
diff --git a/compiler/rustc_errors/src/codes.rs b/compiler/rustc_errors/src/codes.rs
new file mode 100644
index 00000000000..947cf27ca79
--- /dev/null
+++ b/compiler/rustc_errors/src/codes.rs
@@ -0,0 +1,39 @@
+//! This module defines the following.
+//! - The `ErrCode` type.
+//! - A constant for every error code, with a name like `E0123`.
+//! - A static table `DIAGNOSTICS` pairing every error code constant with its
+//!   long description text.
+
+use std::fmt;
+
+rustc_index::newtype_index! {
+    #[max = 9999] // Because all error codes have four digits.
+    #[orderable]
+    #[encodable]
+    #[debug_format = "ErrCode({})"]
+    pub struct ErrCode {}
+}
+
+impl fmt::Display for ErrCode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "E{:04}", self.as_u32())
+    }
+}
+
+macro_rules! define_error_code_constants_and_diagnostics_table {
+    ($($name:ident: $num:literal,)*) => (
+        $(
+            pub const $name: $crate::ErrCode = $crate::ErrCode::from_u32($num);
+        )*
+        pub static DIAGNOSTICS: &[($crate::ErrCode, &str)] = &[
+            $( (
+                $name,
+                include_str!(
+                    concat!("../../rustc_error_codes/src/error_codes/", stringify!($name), ".md")
+                )
+            ), )*
+        ];
+    )
+}
+
+rustc_error_codes::error_codes!(define_error_code_constants_and_diagnostics_table);
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 4934bc2450c..f096f015910 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -1,18 +1,22 @@
 use crate::snippet::Style;
 use crate::{
-    CodeSuggestion, DelayedBugKind, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Level,
-    MultiSpan, SubdiagnosticMessage, Substitution, SubstitutionPart, SuggestionStyle,
+    CodeSuggestion, DiagCtxt, DiagnosticMessage, ErrCode, ErrorGuaranteed, ExplicitBug, Level,
+    MultiSpan, StashKey, SubdiagnosticMessage, Substitution, SubstitutionPart, SuggestionStyle,
 };
-use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
+use rustc_data_structures::fx::FxIndexMap;
 use rustc_error_messages::fluent_value_from_str_list_sep_by_and;
 use rustc_error_messages::FluentValue;
 use rustc_lint_defs::{Applicability, LintExpectationId};
+use rustc_span::source_map::Spanned;
 use rustc_span::symbol::Symbol;
 use rustc_span::{Span, DUMMY_SP};
 use std::borrow::Cow;
 use std::fmt::{self, Debug};
 use std::hash::{Hash, Hasher};
-use std::panic::Location;
+use std::marker::PhantomData;
+use std::ops::{Deref, DerefMut};
+use std::panic;
+use std::thread::panicking;
 
 /// Error type for `Diagnostic`'s `suggestions` field, indicating that
 /// `.disable_suggestions()` was called on the `Diagnostic`.
@@ -22,19 +26,101 @@ pub struct SuggestionsDisabled;
 /// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of
 /// `DiagnosticArg` are converted to `FluentArgs` (consuming the collection) at the start of
 /// diagnostic emission.
-pub type DiagnosticArg<'iter, 'source> =
-    (&'iter DiagnosticArgName<'source>, &'iter DiagnosticArgValue<'source>);
+pub type DiagnosticArg<'iter> = (&'iter DiagnosticArgName, &'iter DiagnosticArgValue);
 
 /// Name of a diagnostic argument.
-pub type DiagnosticArgName<'source> = Cow<'source, str>;
+pub type DiagnosticArgName = Cow<'static, str>;
 
 /// Simplified version of `FluentValue` that can implement `Encodable` and `Decodable`. Converted
 /// to a `FluentValue` by the emitter to be used in diagnostic translation.
 #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
-pub enum DiagnosticArgValue<'source> {
-    Str(Cow<'source, str>),
-    Number(i128),
-    StrListSepByAnd(Vec<Cow<'source, str>>),
+pub enum DiagnosticArgValue {
+    Str(Cow<'static, str>),
+    // This gets converted to a `FluentNumber`, which is an `f64`. An `i32`
+    // safely fits in an `f64`. Any integers bigger than that will be converted
+    // to strings in `into_diagnostic_arg` and stored using the `Str` variant.
+    Number(i32),
+    StrListSepByAnd(Vec<Cow<'static, str>>),
+}
+
+/// 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::EmitResult` without actually performing the emission.
+    #[track_caller]
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult;
+}
+
+impl EmissionGuarantee for ErrorGuaranteed {
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_error_guaranteed()
+    }
+}
+
+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 diagnostics.
+#[derive(Copy, Clone)]
+pub struct BugAbort;
+
+impl EmissionGuarantee for BugAbort {
+    type EmitResult = !;
+
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_nothing();
+        panic::panic_any(ExplicitBug);
+    }
+}
+
+/// Marker type which enables implementation of `create_fatal` and `emit_fatal` functions for
+/// fatal diagnostics.
+#[derive(Copy, Clone)]
+pub struct FatalAbort;
+
+impl EmissionGuarantee for FatalAbort {
+    type EmitResult = !;
+
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_nothing();
+        crate::FatalError.raise()
+    }
+}
+
+impl EmissionGuarantee for rustc_span::fatal_error::FatalError {
+    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
+        db.emit_producing_nothing();
+        rustc_span::fatal_error::FatalError
+    }
+}
+
+/// 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, G: EmissionGuarantee = ErrorGuaranteed> {
+    /// Write out as a diagnostic out of `DiagCtxt`.
+    #[must_use]
+    fn into_diagnostic(self, dcx: &'a DiagCtxt, level: Level) -> DiagnosticBuilder<'a, G>;
+}
+
+impl<'a, T, G> IntoDiagnostic<'a, G> for Spanned<T>
+where
+    T: IntoDiagnostic<'a, G>,
+    G: EmissionGuarantee,
+{
+    fn into_diagnostic(self, dcx: &'a DiagCtxt, level: Level) -> DiagnosticBuilder<'a, G> {
+        self.node.into_diagnostic(dcx, level).with_span(self.span)
+    }
 }
 
 /// Converts a value of a type into a `DiagnosticArg` (typically a field of an `IntoDiagnostic`
@@ -42,23 +128,17 @@ pub enum DiagnosticArgValue<'source> {
 /// being converted rather than on `DiagnosticArgValue`, which enables types from other `rustc_*`
 /// crates to implement this.
 pub trait IntoDiagnosticArg {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static>;
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue;
 }
 
-impl<'source> IntoDiagnosticArg for DiagnosticArgValue<'source> {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        match self {
-            DiagnosticArgValue::Str(s) => DiagnosticArgValue::Str(Cow::Owned(s.into_owned())),
-            DiagnosticArgValue::Number(n) => DiagnosticArgValue::Number(n),
-            DiagnosticArgValue::StrListSepByAnd(l) => DiagnosticArgValue::StrListSepByAnd(
-                l.into_iter().map(|s| Cow::Owned(s.into_owned())).collect(),
-            ),
-        }
+impl IntoDiagnosticArg for DiagnosticArgValue {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
+        self
     }
 }
 
-impl<'source> Into<FluentValue<'source>> for DiagnosticArgValue<'source> {
-    fn into(self) -> FluentValue<'source> {
+impl Into<FluentValue<'static>> for DiagnosticArgValue {
+    fn into(self) -> FluentValue<'static> {
         match self {
             DiagnosticArgValue::Str(s) => From::from(s),
             DiagnosticArgValue::Number(n) => From::from(n),
@@ -75,17 +155,22 @@ where
     Self: Sized,
 {
     /// Add a subdiagnostic to an existing diagnostic.
-    fn add_to_diagnostic(self, diag: &mut Diagnostic) {
+    fn add_to_diagnostic<G: EmissionGuarantee>(self, diag: &mut DiagnosticBuilder<'_, G>) {
         self.add_to_diagnostic_with(diag, |_, m| m);
     }
 
     /// Add a subdiagnostic to an existing diagnostic where `f` is invoked on every message used
     /// (to optionally perform eager translation).
-    fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, f: F)
-    where
-        F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage;
+    fn add_to_diagnostic_with<G: EmissionGuarantee, F: SubdiagnosticMessageOp<G>>(
+        self,
+        diag: &mut DiagnosticBuilder<'_, G>,
+        f: F,
+    );
 }
 
+pub trait SubdiagnosticMessageOp<G> =
+    Fn(&mut DiagnosticBuilder<'_, G>, SubdiagnosticMessage) -> SubdiagnosticMessage;
+
 /// Trait implemented by lint types. This should not be implemented manually. Instead, use
 /// `#[derive(LintDiagnostic)]` -- see [rustc_macros::LintDiagnostic].
 #[rustc_diagnostic_item = "DecorateLint"]
@@ -96,34 +181,6 @@ pub trait DecorateLint<'a, G: EmissionGuarantee> {
     fn msg(&self) -> DiagnosticMessage;
 }
 
-#[must_use]
-#[derive(Clone, Debug, Encodable, Decodable)]
-pub struct Diagnostic {
-    // NOTE(eddyb) this is private to disallow arbitrary after-the-fact changes,
-    // outside of what methods in this crate themselves allow.
-    pub(crate) level: Level,
-
-    pub messages: Vec<(DiagnosticMessage, Style)>,
-    pub code: Option<String>,
-    pub span: MultiSpan,
-    pub children: Vec<SubDiagnostic>,
-    pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
-    args: FxHashMap<DiagnosticArgName<'static>, DiagnosticArgValue<'static>>,
-
-    /// This is not used for highlighting or rendering any error message. Rather, it can be used
-    /// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
-    /// `span` if there is one. Otherwise, it is `DUMMY_SP`.
-    pub sort_span: Span,
-
-    /// If diagnostic is from Lint, custom hash function ignores children.
-    /// Otherwise hash is based on the all the fields.
-    pub is_lint: Option<IsLint>,
-
-    /// With `-Ztrack_diagnostics` enabled,
-    /// we print where in rustc this error was emitted.
-    pub emitted_at: DiagnosticLocation,
-}
-
 #[derive(Clone, Debug, Encodable, Decodable)]
 pub struct DiagnosticLocation {
     file: Cow<'static, str>,
@@ -134,7 +191,7 @@ pub struct DiagnosticLocation {
 impl DiagnosticLocation {
     #[track_caller]
     fn caller() -> Self {
-        let loc = Location::caller();
+        let loc = panic::Location::caller();
         DiagnosticLocation { file: loc.file().into(), line: loc.line(), col: loc.column() }
     }
 }
@@ -153,15 +210,6 @@ pub struct IsLint {
     has_future_breakage: bool,
 }
 
-/// A "sub"-diagnostic attached to a parent diagnostic.
-/// For example, a note attached to an error.
-#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
-pub struct SubDiagnostic {
-    pub level: Level,
-    pub messages: Vec<(DiagnosticMessage, Style)>,
-    pub span: MultiSpan,
-}
-
 #[derive(Debug, PartialEq, Eq)]
 pub struct DiagnosticStyledString(pub Vec<StringPart>);
 
@@ -170,10 +218,10 @@ impl DiagnosticStyledString {
         DiagnosticStyledString(vec![])
     }
     pub fn push_normal<S: Into<String>>(&mut self, t: S) {
-        self.0.push(StringPart::Normal(t.into()));
+        self.0.push(StringPart::normal(t));
     }
     pub fn push_highlighted<S: Into<String>>(&mut self, t: S) {
-        self.0.push(StringPart::Highlighted(t.into()));
+        self.0.push(StringPart::highlighted(t));
     }
     pub fn push<S: Into<String>>(&mut self, t: S, highlight: bool) {
         if highlight {
@@ -183,35 +231,64 @@ impl DiagnosticStyledString {
         }
     }
     pub fn normal<S: Into<String>>(t: S) -> DiagnosticStyledString {
-        DiagnosticStyledString(vec![StringPart::Normal(t.into())])
+        DiagnosticStyledString(vec![StringPart::normal(t)])
     }
 
     pub fn highlighted<S: Into<String>>(t: S) -> DiagnosticStyledString {
-        DiagnosticStyledString(vec![StringPart::Highlighted(t.into())])
+        DiagnosticStyledString(vec![StringPart::highlighted(t)])
     }
 
     pub fn content(&self) -> String {
-        self.0.iter().map(|x| x.content()).collect::<String>()
+        self.0.iter().map(|x| x.content.as_str()).collect::<String>()
     }
 }
 
 #[derive(Debug, PartialEq, Eq)]
-pub enum StringPart {
-    Normal(String),
-    Highlighted(String),
+pub struct StringPart {
+    content: String,
+    style: Style,
 }
 
 impl StringPart {
-    pub fn content(&self) -> &str {
-        match self {
-            &StringPart::Normal(ref s) | &StringPart::Highlighted(ref s) => s,
-        }
+    pub fn normal<S: Into<String>>(content: S) -> StringPart {
+        StringPart { content: content.into(), style: Style::NoStyle }
     }
+
+    pub fn highlighted<S: Into<String>>(content: S) -> StringPart {
+        StringPart { content: content.into(), style: Style::Highlight }
+    }
+}
+
+/// The main part of a diagnostic. Note that `DiagnosticBuilder`, which wraps
+/// this type, is used for most operations, and should be used instead whenever
+/// possible. This type should only be used when `DiagnosticBuilder`'s lifetime
+/// causes difficulties, e.g. when storing diagnostics within `DiagCtxt`.
+#[must_use]
+#[derive(Clone, Debug, Encodable, Decodable)]
+pub struct Diagnostic {
+    // NOTE(eddyb) this is private to disallow arbitrary after-the-fact changes,
+    // outside of what methods in this crate themselves allow.
+    pub(crate) level: Level,
+
+    pub messages: Vec<(DiagnosticMessage, Style)>,
+    pub code: Option<ErrCode>,
+    pub span: MultiSpan,
+    pub children: Vec<SubDiagnostic>,
+    pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
+    args: FxIndexMap<DiagnosticArgName, DiagnosticArgValue>,
+
+    /// This is not used for highlighting or rendering any error message. Rather, it can be used
+    /// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
+    /// `span` if there is one. Otherwise, it is `DUMMY_SP`.
+    pub sort_span: Span,
+
+    pub is_lint: Option<IsLint>,
+
+    /// With `-Ztrack_diagnostics` enabled,
+    /// we print where in rustc this error was emitted.
+    pub(crate) emitted_at: DiagnosticLocation,
 }
 
-// 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 {
@@ -241,19 +318,15 @@ impl Diagnostic {
 
     pub fn is_error(&self) -> bool {
         match self.level {
-            Level::Bug
-            | Level::DelayedBug(DelayedBugKind::Normal)
-            | Level::Fatal
-            | Level::Error
-            | Level::FailureNote => true,
+            Level::Bug | Level::Fatal | Level::Error | Level::DelayedBug => true,
 
             Level::ForceWarning(_)
             | Level::Warning
-            | Level::DelayedBug(DelayedBugKind::GoodPath)
             | Level::Note
             | Level::OnceNote
             | Level::Help
             | Level::OnceHelp
+            | Level::FailureNote
             | Level::Allow
             | Level::Expect(_) => false,
         }
@@ -299,6 +372,216 @@ impl Diagnostic {
         }
     }
 
+    // See comment on `DiagnosticBuilder::subdiagnostic_message_to_diagnostic_message`.
+    pub(crate) fn subdiagnostic_message_to_diagnostic_message(
+        &self,
+        attr: impl Into<SubdiagnosticMessage>,
+    ) -> DiagnosticMessage {
+        let msg =
+            self.messages.iter().map(|(msg, _)| msg).next().expect("diagnostic with no messages");
+        msg.with_subdiagnostic_message(attr.into())
+    }
+
+    pub(crate) fn sub(
+        &mut self,
+        level: Level,
+        message: impl Into<SubdiagnosticMessage>,
+        span: MultiSpan,
+    ) {
+        let sub = SubDiagnostic {
+            level,
+            messages: vec![(
+                self.subdiagnostic_message_to_diagnostic_message(message),
+                Style::NoStyle,
+            )],
+            span,
+        };
+        self.children.push(sub);
+    }
+
+    pub(crate) fn arg(&mut self, name: impl Into<DiagnosticArgName>, arg: impl IntoDiagnosticArg) {
+        self.args.insert(name.into(), arg.into_diagnostic_arg());
+    }
+
+    pub fn args(&self) -> impl Iterator<Item = DiagnosticArg<'_>> {
+        self.args.iter()
+    }
+
+    pub fn replace_args(&mut self, args: FxIndexMap<DiagnosticArgName, DiagnosticArgValue>) {
+        self.args = args;
+    }
+
+    /// Fields used for Hash, and PartialEq trait.
+    fn keys(
+        &self,
+    ) -> (
+        &Level,
+        &[(DiagnosticMessage, Style)],
+        &Option<ErrCode>,
+        &MultiSpan,
+        &[SubDiagnostic],
+        &Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
+        Vec<(&DiagnosticArgName, &DiagnosticArgValue)>,
+        &Option<IsLint>,
+    ) {
+        (
+            &self.level,
+            &self.messages,
+            &self.code,
+            &self.span,
+            &self.children,
+            &self.suggestions,
+            self.args().collect(),
+            // omit self.sort_span
+            &self.is_lint,
+            // omit self.emitted_at
+        )
+    }
+}
+
+impl Hash for Diagnostic {
+    fn hash<H>(&self, state: &mut H)
+    where
+        H: Hasher,
+    {
+        self.keys().hash(state);
+    }
+}
+
+impl PartialEq for Diagnostic {
+    fn eq(&self, other: &Self) -> bool {
+        self.keys() == other.keys()
+    }
+}
+
+/// A "sub"-diagnostic attached to a parent diagnostic.
+/// For example, a note attached to an error.
+#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
+pub struct SubDiagnostic {
+    pub level: Level,
+    pub messages: Vec<(DiagnosticMessage, Style)>,
+    pub span: MultiSpan,
+}
+
+/// Used for emitting structured error messages and other diagnostic information.
+/// Wraps a `Diagnostic`, adding some useful things.
+/// - The `dcx` field, allowing it to (a) emit itself, and (b) do a drop check
+///   that it has been emitted or cancelled.
+/// - The `EmissionGuarantee`, which determines the type returned from `emit`.
+///
+/// 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 `DiagCtxtFlags`.
+#[must_use]
+pub struct DiagnosticBuilder<'a, G: EmissionGuarantee = ErrorGuaranteed> {
+    pub dcx: &'a DiagCtxt,
+
+    /// 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.
+    ///
+    /// 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>>,
+
+    _marker: PhantomData<G>,
+}
+
+// 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!(
+    DiagnosticBuilder<'_, ()>,
+    2 * std::mem::size_of::<usize>()
+);
+
+impl<G: EmissionGuarantee> Deref for DiagnosticBuilder<'_, G> {
+    type Target = Diagnostic;
+
+    fn deref(&self) -> &Diagnostic {
+        self.diag.as_ref().unwrap()
+    }
+}
+
+impl<G: EmissionGuarantee> DerefMut for DiagnosticBuilder<'_, G> {
+    fn deref_mut(&mut self) -> &mut Diagnostic {
+        self.diag.as_mut().unwrap()
+    }
+}
+
+impl<G: EmissionGuarantee> Debug for DiagnosticBuilder<'_, G> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.diag.fmt(f)
+    }
+}
+
+/// `DiagnosticBuilder` impls many `&mut self -> &mut Self` methods. Each one
+/// modifies an existing diagnostic, 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);`.
+///
+/// This macro creates an equivalent `self -> Self` method, with a `with_`
+/// prefix. This can be used in a chained fashion when making a new diagnostic,
+/// e.g. `let err = struct_err(msg).with_code(code);`, or emitting a new
+/// diagnostic, e.g. `struct_err(msg).with_code(code).emit();`.
+///
+/// Although the latter method can be used to modify an existing diagnostic,
+/// e.g. `err = err.with_code(code);`, this should be avoided because the former
+/// method gives shorter code, e.g. `err.code(code);`.
+///
+/// Note: the `with_` methods are added only when needed. If you want to use
+/// one and it's not defined, feel free to add it.
+///
+/// Note: any doc comments must be within the `with_fn!` call.
+macro_rules! with_fn {
+    {
+        $with_f:ident,
+        $(#[$attrs:meta])*
+        pub fn $f:ident(&mut $self:ident, $($name:ident: $ty:ty),* $(,)?) -> &mut Self {
+            $($body:tt)*
+        }
+    } => {
+        // The original function.
+        $(#[$attrs])*
+        #[doc = concat!("See [`DiagnosticBuilder::", stringify!($f), "()`].")]
+        pub fn $f(&mut $self, $($name: $ty),*) -> &mut Self {
+            $($body)*
+        }
+
+        // The `with_*` variant.
+        $(#[$attrs])*
+        #[doc = concat!("See [`DiagnosticBuilder::", stringify!($f), "()`].")]
+        pub fn $with_f(mut $self, $($name: $ty),*) -> Self {
+            $self.$f($($name),*);
+            $self
+        }
+    };
+}
+
+impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
+    #[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 }
+    }
+
     /// Delay emission of this diagnostic as a bug.
     ///
     /// This can be useful in contexts where an error indicates a bug but
@@ -312,13 +595,14 @@ impl Diagnostic {
     #[track_caller]
     pub fn downgrade_to_delayed_bug(&mut self) {
         assert!(
-            self.is_error(),
+            matches!(self.level, Level::Error | Level::DelayedBug),
             "downgrade_to_delayed_bug: cannot downgrade {:?} to DelayedBug: not an error",
             self.level
         );
-        self.level = Level::DelayedBug(DelayedBugKind::Normal);
+        self.level = Level::DelayedBug;
     }
 
+    with_fn! { with_span_label,
     /// Appends a labeled span to the diagnostic.
     ///
     /// Labels are used to convey additional context for the diagnostic's primary span. They will
@@ -333,10 +617,12 @@ impl Diagnostic {
     /// 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));
+        let msg = self.subdiagnostic_message_to_diagnostic_message(label);
+        self.span.push_span_label(span, msg);
         self
-    }
+    } }
 
+    with_fn! { with_span_labels,
     /// Labels all the given spans with the provided label.
     /// See [`Self::span_label()`] for more information.
     pub fn span_labels(&mut self, spans: impl IntoIterator<Item = Span>, label: &str) -> &mut Self {
@@ -344,7 +630,7 @@ impl Diagnostic {
             self.span_label(span, label.to_string());
         }
         self
-    }
+    } }
 
     pub fn replace_span_with(&mut self, after: Span, keep_label: bool) -> &mut Self {
         let before = self.span.clone();
@@ -397,19 +683,16 @@ impl Diagnostic {
         } else {
             (0, found_label.len() - expected_label.len())
         };
-        let mut msg: Vec<_> =
-            vec![(format!("{}{} `", " ".repeat(expected_padding), expected_label), Style::NoStyle)];
-        msg.extend(expected.0.iter().map(|x| match *x {
-            StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
-            StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
-        }));
-        msg.push((format!("`{expected_extra}\n"), Style::NoStyle));
-        msg.push((format!("{}{} `", " ".repeat(found_padding), found_label), Style::NoStyle));
-        msg.extend(found.0.iter().map(|x| match *x {
-            StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
-            StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
-        }));
-        msg.push((format!("`{found_extra}"), Style::NoStyle));
+        let mut msg = vec![StringPart::normal(format!(
+            "{}{} `",
+            " ".repeat(expected_padding),
+            expected_label
+        ))];
+        msg.extend(expected.0.into_iter());
+        msg.push(StringPart::normal(format!("`{expected_extra}\n")));
+        msg.push(StringPart::normal(format!("{}{} `", " ".repeat(found_padding), found_label)));
+        msg.extend(found.0.into_iter());
+        msg.push(StringPart::normal(format!("`{found_extra}")));
 
         // For now, just attach these as notes.
         self.highlighted_note(msg);
@@ -418,49 +701,47 @@ impl Diagnostic {
 
     pub fn note_trait_signature(&mut self, name: Symbol, signature: String) -> &mut Self {
         self.highlighted_note(vec![
-            (format!("`{name}` from trait: `"), Style::NoStyle),
-            (signature, Style::Highlight),
-            ("`".to_string(), Style::NoStyle),
+            StringPart::normal(format!("`{name}` from trait: `")),
+            StringPart::highlighted(signature),
+            StringPart::normal("`"),
         ]);
         self
     }
 
+    with_fn! { with_note,
     /// 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());
         self
-    }
+    } }
 
-    fn highlighted_note<M: Into<SubdiagnosticMessage>>(
-        &mut self,
-        msg: Vec<(M, Style)>,
-    ) -> &mut Self {
+    fn highlighted_note(&mut self, msg: Vec<StringPart>) -> &mut Self {
         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.
+    /// This is like [`DiagnosticBuilder::note()`], but it's only printed once.
     pub fn note_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::OnceNote, msg, MultiSpan::new());
         self
     }
 
+    with_fn! { with_span_note,
     /// Prints the span with a note above it.
-    /// This is like [`Diagnostic::note()`], but it gets its own span.
+    /// This is like [`DiagnosticBuilder::note()`], but it gets its own span.
     #[rustc_lint_diagnostics]
-    pub fn span_note<S: Into<MultiSpan>>(
+    pub fn span_note(
         &mut self,
-        sp: S,
+        sp: impl Into<MultiSpan>,
         msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self {
         self.sub(Level::Note, msg, sp.into());
         self
-    }
+    } }
 
     /// Prints the span with a note above it.
-    /// This is like [`Diagnostic::note()`], but it gets its own span.
+    /// This is like [`DiagnosticBuilder::note_once()`], but it gets its own span.
     pub fn span_note_once<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
@@ -470,15 +751,16 @@ impl Diagnostic {
         self
     }
 
+    with_fn! { with_warn,
     /// 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, msg, MultiSpan::new());
         self
-    }
+    } }
 
     /// Prints the span with a warning above it.
-    /// This is like [`Diagnostic::warn()`], but it gets its own span.
+    /// This is like [`DiagnosticBuilder::warn()`], but it gets its own span.
     #[rustc_lint_diagnostics]
     pub fn span_warn<S: Into<MultiSpan>>(
         &mut self,
@@ -489,28 +771,28 @@ impl Diagnostic {
         self
     }
 
+    with_fn! { with_help,
     /// 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());
         self
-    }
+    } }
 
-    /// Prints the span with a help above it.
-    /// This is like [`Diagnostic::help()`], but it gets its own span.
+    /// This is like [`DiagnosticBuilder::help()`], but it's only printed once.
     pub fn help_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         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 {
+    pub fn highlighted_help(&mut self, msg: Vec<StringPart>) -> &mut Self {
         self.sub_with_highlights(Level::Help, msg, MultiSpan::new());
         self
     }
 
     /// Prints the span with some help above it.
-    /// This is like [`Diagnostic::help()`], but it gets its own span.
+    /// This is like [`DiagnosticBuilder::help()`], but it gets its own span.
     #[rustc_lint_diagnostics]
     pub fn span_help<S: Into<MultiSpan>>(
         &mut self,
@@ -531,11 +813,23 @@ impl Diagnostic {
 
     /// Helper for pushing to `self.suggestions`, if available (not disable).
     fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
+        for subst in &suggestion.substitutions {
+            for part in &subst.parts {
+                let span = part.span;
+                let call_site = span.ctxt().outer_expn_data().call_site;
+                if span.in_derive_expansion() && span.overlaps_or_adjacent(call_site) {
+                    // Ignore if spans is from derive macro.
+                    return;
+                }
+            }
+        }
+
         if let Ok(suggestions) = &mut self.suggestions {
             suggestions.push(suggestion);
         }
     }
 
+    with_fn! { with_multipart_suggestion,
     /// Show a suggestion that has multiple parts to it.
     /// In other words, multiple changes need to be applied as part of this suggestion.
     pub fn multipart_suggestion(
@@ -550,7 +844,7 @@ impl Diagnostic {
             applicability,
             SuggestionStyle::ShowCode,
         )
-    }
+    } }
 
     /// Show a suggestion that has multiple parts to it, always as it's own subdiagnostic.
     /// In other words, multiple changes need to be applied as part of this suggestion.
@@ -567,7 +861,8 @@ impl Diagnostic {
             SuggestionStyle::ShowAlways,
         )
     }
-    /// [`Diagnostic::multipart_suggestion()`] but you can set the [`SuggestionStyle`].
+
+    /// [`DiagnosticBuilder::multipart_suggestion()`] but you can set the [`SuggestionStyle`].
     pub fn multipart_suggestion_with_style(
         &mut self,
         msg: impl Into<SubdiagnosticMessage>,
@@ -624,6 +919,7 @@ impl Diagnostic {
         )
     }
 
+    with_fn! { with_span_suggestion,
     /// Prints out a message with a suggested edit of the code.
     ///
     /// In case of short messages and a simple suggestion, rustc displays it as a label:
@@ -656,9 +952,9 @@ impl Diagnostic {
             SuggestionStyle::ShowCode,
         );
         self
-    }
+    } }
 
-    /// [`Diagnostic::span_suggestion()`] but you can set the [`SuggestionStyle`].
+    /// [`DiagnosticBuilder::span_suggestion()`] but you can set the [`SuggestionStyle`].
     pub fn span_suggestion_with_style(
         &mut self,
         sp: Span,
@@ -682,6 +978,7 @@ impl Diagnostic {
         self
     }
 
+    with_fn! { with_span_suggestion_verbose,
     /// Always show the suggested change.
     pub fn span_suggestion_verbose(
         &mut self,
@@ -698,10 +995,11 @@ impl Diagnostic {
             SuggestionStyle::ShowAlways,
         );
         self
-    }
+    } }
 
+    with_fn! { with_span_suggestions,
     /// Prints out a message with multiple suggested edits of the code.
-    /// See also [`Diagnostic::span_suggestion()`].
+    /// See also [`DiagnosticBuilder::span_suggestion()`].
     pub fn span_suggestions(
         &mut self,
         sp: Span,
@@ -716,9 +1014,8 @@ impl Diagnostic {
             applicability,
             SuggestionStyle::ShowCode,
         )
-    }
+    } }
 
-    /// [`Diagnostic::span_suggestions()`] but you can set the [`SuggestionStyle`].
     pub fn span_suggestions_with_style(
         &mut self,
         sp: Span,
@@ -748,7 +1045,7 @@ impl Diagnostic {
 
     /// Prints out a message with multiple suggested edits of the code, where each edit consists of
     /// multiple parts.
-    /// See also [`Diagnostic::multipart_suggestion()`].
+    /// See also [`DiagnosticBuilder::multipart_suggestion()`].
     pub fn multipart_suggestions(
         &mut self,
         msg: impl Into<SubdiagnosticMessage>,
@@ -790,6 +1087,7 @@ impl Diagnostic {
         self
     }
 
+    with_fn! { with_span_suggestion_short,
     /// Prints out a message with a suggested edit of the code. If the suggestion is presented
     /// inline, it will only show the message and not the suggestion.
     ///
@@ -809,7 +1107,7 @@ impl Diagnostic {
             SuggestionStyle::HideCodeInline,
         );
         self
-    }
+    } }
 
     /// Prints out a message for a suggestion without showing the suggested code.
     ///
@@ -834,6 +1132,7 @@ impl Diagnostic {
         self
     }
 
+    with_fn! { with_tool_only_span_suggestion,
     /// Adds a suggestion to the JSON output that will not be shown in the CLI.
     ///
     /// This is intended to be used for suggestions that are *very* obvious in what the changes
@@ -854,20 +1153,13 @@ impl Diagnostic {
             SuggestionStyle::CompletelyHidden,
         );
         self
-    }
-
-    /// Add a subdiagnostic from a type that implements `Subdiagnostic` (see
-    /// [rustc_macros::Subdiagnostic]).
-    pub fn subdiagnostic(&mut self, subdiagnostic: impl AddToDiagnostic) -> &mut Self {
-        subdiagnostic.add_to_diagnostic(self);
-        self
-    }
+    } }
 
     /// Add a subdiagnostic from a type that implements `Subdiagnostic` (see
     /// [rustc_macros::Subdiagnostic]). Performs eager translation of any translatable messages
     /// used in the subdiagnostic, so suitable for use with repeated messages (i.e. re-use of
     /// interpolated variables).
-    pub fn eager_subdiagnostic(
+    pub fn subdiagnostic(
         &mut self,
         dcx: &crate::DiagCtxt,
         subdiagnostic: impl AddToDiagnostic,
@@ -880,75 +1172,54 @@ impl Diagnostic {
         self
     }
 
-    pub fn span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
+    with_fn! { with_span,
+    /// Add a span.
+    pub fn span(&mut self, sp: impl Into<MultiSpan>) -> &mut Self {
         self.span = sp.into();
         if let Some(span) = self.span.primary_span() {
             self.sort_span = span;
         }
         self
-    }
+    } }
 
     pub fn is_lint(&mut self, name: String, has_future_breakage: bool) -> &mut Self {
         self.is_lint = Some(IsLint { name, has_future_breakage });
         self
     }
 
-    pub fn code(&mut self, s: String) -> &mut Self {
-        self.code = Some(s);
-        self
-    }
-
-    pub fn clear_code(&mut self) -> &mut Self {
-        self.code = None;
+    with_fn! { with_code,
+    /// Add an error code.
+    pub fn code(&mut self, code: ErrCode) -> &mut Self {
+        self.code = Some(code);
         self
-    }
-
-    pub fn get_code(&self) -> Option<&str> {
-        self.code.as_deref()
-    }
+    } }
 
+    with_fn! { with_primary_message,
+    /// Add a primary message.
     pub fn primary_message(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
         self.messages[0] = (msg.into(), Style::NoStyle);
         self
-    }
-
-    // Exact iteration order of diagnostic arguments shouldn't make a difference to output because
-    // they're only used in interpolation.
-    #[allow(rustc::potential_query_instability)]
-    pub fn args(&self) -> impl Iterator<Item = DiagnosticArg<'_, 'static>> {
-        self.args.iter()
-    }
+    } }
 
+    with_fn! { with_arg,
+    /// Add an argument.
     pub fn arg(
         &mut self,
-        name: impl Into<Cow<'static, str>>,
+        name: impl Into<DiagnosticArgName>,
         arg: impl IntoDiagnosticArg,
     ) -> &mut Self {
-        self.args.insert(name.into(), arg.into_diagnostic_arg());
+        self.deref_mut().arg(name, arg);
         self
-    }
-
-    pub fn replace_args(
-        &mut self,
-        args: FxHashMap<DiagnosticArgName<'static>, DiagnosticArgValue<'static>>,
-    ) {
-        self.args = args;
-    }
-
-    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).
-    fn subdiagnostic_message_to_diagnostic_message(
+    pub(crate) fn subdiagnostic_message_to_diagnostic_message(
         &self,
         attr: impl Into<SubdiagnosticMessage>,
     ) -> DiagnosticMessage {
-        let msg =
-            self.messages.iter().map(|(msg, _)| msg).next().expect("diagnostic with no messages");
-        msg.with_subdiagnostic_message(attr.into())
+        self.deref().subdiagnostic_message_to_diagnostic_message(attr)
     }
 
     /// Convenience function for internal use, clients should use one of the
@@ -956,70 +1227,125 @@ impl Diagnostic {
     ///
     /// Used by `proc_macro_server` for implementing `server::Diagnostic`.
     pub fn sub(&mut self, level: Level, message: impl Into<SubdiagnosticMessage>, span: MultiSpan) {
-        let sub = SubDiagnostic {
-            level,
-            messages: vec![(
-                self.subdiagnostic_message_to_diagnostic_message(message),
-                Style::NoStyle,
-            )],
-            span,
-        };
-        self.children.push(sub);
+        self.deref_mut().sub(level, message, span);
     }
 
     /// Convenience function for internal use, clients should use one of the
     /// public methods above.
-    fn sub_with_highlights<M: Into<SubdiagnosticMessage>>(
-        &mut self,
-        level: Level,
-        messages: Vec<(M, Style)>,
-        span: MultiSpan,
-    ) {
+    fn sub_with_highlights(&mut self, level: Level, messages: Vec<StringPart>, span: MultiSpan) {
         let messages = messages
             .into_iter()
-            .map(|m| (self.subdiagnostic_message_to_diagnostic_message(m.0), m.1))
+            .map(|m| (self.subdiagnostic_message_to_diagnostic_message(m.content), m.style))
             .collect();
         let sub = SubDiagnostic { level, messages, span };
         self.children.push(sub);
     }
 
-    /// Fields used for Hash, and PartialEq trait
-    fn keys(
-        &self,
-    ) -> (
-        &Level,
-        &[(DiagnosticMessage, Style)],
-        Vec<(&Cow<'static, str>, &DiagnosticArgValue<'static>)>,
-        &Option<String>,
-        &Option<IsLint>,
-        &MultiSpan,
-        &Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
-        Option<&[SubDiagnostic]>,
-    ) {
-        (
-            &self.level,
-            &self.messages,
-            self.args().collect(),
-            &self.code,
-            &self.is_lint,
-            &self.span,
-            &self.suggestions,
-            (if self.is_lint.is_some() { None } else { Some(&self.children) }),
-        )
+    /// 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())
     }
-}
 
-impl Hash for Diagnostic {
-    fn hash<H>(&self, state: &mut H)
-    where
-        H: Hasher,
-    {
-        self.keys().hash(state);
+    /// 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);
+    }
+
+    /// `ErrorGuaranteed::emit_producing_guarantee` uses this.
+    fn emit_producing_error_guaranteed(mut self) -> ErrorGuaranteed {
+        let diag = self.take_diag();
+
+        // The only error levels that produce `ErrorGuaranteed` are
+        // `Error` and `DelayedBug`. But `DelayedBug` should never occur here
+        // because delayed bugs have their level changed to `Bug` when they are
+        // actually printed, so they produce an ICE.
+        //
+        // (Also, even though `level` isn't `pub`, the whole `Diagnostic` could
+        // be overwritten with a new one thanks to `DerefMut`. So this assert
+        // protects against that, too.)
+        assert!(
+            matches!(diag.level, Level::Error | Level::DelayedBug),
+            "invalid diagnostic level ({:?})",
+            diag.level,
+        );
+
+        let guar = self.dcx.emit_diagnostic(diag);
+        guar.unwrap()
+    }
+
+    /// Emit and consume the diagnostic.
+    #[track_caller]
+    pub fn emit(self) -> G::EmitResult {
+        G::emit_producing_guarantee(self)
+    }
+
+    /// Emit the diagnostic unless `delay` is true,
+    /// in which case the emission will be delayed as a bug.
+    ///
+    /// See `emit` and `delay_as_bug` for details.
+    #[track_caller]
+    pub fn emit_unless(mut self, delay: bool) -> G::EmitResult {
+        if delay {
+            self.downgrade_to_delayed_bug();
+        }
+        self.emit()
+    }
+
+    /// Cancel and consume the diagnostic. (A diagnostic must either be emitted or
+    /// cancelled or it will panic when dropped).
+    pub fn cancel(mut self) {
+        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 [`DiagCtxt::steal_diagnostic()`].
+    pub fn stash(mut self, span: Span, key: StashKey) {
+        self.dcx.stash_diagnostic(span, key, self.take_diag());
+    }
+
+    /// Delay emission of this diagnostic as a bug.
+    ///
+    /// This can be useful in contexts where an error indicates a bug but
+    /// typically this only happens when other compilation errors have already
+    /// happened. In those cases this can be used to defer emission of this
+    /// diagnostic as a bug in the compiler only if no other errors have been
+    /// emitted.
+    ///
+    /// 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::EmitResult {
+        self.downgrade_to_delayed_bug();
+        self.emit()
     }
 }
 
-impl PartialEq for Diagnostic {
-    fn eq(&self, other: &Self) -> bool {
-        self.keys() == other.keys()
+/// 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.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");
+            }
+            _ => {}
+        }
     }
 }
+
+#[macro_export]
+macro_rules! struct_span_code_err {
+    ($dcx:expr, $span:expr, $code:expr, $($message:tt)*) => ({
+        $dcx.struct_span_err($span, format!($($message)*)).with_code($code)
+    })
+}
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
deleted file mode 100644
index 87e2d295c7f..00000000000
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ /dev/null
@@ -1,450 +0,0 @@
-use crate::diagnostic::IntoDiagnosticArg;
-use crate::{DiagCtxt, Level, MultiSpan, StashKey};
-use crate::{
-    Diagnostic, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed, ExplicitBug,
-    SubdiagnosticMessage,
-};
-use rustc_lint_defs::Applicability;
-use rustc_span::source_map::Spanned;
-
-use rustc_span::Span;
-use std::borrow::Cow;
-use std::fmt::{self, Debug};
-use std::marker::PhantomData;
-use std::ops::{Deref, DerefMut};
-use std::panic;
-use std::thread::panicking;
-
-/// 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, G: EmissionGuarantee = ErrorGuaranteed> {
-    /// Write out as a diagnostic out of `DiagCtxt`.
-    #[must_use]
-    fn into_diagnostic(self, dcx: &'a DiagCtxt, level: Level) -> DiagnosticBuilder<'a, G>;
-}
-
-impl<'a, T, G> IntoDiagnostic<'a, G> for Spanned<T>
-where
-    T: IntoDiagnostic<'a, G>,
-    G: EmissionGuarantee,
-{
-    fn into_diagnostic(self, dcx: &'a DiagCtxt, level: Level) -> DiagnosticBuilder<'a, G> {
-        self.node.into_diagnostic(dcx, level).with_span(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 `DiagCtxtFlags`.
-#[must_use]
-pub struct DiagnosticBuilder<'a, G: EmissionGuarantee = ErrorGuaranteed> {
-    pub dcx: &'a DiagCtxt,
-
-    /// 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.
-    ///
-    /// 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>>,
-
-    _marker: PhantomData<G>,
-}
-
-// 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!(
-    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::EmitResult` without actually performing the emission.
-    #[track_caller]
-    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult;
-}
-
-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())
-    }
-
-    /// 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);
-    }
-
-    /// `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();
-
-        // 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,
-        );
-
-        let guar = self.dcx.emit_diagnostic(diag);
-        guar.unwrap()
-    }
-}
-
-impl EmissionGuarantee for ErrorGuaranteed {
-    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
-        db.emit_producing_error_guaranteed()
-    }
-}
-
-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 diagnostics.
-#[derive(Copy, Clone)]
-pub struct BugAbort;
-
-impl EmissionGuarantee for BugAbort {
-    type EmitResult = !;
-
-    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
-        db.emit_producing_nothing();
-        panic::panic_any(ExplicitBug);
-    }
-}
-
-/// Marker type which enables implementation of `create_fatal` and `emit_fatal` functions for
-/// fatal diagnostics.
-#[derive(Copy, Clone)]
-pub struct FatalAbort;
-
-impl EmissionGuarantee for FatalAbort {
-    type EmitResult = !;
-
-    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
-        db.emit_producing_nothing();
-        crate::FatalError.raise()
-    }
-}
-
-impl EmissionGuarantee for rustc_span::fatal_error::FatalError {
-    fn emit_producing_guarantee(db: DiagnosticBuilder<'_, Self>) -> Self::EmitResult {
-        db.emit_producing_nothing();
-        rustc_span::fatal_error::FatalError
-    }
-}
-
-/// `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, which has a `with_` prefix added.
-///   It is mostly used in a chained fashion when producing a new diagnostic,
-///   e.g. `let err = struct_err(msg).with_code(code)`, or when emitting a new
-///   diagnostic , e.g. `struct_err(msg).with_code(code).emit()`.
-///
-/// Although the latter method can be used to modify an existing diagnostic,
-/// e.g. `err = err.with_code(code)`, this should be avoided because the former
-/// method gives shorter code, e.g. `err.code(code)`.
-macro_rules! forward {
-    (
-        ($f:ident, $with_f:ident)($($name:ident: $ty:ty),* $(,)?)
-    ) => {
-        #[doc = concat!("See [`Diagnostic::", stringify!($f), "()`].")]
-        pub fn $f(&mut self, $($name: $ty),*) -> &mut Self {
-            self.diag.as_mut().unwrap().$f($($name),*);
-            self
-        }
-        #[doc = concat!("See [`Diagnostic::", stringify!($f), "()`].")]
-        pub fn $with_f(mut self, $($name: $ty),*) -> Self {
-            self.diag.as_mut().unwrap().$f($($name),*);
-            self
-        }
-    };
-}
-
-impl<G: EmissionGuarantee> Deref for DiagnosticBuilder<'_, G> {
-    type Target = Diagnostic;
-
-    fn deref(&self) -> &Diagnostic {
-        self.diag.as_ref().unwrap()
-    }
-}
-
-impl<G: EmissionGuarantee> DerefMut for DiagnosticBuilder<'_, G> {
-    fn deref_mut(&mut self) -> &mut Diagnostic {
-        self.diag.as_mut().unwrap()
-    }
-}
-
-impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
-    #[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(self) -> G::EmitResult {
-        G::emit_producing_guarantee(self)
-    }
-
-    /// Emit the diagnostic unless `delay` is true,
-    /// in which case the emission will be delayed as a bug.
-    ///
-    /// See `emit` and `delay_as_bug` for details.
-    #[track_caller]
-    pub fn emit_unless(mut self, delay: bool) -> G::EmitResult {
-        if delay {
-            self.downgrade_to_delayed_bug();
-        }
-        self.emit()
-    }
-
-    /// Cancel and consume the diagnostic. (A diagnostic must either be emitted or
-    /// cancelled or it will panic when dropped).
-    pub fn cancel(mut self) {
-        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 [`DiagCtxt::steal_diagnostic()`].
-    pub fn stash(self, span: Span, key: StashKey) {
-        self.dcx.stash_diagnostic(span, key, self.into_diagnostic());
-    }
-
-    /// Converts the builder to a `Diagnostic` for later emission.
-    pub fn into_diagnostic(mut self) -> Diagnostic {
-        self.take_diag()
-    }
-
-    /// Delay emission of this diagnostic as a bug.
-    ///
-    /// This can be useful in contexts where an error indicates a bug but
-    /// typically this only happens when other compilation errors have already
-    /// happened. In those cases this can be used to defer emission of this
-    /// diagnostic as a bug in the compiler only if no other errors have been
-    /// emitted.
-    ///
-    /// 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::EmitResult {
-        self.downgrade_to_delayed_bug();
-        self.emit()
-    }
-
-    forward!((span_label, with_span_label)(
-        span: Span,
-        label: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((span_labels, with_span_labels)(
-        spans: impl IntoIterator<Item = Span>,
-        label: &str,
-    ));
-    forward!((note_expected_found, with_note_expected_found)(
-        expected_label: &dyn fmt::Display,
-        expected: DiagnosticStyledString,
-        found_label: &dyn fmt::Display,
-        found: DiagnosticStyledString,
-    ));
-    forward!((note_expected_found_extra, with_note_expected_found_extra)(
-        expected_label: &dyn fmt::Display,
-        expected: DiagnosticStyledString,
-        found_label: &dyn fmt::Display,
-        found: DiagnosticStyledString,
-        expected_extra: &dyn fmt::Display,
-        found_extra: &dyn fmt::Display,
-    ));
-    forward!((note, with_note)(
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((note_once, with_note_once)(
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((span_note, with_span_note)(
-        sp: impl Into<MultiSpan>,
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((span_note_once, with_span_note_once)(
-        sp: impl Into<MultiSpan>,
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((warn, with_warn)(
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((span_warn, with_span_warn)(
-        sp: impl Into<MultiSpan>,
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((help, with_help)(
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((help_once, with_help_once)(
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((span_help, with_span_help_once)(
-        sp: impl Into<MultiSpan>,
-        msg: impl Into<SubdiagnosticMessage>,
-    ));
-    forward!((multipart_suggestion, with_multipart_suggestion)(
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestion: Vec<(Span, String)>,
-        applicability: Applicability,
-    ));
-    forward!((multipart_suggestion_verbose, with_multipart_suggestion_verbose)(
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestion: Vec<(Span, String)>,
-        applicability: Applicability,
-    ));
-    forward!((tool_only_multipart_suggestion, with_tool_only_multipart_suggestion)(
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestion: Vec<(Span, String)>,
-        applicability: Applicability,
-    ));
-    forward!((span_suggestion, with_span_suggestion)(
-        sp: Span,
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestion: impl ToString,
-        applicability: Applicability,
-    ));
-    forward!((span_suggestions, with_span_suggestions)(
-        sp: Span,
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestions: impl IntoIterator<Item = String>,
-        applicability: Applicability,
-    ));
-    forward!((multipart_suggestions, with_multipart_suggestions)(
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestions: impl IntoIterator<Item = Vec<(Span, String)>>,
-        applicability: Applicability,
-    ));
-    forward!((span_suggestion_short, with_span_suggestion_short)(
-        sp: Span,
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestion: impl ToString,
-        applicability: Applicability,
-    ));
-    forward!((span_suggestion_verbose, with_span_suggestion_verbose)(
-        sp: Span,
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestion: impl ToString,
-        applicability: Applicability,
-    ));
-    forward!((span_suggestion_hidden, with_span_suggestion_hidden)(
-        sp: Span,
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestion: impl ToString,
-        applicability: Applicability,
-    ));
-    forward!((tool_only_span_suggestion, with_tool_only_span_suggestion)(
-        sp: Span,
-        msg: impl Into<SubdiagnosticMessage>,
-        suggestion: impl ToString,
-        applicability: Applicability,
-    ));
-    forward!((primary_message, with_primary_message)(
-        msg: impl Into<DiagnosticMessage>,
-    ));
-    forward!((span, with_span)(
-        sp: impl Into<MultiSpan>,
-    ));
-    forward!((is_lint, with_is_lint)(
-        name: String, has_future_breakage: bool,
-    ));
-    forward!((code, with_code)(
-        s: String,
-    ));
-    forward!((arg, with_arg)(
-        name: impl Into<Cow<'static, str>>, arg: impl IntoDiagnosticArg,
-    ));
-    forward!((subdiagnostic, with_subdiagnostic)(
-        subdiagnostic: impl crate::AddToDiagnostic,
-    ));
-    forward!((eager_subdiagnostic, with_eager_subdiagnostic)(
-        dcx: &DiagCtxt,
-        subdiagnostic: impl crate::AddToDiagnostic,
-    ));
-}
-
-impl<G: EmissionGuarantee> Debug for DiagnosticBuilder<'_, G> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.diag.fmt(f)
-    }
-}
-
-/// 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.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");
-            }
-            _ => {}
-        }
-    }
-}
-
-#[macro_export]
-macro_rules! struct_span_code_err {
-    ($dcx:expr, $span:expr, $code:ident, $($message:tt)*) => ({
-        $dcx.struct_span_err($span, format!($($message)*)).with_code($crate::error_code!($code))
-    })
-}
-
-#[macro_export]
-macro_rules! error_code {
-    ($code:ident) => {{ stringify!($code).to_owned() }};
-}
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index 39252dea283..bc1e81642ff 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -1,8 +1,8 @@
 use crate::diagnostic::DiagnosticLocation;
 use crate::{fluent_generated as fluent, AddToDiagnostic};
 use crate::{
-    DiagCtxt, DiagnosticArgValue, DiagnosticBuilder, EmissionGuarantee, IntoDiagnostic,
-    IntoDiagnosticArg, Level,
+    DiagCtxt, DiagnosticArgValue, DiagnosticBuilder, EmissionGuarantee, ErrCode, IntoDiagnostic,
+    IntoDiagnosticArg, Level, SubdiagnosticMessageOp,
 };
 use rustc_ast as ast;
 use rustc_ast_pretty::pprust;
@@ -23,7 +23,7 @@ use std::process::ExitStatus;
 pub struct DiagnosticArgFromDisplay<'a>(pub &'a dyn fmt::Display);
 
 impl IntoDiagnosticArg for DiagnosticArgFromDisplay<'_> {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         self.0.to_string().into_diagnostic_arg()
     }
 }
@@ -41,7 +41,7 @@ impl<'a, T: fmt::Display> From<&'a T> for DiagnosticArgFromDisplay<'a> {
 }
 
 impl<'a, T: Clone + IntoDiagnosticArg> IntoDiagnosticArg for &'a T {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         self.clone().into_diagnostic_arg()
     }
 }
@@ -50,7 +50,7 @@ macro_rules! into_diagnostic_arg_using_display {
     ($( $ty:ty ),+ $(,)?) => {
         $(
             impl IntoDiagnosticArg for $ty {
-                fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+                fn into_diagnostic_arg(self) -> DiagnosticArgValue {
                     self.to_string().into_diagnostic_arg()
                 }
             }
@@ -58,19 +58,28 @@ macro_rules! into_diagnostic_arg_using_display {
     }
 }
 
+macro_rules! into_diagnostic_arg_for_number {
+    ($( $ty:ty ),+ $(,)?) => {
+        $(
+            impl IntoDiagnosticArg for $ty {
+                fn into_diagnostic_arg(self) -> DiagnosticArgValue {
+                    // Convert to a string if it won't fit into `Number`.
+                    if let Ok(n) = TryInto::<i32>::try_into(self) {
+                        DiagnosticArgValue::Number(n)
+                    } else {
+                        self.to_string().into_diagnostic_arg()
+                    }
+                }
+            }
+        )+
+    }
+}
+
 into_diagnostic_arg_using_display!(
     ast::ParamKindOrd,
-    i8,
-    u8,
-    i16,
-    u16,
-    u32,
-    i64,
-    i128,
-    u128,
     std::io::Error,
     Box<dyn std::error::Error>,
-    std::num::NonZeroU32,
+    std::num::NonZero<u32>,
     hir::Target,
     Edition,
     Ident,
@@ -80,22 +89,13 @@ into_diagnostic_arg_using_display!(
     &TargetTriple,
     SplitDebuginfo,
     ExitStatus,
+    ErrCode,
 );
 
-impl IntoDiagnosticArg for i32 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Number(self.into())
-    }
-}
-
-impl IntoDiagnosticArg for u64 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Number(self.into())
-    }
-}
+into_diagnostic_arg_for_number!(i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize);
 
 impl IntoDiagnosticArg for bool {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         if self {
             DiagnosticArgValue::Str(Cow::Borrowed("true"))
         } else {
@@ -105,61 +105,63 @@ impl IntoDiagnosticArg for bool {
 }
 
 impl IntoDiagnosticArg for char {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(format!("{self:?}")))
     }
 }
 
+impl IntoDiagnosticArg for Vec<char> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
+        DiagnosticArgValue::StrListSepByAnd(
+            self.into_iter().map(|c| Cow::Owned(format!("{c:?}"))).collect(),
+        )
+    }
+}
+
 impl IntoDiagnosticArg for Symbol {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         self.to_ident_string().into_diagnostic_arg()
     }
 }
 
 impl<'a> IntoDiagnosticArg for &'a str {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         self.to_string().into_diagnostic_arg()
     }
 }
 
 impl IntoDiagnosticArg for String {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(self))
     }
 }
 
 impl<'a> IntoDiagnosticArg for Cow<'a, str> {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(self.into_owned()))
     }
 }
 
 impl<'a> IntoDiagnosticArg for &'a Path {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(self.display().to_string()))
     }
 }
 
 impl IntoDiagnosticArg for PathBuf {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(self.display().to_string()))
     }
 }
 
-impl IntoDiagnosticArg for usize {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Number(self as i128)
-    }
-}
-
 impl IntoDiagnosticArg for PanicStrategy {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(self.desc().to_string()))
     }
 }
 
 impl IntoDiagnosticArg for hir::ConstContext {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Borrowed(match self {
             hir::ConstContext::ConstFn => "const_fn",
             hir::ConstContext::Static(_) => "static",
@@ -169,49 +171,49 @@ impl IntoDiagnosticArg for hir::ConstContext {
 }
 
 impl IntoDiagnosticArg for ast::Expr {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(pprust::expr_to_string(&self)))
     }
 }
 
 impl IntoDiagnosticArg for ast::Path {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(pprust::path_to_string(&self)))
     }
 }
 
 impl IntoDiagnosticArg for ast::token::Token {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(pprust::token_to_string(&self))
     }
 }
 
 impl IntoDiagnosticArg for ast::token::TokenKind {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(pprust::token_kind_to_string(&self))
     }
 }
 
 impl IntoDiagnosticArg for type_ir::FloatTy {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Borrowed(self.name_str()))
     }
 }
 
 impl IntoDiagnosticArg for std::ffi::CString {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(self.to_string_lossy().into_owned()))
     }
 }
 
 impl IntoDiagnosticArg for rustc_data_structures::small_c_str::SmallCStr {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Owned(self.to_string_lossy().into_owned()))
     }
 }
 
 impl IntoDiagnosticArg for ast::Visibility {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         let s = pprust::vis_to_string(&self);
         let s = s.trim_end().to_string();
         DiagnosticArgValue::Str(Cow::Owned(s))
@@ -219,7 +221,7 @@ impl IntoDiagnosticArg for ast::Visibility {
 }
 
 impl IntoDiagnosticArg for rustc_lint_defs::Level {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Borrowed(self.to_cmd_flag()))
     }
 }
@@ -234,7 +236,7 @@ impl From<Vec<Symbol>> for DiagnosticSymbolList {
 }
 
 impl IntoDiagnosticArg for DiagnosticSymbolList {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::StrListSepByAnd(
             self.0.into_iter().map(|sym| Cow::Owned(format!("`{sym}`"))).collect(),
         )
@@ -242,7 +244,7 @@ impl IntoDiagnosticArg for DiagnosticSymbolList {
 }
 
 impl<Id> IntoDiagnosticArg for hir::def::Res<Id> {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::Borrowed(self.descr()))
     }
 }
@@ -297,7 +299,11 @@ pub struct SingleLabelManySpans {
     pub label: &'static str,
 }
 impl AddToDiagnostic for SingleLabelManySpans {
-    fn add_to_diagnostic_with<F>(self, diag: &mut crate::Diagnostic, _: F) {
+    fn add_to_diagnostic_with<G: EmissionGuarantee, F: SubdiagnosticMessageOp<G>>(
+        self,
+        diag: &mut DiagnosticBuilder<'_, G>,
+        _: F,
+    ) {
         diag.span_labels(self.spans, self.label);
     }
 }
@@ -310,44 +316,20 @@ pub struct ExpectedLifetimeParameter {
     pub count: usize,
 }
 
-#[derive(Subdiagnostic)]
-#[note(errors_delayed_at_with_newline)]
-pub struct DelayedAtWithNewline {
-    #[primary_span]
-    pub span: Span,
-    pub emitted_at: DiagnosticLocation,
-    pub note: Backtrace,
-}
-#[derive(Subdiagnostic)]
-#[note(errors_delayed_at_without_newline)]
-pub struct DelayedAtWithoutNewline {
-    #[primary_span]
-    pub span: Span,
-    pub emitted_at: DiagnosticLocation,
-    pub note: Backtrace,
-}
-
 impl IntoDiagnosticArg for DiagnosticLocation {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::from(self.to_string()))
     }
 }
 
 impl IntoDiagnosticArg for Backtrace {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::from(self.to_string()))
     }
 }
 
-#[derive(Subdiagnostic)]
-#[note(errors_invalid_flushed_delayed_diagnostic_level)]
-pub struct InvalidFlushedDelayedDiagnosticLevel {
-    #[primary_span]
-    pub span: Span,
-    pub level: Level,
-}
 impl IntoDiagnosticArg for Level {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(Cow::from(self.to_string()))
     }
 }
@@ -362,7 +344,7 @@ pub struct IndicateAnonymousLifetime {
 }
 
 impl IntoDiagnosticArg for type_ir::ClosureKind {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue {
         DiagnosticArgValue::Str(self.as_str().into())
     }
 }
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 23efdaea0eb..df94b69004b 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -10,6 +10,7 @@
 use rustc_span::source_map::SourceMap;
 use rustc_span::{FileLines, FileName, SourceFile, Span};
 
+use crate::error::TranslateError;
 use crate::snippet::{
     Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
 };
@@ -17,8 +18,8 @@ use crate::styled_buffer::StyledBuffer;
 use crate::translation::{to_fluent_args, Translate};
 use crate::{
     diagnostic::DiagnosticLocation, CodeSuggestion, DiagCtxt, Diagnostic, DiagnosticMessage,
-    FluentBundle, LazyFallbackBundle, Level, MultiSpan, SubDiagnostic, SubstitutionHighlight,
-    SuggestionStyle, TerminalUrl,
+    ErrCode, FluentBundle, LazyFallbackBundle, Level, MultiSpan, SubDiagnostic,
+    SubstitutionHighlight, SuggestionStyle, TerminalUrl,
 };
 use rustc_lint_defs::pluralize;
 
@@ -193,7 +194,7 @@ pub type DynEmitter = dyn Emitter + DynSend;
 /// Emitter trait for emitting errors.
 pub trait Emitter: Translate {
     /// Emit a structured diagnostic.
-    fn emit_diagnostic(&mut self, diag: &Diagnostic);
+    fn emit_diagnostic(&mut self, diag: Diagnostic);
 
     /// Emit a notification that an artifact has been output.
     /// Currently only supported for the JSON format.
@@ -230,17 +231,17 @@ pub trait Emitter: Translate {
     ///
     /// * If the current `Diagnostic` has only one visible `CodeSuggestion`,
     ///   we format the `help` suggestion depending on the content of the
-    ///   substitutions. In that case, we return the modified span only.
+    ///   substitutions. In that case, we modify the span and clear the
+    ///   suggestions.
     ///
     /// * If the current `Diagnostic` has multiple suggestions,
-    ///   we return the original `primary_span` and the original suggestions.
-    fn primary_span_formatted<'a>(
+    ///   we leave `primary_span` and the suggestions untouched.
+    fn primary_span_formatted(
         &mut self,
-        diag: &'a Diagnostic,
+        primary_span: &mut MultiSpan,
+        suggestions: &mut Vec<CodeSuggestion>,
         fluent_args: &FluentArgs<'_>,
-    ) -> (MultiSpan, &'a [CodeSuggestion]) {
-        let mut primary_span = diag.span.clone();
-        let suggestions = diag.suggestions.as_deref().unwrap_or(&[]);
+    ) {
         if let Some((sugg, rest)) = suggestions.split_first() {
             let msg = self.translate_message(&sugg.msg, fluent_args).map_err(Report::new).unwrap();
             if rest.is_empty() &&
@@ -287,16 +288,15 @@ pub trait Emitter: Translate {
                 primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg);
 
                 // We return only the modified primary_span
-                (primary_span, &[])
+                suggestions.clear();
             } else {
                 // if there are multiple suggestions, print them all in full
                 // to be consistent. We could try to figure out if we can
                 // make one (or the first one) inline, but that would give
                 // undue importance to a semi-random suggestion
-                (primary_span, suggestions)
             }
         } else {
-            (primary_span, suggestions)
+            // do nothing
         }
     }
 
@@ -518,16 +518,15 @@ impl Emitter for HumanEmitter {
         self.sm.as_ref()
     }
 
-    fn emit_diagnostic(&mut self, diag: &Diagnostic) {
+    fn emit_diagnostic(&mut self, mut diag: Diagnostic) {
         let fluent_args = to_fluent_args(diag.args());
 
-        let mut children = diag.children.clone();
-        let (mut primary_span, suggestions) = self.primary_span_formatted(diag, &fluent_args);
-        debug!("emit_diagnostic: suggestions={:?}", suggestions);
+        let mut suggestions = diag.suggestions.unwrap_or(vec![]);
+        self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
 
         self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
-            &mut primary_span,
-            &mut children,
+            &mut diag.span,
+            &mut diag.children,
             &diag.level,
             self.macro_backtrace,
         );
@@ -537,9 +536,9 @@ impl Emitter for HumanEmitter {
             &diag.messages,
             &fluent_args,
             &diag.code,
-            &primary_span,
-            &children,
-            suggestions,
+            &diag.span,
+            &diag.children,
+            &suggestions,
             self.track_diagnostics.then_some(&diag.emitted_at),
         );
     }
@@ -558,7 +557,19 @@ impl Emitter for HumanEmitter {
 /// failures of rustc, as witnessed e.g. in issue #89358.
 pub struct SilentEmitter {
     pub fatal_dcx: DiagCtxt,
-    pub fatal_note: Option<String>,
+    pub fatal_note: String,
+}
+
+pub fn silent_translate<'a>(
+    message: &'a DiagnosticMessage,
+) -> Result<Cow<'_, str>, TranslateError<'_>> {
+    match message {
+        DiagnosticMessage::Str(msg) | DiagnosticMessage::Translated(msg) => Ok(Cow::Borrowed(msg)),
+        DiagnosticMessage::FluentIdentifier(identifier, _) => {
+            // Any value works here.
+            Ok(identifier.clone())
+        }
+    }
 }
 
 impl Translate for SilentEmitter {
@@ -569,6 +580,16 @@ impl Translate for SilentEmitter {
     fn fallback_fluent_bundle(&self) -> &FluentBundle {
         panic!("silent emitter attempted to translate message")
     }
+
+    // Override `translate_message` for the silent emitter because eager translation of
+    // subdiagnostics result in a call to this.
+    fn translate_message<'a>(
+        &'a self,
+        message: &'a DiagnosticMessage,
+        _: &'a FluentArgs<'_>,
+    ) -> Result<Cow<'_, str>, TranslateError<'_>> {
+        silent_translate(message)
+    }
 }
 
 impl Emitter for SilentEmitter {
@@ -576,13 +597,10 @@ impl Emitter for SilentEmitter {
         None
     }
 
-    fn emit_diagnostic(&mut self, d: &Diagnostic) {
-        if d.level == Level::Fatal {
-            let mut d = d.clone();
-            if let Some(ref note) = self.fatal_note {
-                d.note(note.clone());
-            }
-            self.fatal_dcx.emit_diagnostic(d);
+    fn emit_diagnostic(&mut self, mut diag: Diagnostic) {
+        if diag.level == Level::Fatal {
+            diag.sub(Level::Note, self.fatal_note.clone(), MultiSpan::new());
+            self.fatal_dcx.emit_diagnostic(diag);
         }
     }
 }
@@ -1309,7 +1327,7 @@ impl HumanEmitter {
         msp: &MultiSpan,
         msgs: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
-        code: &Option<String>,
+        code: &Option<ErrCode>,
         level: &Level,
         max_line_num_len: usize,
         is_secondary: bool,
@@ -1340,9 +1358,9 @@ impl HumanEmitter {
                 buffer.append(0, "[", Style::Level(*level));
                 let code = if let TerminalUrl::Yes = self.terminal_url {
                     let path = "https://doc.rust-lang.org/error_codes";
-                    Cow::Owned(format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07"))
+                    format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
                 } else {
-                    Cow::Borrowed(code)
+                    code.to_string()
                 };
                 buffer.append(0, &code, Style::Level(*level));
                 buffer.append(0, "]", Style::Level(*level));
@@ -1640,7 +1658,8 @@ impl HumanEmitter {
                     let mut to_add = FxHashMap::default();
 
                     for (depth, style) in depths {
-                        if multilines.remove(&depth).is_none() {
+                        // FIXME(#120456) - is `swap_remove` correct?
+                        if multilines.swap_remove(&depth).is_none() {
                             to_add.insert(depth, style);
                         }
                     }
@@ -1747,9 +1766,17 @@ impl HumanEmitter {
         buffer.append(0, level.to_str(), Style::Level(*level));
         buffer.append(0, ": ", Style::HeaderMsg);
 
+        let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
+        if suggestions
+            .iter()
+            .take(MAX_SUGGESTIONS)
+            .any(|(_, _, _, only_capitalization)| *only_capitalization)
+        {
+            msg.push((" (notice the capitalization difference)".into(), Style::NoStyle));
+        }
         self.msgs_to_buffer(
             &mut buffer,
-            &[(suggestion.msg.to_owned(), Style::NoStyle)],
+            &msg,
             args,
             max_line_num_len,
             "suggestion",
@@ -1758,12 +1785,8 @@ impl HumanEmitter {
 
         let mut row_num = 2;
         draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
-        let mut notice_capitalization = false;
-        for (complete, parts, highlights, only_capitalization) in
-            suggestions.iter().take(MAX_SUGGESTIONS)
-        {
+        for (complete, parts, highlights, _) in suggestions.iter().take(MAX_SUGGESTIONS) {
             debug!(?complete, ?parts, ?highlights);
-            notice_capitalization |= only_capitalization;
 
             let has_deletion = parts.iter().any(|p| p.is_deletion(sm));
             let is_multiline = complete.lines().count() > 1;
@@ -2062,9 +2085,6 @@ impl HumanEmitter {
             let others = suggestions.len() - MAX_SUGGESTIONS;
             let msg = format!("and {} other candidate{}", others, pluralize!(others));
             buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
-        } else if notice_capitalization {
-            let msg = "notice the capitalization difference";
-            buffer.puts(row_num, max_line_num_len + 3, msg, Style::NoStyle);
         }
         emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
         Ok(())
@@ -2076,7 +2096,7 @@ impl HumanEmitter {
         level: &Level,
         messages: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
-        code: &Option<String>,
+        code: &Option<ErrCode>,
         span: &MultiSpan,
         children: &[SubDiagnostic],
         suggestions: &[CodeSuggestion],
@@ -2118,6 +2138,7 @@ impl HumanEmitter {
                 }
                 if !self.short_message {
                     for child in children {
+                        assert!(child.level.can_be_top_or_sub().1);
                         let span = &child.span;
                         if let Err(err) = self.emit_messages_default_inner(
                             span,
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index 51b064f4c61..470e3d52452 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -176,7 +176,7 @@ impl Translate for JsonEmitter {
 }
 
 impl Emitter for JsonEmitter {
-    fn emit_diagnostic(&mut self, diag: &crate::Diagnostic) {
+    fn emit_diagnostic(&mut self, diag: crate::Diagnostic) {
         let data = Diagnostic::from_errors_diagnostic(diag, self);
         let result = self.emit(EmitTyped::Diagnostic(data));
         if let Err(e) = result {
@@ -201,7 +201,7 @@ impl Emitter for JsonEmitter {
                 }
                 FutureBreakageItem {
                     diagnostic: EmitTyped::Diagnostic(Diagnostic::from_errors_diagnostic(
-                        &diag, self,
+                        diag, self,
                     )),
                 }
             })
@@ -340,7 +340,7 @@ struct UnusedExterns<'a, 'b, 'c> {
 }
 
 impl Diagnostic {
-    fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
+    fn from_errors_diagnostic(diag: crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
         let args = to_fluent_args(diag.args());
         let sugg = diag.suggestions.iter().flatten().map(|sugg| {
             let translated_message =
@@ -382,6 +382,28 @@ impl Diagnostic {
                 Ok(())
             }
         }
+
+        let translated_message = je.translate_messages(&diag.messages, &args);
+
+        let code = if let Some(code) = diag.code {
+            Some(DiagnosticCode {
+                code: code.to_string(),
+                explanation: je.registry.as_ref().unwrap().try_find_description(code).ok(),
+            })
+        } else if let Some(IsLint { name, .. }) = &diag.is_lint {
+            Some(DiagnosticCode { code: name.to_string(), explanation: None })
+        } else {
+            None
+        };
+        let level = diag.level.to_str();
+        let spans = DiagnosticSpan::from_multispan(&diag.span, &args, je);
+        let children = diag
+            .children
+            .iter()
+            .map(|c| Diagnostic::from_sub_diagnostic(c, &args, je))
+            .chain(sugg)
+            .collect();
+
         let buf = BufWriter::default();
         let output = buf.clone();
         je.json_rendered
@@ -398,30 +420,12 @@ 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.messages, &args);
-
-        let code = if let Some(code) = &diag.code {
-            Some(DiagnosticCode {
-                code: code.to_string(),
-                explanation: je.registry.as_ref().unwrap().try_find_description(&code).ok(),
-            })
-        } else if let Some(IsLint { name, .. }) = &diag.is_lint {
-            Some(DiagnosticCode { code: name.to_string(), explanation: None })
-        } else {
-            None
-        };
-
         Diagnostic {
             message: translated_message.to_string(),
             code,
-            level: diag.level.to_str(),
-            spans: DiagnosticSpan::from_multispan(&diag.span, &args, je),
-            children: diag
-                .children
-                .iter()
-                .map(|c| Diagnostic::from_sub_diagnostic(c, &args, je))
-                .chain(sugg)
-                .collect(),
+            level,
+            spans,
+            children,
             rendered: Some(output),
         }
     }
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 141547b537d..7e3d15ffc92 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -2,24 +2,30 @@
 //!
 //! This module contains the code for creating and emitting diagnostics.
 
+// tidy-alphabetical-start
+#![allow(incomplete_features)]
+#![allow(internal_features)]
+#![allow(rustc::diagnostic_outside_of_impl)]
+#![allow(rustc::untranslatable_diagnostic)]
+#![cfg_attr(bootstrap, feature(min_specialization))]
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
 #![doc(rust_logo)]
-#![feature(rustdoc_internals)]
 #![feature(array_windows)]
 #![feature(associated_type_defaults)]
 #![feature(box_into_inner)]
+#![feature(box_patterns)]
+#![feature(error_reporter)]
 #![feature(extract_if)]
-#![feature(if_let_guard)]
+#![feature(generic_nonzero)]
 #![feature(let_chains)]
 #![feature(negative_impls)]
 #![feature(never_type)]
 #![feature(rustc_attrs)]
-#![feature(yeet_expr)]
+#![feature(rustdoc_internals)]
+#![feature(trait_alias)]
 #![feature(try_blocks)]
-#![feature(box_patterns)]
-#![feature(error_reporter)]
-#![allow(incomplete_features)]
-#![allow(internal_features)]
+#![feature(yeet_expr)]
+// tidy-alphabetical-end
 
 #[macro_use]
 extern crate rustc_macros;
@@ -29,16 +35,15 @@ extern crate tracing;
 
 extern crate self as rustc_errors;
 
+pub use codes::*;
 pub use diagnostic::{
-    AddToDiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue,
-    DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic,
-};
-pub use diagnostic_builder::{
-    BugAbort, DiagnosticBuilder, EmissionGuarantee, FatalAbort, IntoDiagnostic,
+    AddToDiagnostic, BugAbort, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgName,
+    DiagnosticArgValue, DiagnosticBuilder, DiagnosticStyledString, EmissionGuarantee, FatalAbort,
+    IntoDiagnostic, IntoDiagnosticArg, StringPart, SubDiagnostic, SubdiagnosticMessageOp,
 };
 pub use diagnostic_impls::{
     DiagnosticArgFromDisplay, DiagnosticSymbolList, ExpectedLifetimeParameter,
-    IndicateAnonymousLifetime, InvalidFlushedDelayedDiagnosticLevel, SingleLabelManySpans,
+    IndicateAnonymousLifetime, SingleLabelManySpans,
 };
 pub use emitter::ColorConfig;
 pub use rustc_error_messages::{
@@ -54,7 +59,6 @@ pub use snippet::Style;
 // See https://github.com/rust-lang/rust/pull/115393.
 pub use termcolor::{Color, ColorSpec, WriteColor};
 
-use crate::diagnostic_impls::{DelayedAtWithNewline, DelayedAtWithoutNewline};
 use emitter::{is_case_difference, DynEmitter, Emitter, HumanEmitter};
 use registry::Registry;
 use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
@@ -70,15 +74,16 @@ use std::error::Report;
 use std::fmt;
 use std::hash::Hash;
 use std::io::Write;
-use std::num::NonZeroUsize;
+use std::num::NonZero;
+use std::ops::DerefMut;
 use std::panic;
 use std::path::{Path, PathBuf};
 
 use Level::*;
 
 pub mod annotate_snippet_emitter_writer;
+pub mod codes;
 mod diagnostic;
-mod diagnostic_builder;
 mod diagnostic_impls;
 pub mod emitter;
 pub mod error;
@@ -98,7 +103,6 @@ pub type PResult<'a, T> = Result<T, PErr<'a>>;
 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.)
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
 rustc_data_structures::static_assert_size!(PResult<'_, ()>, 16);
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
@@ -312,7 +316,9 @@ impl CodeSuggestion {
                 // We need to keep track of the difference between the existing code and the added
                 // or deleted code in order to point at the correct column *after* substitution.
                 let mut acc = 0;
+                let mut only_capitalization = false;
                 for part in &substitution.parts {
+                    only_capitalization |= is_case_difference(sm, &part.snippet, part.span);
                     let cur_lo = sm.lookup_char_pos(part.span.lo());
                     if prev_hi.line == cur_lo.line {
                         let mut count =
@@ -385,7 +391,6 @@ impl CodeSuggestion {
                     }
                 }
                 highlights.push(std::mem::take(&mut line_highlight));
-                let only_capitalization = is_case_difference(sm, &buf, bounding_span);
                 // if the replacement already ends with a newline, don't print the next line
                 if !buf.ends_with('\n') {
                     push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
@@ -404,8 +409,8 @@ impl CodeSuggestion {
 /// or `.span_bug` rather than a failed assertion, etc.
 pub struct ExplicitBug;
 
-/// Signifies that the compiler died with an explicit call to `.delay_*_bug`
-/// rather than a failed assertion, etc.
+/// Signifies that the compiler died due to a delayed bug rather than a failed
+/// assertion, etc.
 pub struct DelayedBugPanic;
 
 /// A `DiagCtxt` deals with errors and other compiler output.
@@ -421,34 +426,44 @@ pub struct DiagCtxt {
 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.
-    ///
-    /// This is not necessarily the count that's reported to the user once
-    /// compilation ends.
-    err_count: usize,
+    /// The error guarantees from all emitted errors. The length gives the error count.
+    err_guars: Vec<ErrorGuaranteed>,
+    /// The error guarantee from all emitted lint errors. The length gives the
+    /// lint error count.
+    lint_err_guars: Vec<ErrorGuaranteed>,
+    /// The delayed bugs and their error guarantees.
+    delayed_bugs: Vec<(DelayedDiagnostic, ErrorGuaranteed)>,
+
+    /// The number of stashed errors. Unlike the other counts, this can go up
+    /// and down, so it doesn't guarantee anything.
+    stashed_err_count: usize,
+
+    /// The error count shown to the user at the end.
     deduplicated_err_count: usize,
-    /// The warning count, used for a recap upon finishing
+    /// The warning count shown to the user at the end.
     deduplicated_warn_count: usize,
+
+    emitter: Box<DynEmitter>,
+
+    /// Must we produce a diagnostic to justify the use of the expensive
+    /// `trimmed_def_paths` function?
+    must_produce_diag: bool,
+
     /// Has this diagnostic context printed any diagnostics? (I.e. has
     /// `self.emitter.emit_diagnostic()` been called?
     has_printed: bool,
 
-    emitter: Box<DynEmitter>,
-    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 `good_path_delayed_bugs` check.
+    /// This is used for the `must_produce_diag` check.
     suppressed_expected_diag: bool,
 
     /// This set contains the code of all emitted diagnostics to avoid
     /// emitting the same diagnostic with extended help (`--teach`) twice, which
     /// would be unnecessary repetition.
-    taught_diagnostics: FxHashSet<String>,
+    taught_diagnostics: FxHashSet<ErrCode>,
 
     /// Used to suggest rustc --explain `<error code>`
-    emitted_diagnostic_codes: FxIndexSet<String>,
+    emitted_diagnostic_codes: FxIndexSet<ErrCode>,
 
     /// This set contains a hash of every diagnostic that has been emitted by
     /// this `DiagCtxt`. These hashes is used to avoid emitting the same error
@@ -456,9 +471,10 @@ struct DiagCtxtInner {
     emitted_diagnostics: FxHashSet<Hash128>,
 
     /// Stashed diagnostics emitted in one stage of the compiler that may be
-    /// stolen by other stages (e.g. to improve them and add more information).
-    /// The stashed diagnostics count towards the total error count.
-    /// When `.abort_if_errors()` is called, these are also emitted.
+    /// stolen and emitted/cancelled by other stages (e.g. to improve them and
+    /// add more information). All stashed diagnostics must be emitted with
+    /// `emit_stashed_diagnostics` by the time the `DiagCtxtInner` is dropped,
+    /// otherwise an assertion failure will occur.
     stashed_diagnostics: FxIndexMap<(Span, StashKey), Diagnostic>,
 
     future_breakage_diagnostics: Vec<Diagnostic>,
@@ -506,10 +522,12 @@ pub enum StashKey {
     MaybeFruTypo,
     CallAssocMethod,
     TraitMissingMethod,
+    AssociatedTypeSuggestion,
     OpaqueHiddenTypeMismatch,
     MaybeForgetReturn,
     /// Query cycle detected, stashing in favor of a better error.
     Cycle,
+    UndeterminedMacroResolution,
 }
 
 fn default_track_diagnostic(diag: Diagnostic, f: &mut dyn FnMut(Diagnostic)) {
@@ -519,12 +537,6 @@ fn default_track_diagnostic(diag: Diagnostic, f: &mut dyn FnMut(Diagnostic)) {
 pub static TRACK_DIAGNOSTIC: AtomicRef<fn(Diagnostic, &mut dyn FnMut(Diagnostic))> =
     AtomicRef::new(&(default_track_diagnostic as _));
 
-#[derive(Copy, PartialEq, Eq, Clone, Hash, Debug, Encodable, Decodable)]
-pub enum DelayedBugKind {
-    Normal,
-    GoodPath,
-}
-
 #[derive(Copy, Clone, Default)]
 pub struct DiagCtxtFlags {
     /// If false, warning-level lints are suppressed.
@@ -532,7 +544,7 @@ pub struct DiagCtxtFlags {
     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>,
+    pub treat_err_as_bug: Option<NonZero<usize>>,
     /// Eagerly emit delayed bugs as errors, so that the compiler debugger may
     /// see all of the errors being emitted at once.
     pub eagerly_emit_delayed_bugs: bool,
@@ -547,19 +559,21 @@ pub struct DiagCtxtFlags {
 
 impl Drop for DiagCtxtInner {
     fn drop(&mut self) {
-        self.emit_stashed_diagnostics();
+        // Any stashed diagnostics should have been handled by
+        // `emit_stashed_diagnostics` by now.
+        assert!(self.stashed_diagnostics.is_empty());
 
-        if !self.has_errors() {
-            self.flush_delayed(DelayedBugKind::Normal)
+        if self.err_guars.is_empty() {
+            self.flush_delayed()
         }
 
-        // 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_printed && !self.suppressed_expected_diag && !std::thread::panicking() {
-            self.flush_delayed(DelayedBugKind::GoodPath);
+            if self.must_produce_diag {
+                panic!(
+                    "must_produce_diag: trimmed_def_paths called but no diagnostics emitted; \
+                       use `DelayDm` for lints or `with_no_trimmed_paths` for debugging"
+                );
+            }
         }
 
         if self.check_unstable_expect_diagnostics {
@@ -598,14 +612,15 @@ impl DiagCtxt {
         Self {
             inner: Lock::new(DiagCtxtInner {
                 flags: DiagCtxtFlags { can_emit_warnings: true, ..Default::default() },
-                lint_err_count: 0,
-                err_count: 0,
+                err_guars: Vec::new(),
+                lint_err_guars: Vec::new(),
+                delayed_bugs: Vec::new(),
+                stashed_err_count: 0,
                 deduplicated_err_count: 0,
                 deduplicated_warn_count: 0,
-                has_printed: false,
                 emitter,
-                span_delayed_bugs: Vec::new(),
-                good_path_delayed_bugs: Vec::new(),
+                must_produce_diag: false,
+                has_printed: false,
                 suppressed_expected_diag: false,
                 taught_diagnostics: Default::default(),
                 emitted_diagnostic_codes: Default::default(),
@@ -624,24 +639,25 @@ impl DiagCtxt {
     pub fn eagerly_translate<'a>(
         &self,
         message: DiagnosticMessage,
-        args: impl Iterator<Item = DiagnosticArg<'a, 'static>>,
+        args: impl Iterator<Item = DiagnosticArg<'a>>,
     ) -> SubdiagnosticMessage {
-        SubdiagnosticMessage::Eager(Cow::from(self.eagerly_translate_to_string(message, args)))
+        let inner = self.inner.borrow();
+        inner.eagerly_translate(message, args)
     }
 
     /// Translate `message` eagerly with `args` to `String`.
     pub fn eagerly_translate_to_string<'a>(
         &self,
         message: DiagnosticMessage,
-        args: impl Iterator<Item = DiagnosticArg<'a, 'static>>,
+        args: impl Iterator<Item = DiagnosticArg<'a>>,
     ) -> String {
         let inner = self.inner.borrow();
-        let args = crate::translation::to_fluent_args(args);
-        inner.emitter.translate_message(&message, &args).map_err(Report::new).unwrap().to_string()
+        inner.eagerly_translate_to_string(message, args)
     }
 
     // This is here to not allow mutation of flags;
-    // as of this writing it's only used in tests in librustc_middle.
+    // as of this writing it's used in Session::consider_optimizing and
+    // in tests in rustc_interface.
     pub fn can_emit_warnings(&self) -> bool {
         self.inner.borrow_mut().flags.can_emit_warnings
     }
@@ -652,20 +668,51 @@ impl DiagCtxt {
     /// tools that want to reuse a `Parser` cleaning the previously emitted diagnostics as well as
     /// the overall count of emitted error diagnostics.
     pub fn reset_err_count(&self) {
+        // Use destructuring so that if a field gets added to `DiagCtxtInner`, it's impossible to
+        // fail to update this method as well.
         let mut inner = self.inner.borrow_mut();
-        inner.lint_err_count = 0;
-        inner.err_count = 0;
-        inner.deduplicated_err_count = 0;
-        inner.deduplicated_warn_count = 0;
-        inner.has_printed = false;
-
-        // actually free the underlying memory (which `clear` would not do)
-        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();
-        inner.stashed_diagnostics = Default::default();
+        let DiagCtxtInner {
+            flags: _,
+            err_guars,
+            lint_err_guars,
+            delayed_bugs,
+            stashed_err_count,
+            deduplicated_err_count,
+            deduplicated_warn_count,
+            emitter: _,
+            must_produce_diag,
+            has_printed,
+            suppressed_expected_diag,
+            taught_diagnostics,
+            emitted_diagnostic_codes,
+            emitted_diagnostics,
+            stashed_diagnostics,
+            future_breakage_diagnostics,
+            check_unstable_expect_diagnostics,
+            unstable_expect_diagnostics,
+            fulfilled_expectations,
+            ice_file: _,
+        } = inner.deref_mut();
+
+        // For the `Vec`s and `HashMap`s, we overwrite with an empty container to free the
+        // underlying memory (which `clear` would not do).
+        *err_guars = Default::default();
+        *lint_err_guars = Default::default();
+        *delayed_bugs = Default::default();
+        *stashed_err_count = 0;
+        *deduplicated_err_count = 0;
+        *deduplicated_warn_count = 0;
+        *must_produce_diag = false;
+        *has_printed = false;
+        *suppressed_expected_diag = false;
+        *taught_diagnostics = Default::default();
+        *emitted_diagnostic_codes = Default::default();
+        *emitted_diagnostics = Default::default();
+        *stashed_diagnostics = Default::default();
+        *future_breakage_diagnostics = Default::default();
+        *check_unstable_expect_diagnostics = false;
+        *unstable_expect_diagnostics = Default::default();
+        *fulfilled_expectations = Default::default();
     }
 
     /// Stash a given diagnostic with the given `Span` and [`StashKey`] as the key.
@@ -676,10 +723,8 @@ impl DiagCtxt {
         let key = (span.with_parent(None), key);
 
         if diag.is_error() {
-            if diag.is_lint.is_some() {
-                inner.lint_err_count += 1;
-            } else {
-                inner.err_count += 1;
+            if diag.is_lint.is_none() {
+                inner.stashed_err_count += 1;
             }
         }
 
@@ -693,12 +738,11 @@ impl DiagCtxt {
     pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option<DiagnosticBuilder<'_, ()>> {
         let mut inner = self.inner.borrow_mut();
         let key = (span.with_parent(None), key);
-        let diag = inner.stashed_diagnostics.remove(&key)?;
+        // FIXME(#120456) - is `swap_remove` correct?
+        let diag = inner.stashed_diagnostics.swap_remove(&key)?;
         if diag.is_error() {
-            if diag.is_lint.is_some() {
-                inner.lint_err_count -= 1;
-            } else {
-                inner.err_count -= 1;
+            if diag.is_lint.is_none() {
+                inner.stashed_err_count -= 1;
             }
         }
         Some(DiagnosticBuilder::new_diagnostic(self, diag))
@@ -713,265 +757,51 @@ impl DiagCtxt {
         self.inner.borrow_mut().emit_stashed_diagnostics()
     }
 
-    /// Construct a builder at the `Warning` level at the given `span` and with the `msg`.
-    ///
-    /// An `emit` call on the builder will only emit if `can_emit_warnings` is `true`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_span_warn(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, ()> {
-        self.struct_warn(msg).with_span(span)
-    }
-
-    /// Construct a builder at the `Warning` level with the `msg`.
-    ///
-    /// An `emit` call on the builder will only emit if `can_emit_warnings` is `true`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Warning, 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, Allow, msg)
-    }
-
-    /// Construct a builder at the `Expect` level with the `msg`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_expect(
-        &self,
-        msg: impl Into<DiagnosticMessage>,
-        id: LintExpectationId,
-    ) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Expect(id), msg)
-    }
-
-    /// Construct a builder at the `Error` level at the given `span` and with the `msg`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_span_err(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_> {
-        self.struct_err(msg).with_span(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<'_> {
-        DiagnosticBuilder::new(self, Error, msg)
-    }
-
-    /// Construct a builder at the `Fatal` level at the given `span` and with the `msg`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_span_fatal(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, FatalAbort> {
-        self.struct_fatal(msg).with_span(span)
-    }
-
-    /// Construct a builder at the `Fatal` level with the `msg`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    pub fn struct_fatal(
-        &self,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> 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, Help, msg)
-    }
-
-    /// Construct a builder at the `Note` level with the `msg`.
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    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 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 struct_span_bug(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, BugAbort> {
-        self.struct_bug(msg).with_span(span)
-    }
-
-    #[rustc_lint_diagnostics]
-    #[track_caller]
-    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(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> 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.struct_span_warn(span, msg).emit()
-    }
-
-    pub fn span_bug(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
-        self.struct_span_bug(span, msg).emit()
-    }
-
-    /// 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).
-    #[track_caller]
-    pub fn delayed_bug(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
-        DiagnosticBuilder::<ErrorGuaranteed>::new(self, DelayedBug(DelayedBugKind::Normal), msg)
-            .emit()
-    }
-
-    /// Like `delayed_bug`, but takes an additional span.
-    ///
-    /// 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 span_delayed_bug(
-        &self,
-        sp: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> ErrorGuaranteed {
-        DiagnosticBuilder::<ErrorGuaranteed>::new(self, DelayedBug(DelayedBugKind::Normal), msg)
-            .with_span(sp)
-            .emit()
-    }
-
-    // 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 good_path_delayed_bug(&self, msg: impl Into<DiagnosticMessage>) {
-        DiagnosticBuilder::<()>::new(self, DelayedBug(DelayedBugKind::GoodPath), msg).emit()
-    }
-
-    #[track_caller]
-    #[rustc_lint_diagnostics]
-    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 struct_span_note(
-        &self,
-        span: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-    ) -> DiagnosticBuilder<'_, ()> {
-        DiagnosticBuilder::new(self, Note, msg).with_span(span)
-    }
-
-    #[rustc_lint_diagnostics]
-    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.struct_err(msg).emit()
-    }
-
-    #[rustc_lint_diagnostics]
-    pub fn warn(&self, msg: impl Into<DiagnosticMessage>) {
-        self.struct_warn(msg).emit()
-    }
-
-    #[rustc_lint_diagnostics]
-    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.struct_bug(msg).emit()
+    /// This excludes lint errors, delayed bugs and stashed errors.
+    #[inline]
+    pub fn err_count_excluding_lint_errs(&self) -> usize {
+        self.inner.borrow().err_guars.len()
     }
 
+    /// This excludes delayed bugs and stashed errors.
     #[inline]
     pub fn err_count(&self) -> usize {
-        self.inner.borrow().err_count
+        let inner = self.inner.borrow();
+        inner.err_guars.len() + inner.lint_err_guars.len()
     }
 
-    pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
-        self.inner.borrow().has_errors().then(|| {
-            #[allow(deprecated)]
-            ErrorGuaranteed::unchecked_claim_error_was_emitted()
-        })
+    /// This excludes normal errors, lint errors, and delayed bugs. Unless
+    /// absolutely necessary, avoid using this. It's dubious because stashed
+    /// errors can later be cancelled, so the presence of a stashed error at
+    /// some point of time doesn't guarantee anything -- there are no
+    /// `ErrorGuaranteed`s here.
+    pub fn stashed_err_count(&self) -> usize {
+        self.inner.borrow().stashed_err_count
     }
 
-    pub fn has_errors_or_lint_errors(&self) -> Option<ErrorGuaranteed> {
-        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()
-        })
+    /// This excludes lint errors, delayed bugs, and stashed errors. Unless
+    /// absolutely necessary, prefer `has_errors` to this method.
+    pub fn has_errors_excluding_lint_errors(&self) -> Option<ErrorGuaranteed> {
+        self.inner.borrow().has_errors_excluding_lint_errors()
     }
 
-    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()
-        })
+    /// This excludes delayed bugs and stashed errors.
+    pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
+        self.inner.borrow().has_errors()
     }
 
-    pub fn is_compilation_going_to_fail(&self) -> Option<ErrorGuaranteed> {
-        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()
-        })
+    /// This excludes stashed errors. Unless absolutely necessary, prefer
+    /// `has_errors` to this method.
+    pub fn has_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
+        self.inner.borrow().has_errors_or_delayed_bugs()
     }
 
     pub fn print_error_count(&self, registry: &Registry) {
         let mut inner = self.inner.borrow_mut();
 
-        inner.emit_stashed_diagnostics();
+        // Any stashed diagnostics should have been handled by
+        // `emit_stashed_diagnostics` by now.
+        assert!(inner.stashed_diagnostics.is_empty());
 
         if inner.treat_err_as_bug() {
             return;
@@ -990,14 +820,19 @@ impl DiagCtxt {
 
         match (errors.len(), warnings.len()) {
             (0, 0) => return,
-            (0, _) => inner
-                .emitter
-                .emit_diagnostic(&Diagnostic::new(Warning, DiagnosticMessage::Str(warnings))),
+            (0, _) => {
+                // Use `ForceWarning` rather than `Warning` to guarantee emission, e.g. with a
+                // configuration like `--cap-lints allow --force-warn bare_trait_objects`.
+                inner.emit_diagnostic(Diagnostic::new(
+                    ForceWarning(None),
+                    DiagnosticMessage::Str(warnings),
+                ));
+            }
             (_, 0) => {
-                inner.emit_diagnostic(Diagnostic::new(Fatal, errors));
+                inner.emit_diagnostic(Diagnostic::new(Error, errors));
             }
             (_, _) => {
-                inner.emit_diagnostic(Diagnostic::new(Fatal, format!("{errors}; {warnings}")));
+                inner.emit_diagnostic(Diagnostic::new(Error, format!("{errors}; {warnings}")));
             }
         }
 
@@ -1007,9 +842,9 @@ impl DiagCtxt {
             let mut error_codes = inner
                 .emitted_diagnostic_codes
                 .iter()
-                .filter_map(|code| {
+                .filter_map(|&code| {
                     if registry.try_find_description(code).is_ok().clone() {
-                        Some(code.clone())
+                        Some(code.to_string())
                     } else {
                         None
                     }
@@ -1019,33 +854,34 @@ impl DiagCtxt {
                 error_codes.sort();
                 if error_codes.len() > 1 {
                     let limit = if error_codes.len() > 9 { 9 } else { error_codes.len() };
-                    inner.failure_note(format!(
+                    let msg1 = format!(
                         "Some errors have detailed explanations: {}{}",
                         error_codes[..limit].join(", "),
                         if error_codes.len() > 9 { "..." } else { "." }
-                    ));
-                    inner.failure_note(format!(
+                    );
+                    let msg2 = format!(
                         "For more information about an error, try `rustc --explain {}`.",
                         &error_codes[0]
-                    ));
+                    );
+                    inner.emit_diagnostic(Diagnostic::new(FailureNote, msg1));
+                    inner.emit_diagnostic(Diagnostic::new(FailureNote, msg2));
                 } else {
-                    inner.failure_note(format!(
+                    let msg = format!(
                         "For more information about this error, try `rustc --explain {}`.",
                         &error_codes[0]
-                    ));
+                    );
+                    inner.emit_diagnostic(Diagnostic::new(FailureNote, msg));
                 }
             }
         }
     }
 
-    pub fn take_future_breakage_diagnostics(&self) -> Vec<Diagnostic> {
-        std::mem::take(&mut self.inner.borrow_mut().future_breakage_diagnostics)
-    }
-
+    /// This excludes delayed bugs and stashed errors. Used for early aborts
+    /// after errors occurred -- e.g. because continuing in the face of errors is
+    /// likely to lead to bad results, such as spurious/uninteresting
+    /// additional errors -- when returning an error `Result` is difficult.
     pub fn abort_if_errors(&self) {
-        let mut inner = self.inner.borrow_mut();
-        inner.emit_stashed_diagnostics();
-        if inner.has_errors() {
+        if self.has_errors().is_some() {
             FatalError.raise();
         }
     }
@@ -1055,39 +891,195 @@ impl DiagCtxt {
     ///
     /// Used to suppress emitting the same error multiple times with extended explanation when
     /// calling `-Zteach`.
-    pub fn must_teach(&self, code: &str) -> bool {
-        self.inner.borrow_mut().taught_diagnostics.insert(code.to_string())
-    }
-
-    pub fn force_print_diagnostic(&self, db: Diagnostic) {
-        self.inner.borrow_mut().emitter.emit_diagnostic(&db);
+    pub fn must_teach(&self, code: ErrCode) -> bool {
+        self.inner.borrow_mut().taught_diagnostics.insert(code)
     }
 
     pub fn emit_diagnostic(&self, diagnostic: Diagnostic) -> Option<ErrorGuaranteed> {
         self.inner.borrow_mut().emit_diagnostic(diagnostic)
     }
 
+    pub fn emit_artifact_notification(&self, path: &Path, artifact_type: &str) {
+        self.inner.borrow_mut().emitter.emit_artifact_notification(path, artifact_type);
+    }
+
+    pub fn emit_future_breakage_report(&self) {
+        let mut inner = self.inner.borrow_mut();
+        let diags = std::mem::take(&mut inner.future_breakage_diagnostics);
+        if !diags.is_empty() {
+            inner.emitter.emit_future_breakage_report(diags);
+        }
+    }
+
+    pub fn emit_unused_externs(
+        &self,
+        lint_level: rustc_lint_defs::Level,
+        loud: bool,
+        unused_externs: &[&str],
+    ) {
+        let mut inner = self.inner.borrow_mut();
+
+        // This "error" is an odd duck.
+        // - It's only produce with JSON output.
+        // - It's not emitted the usual way, via `emit_diagnostic`.
+        // - The `$message_type` field is "unused_externs" rather than the usual
+        //   "diagnosic".
+        //
+        // We count it as a lint error because it has a lint level. The value
+        // of `loud` (which comes from "unused-externs" or
+        // "unused-externs-silent"), also affects whether it's treated like a
+        // hard error or not.
+        if loud && lint_level.is_error() {
+            // This `unchecked_error_guaranteed` is valid. It is where the
+            // `ErrorGuaranteed` for unused_extern errors originates.
+            #[allow(deprecated)]
+            inner.lint_err_guars.push(ErrorGuaranteed::unchecked_error_guaranteed());
+            inner.panic_if_treat_err_as_bug();
+        }
+
+        inner.emitter.emit_unused_externs(lint_level, unused_externs)
+    }
+
+    pub fn update_unstable_expectation_id(
+        &self,
+        unstable_to_stable: &FxIndexMap<LintExpectationId, LintExpectationId>,
+    ) {
+        let mut inner = self.inner.borrow_mut();
+        let diags = std::mem::take(&mut inner.unstable_expect_diagnostics);
+        inner.check_unstable_expect_diagnostics = true;
+
+        if !diags.is_empty() {
+            inner.suppressed_expected_diag = true;
+            for mut diag in diags.into_iter() {
+                diag.update_unstable_expectation_id(unstable_to_stable);
+
+                // 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(diag);
+            }
+        }
+
+        inner
+            .stashed_diagnostics
+            .values_mut()
+            .for_each(|diag| diag.update_unstable_expectation_id(unstable_to_stable));
+        inner
+            .future_breakage_diagnostics
+            .iter_mut()
+            .for_each(|diag| diag.update_unstable_expectation_id(unstable_to_stable));
+    }
+
+    /// This methods steals all [`LintExpectationId`]s that are stored inside
+    /// [`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(),
+            "`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) {
+        self.inner.borrow_mut().flush_delayed();
+    }
+
+    /// Used when trimmed_def_paths is called and we must produce a diagnostic
+    /// to justify its cost.
+    pub fn set_must_produce_diag(&self) {
+        self.inner.borrow_mut().must_produce_diag = true;
+    }
+}
+
+// This `impl` block contains only the public diagnostic creation/emission API.
+//
+// Functions beginning with `struct_`/`create_` create a diagnostic. Other
+// functions create and emit a diagnostic all in one go.
+impl DiagCtxt {
+    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
     #[track_caller]
-    pub fn emit_err<'a>(&'a self, err: impl IntoDiagnostic<'a>) -> ErrorGuaranteed {
-        self.create_err(err).emit()
+    pub fn struct_bug(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, BugAbort> {
+        DiagnosticBuilder::new(self, Bug, msg)
     }
 
+    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
     #[track_caller]
-    pub fn create_err<'a>(&'a self, err: impl IntoDiagnostic<'a>) -> DiagnosticBuilder<'a> {
-        err.into_diagnostic(self, Error)
+    pub fn bug(&self, msg: impl Into<DiagnosticMessage>) -> ! {
+        self.struct_bug(msg).emit()
     }
 
+    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
     #[track_caller]
-    pub fn create_warn<'a>(
+    pub fn struct_span_bug(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, BugAbort> {
+        self.struct_bug(msg).with_span(span)
+    }
+
+    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    #[track_caller]
+    pub fn span_bug(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
+        self.struct_span_bug(span, msg).emit()
+    }
+
+    #[track_caller]
+    pub fn create_bug<'a>(
         &'a self,
-        warning: impl IntoDiagnostic<'a, ()>,
-    ) -> DiagnosticBuilder<'a, ()> {
-        warning.into_diagnostic(self, Warning)
+        bug: impl IntoDiagnostic<'a, BugAbort>,
+    ) -> DiagnosticBuilder<'a, BugAbort> {
+        bug.into_diagnostic(self, Bug)
     }
 
     #[track_caller]
-    pub fn emit_warn<'a>(&'a self, warning: impl IntoDiagnostic<'a, ()>) {
-        self.create_warn(warning).emit()
+    pub fn emit_bug<'a>(&'a self, bug: impl IntoDiagnostic<'a, BugAbort>) -> ! {
+        self.create_bug(bug).emit()
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_fatal(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, FatalAbort> {
+        DiagnosticBuilder::new(self, Fatal, msg)
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> ! {
+        self.struct_fatal(msg).emit()
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_span_fatal(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, FatalAbort> {
+        self.struct_fatal(msg).with_span(span)
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn span_fatal(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
+        self.struct_span_fatal(span, msg).emit()
+    }
+
+    #[track_caller]
+    pub fn create_fatal<'a>(
+        &'a self,
+        fatal: impl IntoDiagnostic<'a, FatalAbort>,
+    ) -> DiagnosticBuilder<'a, FatalAbort> {
+        fatal.into_diagnostic(self, Fatal)
+    }
+
+    #[track_caller]
+    pub fn emit_fatal<'a>(&'a self, fatal: impl IntoDiagnostic<'a, FatalAbort>) -> ! {
+        self.create_fatal(fatal).emit()
     }
 
     #[track_caller]
@@ -1106,112 +1098,181 @@ impl DiagCtxt {
         self.create_almost_fatal(fatal).emit()
     }
 
+    // FIXME: This method should be removed (every error should have an associated error code).
+    #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn create_fatal<'a>(
-        &'a self,
-        fatal: impl IntoDiagnostic<'a, FatalAbort>,
-    ) -> DiagnosticBuilder<'a, FatalAbort> {
-        fatal.into_diagnostic(self, Fatal)
+    pub fn struct_err(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_> {
+        DiagnosticBuilder::new(self, Error, msg)
     }
 
+    #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn emit_fatal<'a>(&'a self, fatal: impl IntoDiagnostic<'a, FatalAbort>) -> ! {
-        self.create_fatal(fatal).emit()
+    pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
+        self.struct_err(msg).emit()
     }
 
+    #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn create_bug<'a>(
-        &'a self,
-        bug: impl IntoDiagnostic<'a, BugAbort>,
-    ) -> DiagnosticBuilder<'a, BugAbort> {
-        bug.into_diagnostic(self, Bug)
+    pub fn struct_span_err(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_> {
+        self.struct_err(msg).with_span(span)
     }
 
+    #[rustc_lint_diagnostics]
     #[track_caller]
-    pub fn emit_bug<'a>(&'a self, bug: impl IntoDiagnostic<'a, diagnostic_builder::BugAbort>) -> ! {
-        self.create_bug(bug).emit()
+    pub fn span_err(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> ErrorGuaranteed {
+        self.struct_span_err(span, msg).emit()
     }
 
     #[track_caller]
-    pub fn emit_note<'a>(&'a self, note: impl IntoDiagnostic<'a, ()>) {
-        self.create_note(note).emit()
+    pub fn create_err<'a>(&'a self, err: impl IntoDiagnostic<'a>) -> DiagnosticBuilder<'a> {
+        err.into_diagnostic(self, Error)
     }
 
     #[track_caller]
-    pub fn create_note<'a>(
-        &'a self,
-        note: impl IntoDiagnostic<'a, ()>,
-    ) -> DiagnosticBuilder<'a, ()> {
-        note.into_diagnostic(self, Note)
+    pub fn emit_err<'a>(&'a self, err: impl IntoDiagnostic<'a>) -> ErrorGuaranteed {
+        self.create_err(err).emit()
     }
 
-    pub fn emit_artifact_notification(&self, path: &Path, artifact_type: &str) {
-        self.inner.borrow_mut().emitter.emit_artifact_notification(path, artifact_type);
+    /// Ensures that an error is printed. See `Level::DelayedBug`.
+    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    #[track_caller]
+    pub fn delayed_bug(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
+        DiagnosticBuilder::<ErrorGuaranteed>::new(self, DelayedBug, msg).emit()
     }
 
-    pub fn emit_future_breakage_report(&self, diags: Vec<Diagnostic>) {
-        self.inner.borrow_mut().emitter.emit_future_breakage_report(diags)
+    /// Ensures that an error is printed. See `Level::DelayedBug`.
+    ///
+    /// Note: this function used to be called `delay_span_bug`. It was renamed
+    /// to match similar functions like `span_err`, `span_warn`, etc.
+    // No `#[rustc_lint_diagnostics]` because bug messages aren't user-facing.
+    #[track_caller]
+    pub fn span_delayed_bug(
+        &self,
+        sp: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> ErrorGuaranteed {
+        DiagnosticBuilder::<ErrorGuaranteed>::new(self, DelayedBug, msg).with_span(sp).emit()
     }
 
-    pub fn emit_unused_externs(
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
+        DiagnosticBuilder::new(self, Warning, msg)
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn warn(&self, msg: impl Into<DiagnosticMessage>) {
+        self.struct_warn(msg).emit()
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_span_warn(
         &self,
-        lint_level: rustc_lint_defs::Level,
-        loud: bool,
-        unused_externs: &[&str],
-    ) {
-        let mut inner = self.inner.borrow_mut();
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, ()> {
+        self.struct_warn(msg).with_span(span)
+    }
 
-        if loud && lint_level.is_error() {
-            inner.err_count += 1;
-            inner.panic_if_treat_err_as_bug();
-        }
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn span_warn(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
+        self.struct_span_warn(span, msg).emit()
+    }
 
-        inner.emitter.emit_unused_externs(lint_level, unused_externs)
+    #[track_caller]
+    pub fn create_warn<'a>(
+        &'a self,
+        warning: impl IntoDiagnostic<'a, ()>,
+    ) -> DiagnosticBuilder<'a, ()> {
+        warning.into_diagnostic(self, Warning)
     }
 
-    pub fn update_unstable_expectation_id(
+    #[track_caller]
+    pub fn emit_warn<'a>(&'a self, warning: impl IntoDiagnostic<'a, ()>) {
+        self.create_warn(warning).emit()
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_note(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
+        DiagnosticBuilder::new(self, Note, msg)
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn note(&self, msg: impl Into<DiagnosticMessage>) {
+        self.struct_note(msg).emit()
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_span_note(
         &self,
-        unstable_to_stable: &FxIndexMap<LintExpectationId, LintExpectationId>,
-    ) {
-        let mut inner = self.inner.borrow_mut();
-        let diags = std::mem::take(&mut inner.unstable_expect_diagnostics);
-        inner.check_unstable_expect_diagnostics = true;
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, ()> {
+        self.struct_note(msg).with_span(span)
+    }
 
-        if !diags.is_empty() {
-            inner.suppressed_expected_diag = true;
-            for mut diag in diags.into_iter() {
-                diag.update_unstable_expectation_id(unstable_to_stable);
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn span_note(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
+        self.struct_span_note(span, msg).emit()
+    }
 
-                // 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(diag);
-            }
-        }
+    #[track_caller]
+    pub fn create_note<'a>(
+        &'a self,
+        note: impl IntoDiagnostic<'a, ()>,
+    ) -> DiagnosticBuilder<'a, ()> {
+        note.into_diagnostic(self, Note)
+    }
 
-        inner
-            .stashed_diagnostics
-            .values_mut()
-            .for_each(|diag| diag.update_unstable_expectation_id(unstable_to_stable));
-        inner
-            .future_breakage_diagnostics
-            .iter_mut()
-            .for_each(|diag| diag.update_unstable_expectation_id(unstable_to_stable));
+    #[track_caller]
+    pub fn emit_note<'a>(&'a self, note: impl IntoDiagnostic<'a, ()>) {
+        self.create_note(note).emit()
     }
 
-    /// This methods steals all [`LintExpectationId`]s that are stored inside
-    /// [`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(),
-            "`DiagCtxtInner::unstable_expect_diagnostics` should be empty at this point",
-        );
-        std::mem::take(&mut self.inner.borrow_mut().fulfilled_expectations)
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_help(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
+        DiagnosticBuilder::new(self, Help, msg)
     }
 
-    pub fn flush_delayed(&self) {
-        self.inner.borrow_mut().flush_delayed(DelayedBugKind::Normal);
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_failure_note(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, ()> {
+        DiagnosticBuilder::new(self, FailureNote, msg)
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_allow(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
+        DiagnosticBuilder::new(self, Allow, msg)
+    }
+
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub fn struct_expect(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+        id: LintExpectationId,
+    ) -> DiagnosticBuilder<'_, ()> {
+        DiagnosticBuilder::new(self, Expect(id), msg)
     }
 }
 
@@ -1222,16 +1283,12 @@ impl DiagCtxt {
 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 diag in diags {
-            // Decrement the count tracking the stash; emitting will increment it.
+        let mut guar = None;
+        let has_errors = !self.err_guars.is_empty();
+        for (_, diag) in std::mem::take(&mut self.stashed_diagnostics).into_iter() {
             if diag.is_error() {
-                if diag.is_lint.is_some() {
-                    self.lint_err_count -= 1;
-                } else {
-                    self.err_count -= 1;
+                if diag.is_lint.is_none() {
+                    self.stashed_err_count -= 1;
                 }
             } else {
                 // Unless they're forced, don't flush stashed warnings when
@@ -1241,83 +1298,82 @@ impl DiagCtxtInner {
                     continue;
                 }
             }
-            let reported_this = self.emit_diagnostic(diag);
-            reported = reported.or(reported_this);
+            guar = guar.or(self.emit_diagnostic(diag));
         }
-        reported
+        guar
     }
 
+    // Return value is only `Some` if the level is `Error` or `DelayedBug`.
     fn emit_diagnostic(&mut self, mut diagnostic: Diagnostic) -> Option<ErrorGuaranteed> {
-        // 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
-        // a stable one by the `LintLevelsBuilder`.
-        if let Some(LintExpectationId::Unstable { .. }) = diagnostic.level.get_expectation_id() {
-            self.unstable_expect_diagnostics.push(diagnostic.clone());
-            return None;
-        }
-
-        // FIXME(eddyb) this should check for `has_errors` and stop pushing
-        // 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.
-        match diagnostic.level {
-            DelayedBug(_) if self.flags.eagerly_emit_delayed_bugs => {
-                diagnostic.level = Error;
-            }
-            DelayedBug(DelayedBugKind::Normal) => {
-                let backtrace = std::backtrace::Backtrace::capture();
-                self.span_delayed_bugs
-                    .push(DelayedDiagnostic::with_backtrace(diagnostic.clone(), backtrace));
-
-                #[allow(deprecated)]
-                return Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
-            }
-            DelayedBug(DelayedBugKind::GoodPath) => {
-                let backtrace = std::backtrace::Backtrace::capture();
-                self.good_path_delayed_bugs
-                    .push(DelayedDiagnostic::with_backtrace(diagnostic.clone(), backtrace));
+        assert!(diagnostic.level.can_be_top_or_sub().0);
 
+        if let Some(expectation_id) = diagnostic.level.get_expectation_id() {
+            // 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
+            // a stable one by the `LintLevelsBuilder`.
+            if let LintExpectationId::Unstable { .. } = expectation_id {
+                self.unstable_expect_diagnostics.push(diagnostic);
                 return None;
             }
-            _ => {}
-        }
-
-        // This must come after the possible promotion of `DelayedBug` to
-        // `Error` above.
-        if matches!(diagnostic.level, Error | Fatal) && self.treat_next_err_as_bug() {
-            diagnostic.level = Bug;
+            self.suppressed_expected_diag = true;
+            self.fulfilled_expectations.insert(expectation_id.normalize());
         }
 
         if diagnostic.has_future_breakage() {
             // 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.
+            // so they'll trigger the must_produce_diag check.
             self.suppressed_expected_diag = true;
             self.future_breakage_diagnostics.push(diagnostic.clone());
         }
 
-        if let Some(expectation_id) = diagnostic.level.get_expectation_id() {
-            self.suppressed_expected_diag = true;
-            self.fulfilled_expectations.insert(expectation_id.normalize());
+        // Note that because this comes before the `match` below,
+        // `-Zeagerly-emit-delayed-bugs` continues to work even after we've
+        // issued an error and stopped recording new delayed bugs.
+        if diagnostic.level == DelayedBug && self.flags.eagerly_emit_delayed_bugs {
+            diagnostic.level = Error;
         }
 
-        if diagnostic.level == Warning && !self.flags.can_emit_warnings {
-            if diagnostic.has_future_breakage() {
+        match diagnostic.level {
+            // This must come after the possible promotion of `DelayedBug` to
+            // `Error` above.
+            Fatal | Error if self.treat_next_err_as_bug() => {
+                diagnostic.level = Bug;
+            }
+            DelayedBug => {
+                // If we have already emitted at least one error, we don't need
+                // to record the delayed bug, because it'll never be used.
+                return if let Some(guar) = self.has_errors() {
+                    Some(guar)
+                } else {
+                    let backtrace = std::backtrace::Backtrace::capture();
+                    // This `unchecked_error_guaranteed` is valid. It is where the
+                    // `ErrorGuaranteed` for delayed bugs originates.
+                    #[allow(deprecated)]
+                    let guar = ErrorGuaranteed::unchecked_error_guaranteed();
+                    self.delayed_bugs
+                        .push((DelayedDiagnostic::with_backtrace(diagnostic, backtrace), guar));
+                    Some(guar)
+                };
+            }
+            Warning if !self.flags.can_emit_warnings => {
+                if diagnostic.has_future_breakage() {
+                    (*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {});
+                }
+                return None;
+            }
+            Allow | Expect(_) => {
                 (*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {});
+                return None;
             }
-            return None;
-        }
-
-        if matches!(diagnostic.level, Expect(_) | Allow) {
-            (*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {});
-            return None;
+            _ => {}
         }
 
         let mut guaranteed = None;
         (*TRACK_DIAGNOSTIC)(diagnostic, &mut |mut diagnostic| {
-            if let Some(ref code) = diagnostic.code {
-                self.emitted_diagnostic_codes.insert(code.clone());
+            if let Some(code) = diagnostic.code {
+                self.emitted_diagnostic_codes.insert(code);
             }
 
             let already_emitted = {
@@ -1327,11 +1383,15 @@ impl DiagCtxtInner {
                 !self.emitted_diagnostics.insert(diagnostic_hash)
             };
 
+            let is_error = diagnostic.is_error();
+            let is_lint = diagnostic.is_lint.is_some();
+
             // Only emit the diagnostic if we've been asked to deduplicate or
             // haven't already emitted an equivalent diagnostic.
             if !(self.flags.deduplicate_diagnostics && already_emitted) {
                 debug!(?diagnostic);
                 debug!(?self.emitted_diagnostics);
+
                 let already_emitted_sub = |sub: &mut SubDiagnostic| {
                     debug!(?sub);
                     if sub.level != OnceNote && sub.level != OnceHelp {
@@ -1343,34 +1403,44 @@ impl DiagCtxtInner {
                     debug!(?diagnostic_hash);
                     !self.emitted_diagnostics.insert(diagnostic_hash)
                 };
-
                 diagnostic.children.extract_if(already_emitted_sub).for_each(|_| {});
                 if already_emitted {
-                    diagnostic.note(
-                        "duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`",
-                    );
+                    let msg = "duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`";
+                    diagnostic.sub(Level::Note, msg, MultiSpan::new());
                 }
 
-                self.emitter.emit_diagnostic(&diagnostic);
-                if diagnostic.is_error() {
+                if is_error {
                     self.deduplicated_err_count += 1;
                 } else if matches!(diagnostic.level, ForceWarning(_) | Warning) {
                     self.deduplicated_warn_count += 1;
                 }
                 self.has_printed = true;
+
+                self.emitter.emit_diagnostic(diagnostic);
             }
-            if diagnostic.is_error() {
-                if diagnostic.is_lint.is_some() {
-                    self.lint_err_count += 1;
-                } else {
-                    self.err_count += 1;
+
+            if is_error {
+                // If we have any delayed bugs recorded, we can discard them
+                // because they won't be used. (This should only occur if there
+                // have been no errors previously emitted, because we don't add
+                // new delayed bugs once the first error is emitted.)
+                if !self.delayed_bugs.is_empty() {
+                    assert_eq!(self.lint_err_guars.len() + self.err_guars.len(), 0);
+                    self.delayed_bugs.clear();
+                    self.delayed_bugs.shrink_to_fit();
                 }
-                self.panic_if_treat_err_as_bug();
 
+                // This `unchecked_error_guaranteed` is valid. It is where the
+                // `ErrorGuaranteed` for errors and lint errors originates.
                 #[allow(deprecated)]
-                {
-                    guaranteed = Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
+                let guar = ErrorGuaranteed::unchecked_error_guaranteed();
+                guaranteed = Some(guar);
+                if is_lint {
+                    self.lint_err_guars.push(guar);
+                } else {
+                    self.err_guars.push(guar);
                 }
+                self.panic_if_treat_err_as_bug();
             }
         });
 
@@ -1378,41 +1448,72 @@ impl DiagCtxtInner {
     }
 
     fn treat_err_as_bug(&self) -> bool {
-        self.flags.treat_err_as_bug.is_some_and(|c| self.err_count + self.lint_err_count >= c.get())
+        self.flags
+            .treat_err_as_bug
+            .is_some_and(|c| self.err_guars.len() + self.lint_err_guars.len() >= c.get())
     }
 
     // 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 + 1 >= c.get())
+            .is_some_and(|c| self.err_guars.len() + self.lint_err_guars.len() + 1 >= c.get())
     }
 
-    fn has_errors(&self) -> bool {
-        self.err_count > 0
+    fn has_errors_excluding_lint_errors(&self) -> Option<ErrorGuaranteed> {
+        self.err_guars.get(0).copied()
     }
 
-    fn failure_note(&mut self, msg: impl Into<DiagnosticMessage>) {
-        self.emit_diagnostic(Diagnostic::new(FailureNote, msg));
+    fn has_errors(&self) -> Option<ErrorGuaranteed> {
+        self.has_errors_excluding_lint_errors().or_else(|| self.lint_err_guars.get(0).copied())
     }
 
-    fn flush_delayed(&mut self, kind: DelayedBugKind) {
-        let (bugs, note1) = match kind {
-            DelayedBugKind::Normal => (
-                std::mem::take(&mut self.span_delayed_bugs),
-                "no errors encountered even though `span_delayed_bug` issued",
-            ),
-            DelayedBugKind::GoodPath => (
-                std::mem::take(&mut self.good_path_delayed_bugs),
-                "no warnings or errors encountered even though `good_path_delayed_bugs` issued",
-            ),
-        };
-        let note2 = "those delayed bugs will now be shown as internal compiler errors";
+    fn has_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
+        self.has_errors().or_else(|| self.delayed_bugs.get(0).map(|(_, guar)| guar).copied())
+    }
+
+    /// Translate `message` eagerly with `args` to `SubdiagnosticMessage::Eager`.
+    pub fn eagerly_translate<'a>(
+        &self,
+        message: DiagnosticMessage,
+        args: impl Iterator<Item = DiagnosticArg<'a>>,
+    ) -> SubdiagnosticMessage {
+        SubdiagnosticMessage::Translated(Cow::from(self.eagerly_translate_to_string(message, args)))
+    }
+
+    /// Translate `message` eagerly with `args` to `String`.
+    pub fn eagerly_translate_to_string<'a>(
+        &self,
+        message: DiagnosticMessage,
+        args: impl Iterator<Item = DiagnosticArg<'a>>,
+    ) -> String {
+        let args = crate::translation::to_fluent_args(args);
+        self.emitter.translate_message(&message, &args).map_err(Report::new).unwrap().to_string()
+    }
+
+    fn eagerly_translate_for_subdiag(
+        &self,
+        diag: &Diagnostic,
+        msg: impl Into<SubdiagnosticMessage>,
+    ) -> SubdiagnosticMessage {
+        let args = diag.args();
+        let msg = diag.subdiagnostic_message_to_diagnostic_message(msg);
+        self.eagerly_translate(msg, args)
+    }
 
-        if bugs.is_empty() {
+    fn flush_delayed(&mut self) {
+        // Stashed diagnostics must be emitted before delayed bugs are flushed.
+        // Otherwise, we might ICE prematurely when errors would have
+        // eventually happened.
+        assert!(self.stashed_diagnostics.is_empty());
+
+        if self.delayed_bugs.is_empty() {
             return;
         }
 
+        let bugs: Vec<_> =
+            std::mem::take(&mut self.delayed_bugs).into_iter().map(|(b, _)| b).collect();
+
         // If backtraces are enabled, also print the query stack
         let backtrace = std::env::var_os("RUST_BACKTRACE").map_or(true, |x| &x != "0");
         for (i, bug) in bugs.into_iter().enumerate() {
@@ -1421,9 +1522,9 @@ impl DiagCtxtInner {
             {
                 let _ = write!(
                     &mut out,
-                    "delayed span bug: {}\n{}\n",
+                    "delayed bug: {}\n{}\n",
                     bug.inner
-                        .messages()
+                        .messages
                         .iter()
                         .filter_map(|(msg, _)| msg.as_str())
                         .collect::<String>(),
@@ -1436,21 +1537,27 @@ impl DiagCtxtInner {
                 // frame them better (e.g. separate warnings from them). Also,
                 // make it a note so it doesn't count as an error, because that
                 // could trigger `-Ztreat-err-as-bug`, which we don't want.
+                let note1 = "no errors encountered even though delayed bugs were created";
+                let note2 = "those delayed bugs will now be shown as internal compiler errors";
                 self.emit_diagnostic(Diagnostic::new(Note, note1));
                 self.emit_diagnostic(Diagnostic::new(Note, note2));
             }
 
             let mut bug =
-                if backtrace || self.ice_file.is_none() { bug.decorate() } else { bug.inner };
+                if backtrace || self.ice_file.is_none() { bug.decorate(self) } else { bug.inner };
 
-            // "Undelay" the `DelayedBug`s (into plain `Bug`s).
-            if !matches!(bug.level, DelayedBug(_)) {
+            // "Undelay" the delayed bugs (into plain `Bug`s).
+            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 {
-                    span: bug.span.primary_span().unwrap(),
-                    level: bug.level,
-                });
+                //
+                // We are at the `Diagnostic`/`DiagCtxtInner` level rather than
+                // the usual `DiagnosticBuilder`/`DiagCtxt` level, so we must
+                // augment `bug` in a lower-level fashion.
+                bug.arg("level", bug.level);
+                let msg = crate::fluent_generated::errors_invalid_flushed_delayed_diagnostic_level;
+                let msg = self.eagerly_translate_for_subdiag(&bug, msg); // after the `arg` call
+                bug.sub(Level::Note, msg, bug.span.primary_span().unwrap().into());
             }
             bug.level = Bug;
 
@@ -1464,7 +1571,7 @@ impl DiagCtxtInner {
     fn panic_if_treat_err_as_bug(&self) {
         if self.treat_err_as_bug() {
             let n = self.flags.treat_err_as_bug.map(|c| c.get()).unwrap();
-            assert_eq!(n, self.err_count + self.lint_err_count);
+            assert_eq!(n, self.err_guars.len() + self.lint_err_guars.len());
             if n == 1 {
                 panic!("aborting due to `-Z treat-err-as-bug=1`");
             } else {
@@ -1484,61 +1591,63 @@ impl DelayedDiagnostic {
         DelayedDiagnostic { inner: diagnostic, note: backtrace }
     }
 
-    fn decorate(mut self) -> Diagnostic {
-        match self.note.status() {
-            BacktraceStatus::Captured => {
-                let inner = &self.inner;
-                self.inner.subdiagnostic(DelayedAtWithNewline {
-                    span: inner.span.primary_span().unwrap_or(DUMMY_SP),
-                    emitted_at: inner.emitted_at.clone(),
-                    note: self.note,
-                });
-            }
+    fn decorate(self, dcx: &DiagCtxtInner) -> Diagnostic {
+        // We are at the `Diagnostic`/`DiagCtxtInner` level rather than the
+        // usual `DiagnosticBuilder`/`DiagCtxt` level, so we must construct
+        // `diag` in a lower-level fashion.
+        let mut diag = self.inner;
+        let msg = match self.note.status() {
+            BacktraceStatus::Captured => crate::fluent_generated::errors_delayed_at_with_newline,
             // Avoid the needless newline when no backtrace has been captured,
             // the display impl should just be a single line.
-            _ => {
-                let inner = &self.inner;
-                self.inner.subdiagnostic(DelayedAtWithoutNewline {
-                    span: inner.span.primary_span().unwrap_or(DUMMY_SP),
-                    emitted_at: inner.emitted_at.clone(),
-                    note: self.note,
-                });
-            }
-        }
-
-        self.inner
+            _ => crate::fluent_generated::errors_delayed_at_without_newline,
+        };
+        diag.arg("emitted_at", diag.emitted_at.clone());
+        diag.arg("note", self.note);
+        let msg = dcx.eagerly_translate_for_subdiag(&diag, msg); // after the `arg` calls
+        diag.sub(Level::Note, msg, diag.span.primary_span().unwrap_or(DUMMY_SP).into());
+        diag
     }
 }
 
+/// Level              is_error  EmissionGuarantee         Top-level  Sub   Used in lints?
+/// -----              --------  -----------------         ---------  ---   --------------
+/// Bug                yes       BugAbort                  yes        -     -
+/// Fatal              yes       FatalAbort/FatalError(*)  yes        -     -
+/// Error              yes       ErrorGuaranteed           yes        -     yes
+/// DelayedBug         yes       ErrorGuaranteed           yes        -     -
+/// ForceWarning       -         ()                        yes        -     lint-only
+/// Warning            -         ()                        yes        yes   yes
+/// Note               -         ()                        rare       yes   -
+/// OnceNote           -         ()                        -          yes   lint-only
+/// Help               -         ()                        rare       yes   -
+/// OnceHelp           -         ()                        -          yes   lint-only
+/// FailureNote        -         ()                        rare       -     -
+/// Allow              -         ()                        yes        -     lint-only
+/// Expect             -         ()                        yes        -     lint-only
+///
+/// (*) `FatalAbort` normally, `FatalError` in the non-aborting "almost fatal" case that is
+///     occasionally used.
+///
 #[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` for `Normal` delayed bugs, and `()` for
-    /// `GoodPath` delayed bugs.
-    DelayedBug(DelayedBugKind),
-
     /// 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,
 
     /// An error in the code being compiled, which prevents compilation from finishing. This is the
     /// most common case.
-    ///
-    /// Its `EmissionGuarantee` is `ErrorGuaranteed`.
     Error,
 
+    /// 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.
+    DelayedBug,
+
     /// A `force-warn` lint warning about the code being compiled. Does not prevent compilation
     /// from finishing.
     ///
@@ -1547,45 +1656,29 @@ pub enum Level {
     ForceWarning(Option<LintExpectationId>),
 
     /// A warning about the code being compiled. Does not prevent compilation from finishing.
-    ///
-    /// Its `EmissionGuarantee` is `()`.
+    /// Will be skipped if `can_emit_warnings` is false.
     Warning,
 
-    /// A message giving additional context. Rare, because notes are more commonly attached to other
-    /// diagnostics such as errors.
-    ///
-    /// Its `EmissionGuarantee` is `()`.
+    /// A message giving additional context.
     Note,
 
-    /// A note that is only emitted once. Rare, mostly used in circumstances relating to lints.
-    ///
-    /// Its `EmissionGuarantee` is `()`.
+    /// A note that is only emitted once.
     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 `()`.
+    /// A message suggesting how to fix something.
     Help,
 
-    /// A help that is only emitted once. Rare.
-    ///
-    /// Its `EmissionGuarantee` is `()`.
+    /// A help that is only emitted once.
     OnceHelp,
 
-    /// Similar to `Note`, but used in cases where compilation has failed. Rare.
-    ///
-    /// Its `EmissionGuarantee` is `()`.
+    /// Similar to `Note`, but used in cases where compilation has failed. When printed for human
+    /// consumption, it doesn't have any kind of `note:` label.
     FailureNote,
 
     /// Only used for lints.
-    ///
-    /// Its `EmissionGuarantee` is `()`.
     Allow,
 
     /// Only used for lints.
-    ///
-    /// Its `EmissionGuarantee` is `()`.
     Expect(LintExpectationId),
 }
 
@@ -1599,7 +1692,7 @@ impl Level {
     fn color(self) -> ColorSpec {
         let mut spec = ColorSpec::new();
         match self {
-            Bug | DelayedBug(_) | Fatal | Error => {
+            Bug | Fatal | Error | DelayedBug => {
                 spec.set_fg(Some(Color::Red)).set_intense(true);
             }
             ForceWarning(_) | Warning => {
@@ -1619,7 +1712,7 @@ impl Level {
 
     pub fn to_str(self) -> &'static str {
         match self {
-            Bug | DelayedBug(_) => "error: internal compiler error",
+            Bug | DelayedBug => "error: internal compiler error",
             Fatal | Error => "error",
             ForceWarning(_) | Warning => "warning",
             Note | OnceNote => "note",
@@ -1639,18 +1732,31 @@ impl Level {
             _ => None,
         }
     }
+
+    // Can this level be used in a top-level diagnostic message and/or a
+    // subdiagnostic message?
+    fn can_be_top_or_sub(&self) -> (bool, bool) {
+        match self {
+            Bug | DelayedBug | Fatal | Error | ForceWarning(_) | FailureNote | Allow
+            | Expect(_) => (true, false),
+
+            Warning | Note | Help => (true, true),
+
+            OnceNote | OnceHelp => (false, true),
+        }
+    }
 }
 
 // FIXME(eddyb) this doesn't belong here AFAICT, should be moved to callsite.
-pub fn add_elided_lifetime_in_path_suggestion(
+pub fn add_elided_lifetime_in_path_suggestion<G: EmissionGuarantee>(
     source_map: &SourceMap,
-    diag: &mut Diagnostic,
+    diag: &mut DiagnosticBuilder<'_, G>,
     n: usize,
     path_span: Span,
     incl_angl_brckt: bool,
     insertion_span: Span,
 ) {
-    diag.subdiagnostic(ExpectedLifetimeParameter { span: path_span, count: n });
+    diag.subdiagnostic(diag.dcx, ExpectedLifetimeParameter { span: path_span, count: n });
     if !source_map.is_span_accessible(insertion_span) {
         // Do not try to suggest anything if generated by a proc-macro.
         return;
@@ -1659,11 +1765,10 @@ pub fn add_elided_lifetime_in_path_suggestion(
     let suggestion =
         if incl_angl_brckt { format!("<{anon_lts}>") } else { format!("{anon_lts}, ") };
 
-    diag.subdiagnostic(IndicateAnonymousLifetime {
-        span: insertion_span.shrink_to_hi(),
-        count: n,
-        suggestion,
-    });
+    diag.subdiagnostic(
+        diag.dcx,
+        IndicateAnonymousLifetime { span: insertion_span.shrink_to_hi(), count: n, suggestion },
+    );
 }
 
 pub fn report_ambiguity_error<'a, G: EmissionGuarantee>(
diff --git a/compiler/rustc_errors/src/lock.rs b/compiler/rustc_errors/src/lock.rs
index bd5cf49b56b..0aeb511214b 100644
--- a/compiler/rustc_errors/src/lock.rs
+++ b/compiler/rustc_errors/src/lock.rs
@@ -27,7 +27,8 @@ pub fn acquire_global_lock(name: &str) -> Box<dyn Any> {
     impl Drop for Handle {
         fn drop(&mut self) {
             unsafe {
-                CloseHandle(self.0);
+                // FIXME can panic here
+                CloseHandle(self.0).unwrap();
             }
         }
     }
@@ -37,7 +38,8 @@ pub fn acquire_global_lock(name: &str) -> Box<dyn Any> {
     impl Drop for Guard {
         fn drop(&mut self) {
             unsafe {
-                ReleaseMutex((self.0).0);
+                // FIXME can panic here
+                ReleaseMutex((self.0).0).unwrap();
             }
         }
     }
diff --git a/compiler/rustc_errors/src/registry.rs b/compiler/rustc_errors/src/registry.rs
index f26d8e7ebdc..8834d04d971 100644
--- a/compiler/rustc_errors/src/registry.rs
+++ b/compiler/rustc_errors/src/registry.rs
@@ -1,3 +1,4 @@
+use crate::ErrCode;
 use rustc_data_structures::fx::FxHashMap;
 
 #[derive(Debug)]
@@ -5,17 +6,17 @@ pub struct InvalidErrorCode;
 
 #[derive(Clone)]
 pub struct Registry {
-    long_descriptions: FxHashMap<&'static str, &'static str>,
+    long_descriptions: FxHashMap<ErrCode, &'static str>,
 }
 
 impl Registry {
-    pub fn new(long_descriptions: &[(&'static str, &'static str)]) -> Registry {
+    pub fn new(long_descriptions: &[(ErrCode, &'static str)]) -> Registry {
         Registry { long_descriptions: long_descriptions.iter().copied().collect() }
     }
 
     /// Returns `InvalidErrorCode` if the code requested does not exist in the
     /// registry.
-    pub fn try_find_description(&self, code: &str) -> Result<&'static str, InvalidErrorCode> {
-        self.long_descriptions.get(code).copied().ok_or(InvalidErrorCode)
+    pub fn try_find_description(&self, code: ErrCode) -> Result<&'static str, InvalidErrorCode> {
+        self.long_descriptions.get(&code).copied().ok_or(InvalidErrorCode)
     }
 }
diff --git a/compiler/rustc_errors/src/snippet.rs b/compiler/rustc_errors/src/snippet.rs
index 98eb70b5fce..b55f7853885 100644
--- a/compiler/rustc_errors/src/snippet.rs
+++ b/compiler/rustc_errors/src/snippet.rs
@@ -197,7 +197,7 @@ pub struct StyledString {
     pub style: Style,
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
 pub enum Style {
     MainHeaderMsg,
     HeaderMsg,
diff --git a/compiler/rustc_errors/src/translation.rs b/compiler/rustc_errors/src/translation.rs
index ed35eb1b6c4..5f074dbbbad 100644
--- a/compiler/rustc_errors/src/translation.rs
+++ b/compiler/rustc_errors/src/translation.rs
@@ -2,7 +2,7 @@ use crate::error::{TranslateError, TranslateErrorKind};
 use crate::snippet::Style;
 use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
 use rustc_data_structures::sync::Lrc;
-use rustc_error_messages::FluentArgs;
+pub use rustc_error_messages::FluentArgs;
 use std::borrow::Cow;
 use std::env;
 use std::error::Report;
@@ -12,9 +12,9 @@ use std::error::Report;
 ///
 /// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
 /// passed around as a reference thereafter.
-pub fn to_fluent_args<'iter, 'arg: 'iter>(
-    iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>,
-) -> FluentArgs<'arg> {
+pub fn to_fluent_args<'iter>(
+    iter: impl Iterator<Item = DiagnosticArg<'iter>>,
+) -> FluentArgs<'static> {
     let mut args = if let Some(size) = iter.size_hint().1 {
         FluentArgs::with_capacity(size)
     } else {
@@ -61,7 +61,7 @@ pub trait Translate {
     ) -> Result<Cow<'_, str>, TranslateError<'_>> {
         trace!(?message, ?args);
         let (identifier, attr) = match message {
-            DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
+            DiagnosticMessage::Str(msg) | DiagnosticMessage::Translated(msg) => {
                 return Ok(Cow::Borrowed(msg));
             }
             DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),