From 366ef954073bdaebe83e6619430ae0e8d17b9850 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= <me@fmease.dev>
Date: Wed, 22 May 2024 14:45:48 +0200
Subject: [PATCH 1/4] Slightly clean up some lint infra code

* inline `LintBuffer::add_lint`, it only had a single use
* update a lint infra example code snippet
  * it used the wrong API (the snippet isn't tested)
  * presumably the arguments were updated from builder to diag struct style
    at some point without updating the method
---
 compiler/rustc_lint_defs/src/lib.rs          | 25 +++++++-------------
 compiler/rustc_macros/src/diagnostics/mod.rs |  2 +-
 2 files changed, 10 insertions(+), 17 deletions(-)

diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 1941b0b12643f..c8a9fb02bf2e2 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -764,19 +764,7 @@ pub struct LintBuffer {
 
 impl LintBuffer {
     pub fn add_early_lint(&mut self, early_lint: BufferedEarlyLint) {
-        let arr = self.map.entry(early_lint.node_id).or_default();
-        arr.push(early_lint);
-    }
-
-    pub fn add_lint(
-        &mut self,
-        lint: &'static Lint,
-        node_id: NodeId,
-        span: MultiSpan,
-        diagnostic: BuiltinLintDiag,
-    ) {
-        let lint_id = LintId::of(lint);
-        self.add_early_lint(BufferedEarlyLint { lint_id, node_id, span, diagnostic });
+        self.map.entry(early_lint.node_id).or_default().push(early_lint);
     }
 
     pub fn take(&mut self, id: NodeId) -> Vec<BufferedEarlyLint> {
@@ -787,11 +775,16 @@ impl LintBuffer {
     pub fn buffer_lint(
         &mut self,
         lint: &'static Lint,
-        id: NodeId,
-        sp: impl Into<MultiSpan>,
+        node_id: NodeId,
+        span: impl Into<MultiSpan>,
         diagnostic: BuiltinLintDiag,
     ) {
-        self.add_lint(lint, id, sp.into(), diagnostic)
+        self.add_early_lint(BufferedEarlyLint {
+            lint_id: LintId::of(lint),
+            node_id,
+            span: span.into(),
+            diagnostic,
+        });
     }
 }
 
diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs
index 389d88bebc786..134045d0644c0 100644
--- a/compiler/rustc_macros/src/diagnostics/mod.rs
+++ b/compiler/rustc_macros/src/diagnostics/mod.rs
@@ -91,7 +91,7 @@ pub fn diagnostic_derive(mut s: Structure<'_>) -> TokenStream {
 /// Then, later, to emit the error:
 ///
 /// ```ignore (rust)
-/// cx.span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg_span, AtomicOrderingInvalidLint {
+/// cx.emit_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg_span, AtomicOrderingInvalidLint {
 ///     method,
 ///     success_ordering,
 ///     fail_ordering,

From 06bc4fc67145e3a7be9b5a2cf2b5968cef36e587 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= <me@fmease.dev>
Date: Wed, 22 May 2024 16:46:05 +0200
Subject: [PATCH 2/4] Remove `LintDiagnostic::msg`

* instead simply set the primary message inside the lint decorator functions
* it used to be this way before [#]101986 which introduced `msg` to prevent
  good path delayed bugs (which no longer exist) from firing under certain
  circumstances when lints were suppressed / silenced
* this is no longer necessary for various reasons I presume
* it shaves off complexity and makes further changes easier to implement
---
 .../rustc_codegen_ssa/src/codegen_attrs.rs    |   2 +-
 compiler/rustc_errors/src/diagnostic.rs       |   2 -
 .../rustc_hir_analysis/src/check/check.rs     |  18 ++-
 .../src/check/intrinsicck.rs                  |   2 +-
 .../rustc_hir_analysis/src/check_unused.rs    |  13 +-
 .../src/collect/generics_of.rs                |   5 +-
 .../src/hir_ty_lowering/generics.rs           |   5 +-
 .../src/hir_ty_lowering/lint.rs               |   4 +-
 .../src/hir_ty_lowering/mod.rs                |  45 +++---
 .../rustc_hir_typeck/src/fn_ctxt/_impl.rs     |  20 +--
 .../src/method/prelude_edition_lints.rs       | 130 +++++++++---------
 compiler/rustc_hir_typeck/src/method/probe.rs |  85 ++++++------
 compiler/rustc_hir_typeck/src/pat.rs          |  19 +--
 compiler/rustc_hir_typeck/src/upvar.rs        |   3 +-
 compiler/rustc_lint/src/context.rs            |  31 ++---
 .../rustc_lint/src/impl_trait_overcaptures.rs |   5 +-
 compiler/rustc_lint/src/levels.rs             |  42 +++---
 compiler/rustc_lint/src/lints.rs              |  56 ++------
 compiler/rustc_lint/src/non_fmt_panic.rs      |   3 +-
 .../src/diagnostics/diagnostic.rs             |  32 ++---
 compiler/rustc_middle/src/lint.rs             |  10 +-
 compiler/rustc_middle/src/middle/stability.rs |  21 ++-
 .../rustc_middle/src/mir/interpret/queries.rs |   3 +-
 compiler/rustc_middle/src/ty/context.rs       |  19 +--
 compiler/rustc_mir_transform/src/errors.rs    |  22 ++-
 compiler/rustc_pattern_analysis/src/lints.rs  |   1 -
 .../src/traits/object_safety.rs               |  63 ++++-----
 .../src/traits/specialize/mod.rs              |  21 ++-
 src/librustdoc/core.rs                        |   2 +-
 src/librustdoc/html/markdown.rs               |  11 +-
 .../passes/check_doc_test_visibility.rs       |  15 +-
 .../passes/collect_intra_doc_links.rs         |   4 +-
 src/librustdoc/passes/lint/bare_urls.rs       |   5 +-
 .../passes/lint/check_code_block_syntax.rs    |   4 +-
 src/librustdoc/passes/lint/html_tags.rs       |   5 +-
 .../passes/lint/redundant_explicit_links.rs   |  10 +-
 .../passes/lint/unescaped_backticks.rs        |  73 ++++++++--
 .../clippy/clippy_utils/src/diagnostics.rs    |  18 ++-
 .../tests/ui-internal/disallow_span_lint.rs   |   4 +-
 .../ui-internal/disallow_span_lint.stderr     |   8 +-
 .../ui-fulldeps/internal-lints/diagnostics.rs |   8 --
 .../internal-lints/diagnostics.stderr         |   6 +-
 .../session-diagnostic/diagnostic-derive.rs   |   1 -
 .../diagnostic-derive.stderr                  |  62 ++++-----
 44 files changed, 430 insertions(+), 488 deletions(-)

diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index 9bf055b173946..304c604100afb 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -564,8 +564,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
                     lint::builtin::INLINE_NO_SANITIZE,
                     hir_id,
                     no_sanitize_span,
-                    "`no_sanitize` will have no effect after inlining",
                     |lint| {
+                        lint.primary_message("`no_sanitize` will have no effect after inlining");
                         lint.span_note(inline_span, "inlining requested here");
                     },
                 )
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 18bb71bd99f73..34b569c42068e 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -200,8 +200,6 @@ pub trait SubdiagMessageOp<G: EmissionGuarantee> =
 pub trait LintDiagnostic<'a, G: EmissionGuarantee> {
     /// Decorate and emit a lint.
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>);
-
-    fn msg(&self) -> DiagMessage;
 }
 
 #[derive(Clone, Debug, Encodable, Decodable)]
diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs
index b5c067514059a..b46b1d5a26b46 100644
--- a/compiler/rustc_hir_analysis/src/check/check.rs
+++ b/compiler/rustc_hir_analysis/src/check/check.rs
@@ -46,13 +46,9 @@ pub fn check_abi(tcx: TyCtxt<'_>, hir_id: hir::HirId, span: Span, abi: Abi) {
             .emit();
         }
         None => {
-            tcx.node_span_lint(
-                UNSUPPORTED_CALLING_CONVENTIONS,
-                hir_id,
-                span,
-                "use of calling convention not supported on this target",
-                |_| {},
-            );
+            tcx.node_span_lint(UNSUPPORTED_CALLING_CONVENTIONS, hir_id, span, |lint| {
+                lint.primary_message("use of calling convention not supported on this target");
+            });
         }
     }
 
@@ -243,8 +239,8 @@ fn check_static_inhabited(tcx: TyCtxt<'_>, def_id: LocalDefId) {
             UNINHABITED_STATIC,
             tcx.local_def_id_to_hir_id(def_id),
             span,
-            "static of uninhabited type",
             |lint| {
+                lint.primary_message("static of uninhabited type");
                 lint
                 .note("uninhabited statics cannot be initialized, and any access would be an immediate error");
             },
@@ -1315,9 +1311,11 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
                     REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
                     tcx.local_def_id_to_hir_id(adt.did().expect_local()),
                     span,
-                    "zero-sized fields in `repr(transparent)` cannot \
-                    contain external non-exhaustive types",
                     |lint| {
+                        lint.primary_message(
+                            "zero-sized fields in `repr(transparent)` cannot \
+                             contain external non-exhaustive types",
+                        );
                         let note = if non_exhaustive {
                             "is marked with `#[non_exhaustive]`"
                         } else {
diff --git a/compiler/rustc_hir_analysis/src/check/intrinsicck.rs b/compiler/rustc_hir_analysis/src/check/intrinsicck.rs
index b09de1a4a0989..2672614a89548 100644
--- a/compiler/rustc_hir_analysis/src/check/intrinsicck.rs
+++ b/compiler/rustc_hir_analysis/src/check/intrinsicck.rs
@@ -281,8 +281,8 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> {
                     lint::builtin::ASM_SUB_REGISTER,
                     expr.hir_id,
                     spans,
-                    "formatting may not be suitable for sub-register argument",
                     |lint| {
+                        lint.primary_message("formatting may not be suitable for sub-register argument");
                         lint.span_label(expr.span, "for this argument");
                         lint.help(format!(
                             "use `{{{idx}:{suggested_modifier}}}` to have the register formatted as `{suggested_result}` (for {suggested_size}-bit values)",
diff --git a/compiler/rustc_hir_analysis/src/check_unused.rs b/compiler/rustc_hir_analysis/src/check_unused.rs
index aa5db4f6aa7bb..ed23dc2a827b4 100644
--- a/compiler/rustc_hir_analysis/src/check_unused.rs
+++ b/compiler/rustc_hir_analysis/src/check_unused.rs
@@ -35,11 +35,12 @@ fn check_unused_traits(tcx: TyCtxt<'_>, (): ()) {
             continue;
         }
         let (path, _) = item.expect_use();
-        let msg = if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(path.span) {
-            format!("unused import: `{snippet}`")
-        } else {
-            "unused import".to_owned()
-        };
-        tcx.node_span_lint(lint::builtin::UNUSED_IMPORTS, item.hir_id(), path.span, msg, |_| {});
+        tcx.node_span_lint(lint::builtin::UNUSED_IMPORTS, item.hir_id(), path.span, |lint| {
+            if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(path.span) {
+                lint.primary_message(format!("unused import: `{snippet}`"));
+            } else {
+                lint.primary_message("unused import");
+            }
+        });
     }
 }
diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs
index 6b41f79cf1e7f..abdf85ad707bc 100644
--- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs
+++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs
@@ -317,8 +317,9 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
                             lint::builtin::INVALID_TYPE_PARAM_DEFAULT,
                             param.hir_id,
                             param.span,
-                            TYPE_DEFAULT_NOT_ALLOWED,
-                            |_| {},
+                            |lint| {
+                                lint.primary_message(TYPE_DEFAULT_NOT_ALLOWED);
+                            },
                         );
                     }
                     Defaults::Deny => {
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs
index 749f78e79201e..7b67030836d13 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs
@@ -646,8 +646,9 @@ pub(crate) fn prohibit_explicit_late_bound_lifetimes(
                 LATE_BOUND_LIFETIME_ARGUMENTS,
                 args.args[0].hir_id(),
                 multispan,
-                msg,
-                |_| {},
+                |lint| {
+                    lint.primary_message(msg);
+                },
             );
         }
 
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs
index d9d36f5299b58..997db338a43fd 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs
@@ -72,8 +72,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             self.maybe_suggest_assoc_ty_bound(self_ty, &mut diag);
             diag.stash(self_ty.span, StashKey::TraitMissingMethod);
         } else {
-            let msg = "trait objects without an explicit `dyn` are deprecated";
-            tcx.node_span_lint(BARE_TRAIT_OBJECTS, self_ty.hir_id, self_ty.span, msg, |lint| {
+            tcx.node_span_lint(BARE_TRAIT_OBJECTS, self_ty.hir_id, self_ty.span, |lint| {
+                lint.primary_message("trait objects without an explicit `dyn` are deprecated");
                 if self_ty.span.can_be_used_for_suggestions() {
                     lint.multipart_suggestion_verbose(
                         "if this is an object-safe trait, use `dyn`",
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
index 8caeb85204b75..c0da9941dc12e 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
@@ -1164,33 +1164,28 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         let ty = self.lower_assoc_ty(span, assoc_ty_did, assoc_segment, bound);
 
         if let Some(variant_def_id) = variant_resolution {
-            tcx.node_span_lint(
-                AMBIGUOUS_ASSOCIATED_ITEMS,
-                hir_ref_id,
-                span,
-                "ambiguous associated item",
-                |lint| {
-                    let mut could_refer_to = |kind: DefKind, def_id, also| {
-                        let note_msg = format!(
-                            "`{}` could{} refer to the {} defined here",
-                            assoc_ident,
-                            also,
-                            tcx.def_kind_descr(kind, def_id)
-                        );
-                        lint.span_note(tcx.def_span(def_id), note_msg);
-                    };
+            tcx.node_span_lint(AMBIGUOUS_ASSOCIATED_ITEMS, hir_ref_id, span, |lint| {
+                lint.primary_message("ambiguous associated item");
+                let mut could_refer_to = |kind: DefKind, def_id, also| {
+                    let note_msg = format!(
+                        "`{}` could{} refer to the {} defined here",
+                        assoc_ident,
+                        also,
+                        tcx.def_kind_descr(kind, def_id)
+                    );
+                    lint.span_note(tcx.def_span(def_id), note_msg);
+                };
 
-                    could_refer_to(DefKind::Variant, variant_def_id, "");
-                    could_refer_to(DefKind::AssocTy, assoc_ty_did, " also");
+                could_refer_to(DefKind::Variant, variant_def_id, "");
+                could_refer_to(DefKind::AssocTy, assoc_ty_did, " also");
 
-                    lint.span_suggestion(
-                        span,
-                        "use fully-qualified syntax",
-                        format!("<{} as {}>::{}", qself_ty, tcx.item_name(trait_did), assoc_ident),
-                        Applicability::MachineApplicable,
-                    );
-                },
-            );
+                lint.span_suggestion(
+                    span,
+                    "use fully-qualified syntax",
+                    format!("<{} as {}>::{}", qself_ty, tcx.item_name(trait_did), assoc_ident),
+                    Applicability::MachineApplicable,
+                );
+            });
         }
         Ok((ty, DefKind::AssocTy, assoc_ty_did))
     }
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
index 77f90c0c1310c..301ebe685d924 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
@@ -63,19 +63,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 debug!("warn_if_unreachable: id={:?} span={:?} kind={}", id, span, kind);
 
                 let msg = format!("unreachable {kind}");
-                self.tcx().node_span_lint(
-                    lint::builtin::UNREACHABLE_CODE,
-                    id,
-                    span,
-                    msg.clone(),
-                    |lint| {
-                        lint.span_label(span, msg).span_label(
-                            orig_span,
-                            custom_note
-                                .unwrap_or("any code following this expression is unreachable"),
-                        );
-                    },
-                )
+                self.tcx().node_span_lint(lint::builtin::UNREACHABLE_CODE, id, span, |lint| {
+                    lint.primary_message(msg.clone());
+                    lint.span_label(span, msg).span_label(
+                        orig_span,
+                        custom_note.unwrap_or("any code following this expression is unreachable"),
+                    );
+                })
             }
         }
     }
diff --git a/compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs b/compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs
index e9eab6969b34e..3ee10f74d98a4 100644
--- a/compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs
+++ b/compiler/rustc_hir_typeck/src/method/prelude_edition_lints.rs
@@ -91,11 +91,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 prelude_or_array_lint,
                 self_expr.hir_id,
                 self_expr.span,
-                format!(
-                    "trait method `{}` will become ambiguous in Rust {edition}",
-                    segment.ident.name
-                ),
                 |lint| {
+                    lint.primary_message(format!(
+                        "trait method `{}` will become ambiguous in Rust {edition}",
+                        segment.ident.name
+                    ));
+
                     let sp = self_expr.span;
 
                     let derefs = "*".repeat(pick.autoderefs);
@@ -144,11 +145,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 prelude_or_array_lint,
                 call_expr.hir_id,
                 call_expr.span,
-                format!(
-                    "trait method `{}` will become ambiguous in Rust {edition}",
-                    segment.ident.name
-                ),
                 |lint| {
+                    lint.primary_message(format!(
+                        "trait method `{}` will become ambiguous in Rust {edition}",
+                        segment.ident.name
+                    ));
+
                     let sp = call_expr.span;
                     let trait_name = self.trait_path_or_bare_name(
                         span,
@@ -251,73 +253,67 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             return;
         }
 
-        self.tcx.node_span_lint(
-            RUST_2021_PRELUDE_COLLISIONS,
-            expr_id,
-            span,
-            format!(
+        self.tcx.node_span_lint(RUST_2021_PRELUDE_COLLISIONS, expr_id, span, |lint| {
+            lint.primary_message(format!(
                 "trait-associated function `{}` will become ambiguous in Rust 2021",
                 method_name.name
-            ),
-            |lint| {
-                // "type" refers to either a type or, more likely, a trait from which
-                // the associated function or method is from.
-                let container_id = pick.item.container_id(self.tcx);
-                let trait_path = self.trait_path_or_bare_name(span, expr_id, container_id);
-                let trait_generics = self.tcx.generics_of(container_id);
-
-                let trait_name =
-                    if trait_generics.own_params.len() <= trait_generics.has_self as usize {
-                        trait_path
-                    } else {
-                        let counts = trait_generics.own_counts();
-                        format!(
-                            "{}<{}>",
-                            trait_path,
+            ));
+
+            // "type" refers to either a type or, more likely, a trait from which
+            // the associated function or method is from.
+            let container_id = pick.item.container_id(self.tcx);
+            let trait_path = self.trait_path_or_bare_name(span, expr_id, container_id);
+            let trait_generics = self.tcx.generics_of(container_id);
+
+            let trait_name =
+                if trait_generics.own_params.len() <= trait_generics.has_self as usize {
+                    trait_path
+                } else {
+                    let counts = trait_generics.own_counts();
+                    format!(
+                        "{}<{}>",
+                        trait_path,
+                        std::iter::repeat("'_")
+                            .take(counts.lifetimes)
+                            .chain(std::iter::repeat("_").take(
+                                counts.types + counts.consts - trait_generics.has_self as usize
+                            ))
+                            .collect::<Vec<_>>()
+                            .join(", ")
+                    )
+                };
+
+            let mut self_ty_name = self_ty_span
+                .find_ancestor_inside(span)
+                .and_then(|span| self.sess().source_map().span_to_snippet(span).ok())
+                .unwrap_or_else(|| self_ty.to_string());
+
+            // Get the number of generics the self type has (if an Adt) unless we can determine that
+            // the user has written the self type with generics already which we (naively) do by looking
+            // for a "<" in `self_ty_name`.
+            if !self_ty_name.contains('<') {
+                if let ty::Adt(def, _) = self_ty.kind() {
+                    let generics = self.tcx.generics_of(def.did());
+                    if !generics.is_own_empty() {
+                        let counts = generics.own_counts();
+                        self_ty_name += &format!(
+                            "<{}>",
                             std::iter::repeat("'_")
                                 .take(counts.lifetimes)
-                                .chain(std::iter::repeat("_").take(
-                                    counts.types + counts.consts - trait_generics.has_self as usize
-                                ))
+                                .chain(std::iter::repeat("_").take(counts.types + counts.consts))
                                 .collect::<Vec<_>>()
                                 .join(", ")
-                        )
-                    };
-
-                let mut self_ty_name = self_ty_span
-                    .find_ancestor_inside(span)
-                    .and_then(|span| self.sess().source_map().span_to_snippet(span).ok())
-                    .unwrap_or_else(|| self_ty.to_string());
-
-                // Get the number of generics the self type has (if an Adt) unless we can determine that
-                // the user has written the self type with generics already which we (naively) do by looking
-                // for a "<" in `self_ty_name`.
-                if !self_ty_name.contains('<') {
-                    if let ty::Adt(def, _) = self_ty.kind() {
-                        let generics = self.tcx.generics_of(def.did());
-                        if !generics.is_own_empty() {
-                            let counts = generics.own_counts();
-                            self_ty_name += &format!(
-                                "<{}>",
-                                std::iter::repeat("'_")
-                                    .take(counts.lifetimes)
-                                    .chain(
-                                        std::iter::repeat("_").take(counts.types + counts.consts)
-                                    )
-                                    .collect::<Vec<_>>()
-                                    .join(", ")
-                            );
-                        }
+                        );
                     }
                 }
-                lint.span_suggestion(
-                    span,
-                    "disambiguate the associated function",
-                    format!("<{} as {}>::{}", self_ty_name, trait_name, method_name.name,),
-                    Applicability::MachineApplicable,
-                );
-            },
-        );
+            }
+            lint.span_suggestion(
+                span,
+                "disambiguate the associated function",
+                format!("<{} as {}>::{}", self_ty_name, trait_name, method_name.name,),
+                Applicability::MachineApplicable,
+            );
+        });
     }
 
     fn trait_path_or_bare_name(
diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs
index e0a60337c3ba1..4d33f7e237e3a 100644
--- a/compiler/rustc_hir_typeck/src/method/probe.rs
+++ b/compiler/rustc_hir_typeck/src/method/probe.rs
@@ -408,8 +408,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     lint::builtin::TYVAR_BEHIND_RAW_POINTER,
                     scope_expr_id,
                     span,
-                    "type annotations needed",
-                    |_| {},
+                    |lint| {
+                        lint.primary_message("type annotations needed");
+                    },
                 );
             } else {
                 // Ended up encountering a type variable when doing autoderef,
@@ -1279,53 +1280,49 @@ impl<'tcx> Pick<'tcx> {
             return;
         }
         let def_kind = self.item.kind.as_def_kind();
-        tcx.node_span_lint(
-            lint::builtin::UNSTABLE_NAME_COLLISIONS,
-            scope_expr_id,
-            span,
-            format!(
+        tcx.node_span_lint(lint::builtin::UNSTABLE_NAME_COLLISIONS, scope_expr_id, span, |lint| {
+            lint.primary_message(format!(
                 "{} {} with this name may be added to the standard library in the future",
                 tcx.def_kind_descr_article(def_kind, self.item.def_id),
                 tcx.def_kind_descr(def_kind, self.item.def_id),
-            ),
-            |lint| {
-                match (self.item.kind, self.item.container) {
-                    (ty::AssocKind::Fn, _) => {
-                        // FIXME: This should be a `span_suggestion` instead of `help`
-                        // However `self.span` only
-                        // highlights the method name, so we can't use it. Also consider reusing
-                        // the code from `report_method_error()`.
-                        lint.help(format!(
-                            "call with fully qualified syntax `{}(...)` to keep using the current \
+            ));
+
+            match (self.item.kind, self.item.container) {
+                (ty::AssocKind::Fn, _) => {
+                    // FIXME: This should be a `span_suggestion` instead of `help`
+                    // However `self.span` only
+                    // highlights the method name, so we can't use it. Also consider reusing
+                    // the code from `report_method_error()`.
+                    lint.help(format!(
+                        "call with fully qualified syntax `{}(...)` to keep using the current \
                              method",
-                            tcx.def_path_str(self.item.def_id),
-                        ));
-                    }
-                    (ty::AssocKind::Const, ty::AssocItemContainer::TraitContainer) => {
-                        let def_id = self.item.container_id(tcx);
-                        lint.span_suggestion(
-                            span,
-                            "use the fully qualified path to the associated const",
-                            format!(
-                                "<{} as {}>::{}",
-                                self.self_ty,
-                                tcx.def_path_str(def_id),
-                                self.item.name
-                            ),
-                            Applicability::MachineApplicable,
-                        );
-                    }
-                    _ => {}
+                        tcx.def_path_str(self.item.def_id),
+                    ));
                 }
-                tcx.disabled_nightly_features(
-                    lint,
-                    Some(scope_expr_id),
-                    self.unstable_candidates.iter().map(|(candidate, feature)| {
-                        (format!(" `{}`", tcx.def_path_str(candidate.item.def_id)), *feature)
-                    }),
-                );
-            },
-        );
+                (ty::AssocKind::Const, ty::AssocItemContainer::TraitContainer) => {
+                    let def_id = self.item.container_id(tcx);
+                    lint.span_suggestion(
+                        span,
+                        "use the fully qualified path to the associated const",
+                        format!(
+                            "<{} as {}>::{}",
+                            self.self_ty,
+                            tcx.def_path_str(def_id),
+                            self.item.name
+                        ),
+                        Applicability::MachineApplicable,
+                    );
+                }
+                _ => {}
+            }
+            tcx.disabled_nightly_features(
+                lint,
+                Some(scope_expr_id),
+                self.unstable_candidates.iter().map(|(candidate, feature)| {
+                    (format!(" `{}`", tcx.def_path_str(candidate.item.def_id)), *feature)
+                }),
+            );
+        });
     }
 }
 
diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs
index b9b220d5af8e4..b37aba386196b 100644
--- a/compiler/rustc_hir_typeck/src/pat.rs
+++ b/compiler/rustc_hir_typeck/src/pat.rs
@@ -1950,15 +1950,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             &unmentioned_fields.iter().map(|(_, i)| i).collect::<Vec<_>>(),
         );
 
-        self.tcx.node_span_lint(NON_EXHAUSTIVE_OMITTED_PATTERNS, pat.hir_id, pat.span, "some fields are not explicitly listed", |lint| {
-        lint.span_label(pat.span, format!("field{} {} not listed", rustc_errors::pluralize!(unmentioned_fields.len()), joined_patterns));
-        lint.help(
-            "ensure that all fields are mentioned explicitly by adding the suggested fields",
-        );
-        lint.note(format!(
-            "the pattern is of type `{ty}` and the `non_exhaustive_omitted_patterns` attribute was found",
-        ));
-    });
+        self.tcx.node_span_lint(NON_EXHAUSTIVE_OMITTED_PATTERNS, pat.hir_id, pat.span, |lint| {
+            lint.primary_message("some fields are not explicitly listed");
+            lint.span_label(pat.span, format!("field{} {} not listed", rustc_errors::pluralize!(unmentioned_fields.len()), joined_patterns));
+            lint.help(
+                "ensure that all fields are mentioned explicitly by adding the suggested fields",
+            );
+            lint.note(format!(
+                "the pattern is of type `{ty}` and the `non_exhaustive_omitted_patterns` attribute was found",
+            ));
+        });
     }
 
     /// Returns a diagnostic reporting a struct pattern which does not mention some fields.
diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs
index 9d16f0d48159e..89ac33dcfc026 100644
--- a/compiler/rustc_hir_typeck/src/upvar.rs
+++ b/compiler/rustc_hir_typeck/src/upvar.rs
@@ -928,8 +928,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 lint::builtin::RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES,
                 closure_hir_id,
                 closure_head_span,
-                reasons.migration_message(),
                 |lint| {
+                    lint.primary_message(reasons.migration_message());
+
                     for NeededMigration { var_hir_id, diagnostics_info } in &need_migrations {
                         // Labels all the usage of the captured variable and why they are responsible
                         // for migration being needed
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index deeb3ae090c57..34b84658dc0b9 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -21,7 +21,7 @@ use crate::passes::{EarlyLintPassObject, LateLintPassObject};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sync;
 use rustc_data_structures::unord::UnordMap;
-use rustc_errors::{Diag, DiagMessage, LintDiagnostic, MultiSpan};
+use rustc_errors::{Diag, LintDiagnostic, MultiSpan};
 use rustc_feature::Features;
 use rustc_hir as hir;
 use rustc_hir::def::Res;
@@ -556,7 +556,6 @@ pub trait LintContext {
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        msg: impl Into<DiagMessage>,
         decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     );
 
@@ -568,8 +567,8 @@ pub trait LintContext {
         span: S,
         decorator: impl for<'a> LintDiagnostic<'a, ()>,
     ) {
-        self.opt_span_lint(lint, Some(span), decorator.msg(), |diag| {
-            decorator.decorate_lint(diag);
+        self.opt_span_lint(lint, Some(span), |lint| {
+            decorator.decorate_lint(lint);
         });
     }
 
@@ -581,17 +580,16 @@ pub trait LintContext {
         &self,
         lint: &'static Lint,
         span: S,
-        msg: impl Into<DiagMessage>,
         decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
-        self.opt_span_lint(lint, Some(span), msg, decorate);
+        self.opt_span_lint(lint, Some(span), decorate);
     }
 
     /// Emit a lint from a lint struct (some type that implements `LintDiagnostic`, typically
     /// generated by `#[derive(LintDiagnostic)]`).
     fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> LintDiagnostic<'a, ()>) {
-        self.opt_span_lint(lint, None as Option<Span>, decorator.msg(), |diag| {
-            decorator.decorate_lint(diag);
+        self.opt_span_lint(lint, None as Option<Span>, |lint| {
+            decorator.decorate_lint(lint);
         });
     }
 
@@ -599,13 +597,8 @@ pub trait LintContext {
     ///
     /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
     #[rustc_lint_diagnostics]
-    fn lint(
-        &self,
-        lint: &'static Lint,
-        msg: impl Into<DiagMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
-    ) {
-        self.opt_span_lint(lint, None as Option<Span>, msg, decorate);
+    fn lint(&self, lint: &'static Lint, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)) {
+        self.opt_span_lint(lint, None as Option<Span>, decorate);
     }
 
     /// This returns the lint level for the given lint at the current location.
@@ -668,14 +661,13 @@ impl<'tcx> LintContext for LateContext<'tcx> {
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        msg: impl Into<DiagMessage>,
         decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
         let hir_id = self.last_node_with_lint_attrs;
 
         match span {
-            Some(s) => self.tcx.node_span_lint(lint, hir_id, s, msg, decorate),
-            None => self.tcx.node_lint(lint, hir_id, msg, decorate),
+            Some(s) => self.tcx.node_span_lint(lint, hir_id, s, decorate),
+            None => self.tcx.node_lint(lint, hir_id, decorate),
         }
     }
 
@@ -695,10 +687,9 @@ impl LintContext for EarlyContext<'_> {
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        msg: impl Into<DiagMessage>,
         decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
-        self.builder.opt_span_lint(lint, span.map(|s| s.into()), msg, decorate)
+        self.builder.opt_span_lint(lint, span.map(|s| s.into()), decorate)
     }
 
     fn get_lint_level(&self, lint: &'static Lint) -> Level {
diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs
index 30bf80b915b87..ca3e1ee06d946 100644
--- a/compiler/rustc_lint/src/impl_trait_overcaptures.rs
+++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs
@@ -369,6 +369,7 @@ struct ImplTraitOvercapturesLint<'tcx> {
 
 impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> {
     fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_impl_trait_overcaptures);
         diag.arg("self_ty", self.self_ty.to_string())
             .arg("num_captured", self.num_captured)
             .span_note(self.uncaptured_spans, fluent::lint_note)
@@ -382,10 +383,6 @@ impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> {
             );
         }
     }
-
-    fn msg(&self) -> rustc_errors::DiagMessage {
-        fluent::lint_impl_trait_overcaptures
-    }
 }
 
 #[derive(LintDiagnostic)]
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 12b3d1d2f9e4f..5d0656865461b 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -16,7 +16,7 @@ use crate::{
 use rustc_ast as ast;
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxIndexMap;
-use rustc_errors::{Diag, DiagMessage, LintDiagnostic, MultiSpan};
+use rustc_errors::{Diag, LintDiagnostic, MultiSpan};
 use rustc_feature::{Features, GateIssue};
 use rustc_hir as hir;
 use rustc_hir::intravisit::{self, Visitor};
@@ -1063,26 +1063,19 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
             // FIXME: make this translatable
             #[allow(rustc::diagnostic_outside_of_impl)]
             #[allow(rustc::untranslatable_diagnostic)]
-            lint_level(
-                self.sess,
-                lint,
-                level,
-                src,
-                Some(span.into()),
-                fluent::lint_unknown_gated_lint,
-                |lint| {
-                    lint.arg("name", lint_id.lint.name_lower());
-                    lint.note(fluent::lint_note);
-                    rustc_session::parse::add_feature_diagnostics_for_issue(
-                        lint,
-                        &self.sess,
-                        feature,
-                        GateIssue::Language,
-                        lint_from_cli,
-                        None,
-                    );
-                },
-            );
+            lint_level(self.sess, lint, level, src, Some(span.into()), |lint| {
+                lint.primary_message(fluent::lint_unknown_gated_lint);
+                lint.arg("name", lint_id.lint.name_lower());
+                lint.note(fluent::lint_note);
+                rustc_session::parse::add_feature_diagnostics_for_issue(
+                    lint,
+                    &self.sess,
+                    feature,
+                    GateIssue::Language,
+                    lint_from_cli,
+                    None,
+                );
+            });
         }
 
         false
@@ -1103,11 +1096,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
         &self,
         lint: &'static Lint,
         span: Option<MultiSpan>,
-        msg: impl Into<DiagMessage>,
         decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
         let (level, src) = self.lint_level(lint);
-        lint_level(self.sess, lint, level, src, span, msg, decorate)
+        lint_level(self.sess, lint, level, src, span, decorate)
     }
 
     #[track_caller]
@@ -1118,7 +1110,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
         decorate: impl for<'a> LintDiagnostic<'a, ()>,
     ) {
         let (level, src) = self.lint_level(lint);
-        lint_level(self.sess, lint, level, src, Some(span), decorate.msg(), |lint| {
+        lint_level(self.sess, lint, level, src, Some(span), |lint| {
             decorate.decorate_lint(lint);
         });
     }
@@ -1126,7 +1118,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
     #[track_caller]
     pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) {
         let (level, src) = self.lint_level(lint);
-        lint_level(self.sess, lint, level, src, None, decorate.msg(), |lint| {
+        lint_level(self.sess, lint, level, src, None, |lint| {
             decorate.decorate_lint(lint);
         });
     }
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 3bd6faca37963..4b1626ca5560f 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -145,12 +145,9 @@ pub struct BuiltinMissingDebugImpl<'a> {
 // Needed for def_path_str
 impl<'a> LintDiagnostic<'a, ()> for BuiltinMissingDebugImpl<'_> {
     fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_builtin_missing_debug_impl);
         diag.arg("debug", self.tcx.def_path_str(self.def_id));
     }
-
-    fn msg(&self) -> DiagMessage {
-        fluent::lint_builtin_missing_debug_impl
-    }
 }
 
 #[derive(LintDiagnostic)]
@@ -250,6 +247,7 @@ pub struct BuiltinUngatedAsyncFnTrackCaller<'a> {
 
 impl<'a> LintDiagnostic<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_ungated_async_fn_track_caller);
         diag.span_label(self.label, fluent::lint_label);
         rustc_session::parse::add_feature_diagnostics(
             diag,
@@ -257,10 +255,6 @@ impl<'a> LintDiagnostic<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> {
             sym::async_fn_track_caller,
         );
     }
-
-    fn msg(&self) -> DiagMessage {
-        fluent::lint_ungated_async_fn_track_caller
-    }
 }
 
 #[derive(LintDiagnostic)]
@@ -432,6 +426,7 @@ pub struct BuiltinUnpermittedTypeInit<'a> {
 
 impl<'a> LintDiagnostic<'a, ()> for BuiltinUnpermittedTypeInit<'_> {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(self.msg);
         diag.arg("ty", self.ty);
         diag.span_label(self.label, fluent::lint_builtin_unpermitted_type_init_label);
         if let InhabitedPredicate::True = self.ty.inhabited_predicate(self.tcx) {
@@ -443,10 +438,6 @@ impl<'a> LintDiagnostic<'a, ()> for BuiltinUnpermittedTypeInit<'_> {
         }
         self.sub.add_to_diag(diag);
     }
-
-    fn msg(&self) -> DiagMessage {
-        self.msg.clone()
-    }
 }
 
 // FIXME(davidtwco): make translatable
@@ -1168,6 +1159,7 @@ pub struct NonFmtPanicUnused {
 // Used because of two suggestions based on one Option<Span>
 impl<'a> LintDiagnostic<'a, ()> for NonFmtPanicUnused {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_non_fmt_panic_unused);
         diag.arg("count", self.count);
         diag.note(fluent::lint_note);
         if let Some(span) = self.suggestion {
@@ -1185,10 +1177,6 @@ impl<'a> LintDiagnostic<'a, ()> for NonFmtPanicUnused {
             );
         }
     }
-
-    fn msg(&self) -> DiagMessage {
-        fluent::lint_non_fmt_panic_unused
-    }
 }
 
 #[derive(LintDiagnostic)]
@@ -1410,13 +1398,10 @@ pub struct DropTraitConstraintsDiag<'a> {
 // Needed for def_path_str
 impl<'a> LintDiagnostic<'a, ()> for DropTraitConstraintsDiag<'_> {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_drop_trait_constraints);
         diag.arg("predicate", self.predicate);
         diag.arg("needs_drop", self.tcx.def_path_str(self.def_id));
     }
-
-    fn msg(&self) -> DiagMessage {
-        fluent::lint_drop_trait_constraints
-    }
 }
 
 pub struct DropGlue<'a> {
@@ -1427,12 +1412,9 @@ pub struct DropGlue<'a> {
 // Needed for def_path_str
 impl<'a> LintDiagnostic<'a, ()> for DropGlue<'_> {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_drop_glue);
         diag.arg("needs_drop", self.tcx.def_path_str(self.def_id));
     }
-
-    fn msg(&self) -> DiagMessage {
-        fluent::lint_drop_glue
-    }
 }
 
 // types.rs
@@ -1710,6 +1692,7 @@ pub struct ImproperCTypes<'a> {
 // Used because of the complexity of Option<DiagMessage>, DiagMessage, and Option<Span>
 impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_improper_ctypes);
         diag.arg("ty", self.ty);
         diag.arg("desc", self.desc);
         diag.span_label(self.label, fluent::lint_label);
@@ -1721,10 +1704,6 @@ impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> {
             diag.span_note(note, fluent::lint_note);
         }
     }
-
-    fn msg(&self) -> DiagMessage {
-        fluent::lint_improper_ctypes
-    }
 }
 
 #[derive(LintDiagnostic)]
@@ -1853,6 +1832,7 @@ pub enum UnusedDefSuggestion {
 // Needed because of def_path_str
 impl<'a> LintDiagnostic<'a, ()> for UnusedDef<'_, '_> {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_unused_def);
         diag.arg("pre", self.pre);
         diag.arg("post", self.post);
         diag.arg("def", self.cx.tcx.def_path_str(self.def_id));
@@ -1864,10 +1844,6 @@ impl<'a> LintDiagnostic<'a, ()> for UnusedDef<'_, '_> {
             diag.subdiagnostic(diag.dcx, sugg);
         }
     }
-
-    fn msg(&self) -> DiagMessage {
-        fluent::lint_unused_def
-    }
 }
 
 #[derive(LintDiagnostic)]
@@ -1936,15 +1912,12 @@ pub struct AsyncFnInTraitDiag {
 
 impl<'a> LintDiagnostic<'a, ()> for AsyncFnInTraitDiag {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_async_fn_in_trait);
         diag.note(fluent::lint_note);
         if let Some(sugg) = self.sugg {
             diag.multipart_suggestion(fluent::lint_suggestion, sugg, Applicability::MaybeIncorrect);
         }
     }
-
-    fn msg(&self) -> DiagMessage {
-        fluent::lint_async_fn_in_trait
-    }
 }
 
 #[derive(LintDiagnostic)]
@@ -2260,10 +2233,8 @@ pub struct UnstableFeature {
 }
 
 impl<'a> LintDiagnostic<'a, ()> for UnstableFeature {
-    fn decorate_lint<'b>(self, _diag: &'b mut Diag<'a, ()>) {}
-
-    fn msg(&self) -> DiagMessage {
-        self.msg.clone()
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(self.msg);
     }
 }
 
@@ -2725,12 +2696,9 @@ pub struct AmbiguousGlobImports {
 
 impl<'a, G: EmissionGuarantee> LintDiagnostic<'a, G> for AmbiguousGlobImports {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) {
+        diag.primary_message(self.ambiguity.msg.clone());
         rustc_errors::report_ambiguity_error(diag, self.ambiguity);
     }
-
-    fn msg(&self) -> DiagMessage {
-        DiagMessage::Str(self.ambiguity.msg.clone().into())
-    }
 }
 
 #[derive(LintDiagnostic)]
diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs
index a6057afcbd6b5..c9d6785411237 100644
--- a/compiler/rustc_lint/src/non_fmt_panic.rs
+++ b/compiler/rustc_lint/src/non_fmt_panic.rs
@@ -123,7 +123,8 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
     }
 
     #[allow(rustc::diagnostic_outside_of_impl)]
-    cx.span_lint(NON_FMT_PANICS, arg_span, fluent::lint_non_fmt_panic, |lint| {
+    cx.span_lint(NON_FMT_PANICS, arg_span, |lint| {
+        lint.primary_message(fluent::lint_non_fmt_panic);
         lint.arg("name", symbol);
         lint.note(fluent::lint_note);
         lint.note(fluent::lint_more_info_note);
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index 9d21d88165e05..ef6005283d6b2 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -105,25 +105,12 @@ impl<'a> LintDiagnosticDerive<'a> {
     pub(crate) fn into_tokens(self) -> TokenStream {
         let LintDiagnosticDerive { mut structure } = self;
         let kind = DiagnosticDeriveKind::LintDiagnostic;
+        let slugs = RefCell::new(Vec::new());
         let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
             let preamble = builder.preamble(variant);
             let body = builder.body(variant);
 
-            let formatting_init = &builder.formatting_init;
-            quote! {
-                #preamble
-                #formatting_init
-                #body
-                diag
-            }
-        });
-
-        let slugs = RefCell::new(Vec::new());
-        let msg = kind.each_variant(&mut structure, |mut builder, variant| {
-            // Collect the slug by generating the preamble.
-            let _ = builder.preamble(variant);
-
-            match builder.slug.value_ref() {
+            let primary_message = match builder.slug.value_ref() {
                 None => {
                     span_err(builder.span, "diagnostic slug not specified")
                         .help(
@@ -146,9 +133,18 @@ impl<'a> LintDiagnosticDerive<'a> {
                 Some(slug) => {
                     slugs.borrow_mut().push(slug.clone());
                     quote! {
-                        crate::fluent_generated::#slug.into()
+                        diag.primary_message(crate::fluent_generated::#slug);
                     }
                 }
+            };
+
+            let formatting_init = &builder.formatting_init;
+            quote! {
+                #primary_message
+                #preamble
+                #formatting_init
+                #body
+                diag
             }
         });
 
@@ -161,10 +157,6 @@ impl<'a> LintDiagnosticDerive<'a> {
                 ) {
                     #implementation;
                 }
-
-                fn msg(&self) -> rustc_errors::DiagMessage {
-                    #msg
-                }
             }
         });
         for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs
index 086582e60a3c0..1e374715fd09d 100644
--- a/compiler/rustc_middle/src/lint.rs
+++ b/compiler/rustc_middle/src/lint.rs
@@ -2,7 +2,7 @@ use std::cmp;
 
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sorted_map::SortedMap;
-use rustc_errors::{Diag, DiagMessage, MultiSpan};
+use rustc_errors::{Diag, MultiSpan};
 use rustc_hir::{HirId, ItemLocalId};
 use rustc_macros::HashStable;
 use rustc_session::lint::{
@@ -269,7 +269,6 @@ pub fn lint_level(
     level: Level,
     src: LintLevelSource,
     span: Option<MultiSpan>,
-    msg: impl Into<DiagMessage>,
     decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
 ) {
     // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
@@ -281,7 +280,6 @@ pub fn lint_level(
         level: Level,
         src: LintLevelSource,
         span: Option<MultiSpan>,
-        msg: impl Into<DiagMessage>,
         decorate: Box<dyn '_ + for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)>,
     ) {
         // Check for future incompatibility lints and issue a stronger warning.
@@ -350,10 +348,6 @@ pub fn lint_level(
             }
         }
 
-        // Delay evaluating and setting the primary message until after we've
-        // suppressed the lint due to macros.
-        err.primary_message(msg);
-
         err.is_lint(lint.name_lower(), has_future_breakage);
 
         // Lint diagnostics that are covered by the expect level will not be emitted outside
@@ -418,7 +412,7 @@ pub fn lint_level(
         explain_lint_level_source(lint, level, src, &mut err);
         err.emit()
     }
-    lint_level_impl(sess, lint, level, src, span, msg, Box::new(decorate))
+    lint_level_impl(sess, lint, level, src, span, Box::new(decorate))
 }
 
 /// Returns whether `span` originates in a foreign crate's external macro.
diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs
index e5df05763b022..30f93577b7fa3 100644
--- a/compiler/rustc_middle/src/middle/stability.rs
+++ b/compiler/rustc_middle/src/middle/stability.rs
@@ -156,6 +156,13 @@ pub struct Deprecated {
 
 impl<'a, G: EmissionGuarantee> rustc_errors::LintDiagnostic<'a, G> for Deprecated {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) {
+        diag.primary_message(match &self.since_kind {
+            DeprecatedSinceKind::InEffect => crate::fluent_generated::middle_deprecated,
+            DeprecatedSinceKind::InFuture => crate::fluent_generated::middle_deprecated_in_future,
+            DeprecatedSinceKind::InVersion(_) => {
+                crate::fluent_generated::middle_deprecated_in_version
+            }
+        });
         diag.arg("kind", self.kind);
         diag.arg("path", self.path);
         if let DeprecatedSinceKind::InVersion(version) = self.since_kind {
@@ -171,16 +178,6 @@ impl<'a, G: EmissionGuarantee> rustc_errors::LintDiagnostic<'a, G> for Deprecate
             diag.subdiagnostic(diag.dcx, sub);
         }
     }
-
-    fn msg(&self) -> rustc_errors::DiagMessage {
-        match &self.since_kind {
-            DeprecatedSinceKind::InEffect => crate::fluent_generated::middle_deprecated,
-            DeprecatedSinceKind::InFuture => crate::fluent_generated::middle_deprecated_in_future,
-            DeprecatedSinceKind::InVersion(_) => {
-                crate::fluent_generated::middle_deprecated_in_version
-            }
-        }
-    }
 }
 
 fn deprecated_since_kind(is_in_effect: bool, since: DeprecatedSince) -> DeprecatedSinceKind {
@@ -597,7 +594,9 @@ impl<'tcx> TyCtxt<'tcx> {
         unmarked: impl FnOnce(Span, DefId),
     ) -> bool {
         let soft_handler = |lint, span, msg: String| {
-            self.node_span_lint(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, msg, |_| {})
+            self.node_span_lint(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
+                lint.primary_message(msg);
+            })
         };
         let eval_result =
             self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable);
diff --git a/compiler/rustc_middle/src/mir/interpret/queries.rs b/compiler/rustc_middle/src/mir/interpret/queries.rs
index 04d6301116eea..9addf937f94b3 100644
--- a/compiler/rustc_middle/src/mir/interpret/queries.rs
+++ b/compiler/rustc_middle/src/mir/interpret/queries.rs
@@ -112,8 +112,7 @@ impl<'tcx> TyCtxt<'tcx> {
                                 lint::builtin::CONST_EVALUATABLE_UNCHECKED,
                                 self.local_def_id_to_hir_id(local_def_id),
                                 self.def_span(ct.def),
-                                "cannot use constants which depend on generic parameters in types",
-                                |_| {},
+                                |lint| { lint.primary_message("cannot use constants which depend on generic parameters in types"); },
                             )
                         }
                     }
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 8185c99c2fd20..eaa969a90f8a0 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -48,9 +48,7 @@ use rustc_data_structures::sync::{self, FreezeReadGuard, Lock, Lrc, RwLock, Work
 #[cfg(parallel_compiler)]
 use rustc_data_structures::sync::{DynSend, DynSync};
 use rustc_data_structures::unord::UnordSet;
-use rustc_errors::{
-    Applicability, Diag, DiagCtxt, DiagMessage, ErrorGuaranteed, LintDiagnostic, MultiSpan,
-};
+use rustc_errors::{Applicability, Diag, DiagCtxt, ErrorGuaranteed, LintDiagnostic, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE};
@@ -2475,10 +2473,9 @@ impl<'tcx> TyCtxt<'tcx> {
         span: impl Into<MultiSpan>,
         decorator: impl for<'a> LintDiagnostic<'a, ()>,
     ) {
-        let msg = decorator.msg();
         let (level, src) = self.lint_level_at_node(lint, hir_id);
-        lint_level(self.sess, lint, level, src, Some(span.into()), msg, |diag| {
-            decorator.decorate_lint(diag);
+        lint_level(self.sess, lint, level, src, Some(span.into()), |lint| {
+            decorator.decorate_lint(lint);
         })
     }
 
@@ -2492,11 +2489,10 @@ impl<'tcx> TyCtxt<'tcx> {
         lint: &'static Lint,
         hir_id: HirId,
         span: impl Into<MultiSpan>,
-        msg: impl Into<DiagMessage>,
         decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
         let (level, src) = self.lint_level_at_node(lint, hir_id);
-        lint_level(self.sess, lint, level, src, Some(span.into()), msg, decorate);
+        lint_level(self.sess, lint, level, src, Some(span.into()), decorate);
     }
 
     /// Find the crate root and the appropriate span where `use` and outer attributes can be
@@ -2547,8 +2543,8 @@ impl<'tcx> TyCtxt<'tcx> {
         id: HirId,
         decorator: impl for<'a> LintDiagnostic<'a, ()>,
     ) {
-        self.node_lint(lint, id, decorator.msg(), |diag| {
-            decorator.decorate_lint(diag);
+        self.node_lint(lint, id, |lint| {
+            decorator.decorate_lint(lint);
         })
     }
 
@@ -2561,11 +2557,10 @@ impl<'tcx> TyCtxt<'tcx> {
         self,
         lint: &'static Lint,
         id: HirId,
-        msg: impl Into<DiagMessage>,
         decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
         let (level, src) = self.lint_level_at_node(lint, id);
-        lint_level(self.sess, lint, level, src, None, msg, decorate);
+        lint_level(self.sess, lint, level, src, None, decorate);
     }
 
     pub fn in_scope_traits(self, id: HirId) -> Option<&'tcx [TraitCandidate]> {
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 0634e321ea303..b28dcb38cb6bb 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -1,4 +1,4 @@
-use rustc_errors::{codes::*, Diag, DiagMessage, LintDiagnostic};
+use rustc_errors::{codes::*, Diag, LintDiagnostic};
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_middle::mir::AssertKind;
 use rustc_middle::ty::TyCtxt;
@@ -50,18 +50,15 @@ pub(crate) enum AssertLintKind {
 
 impl<'a, P: std::fmt::Debug> LintDiagnostic<'a, ()> for AssertLint<P> {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
-        let message = self.assert_kind.diagnostic_message();
+        diag.primary_message(match self.lint_kind {
+            AssertLintKind::ArithmeticOverflow => fluent::mir_transform_arithmetic_overflow,
+            AssertLintKind::UnconditionalPanic => fluent::mir_transform_operation_will_panic,
+        });
+        let label = self.assert_kind.diagnostic_message();
         self.assert_kind.add_args(&mut |name, value| {
             diag.arg(name, value);
         });
-        diag.span_label(self.span, message);
-    }
-
-    fn msg(&self) -> DiagMessage {
-        match self.lint_kind {
-            AssertLintKind::ArithmeticOverflow => fluent::mir_transform_arithmetic_overflow,
-            AssertLintKind::UnconditionalPanic => fluent::mir_transform_operation_will_panic,
-        }
+        diag.span_label(self.span, label);
     }
 }
 
@@ -104,6 +101,7 @@ pub(crate) struct MustNotSupend<'tcx, 'a> {
 // Needed for def_path_str
 impl<'a> LintDiagnostic<'a, ()> for MustNotSupend<'_, '_> {
     fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
+        diag.primary_message(fluent::mir_transform_must_not_suspend);
         diag.span_label(self.yield_sp, fluent::_subdiag::label);
         if let Some(reason) = self.reason {
             diag.subdiagnostic(diag.dcx, reason);
@@ -113,10 +111,6 @@ impl<'a> LintDiagnostic<'a, ()> for MustNotSupend<'_, '_> {
         diag.arg("def_path", self.tcx.def_path_str(self.def_id));
         diag.arg("post", self.post);
     }
-
-    fn msg(&self) -> rustc_errors::DiagMessage {
-        fluent::mir_transform_must_not_suspend
-    }
 }
 
 #[derive(Subdiagnostic)]
diff --git a/compiler/rustc_pattern_analysis/src/lints.rs b/compiler/rustc_pattern_analysis/src/lints.rs
index 1d10c9df4ab29..892aacd7ac5d9 100644
--- a/compiler/rustc_pattern_analysis/src/lints.rs
+++ b/compiler/rustc_pattern_analysis/src/lints.rs
@@ -99,7 +99,6 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'p, 'tcx>(
 
                 use rustc_errors::LintDiagnostic;
                 let mut err = rcx.tcx.dcx().struct_span_warn(arm.pat.data().span, "");
-                err.primary_message(decorator.msg());
                 decorator.decorate_lint(&mut err);
                 err.emit();
             }
diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs
index 8ce1271fc17a8..f4051561dae4f 100644
--- a/compiler/rustc_trait_selection/src/traits/object_safety.rs
+++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs
@@ -13,7 +13,7 @@ use super::elaborate;
 use crate::infer::TyCtxtInferExt;
 use crate::traits::query::evaluate_obligation::InferCtxtExt;
 use crate::traits::{self, Obligation, ObligationCause};
-use rustc_errors::{DelayDm, FatalError, MultiSpan};
+use rustc_errors::{FatalError, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_middle::query::Providers;
@@ -162,41 +162,36 @@ fn lint_object_unsafe_trait(
 ) {
     // Using `CRATE_NODE_ID` is wrong, but it's hard to get a more precise id.
     // It's also hard to get a use site span, so we use the method definition span.
-    tcx.node_span_lint(
-        WHERE_CLAUSES_OBJECT_SAFETY,
-        hir::CRATE_HIR_ID,
-        span,
-        DelayDm(|| format!("the trait `{}` cannot be made into an object", tcx.def_path_str(trait_def_id))),
-        |err| {
-            let node = tcx.hir().get_if_local(trait_def_id);
-            let mut spans = MultiSpan::from_span(span);
-            if let Some(hir::Node::Item(item)) = node {
-                spans.push_span_label(
-                    item.ident.span,
-                    "this trait cannot be made into an object...",
-                );
-                spans.push_span_label(span, format!("...because {}", violation.error_msg()));
-            } else {
-                spans.push_span_label(
-                    span,
-                    format!(
-                        "the trait cannot be made into an object because {}",
-                        violation.error_msg()
-                    ),
-                );
-            };
-            err.span_note(
-                spans,
-                "for a trait to be \"object safe\" it needs to allow building a vtable to allow the \
+    tcx.node_span_lint(WHERE_CLAUSES_OBJECT_SAFETY, hir::CRATE_HIR_ID, span, |err| {
+        err.primary_message(format!(
+            "the trait `{}` cannot be made into an object",
+            tcx.def_path_str(trait_def_id)
+        ));
+        let node = tcx.hir().get_if_local(trait_def_id);
+        let mut spans = MultiSpan::from_span(span);
+        if let Some(hir::Node::Item(item)) = node {
+            spans.push_span_label(item.ident.span, "this trait cannot be made into an object...");
+            spans.push_span_label(span, format!("...because {}", violation.error_msg()));
+        } else {
+            spans.push_span_label(
+                span,
+                format!(
+                    "the trait cannot be made into an object because {}",
+                    violation.error_msg()
+                ),
+            );
+        };
+        err.span_note(
+            spans,
+            "for a trait to be \"object safe\" it needs to allow building a vtable to allow the \
                 call to be resolvable dynamically; for more information visit \
                 <https://doc.rust-lang.org/reference/items/traits.html#object-safety>",
-            );
-            if node.is_some() {
-                // Only provide the help if its a local trait, otherwise it's not
-                violation.solution().add_to(err);
-            }
-        },
-    );
+        );
+        if node.is_some() {
+            // Only provide the help if its a local trait, otherwise it's not
+            violation.solution().add_to(err);
+        }
+    });
 }
 
 fn sized_trait_bound_spans<'tcx>(
diff --git a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
index 826bb706f48b1..c2727ae6bfd9a 100644
--- a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs
@@ -21,7 +21,7 @@ use crate::traits::{
     self, coherence, FutureCompatOverlapErrorKind, ObligationCause, ObligationCtxt,
 };
 use rustc_data_structures::fx::FxIndexSet;
-use rustc_errors::{codes::*, DelayDm, Diag, EmissionGuarantee};
+use rustc_errors::{codes::*, Diag, EmissionGuarantee};
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::bug;
 use rustc_middle::ty::{self, ImplSubject, Ty, TyCtxt, TypeVisitableExt};
@@ -449,7 +449,7 @@ fn report_conflicting_impls<'tcx>(
         }
     }
 
-    let msg = DelayDm(|| {
+    let msg = || {
         format!(
             "conflicting implementations of trait `{}`{}{}",
             overlap.trait_ref.print_trait_sugared(),
@@ -459,7 +459,7 @@ fn report_conflicting_impls<'tcx>(
                 _ => "",
             }
         )
-    });
+    };
 
     // Don't report overlap errors if the header references error
     if let Err(err) = (overlap.trait_ref, overlap.self_ty).error_reported() {
@@ -471,7 +471,7 @@ fn report_conflicting_impls<'tcx>(
             let reported = if overlap.with_impl.is_local()
                 || tcx.ensure().orphan_check_impl(impl_def_id).is_ok()
             {
-                let mut err = tcx.dcx().struct_span_err(impl_span, msg);
+                let mut err = tcx.dcx().struct_span_err(impl_span, msg());
                 err.code(E0119);
                 decorate(tcx, &overlap, impl_span, &mut err);
                 err.emit()
@@ -485,15 +485,10 @@ fn report_conflicting_impls<'tcx>(
                 FutureCompatOverlapErrorKind::OrderDepTraitObjects => ORDER_DEPENDENT_TRAIT_OBJECTS,
                 FutureCompatOverlapErrorKind::LeakCheck => COHERENCE_LEAK_CHECK,
             };
-            tcx.node_span_lint(
-                lint,
-                tcx.local_def_id_to_hir_id(impl_def_id),
-                impl_span,
-                msg,
-                |err| {
-                    decorate(tcx, &overlap, impl_span, err);
-                },
-            );
+            tcx.node_span_lint(lint, tcx.local_def_id_to_hir_id(impl_def_id), impl_span, |err| {
+                err.primary_message(msg());
+                decorate(tcx, &overlap, impl_span, err);
+            });
             Ok(())
         }
     }
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index feb03b9a823e8..4b132cf60d3ba 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -372,8 +372,8 @@ pub(crate) fn run_global_ctxt(
         tcx.node_lint(
             crate::lint::MISSING_CRATE_LEVEL_DOCS,
             DocContext::as_local_hir_id(tcx, krate.module.item_id).unwrap(),
-            "no documentation found for this crate's top-level module",
             |lint| {
+                lint.primary_message("no documentation found for this crate's top-level module");
                 lint.help(help);
             },
         );
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 5c5651f3ef0e6..362f9021671f5 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -834,8 +834,9 @@ impl<'tcx> ExtraInfo<'tcx> {
                 crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
                 self.tcx.local_def_id_to_hir_id(def_id),
                 self.sp,
-                msg,
-                |_| {},
+                |lint| {
+                    lint.primary_message(msg);
+                },
             );
         }
     }
@@ -850,8 +851,10 @@ impl<'tcx> ExtraInfo<'tcx> {
                 crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
                 self.tcx.local_def_id_to_hir_id(def_id),
                 self.sp,
-                msg,
-                f,
+                |lint| {
+                    lint.primary_message(msg);
+                    f(lint);
+                },
             );
         }
     }
diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs
index e85b998bfbe1b..257bab3e9fcb0 100644
--- a/src/librustdoc/passes/check_doc_test_visibility.rs
+++ b/src/librustdoc/passes/check_doc_test_visibility.rs
@@ -125,13 +125,9 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item
         if should_have_doc_example(cx, item) {
             debug!("reporting error for {item:?} (hir_id={hir_id:?})");
             let sp = item.attr_span(cx.tcx);
-            cx.tcx.node_span_lint(
-                crate::lint::MISSING_DOC_CODE_EXAMPLES,
-                hir_id,
-                sp,
-                "missing code example in this documentation",
-                |_| {},
-            );
+            cx.tcx.node_span_lint(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id, sp, |lint| {
+                lint.primary_message("missing code example in this documentation");
+            });
         }
     } else if tests.found_tests > 0
         && !cx.cache.effective_visibilities.is_exported(cx.tcx, item.item_id.expect_def_id())
@@ -140,8 +136,9 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item
             crate::lint::PRIVATE_DOC_TESTS,
             hir_id,
             item.attr_span(cx.tcx),
-            "documentation test in private item",
-            |_| {},
+            |lint| {
+                lint.primary_message("documentation test in private item");
+            },
         );
     }
 }
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 747f5c0a83562..8ab24a8c12e58 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -1689,7 +1689,9 @@ fn report_diagnostic(
 
     let sp = item.attr_span(tcx);
 
-    tcx.node_span_lint(lint, hir_id, sp, msg, |lint| {
+    tcx.node_span_lint(lint, hir_id, sp, |lint| {
+        lint.primary_message(msg);
+
         let (span, link_range) = match link_range {
             MarkdownLinkRange::Destination(md_range) => {
                 let mut md_range = md_range.clone();
diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs
index c6989fbbf255e..8f68f6ff4764a 100644
--- a/src/librustdoc/passes/lint/bare_urls.rs
+++ b/src/librustdoc/passes/lint/bare_urls.rs
@@ -24,8 +24,9 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) {
                 let sp =
                     source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs.doc_strings)
                         .unwrap_or_else(|| item.attr_span(cx.tcx));
-                cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, msg, |lint| {
-                    lint.note("bare URLs are not automatically turned into clickable links")
+                cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| {
+                    lint.primary_message(msg)
+                        .note("bare URLs are not automatically turned into clickable links")
                         .span_suggestion(
                             sp,
                             "use an automatic link instead",
diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs
index 7b81b5e63be6f..9562559fba269 100644
--- a/src/librustdoc/passes/lint/check_code_block_syntax.rs
+++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs
@@ -99,7 +99,9 @@ fn check_rust_syntax(
     // All points of divergence have been handled earlier so this can be
     // done the same way whether the span is precise or not.
     let hir_id = cx.tcx.local_def_id_to_hir_id(local_id);
-    cx.tcx.node_span_lint(crate::lint::INVALID_RUST_CODEBLOCKS, hir_id, sp, msg, |lint| {
+    cx.tcx.node_span_lint(crate::lint::INVALID_RUST_CODEBLOCKS, hir_id, sp, |lint| {
+        lint.primary_message(msg);
+
         let explanation = if is_ignore {
             "`ignore` code blocks require valid Rust code for syntax highlighting; \
                     mark blocks that do not contain Rust code as text"
diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs
index da3770aa927af..25b0c61b826f9 100644
--- a/src/librustdoc/passes/lint/html_tags.rs
+++ b/src/librustdoc/passes/lint/html_tags.rs
@@ -25,8 +25,11 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
                 Some(sp) => sp,
                 None => item.attr_span(tcx),
             };
-            tcx.node_span_lint(crate::lint::INVALID_HTML_TAGS, hir_id, sp, msg, |lint| {
+            tcx.node_span_lint(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| {
                 use rustc_lint_defs::Applicability;
+
+                lint.primary_message(msg);
+
                 // If a tag looks like `<this>`, it might actually be a generic.
                 // We don't try to detect stuff `<like, this>` because that's not valid HTML,
                 // and we don't try to detect stuff `<like this>` because that's not valid Rust.
diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs
index 098860245951a..7ab974046b9c7 100644
--- a/src/librustdoc/passes/lint/redundant_explicit_links.rs
+++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs
@@ -188,8 +188,9 @@ fn check_inline_or_reference_unknown_redundancy(
             &item.attrs.doc_strings,
         )?;
 
-        cx.tcx.node_span_lint(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| {
-            lint.span_label(explicit_span, "explicit target is redundant")
+        cx.tcx.node_span_lint(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, |lint| {
+            lint.primary_message("redundant explicit link target")
+                .span_label(explicit_span, "explicit target is redundant")
                 .span_label(display_span, "because label contains path that resolves to same destination")
                 .note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links")
                 .span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways);
@@ -238,8 +239,9 @@ fn check_reference_redundancy(
             &item.attrs.doc_strings,
         )?;
 
-        cx.tcx.node_span_lint(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| {
-            lint.span_label(explicit_span, "explicit target is redundant")
+        cx.tcx.node_span_lint(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, |lint| {
+            lint.primary_message("redundant explicit link target")
+            .span_label(explicit_span, "explicit target is redundant")
                 .span_label(display_span, "because label contains path that resolves to same destination")
                 .span_note(def_span, "referenced explicit link target defined here")
                 .note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links")
diff --git a/src/librustdoc/passes/lint/unescaped_backticks.rs b/src/librustdoc/passes/lint/unescaped_backticks.rs
index 4ea926cb79aab..be9670077d171 100644
--- a/src/librustdoc/passes/lint/unescaped_backticks.rs
+++ b/src/librustdoc/passes/lint/unescaped_backticks.rs
@@ -56,17 +56,28 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
                 )
                 .unwrap_or_else(|| item.attr_span(tcx));
 
-                tcx.node_span_lint(crate::lint::UNESCAPED_BACKTICKS, hir_id, span, "unescaped backtick", |lint| {
+                tcx.node_span_lint(crate::lint::UNESCAPED_BACKTICKS, hir_id, span, |lint| {
+                    lint.primary_message("unescaped backtick");
+
                     let mut help_emitted = false;
 
                     match element.prev_code_guess {
                         PrevCodeGuess::None => {}
                         PrevCodeGuess::Start { guess, .. } => {
                             // "foo` `bar`" -> "`foo` `bar`"
-                            if let Some(suggest_index) = clamp_start(guess, &element.suggestible_ranges)
+                            if let Some(suggest_index) =
+                                clamp_start(guess, &element.suggestible_ranges)
                                 && can_suggest_backtick(&dox, suggest_index)
                             {
-                                suggest_insertion(cx, item, &dox, lint, suggest_index, '`', "the opening backtick of a previous inline code may be missing");
+                                suggest_insertion(
+                                    cx,
+                                    item,
+                                    &dox,
+                                    lint,
+                                    suggest_index,
+                                    '`',
+                                    "the opening backtick of a previous inline code may be missing",
+                                );
                                 help_emitted = true;
                             }
                         }
@@ -76,7 +87,15 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
                             // an inline code node and we intentionally "break" the inline code here.
                             let suggest_index = guess;
                             if can_suggest_backtick(&dox, suggest_index) {
-                                suggest_insertion(cx, item, &dox, lint, suggest_index, '`', "a previous inline code might be longer than expected");
+                                suggest_insertion(
+                                    cx,
+                                    item,
+                                    &dox,
+                                    lint,
+                                    suggest_index,
+                                    '`',
+                                    "a previous inline code might be longer than expected",
+                                );
                                 help_emitted = true;
                             }
                         }
@@ -84,11 +103,21 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
 
                     if !element.prev_code_guess.is_confident() {
                         // "`foo` bar`" -> "`foo` `bar`"
-                        if let Some(guess) = guess_start_of_code(&dox, element.element_range.start..backtick_index)
-                            && let Some(suggest_index) = clamp_start(guess, &element.suggestible_ranges)
+                        if let Some(guess) =
+                            guess_start_of_code(&dox, element.element_range.start..backtick_index)
+                            && let Some(suggest_index) =
+                                clamp_start(guess, &element.suggestible_ranges)
                             && can_suggest_backtick(&dox, suggest_index)
                         {
-                            suggest_insertion(cx, item, &dox, lint, suggest_index, '`', "the opening backtick of an inline code may be missing");
+                            suggest_insertion(
+                                cx,
+                                item,
+                                &dox,
+                                lint,
+                                suggest_index,
+                                '`',
+                                "the opening backtick of an inline code may be missing",
+                            );
                             help_emitted = true;
                         }
 
@@ -96,21 +125,41 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
                         // Don't suggest closing backtick after single trailing char,
                         // if we already suggested opening backtick. For example:
                         // "foo`." -> "`foo`." or "foo`s" -> "`foo`s".
-                        if let Some(guess) = guess_end_of_code(&dox, backtick_index + 1..element.element_range.end)
-                            && let Some(suggest_index) = clamp_end(guess, &element.suggestible_ranges)
+                        if let Some(guess) =
+                            guess_end_of_code(&dox, backtick_index + 1..element.element_range.end)
+                            && let Some(suggest_index) =
+                                clamp_end(guess, &element.suggestible_ranges)
                             && can_suggest_backtick(&dox, suggest_index)
                             && (!help_emitted || suggest_index - backtick_index > 2)
                         {
-                            suggest_insertion(cx, item, &dox, lint, suggest_index, '`', "the closing backtick of an inline code may be missing");
+                            suggest_insertion(
+                                cx,
+                                item,
+                                &dox,
+                                lint,
+                                suggest_index,
+                                '`',
+                                "the closing backtick of an inline code may be missing",
+                            );
                             help_emitted = true;
                         }
                     }
 
                     if !help_emitted {
-                        lint.help("the opening or closing backtick of an inline code may be missing");
+                        lint.help(
+                            "the opening or closing backtick of an inline code may be missing",
+                        );
                     }
 
-                    suggest_insertion(cx, item, &dox, lint, backtick_index, '\\', "if you meant to use a literal backtick, escape it");
+                    suggest_insertion(
+                        cx,
+                        item,
+                        &dox,
+                        lint,
+                        backtick_index,
+                        '\\',
+                        "if you meant to use a literal backtick, escape it",
+                    );
                 });
             }
             Event::Code(_) => {
diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs
index dc0a139e3c786..0641d37cd9a67 100644
--- a/src/tools/clippy/clippy_utils/src/diagnostics.rs
+++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs
@@ -61,7 +61,8 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) {
 /// ```
 pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: impl Into<DiagMessage>) {
     #[expect(clippy::disallowed_methods)]
-    cx.span_lint(lint, sp, msg.into(), |diag| {
+    cx.span_lint(lint, sp, |diag| {
+        diag.primary_message(msg);
         docs_link(diag, lint);
     });
 }
@@ -109,7 +110,8 @@ pub fn span_lint_and_help<T: LintContext>(
     help: impl Into<SubdiagMessage>,
 ) {
     #[expect(clippy::disallowed_methods)]
-    cx.span_lint(lint, span, msg.into(), |diag| {
+    cx.span_lint(lint, span, |diag| {
+        diag.primary_message(msg);
         if let Some(help_span) = help_span {
             diag.span_help(help_span, help.into());
         } else {
@@ -165,7 +167,8 @@ pub fn span_lint_and_note<T: LintContext>(
     note: impl Into<SubdiagMessage>,
 ) {
     #[expect(clippy::disallowed_methods)]
-    cx.span_lint(lint, span, msg.into(), |diag| {
+    cx.span_lint(lint, span, |diag| {
+        diag.primary_message(msg);
         if let Some(note_span) = note_span {
             diag.span_note(note_span, note.into());
         } else {
@@ -201,7 +204,8 @@ where
     F: FnOnce(&mut Diag<'_, ()>),
 {
     #[expect(clippy::disallowed_methods)]
-    cx.span_lint(lint, sp, msg, |diag| {
+    cx.span_lint(lint, sp, |diag| {
+        diag.primary_message(msg);
         f(diag);
         docs_link(diag, lint);
     });
@@ -233,7 +237,8 @@ where
 /// the `#[allow]` will work.
 pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: impl Into<DiagMessage>) {
     #[expect(clippy::disallowed_methods)]
-    cx.tcx.node_span_lint(lint, hir_id, sp, msg.into(), |diag| {
+    cx.tcx.node_span_lint(lint, hir_id, sp, |diag| {
+        diag.primary_message(msg);
         docs_link(diag, lint);
     });
 }
@@ -271,7 +276,8 @@ pub fn span_lint_hir_and_then(
     f: impl FnOnce(&mut Diag<'_, ()>),
 ) {
     #[expect(clippy::disallowed_methods)]
-    cx.tcx.node_span_lint(lint, hir_id, sp, msg.into(), |diag| {
+    cx.tcx.node_span_lint(lint, hir_id, sp, |diag| {
+        diag.primary_message(msg);
         f(diag);
         docs_link(diag, lint);
     });
diff --git a/src/tools/clippy/tests/ui-internal/disallow_span_lint.rs b/src/tools/clippy/tests/ui-internal/disallow_span_lint.rs
index 5a2a868ed3ec6..b91a83308b5f5 100644
--- a/src/tools/clippy/tests/ui-internal/disallow_span_lint.rs
+++ b/src/tools/clippy/tests/ui-internal/disallow_span_lint.rs
@@ -11,11 +11,11 @@ use rustc_lint::{Lint, LintContext};
 use rustc_middle::ty::TyCtxt;
 
 pub fn a(cx: impl LintContext, lint: &'static Lint, span: impl Into<MultiSpan>, msg: impl Into<DiagMessage>) {
-    cx.span_lint(lint, span, msg, |_| {});
+    cx.span_lint(lint, span, |lint| { lint.primary_message(msg); });
 }
 
 pub fn b(tcx: TyCtxt<'_>, lint: &'static Lint, hir_id: HirId, span: impl Into<MultiSpan>, msg: impl Into<DiagMessage>) {
-    tcx.node_span_lint(lint, hir_id, span, msg, |_| {});
+    tcx.node_span_lint(lint, hir_id, span, |lint| { lint.primary_message(msg); });
 }
 
 fn main() {}
diff --git a/src/tools/clippy/tests/ui-internal/disallow_span_lint.stderr b/src/tools/clippy/tests/ui-internal/disallow_span_lint.stderr
index cfc590bed3697..1cfbc8efc8ed1 100644
--- a/src/tools/clippy/tests/ui-internal/disallow_span_lint.stderr
+++ b/src/tools/clippy/tests/ui-internal/disallow_span_lint.stderr
@@ -1,8 +1,8 @@
 error: use of a disallowed method `rustc_lint::context::LintContext::span_lint`
   --> tests/ui-internal/disallow_span_lint.rs:14:5
    |
-LL |     cx.span_lint(lint, span, msg, |_| {});
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     cx.span_lint(lint, span, |lint| { lint.primary_message(msg); });
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead (from clippy.toml)
    = note: `-D clippy::disallowed-methods` implied by `-D warnings`
@@ -11,8 +11,8 @@ LL |     cx.span_lint(lint, span, msg, |_| {});
 error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint`
   --> tests/ui-internal/disallow_span_lint.rs:18:5
    |
-LL |     tcx.node_span_lint(lint, hir_id, span, msg, |_| {});
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     tcx.node_span_lint(lint, hir_id, span, |lint| { lint.primary_message(msg); });
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead (from clippy.toml)
 
diff --git a/tests/ui-fulldeps/internal-lints/diagnostics.rs b/tests/ui-fulldeps/internal-lints/diagnostics.rs
index 380b2b6c431d5..7c3a2c97474de 100644
--- a/tests/ui-fulldeps/internal-lints/diagnostics.rs
+++ b/tests/ui-fulldeps/internal-lints/diagnostics.rs
@@ -85,10 +85,6 @@ impl<'a> LintDiagnostic<'a, ()> for UntranslatableInLintDiagnostic {
         diag.note("untranslatable diagnostic");
         //~^ ERROR diagnostics should be created using translatable messages
     }
-
-    fn msg(&self) -> DiagMessage {
-        unreachable!();
-    }
 }
 
 pub struct TranslatableInLintDiagnostic;
@@ -97,10 +93,6 @@ impl<'a> LintDiagnostic<'a, ()> for TranslatableInLintDiagnostic {
     fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
         diag.note(crate::fluent_generated::no_crate_note);
     }
-
-    fn msg(&self) -> DiagMessage {
-        unreachable!();
-    }
 }
 
 pub fn make_diagnostics<'a>(dcx: &'a DiagCtxt) {
diff --git a/tests/ui-fulldeps/internal-lints/diagnostics.stderr b/tests/ui-fulldeps/internal-lints/diagnostics.stderr
index ee5400f6f9514..669324ce5d4c1 100644
--- a/tests/ui-fulldeps/internal-lints/diagnostics.stderr
+++ b/tests/ui-fulldeps/internal-lints/diagnostics.stderr
@@ -23,7 +23,7 @@ LL |         diag.note("untranslatable diagnostic");
    |              ^^^^
 
 error: diagnostics should only be created in `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls
-  --> $DIR/diagnostics.rs:107:21
+  --> $DIR/diagnostics.rs:99:21
    |
 LL |     let _diag = dcx.struct_err(crate::fluent_generated::no_crate_example);
    |                     ^^^^^^^^^^
@@ -35,13 +35,13 @@ LL | #![deny(rustc::diagnostic_outside_of_impl)]
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: diagnostics should only be created in `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls
-  --> $DIR/diagnostics.rs:110:21
+  --> $DIR/diagnostics.rs:102:21
    |
 LL |     let _diag = dcx.struct_err("untranslatable diagnostic");
    |                     ^^^^^^^^^^
 
 error: diagnostics should be created using translatable messages
-  --> $DIR/diagnostics.rs:110:21
+  --> $DIR/diagnostics.rs:102:21
    |
 LL |     let _diag = dcx.struct_err("untranslatable diagnostic");
    |                     ^^^^^^^^^^
diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
index 043c7c1665402..4f50837d5fe49 100644
--- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
+++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
@@ -603,7 +603,6 @@ struct LintAttributeOnSessionDiag {}
 #[derive(LintDiagnostic)]
 #[lint(no_crate_example, code = E0123)]
 //~^ ERROR `#[lint(...)]` is not a valid attribute
-//~| ERROR `#[lint(...)]` is not a valid attribute
 //~| ERROR diagnostic slug not specified
 //~| ERROR cannot find attribute `lint` in this scope
 struct LintAttributeOnLintDiag {}
diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
index 1266430e2f776..e36c6852d3b20 100644
--- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
+++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
@@ -378,14 +378,6 @@ error: `#[lint(...)]` is not a valid attribute
 LL | #[lint(no_crate_example, code = E0123)]
    | ^
 
-error: `#[lint(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:604:1
-   |
-LL | #[lint(no_crate_example, code = E0123)]
-   | ^
-   |
-   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
-
 error: diagnostic slug not specified
   --> $DIR/diagnostic-derive.rs:604:1
    |
@@ -395,19 +387,19 @@ LL | #[lint(no_crate_example, code = E0123)]
    = help: specify the slug as the first argument to the attribute, such as `#[diag(compiletest_example)]`
 
 error: specified multiple times
-  --> $DIR/diagnostic-derive.rs:614:53
+  --> $DIR/diagnostic-derive.rs:613:53
    |
 LL |     #[suggestion(no_crate_suggestion, code = "...", code = ",,,")]
    |                                                     ^^^^
    |
 note: previously specified here
-  --> $DIR/diagnostic-derive.rs:614:39
+  --> $DIR/diagnostic-derive.rs:613:39
    |
 LL |     #[suggestion(no_crate_suggestion, code = "...", code = ",,,")]
    |                                       ^^^^
 
 error: wrong types for suggestion
-  --> $DIR/diagnostic-derive.rs:623:24
+  --> $DIR/diagnostic-derive.rs:622:24
    |
 LL |     suggestion: (Span, usize),
    |                        ^^^^^
@@ -415,7 +407,7 @@ LL |     suggestion: (Span, usize),
    = help: `#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`
 
 error: wrong types for suggestion
-  --> $DIR/diagnostic-derive.rs:631:17
+  --> $DIR/diagnostic-derive.rs:630:17
    |
 LL |     suggestion: (Span,),
    |                 ^^^^^^^
@@ -423,13 +415,13 @@ LL |     suggestion: (Span,),
    = help: `#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`
 
 error: suggestion without `code = "..."`
-  --> $DIR/diagnostic-derive.rs:638:5
+  --> $DIR/diagnostic-derive.rs:637:5
    |
 LL |     #[suggestion(no_crate_suggestion)]
    |     ^
 
 error: `#[multipart_suggestion(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:645:1
+  --> $DIR/diagnostic-derive.rs:644:1
    |
 LL | #[multipart_suggestion(no_crate_suggestion)]
    | ^
@@ -437,7 +429,7 @@ LL | #[multipart_suggestion(no_crate_suggestion)]
    = help: consider creating a `Subdiagnostic` instead
 
 error: `#[multipart_suggestion(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:648:1
+  --> $DIR/diagnostic-derive.rs:647:1
    |
 LL | #[multipart_suggestion()]
    | ^
@@ -445,7 +437,7 @@ LL | #[multipart_suggestion()]
    = help: consider creating a `Subdiagnostic` instead
 
 error: `#[multipart_suggestion(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:652:5
+  --> $DIR/diagnostic-derive.rs:651:5
    |
 LL |     #[multipart_suggestion(no_crate_suggestion)]
    |     ^
@@ -453,7 +445,7 @@ LL |     #[multipart_suggestion(no_crate_suggestion)]
    = help: consider creating a `Subdiagnostic` instead
 
 error: `#[suggestion(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:660:1
+  --> $DIR/diagnostic-derive.rs:659:1
    |
 LL | #[suggestion(no_crate_suggestion, code = "...")]
    | ^
@@ -461,7 +453,7 @@ LL | #[suggestion(no_crate_suggestion, code = "...")]
    = help: `#[label]` and `#[suggestion]` can only be applied to fields
 
 error: `#[label]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:669:1
+  --> $DIR/diagnostic-derive.rs:668:1
    |
 LL | #[label]
    | ^
@@ -469,61 +461,61 @@ LL | #[label]
    = help: `#[label]` and `#[suggestion]` can only be applied to fields
 
 error: `#[subdiagnostic(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:703:5
+  --> $DIR/diagnostic-derive.rs:702:5
    |
 LL |     #[subdiagnostic(bad)]
    |     ^
 
 error: `#[subdiagnostic = ...]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:711:5
+  --> $DIR/diagnostic-derive.rs:710:5
    |
 LL |     #[subdiagnostic = "bad"]
    |     ^
 
 error: `#[subdiagnostic(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:719:5
+  --> $DIR/diagnostic-derive.rs:718:5
    |
 LL |     #[subdiagnostic(bad, bad)]
    |     ^
 
 error: `#[subdiagnostic(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:727:5
+  --> $DIR/diagnostic-derive.rs:726:5
    |
 LL |     #[subdiagnostic("bad")]
    |     ^
 
 error: `#[subdiagnostic(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:735:5
+  --> $DIR/diagnostic-derive.rs:734:5
    |
 LL |     #[subdiagnostic(eager)]
    |     ^
 
 error: `#[subdiagnostic(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:743:5
+  --> $DIR/diagnostic-derive.rs:742:5
    |
 LL |     #[subdiagnostic(eager)]
    |     ^
 
 error: `#[subdiagnostic(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:764:5
+  --> $DIR/diagnostic-derive.rs:763:5
    |
 LL |     #[subdiagnostic(eager)]
    |     ^
 
 error: expected at least one string literal for `code(...)`
-  --> $DIR/diagnostic-derive.rs:795:23
+  --> $DIR/diagnostic-derive.rs:794:23
    |
 LL |     #[suggestion(code())]
    |                       ^
 
 error: `code(...)` must contain only string literals
-  --> $DIR/diagnostic-derive.rs:803:23
+  --> $DIR/diagnostic-derive.rs:802:23
    |
 LL |     #[suggestion(code(foo))]
    |                       ^^^
 
 error: `#[suggestion(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:827:5
+  --> $DIR/diagnostic-derive.rs:826:5
    |
 LL |     #[suggestion(no_crate_suggestion, code = "")]
    |     ^
@@ -539,13 +531,13 @@ LL | #[diag = "E0123"]
    |        ^ maybe a missing crate `core`?
 
 error[E0433]: failed to resolve: maybe a missing crate `core`?
-  --> $DIR/diagnostic-derive.rs:803:23
+  --> $DIR/diagnostic-derive.rs:802:23
    |
 LL |     #[suggestion(code(foo))]
    |                       ^^^ maybe a missing crate `core`?
 
 error[E0433]: failed to resolve: maybe a missing crate `core`?
-  --> $DIR/diagnostic-derive.rs:812:25
+  --> $DIR/diagnostic-derive.rs:811:25
    |
 LL |     #[suggestion(code = 3)]
    |                         ^ maybe a missing crate `core`?
@@ -587,19 +579,19 @@ LL | #[lint(no_crate_example, code = E0123)]
    |   ^^^^ help: a built-in attribute with a similar name exists: `link`
 
 error: cannot find attribute `multipart_suggestion` in this scope
-  --> $DIR/diagnostic-derive.rs:645:3
+  --> $DIR/diagnostic-derive.rs:644:3
    |
 LL | #[multipart_suggestion(no_crate_suggestion)]
    |   ^^^^^^^^^^^^^^^^^^^^
 
 error: cannot find attribute `multipart_suggestion` in this scope
-  --> $DIR/diagnostic-derive.rs:648:3
+  --> $DIR/diagnostic-derive.rs:647:3
    |
 LL | #[multipart_suggestion()]
    |   ^^^^^^^^^^^^^^^^^^^^
 
 error: cannot find attribute `multipart_suggestion` in this scope
-  --> $DIR/diagnostic-derive.rs:652:7
+  --> $DIR/diagnostic-derive.rs:651:7
    |
 LL |     #[multipart_suggestion(no_crate_suggestion)]
    |       ^^^^^^^^^^^^^^^^^^^^
@@ -611,7 +603,7 @@ LL | #[diag(nonsense, code = E0123)]
    |        ^^^^^^^^ not found in `crate::fluent_generated`
 
 error[E0425]: cannot find value `__code_34` in this scope
-  --> $DIR/diagnostic-derive.rs:809:10
+  --> $DIR/diagnostic-derive.rs:808:10
    |
 LL | #[derive(Diagnostic)]
    |          ^^^^^^^^^^ not found in this scope
@@ -632,7 +624,7 @@ note: required by a bound in `Diag::<'a, G>::arg`
   --> $COMPILER_DIR/rustc_errors/src/diagnostic.rs:LL:CC
    = note: this error originates in the macro `with_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: aborting due to 86 previous errors
+error: aborting due to 85 previous errors
 
 Some errors have detailed explanations: E0277, E0425, E0433.
 For more information about an error, try `rustc --explain E0277`.

From 9f67c50128f7ef57295c5f9b56e22031f4ed8d03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= <me@fmease.dev>
Date: Thu, 23 May 2024 03:50:43 +0200
Subject: [PATCH 3/4] Remove `DelayDm`

With the removal of `LintDiagnostic::msg` / the `msg` param from
lint diag APIs, primary messages for lint diags are always constructed
lazily inside decorator fns rendering this wrapper type unused / useless.
---
 compiler/rustc_error_messages/src/lib.rs     | 11 -----------
 compiler/rustc_errors/src/lib.rs             |  6 +++---
 compiler/rustc_middle/src/ty/print/pretty.rs |  2 +-
 3 files changed, 4 insertions(+), 15 deletions(-)

diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index ad82c2d96e7a7..c04386c03f2f3 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -367,17 +367,6 @@ impl From<Cow<'static, str>> for DiagMessage {
     }
 }
 
-/// A workaround for must_produce_diag ICEs when formatting types in disabled lints.
-///
-/// Delays formatting until `.into(): DiagMessage` is used.
-pub struct DelayDm<F>(pub F);
-
-impl<F: FnOnce() -> String> From<DelayDm<F>> for DiagMessage {
-    fn from(DelayDm(f): DelayDm<F>) -> Self {
-        DiagMessage::from(f())
-    }
-}
-
 /// Translating *into* a subdiagnostic message from a diagnostic message is a little strange - but
 /// the subdiagnostic functions (e.g. `span_label`) take a `SubdiagMessage` and the
 /// subdiagnostic derive refers to typed identifiers that are `DiagMessage`s, so need to be
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 3b884ff864ab1..4dc5d84b318bd 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -39,7 +39,7 @@ pub use diagnostic_impls::{
 };
 pub use emitter::ColorConfig;
 pub use rustc_error_messages::{
-    fallback_fluent_bundle, fluent_bundle, DelayDm, DiagMessage, FluentBundle, LanguageIdentifier,
+    fallback_fluent_bundle, fluent_bundle, DiagMessage, FluentBundle, LanguageIdentifier,
     LazyFallbackBundle, MultiSpan, SpanLabel, SubdiagMessage,
 };
 pub use rustc_lint_defs::{pluralize, Applicability};
@@ -572,8 +572,8 @@ impl Drop for DiagCtxtInner {
             if let Some(backtrace) = &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. \
-                       called at: {backtrace}"
+                     `with_no_trimmed_paths` for debugging. \
+                     called at: {backtrace}"
                 );
             }
         }
diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs
index 0dbb17e9db44e..cfe52980e1cc9 100644
--- a/compiler/rustc_middle/src/ty/print/pretty.rs
+++ b/compiler/rustc_middle/src/ty/print/pretty.rs
@@ -3327,7 +3327,7 @@ fn for_each_def(tcx: TyCtxt<'_>, mut collect_fn: impl for<'b> FnMut(&'b Ident, N
 ///
 /// The implementation uses similar import discovery logic to that of 'use' suggestions.
 ///
-/// See also [`DelayDm`](rustc_error_messages::DelayDm) and [`with_no_trimmed_paths!`].
+/// See also [`with_no_trimmed_paths!`].
 // this is pub to be able to intra-doc-link it
 pub fn trimmed_def_paths(tcx: TyCtxt<'_>, (): ()) -> DefIdMap<Symbol> {
     // Trimming paths is expensive and not optimized, since we expect it to only be used for error

From 37bf2d2dabdbdce9473b0fed1708fcbf31ba9c1a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= <me@fmease.dev>
Date: Wed, 22 May 2024 17:03:25 +0200
Subject: [PATCH 4/4] Delay the construction of early lint diag structs

Fixes a slew of perf regressions.
---
 compiler/rustc_lint/src/context.rs            |   4 +-
 .../rustc_lint/src/context/diagnostics.rs     | 383 +++++++-----------
 2 files changed, 156 insertions(+), 231 deletions(-)

diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 34b84658dc0b9..1a51f4f388d67 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -539,7 +539,9 @@ impl EarlyContext<'_> {
         span: MultiSpan,
         diagnostic: BuiltinLintDiag,
     ) {
-        diagnostics::emit_buffered_lint(self, lint, span, diagnostic)
+        self.opt_span_lint(lint, Some(span), |diag| {
+            diagnostics::decorate_lint(self.sess(), diagnostic, diag);
+        });
     }
 }
 
diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs
index 236eeee615218..1d9365ab96835 100644
--- a/compiler/rustc_lint/src/context/diagnostics.rs
+++ b/compiler/rustc_lint/src/context/diagnostics.rs
@@ -4,23 +4,18 @@
 use std::borrow::Cow;
 
 use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
-use rustc_errors::Applicability;
-use rustc_errors::{elided_lifetime_in_path_suggestion, DiagArgValue, MultiSpan};
+use rustc_errors::elided_lifetime_in_path_suggestion;
+use rustc_errors::{Applicability, Diag, DiagArgValue, LintDiagnostic};
 use rustc_middle::middle::stability;
-use rustc_session::lint::{BuiltinLintDiag, Lint};
+use rustc_session::lint::BuiltinLintDiag;
+use rustc_session::Session;
 use rustc_span::BytePos;
 
-use crate::{lints, EarlyContext, LintContext as _};
+use crate::lints;
 
 mod check_cfg;
 
-pub(super) fn emit_buffered_lint(
-    ctx: &EarlyContext<'_>,
-    lint: &'static Lint,
-    span: MultiSpan,
-    diagnostic: BuiltinLintDiag,
-) {
-    let sess = ctx.sess();
+pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Diag<'_, ()>) {
     match diagnostic {
         BuiltinLintDiag::UnicodeTextFlow(comment_span, content) => {
             let spans: Vec<_> = content
@@ -39,16 +34,14 @@ pub(super) fn emit_buffered_lint(
             let suggestions = (!spans.is_empty()).then_some(lints::UnicodeTextFlowSuggestion {
                 spans: spans.iter().map(|(_c, span)| *span).collect(),
             });
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::UnicodeTextFlow {
-                    comment_span,
-                    characters,
-                    suggestions,
-                    num_codepoints: spans.len(),
-                },
-            )
+
+            lints::UnicodeTextFlow {
+                comment_span,
+                characters,
+                suggestions,
+                num_codepoints: spans.len(),
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::AbsPathWithModule(mod_span) => {
             let (replacement, applicability) = match sess.source_map().span_to_snippet(mod_span) {
@@ -61,48 +54,35 @@ pub(super) fn emit_buffered_lint(
                 }
                 Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders),
             };
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::AbsPathWithModule {
-                    sugg: lints::AbsPathWithModuleSugg {
-                        span: mod_span,
-                        applicability,
-                        replacement,
-                    },
-                },
-            );
-        }
-        BuiltinLintDiag::ProcMacroDeriveResolutionFallback { span: macro_span, ns, ident } => ctx
-            .emit_span_lint(
-                lint,
-                span,
-                lints::ProcMacroDeriveResolutionFallback { span: macro_span, ns, ident },
-            ),
-        BuiltinLintDiag::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => ctx
-            .emit_span_lint(
-                lint,
-                span,
-                lints::MacroExpandedMacroExportsAccessedByAbsolutePaths { definition: span_def },
-            ),
+            lints::AbsPathWithModule {
+                sugg: lints::AbsPathWithModuleSugg { span: mod_span, applicability, replacement },
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::ProcMacroDeriveResolutionFallback { span: macro_span, ns, ident } => {
+            lints::ProcMacroDeriveResolutionFallback { span: macro_span, ns, ident }
+                .decorate_lint(diag)
+        }
+        BuiltinLintDiag::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => {
+            lints::MacroExpandedMacroExportsAccessedByAbsolutePaths { definition: span_def }
+                .decorate_lint(diag)
+        }
+
         BuiltinLintDiag::ElidedLifetimesInPaths(n, path_span, incl_angl_brckt, insertion_span) => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::ElidedLifetimesInPaths {
-                    subdiag: elided_lifetime_in_path_suggestion(
-                        sess.source_map(),
-                        n,
-                        path_span,
-                        incl_angl_brckt,
-                        insertion_span,
-                    ),
-                },
-            );
+            lints::ElidedLifetimesInPaths {
+                subdiag: elided_lifetime_in_path_suggestion(
+                    sess.source_map(),
+                    n,
+                    path_span,
+                    incl_angl_brckt,
+                    insertion_span,
+                ),
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::UnknownCrateTypes { span, candidate } => {
             let sugg = candidate.map(|candidate| lints::UnknownCrateTypesSub { span, candidate });
-            ctx.emit_span_lint(lint, span, lints::UnknownCrateTypes { sugg });
+            lints::UnknownCrateTypes { sugg }.decorate_lint(diag);
         }
         BuiltinLintDiag::UnusedImports {
             remove_whole_use,
@@ -119,18 +99,15 @@ pub(super) fn emit_buffered_lint(
             let test_module_span =
                 test_module_span.map(|span| sess.source_map().guess_head_span(span));
 
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::UnusedImports {
-                    sugg,
-                    test_module_span,
-                    num_snippets: span_snippets.len(),
-                    span_snippets: DiagArgValue::StrListSepByAnd(
-                        span_snippets.into_iter().map(Cow::Owned).collect(),
-                    ),
-                },
-            );
+            lints::UnusedImports {
+                sugg,
+                test_module_span,
+                num_snippets: span_snippets.len(),
+                span_snippets: DiagArgValue::StrListSepByAnd(
+                    span_snippets.into_iter().map(Cow::Owned).collect(),
+                ),
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::RedundantImport(spans, ident) => {
             let subs = spans
@@ -144,7 +121,7 @@ pub(super) fn emit_buffered_lint(
                     })(span)
                 })
                 .collect();
-            ctx.emit_span_lint(lint, span, lints::RedundantImport { subs, ident });
+            lints::RedundantImport { subs, ident }.decorate_lint(diag);
         }
         BuiltinLintDiag::DeprecatedMacro {
             suggestion,
@@ -158,90 +135,63 @@ pub(super) fn emit_buffered_lint(
                 kind: "macro".to_owned(),
                 suggestion,
             });
-            ctx.emit_span_lint(
-                lint,
-                span,
-                stability::Deprecated { sub, kind: "macro".to_owned(), path, note, since_kind },
-            );
+
+            stability::Deprecated { sub, kind: "macro".to_owned(), path, note, since_kind }
+                .decorate_lint(diag);
         }
         BuiltinLintDiag::UnusedDocComment(attr_span) => {
-            ctx.emit_span_lint(lint, span, lints::UnusedDocComment { span: attr_span });
+            lints::UnusedDocComment { span: attr_span }.decorate_lint(diag);
         }
         BuiltinLintDiag::PatternsInFnsWithoutBody { span: remove_span, ident, is_foreign } => {
             let sub = lints::PatternsInFnsWithoutBodySub { ident, span: remove_span };
-
-            ctx.emit_span_lint(
-                lint,
-                span,
-                if is_foreign {
-                    lints::PatternsInFnsWithoutBody::Foreign { sub }
-                } else {
-                    lints::PatternsInFnsWithoutBody::Bodiless { sub }
-                },
-            );
+            if is_foreign {
+                lints::PatternsInFnsWithoutBody::Foreign { sub }
+            } else {
+                lints::PatternsInFnsWithoutBody::Bodiless { sub }
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::MissingAbi(label_span, default_abi) => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::MissingAbi { span: label_span, default_abi: default_abi.name() },
-            );
+            lints::MissingAbi { span: label_span, default_abi: default_abi.name() }
+                .decorate_lint(diag);
         }
         BuiltinLintDiag::LegacyDeriveHelpers(label_span) => {
-            ctx.emit_span_lint(lint, span, lints::LegacyDeriveHelpers { span: label_span });
+            lints::LegacyDeriveHelpers { span: label_span }.decorate_lint(diag);
         }
         BuiltinLintDiag::ProcMacroBackCompat { crate_name, fixed_version } => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::ProcMacroBackCompat { crate_name, fixed_version },
-            );
+            lints::ProcMacroBackCompat { crate_name, fixed_version }.decorate_lint(diag);
         }
         BuiltinLintDiag::OrPatternsBackCompat(suggestion_span, suggestion) => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::OrPatternsBackCompat { span: suggestion_span, suggestion },
-            );
+            lints::OrPatternsBackCompat { span: suggestion_span, suggestion }.decorate_lint(diag);
         }
         BuiltinLintDiag::ReservedPrefix(label_span, prefix) => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::ReservedPrefix {
-                    label: label_span,
-                    suggestion: label_span.shrink_to_hi(),
-                    prefix,
-                },
-            );
+            lints::ReservedPrefix {
+                label: label_span,
+                suggestion: label_span.shrink_to_hi(),
+                prefix,
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name },
-            );
+            lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag);
         }
         BuiltinLintDiag::TrailingMacro(is_trailing, name) => {
-            ctx.emit_span_lint(lint, span, lints::TrailingMacro { is_trailing, name });
+            lints::TrailingMacro { is_trailing, name }.decorate_lint(diag);
         }
         BuiltinLintDiag::BreakWithLabelAndLoop(sugg_span) => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::BreakWithLabelAndLoop {
-                    sub: lints::BreakWithLabelAndLoopSub {
-                        left: sugg_span.shrink_to_lo(),
-                        right: sugg_span.shrink_to_hi(),
-                    },
+            lints::BreakWithLabelAndLoop {
+                sub: lints::BreakWithLabelAndLoopSub {
+                    left: sugg_span.shrink_to_lo(),
+                    right: sugg_span.shrink_to_hi(),
                 },
-            );
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::UnexpectedCfgName(name, value) => {
-            ctx.emit_span_lint(lint, span, check_cfg::unexpected_cfg_name(sess, name, value));
+            check_cfg::unexpected_cfg_name(sess, name, value).decorate_lint(diag);
         }
         BuiltinLintDiag::UnexpectedCfgValue(name, value) => {
-            ctx.emit_span_lint(lint, span, check_cfg::unexpected_cfg_value(sess, name, value));
+            check_cfg::unexpected_cfg_value(sess, name, value).decorate_lint(diag);
         }
         BuiltinLintDiag::DeprecatedWhereclauseLocation(left_sp, sugg) => {
             let suggestion = match sugg {
@@ -252,7 +202,7 @@ pub(super) fn emit_buffered_lint(
                 },
                 None => lints::DeprecatedWhereClauseLocationSugg::RemoveWhere { span: left_sp },
             };
-            ctx.emit_span_lint(lint, span, lints::DeprecatedWhereClauseLocation { suggestion });
+            lints::DeprecatedWhereClauseLocation { suggestion }.decorate_lint(diag);
         }
         BuiltinLintDiag::SingleUseLifetime {
             param_span,
@@ -279,15 +229,12 @@ pub(super) fn emit_buffered_lint(
                 None
             };
 
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::SingleUseLifetime { suggestion, param_span, use_span, ident },
-            );
+            lints::SingleUseLifetime { suggestion, param_span, use_span, ident }
+                .decorate_lint(diag);
         }
         BuiltinLintDiag::SingleUseLifetime { use_span: None, deletion_span, ident, .. } => {
             debug!(?deletion_span);
-            ctx.emit_span_lint(lint, span, lints::UnusedLifetime { deletion_span, ident });
+            lints::UnusedLifetime { deletion_span, ident }.decorate_lint(diag);
         }
         BuiltinLintDiag::NamedArgumentUsedPositionally {
             position_sp_to_replace,
@@ -315,35 +262,29 @@ pub(super) fn emit_buffered_lint(
                 (None, String::new())
             };
 
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::NamedArgumentUsedPositionally {
-                    named_arg_sp,
-                    position_label_sp: position_sp_for_msg,
-                    suggestion,
-                    name,
-                    named_arg_name,
-                },
-            )
+            lints::NamedArgumentUsedPositionally {
+                named_arg_sp,
+                position_label_sp: position_sp_for_msg,
+                suggestion,
+                name,
+                named_arg_name,
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::ByteSliceInPackedStructWithDerive { ty } => {
-            ctx.emit_span_lint(lint, span, lints::ByteSliceInPackedStructWithDerive { ty })
+            lints::ByteSliceInPackedStructWithDerive { ty }.decorate_lint(diag);
         }
         BuiltinLintDiag::UnusedExternCrate { removal_span } => {
-            ctx.emit_span_lint(lint, span, lints::UnusedExternCrate { removal_span })
+            lints::UnusedExternCrate { removal_span }.decorate_lint(diag);
         }
         BuiltinLintDiag::ExternCrateNotIdiomatic { vis_span, ident_span } => {
             let suggestion_span = vis_span.between(ident_span);
             let code = if vis_span.is_empty() { "use " } else { " use " };
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::ExternCrateNotIdiomatic { span: suggestion_span, code },
-            );
+
+            lints::ExternCrateNotIdiomatic { span: suggestion_span, code }.decorate_lint(diag);
         }
         BuiltinLintDiag::AmbiguousGlobImports { diag: ambiguity } => {
-            ctx.emit_span_lint(lint, span, lints::AmbiguousGlobImports { ambiguity });
+            lints::AmbiguousGlobImports { ambiguity }.decorate_lint(diag);
         }
         BuiltinLintDiag::AmbiguousGlobReexports {
             name,
@@ -351,16 +292,13 @@ pub(super) fn emit_buffered_lint(
             first_reexport_span,
             duplicate_reexport_span,
         } => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::AmbiguousGlobReexports {
-                    first_reexport: first_reexport_span,
-                    duplicate_reexport: duplicate_reexport_span,
-                    name,
-                    namespace,
-                },
-            );
+            lints::AmbiguousGlobReexports {
+                first_reexport: first_reexport_span,
+                duplicate_reexport: duplicate_reexport_span,
+                name,
+                namespace,
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::HiddenGlobReexports {
             name,
@@ -368,127 +306,112 @@ pub(super) fn emit_buffered_lint(
             glob_reexport_span,
             private_item_span,
         } => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::HiddenGlobReexports {
-                    glob_reexport: glob_reexport_span,
-                    private_item: private_item_span,
+            lints::HiddenGlobReexports {
+                glob_reexport: glob_reexport_span,
+                private_item: private_item_span,
 
-                    name,
-                    namespace,
-                },
-            );
+                name,
+                namespace,
+            }
+            .decorate_lint(diag);
         }
         BuiltinLintDiag::UnusedQualifications { removal_span } => {
-            ctx.emit_span_lint(lint, span, lints::UnusedQualifications { removal_span });
+            lints::UnusedQualifications { removal_span }.decorate_lint(diag);
         }
         BuiltinLintDiag::AssociatedConstElidedLifetime { elided, span: lt_span } => {
             let lt_span = if elided { lt_span.shrink_to_hi() } else { lt_span };
             let code = if elided { "'static " } else { "'static" };
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::AssociatedConstElidedLifetime { span: lt_span, code, elided },
-            );
+            lints::AssociatedConstElidedLifetime { span: lt_span, code, elided }
+                .decorate_lint(diag);
         }
         BuiltinLintDiag::RedundantImportVisibility { max_vis, span: vis_span, import_vis } => {
-            ctx.emit_span_lint(
-                lint,
-                span,
-                lints::RedundantImportVisibility { span: vis_span, help: (), max_vis, import_vis },
-            );
+            lints::RedundantImportVisibility { span: vis_span, help: (), max_vis, import_vis }
+                .decorate_lint(diag);
         }
         BuiltinLintDiag::UnknownDiagnosticAttribute { span: typo_span, typo_name } => {
             let typo = typo_name.map(|typo_name| lints::UnknownDiagnosticAttributeTypoSugg {
                 span: typo_span,
                 typo_name,
             });
-            ctx.emit_span_lint(lint, span, lints::UnknownDiagnosticAttribute { typo });
+            lints::UnknownDiagnosticAttribute { typo }.decorate_lint(diag);
         }
         BuiltinLintDiag::MacroUseDeprecated => {
-            ctx.emit_span_lint(lint, span, lints::MacroUseDeprecated)
+            lints::MacroUseDeprecated.decorate_lint(diag);
         }
-        BuiltinLintDiag::UnusedMacroUse => ctx.emit_span_lint(lint, span, lints::UnusedMacroUse),
+        BuiltinLintDiag::UnusedMacroUse => lints::UnusedMacroUse.decorate_lint(diag),
         BuiltinLintDiag::PrivateExternCrateReexport(ident) => {
-            ctx.emit_span_lint(lint, span, lints::PrivateExternCrateReexport { ident })
+            lints::PrivateExternCrateReexport { ident }.decorate_lint(diag);
         }
-        BuiltinLintDiag::UnusedLabel => ctx.emit_span_lint(lint, span, lints::UnusedLabel),
+        BuiltinLintDiag::UnusedLabel => lints::UnusedLabel.decorate_lint(diag),
         BuiltinLintDiag::MacroIsPrivate(ident) => {
-            ctx.emit_span_lint(lint, span, lints::MacroIsPrivate { ident })
+            lints::MacroIsPrivate { ident }.decorate_lint(diag);
         }
         BuiltinLintDiag::UnusedMacroDefinition(name) => {
-            ctx.emit_span_lint(lint, span, lints::UnusedMacroDefinition { name })
+            lints::UnusedMacroDefinition { name }.decorate_lint(diag);
         }
         BuiltinLintDiag::MacroRuleNeverUsed(n, name) => {
-            ctx.emit_span_lint(lint, span, lints::MacroRuleNeverUsed { n: n + 1, name })
+            lints::MacroRuleNeverUsed { n: n + 1, name }.decorate_lint(diag);
         }
         BuiltinLintDiag::UnstableFeature(msg) => {
-            ctx.emit_span_lint(lint, span, lints::UnstableFeature { msg })
+            lints::UnstableFeature { msg }.decorate_lint(diag);
         }
         BuiltinLintDiag::AvoidUsingIntelSyntax => {
-            ctx.emit_span_lint(lint, span, lints::AvoidIntelSyntax)
+            lints::AvoidIntelSyntax.decorate_lint(diag);
         }
         BuiltinLintDiag::AvoidUsingAttSyntax => {
-            ctx.emit_span_lint(lint, span, lints::AvoidAttSyntax)
+            lints::AvoidAttSyntax.decorate_lint(diag);
         }
         BuiltinLintDiag::IncompleteInclude => {
-            ctx.emit_span_lint(lint, span, lints::IncompleteInclude)
+            lints::IncompleteInclude.decorate_lint(diag);
         }
         BuiltinLintDiag::UnnameableTestItems => {
-            ctx.emit_span_lint(lint, span, lints::UnnameableTestItems)
+            lints::UnnameableTestItems.decorate_lint(diag);
         }
         BuiltinLintDiag::DuplicateMacroAttribute => {
-            ctx.emit_span_lint(lint, span, lints::DuplicateMacroAttribute)
+            lints::DuplicateMacroAttribute.decorate_lint(diag);
         }
         BuiltinLintDiag::CfgAttrNoAttributes => {
-            ctx.emit_span_lint(lint, span, lints::CfgAttrNoAttributes)
+            lints::CfgAttrNoAttributes.decorate_lint(diag);
         }
         BuiltinLintDiag::CrateTypeInCfgAttr => {
-            ctx.emit_span_lint(lint, span, lints::CrateTypeInCfgAttr)
+            lints::CrateTypeInCfgAttr.decorate_lint(diag);
         }
         BuiltinLintDiag::CrateNameInCfgAttr => {
-            ctx.emit_span_lint(lint, span, lints::CrateNameInCfgAttr)
+            lints::CrateNameInCfgAttr.decorate_lint(diag);
         }
         BuiltinLintDiag::MissingFragmentSpecifier => {
-            ctx.emit_span_lint(lint, span, lints::MissingFragmentSpecifier)
+            lints::MissingFragmentSpecifier.decorate_lint(diag);
         }
         BuiltinLintDiag::MetaVariableStillRepeating(name) => {
-            ctx.emit_span_lint(lint, span, lints::MetaVariableStillRepeating { name })
+            lints::MetaVariableStillRepeating { name }.decorate_lint(diag);
         }
         BuiltinLintDiag::MetaVariableWrongOperator => {
-            ctx.emit_span_lint(lint, span, lints::MetaVariableWrongOperator)
+            lints::MetaVariableWrongOperator.decorate_lint(diag);
         }
         BuiltinLintDiag::DuplicateMatcherBinding => {
-            ctx.emit_span_lint(lint, span, lints::DuplicateMatcherBinding)
+            lints::DuplicateMatcherBinding.decorate_lint(diag);
         }
         BuiltinLintDiag::UnknownMacroVariable(name) => {
-            ctx.emit_span_lint(lint, span, lints::UnknownMacroVariable { name })
-        }
-        BuiltinLintDiag::UnusedCrateDependency { extern_crate, local_crate } => ctx.emit_span_lint(
-            lint,
-            span,
-            lints::UnusedCrateDependency { extern_crate, local_crate },
-        ),
-        BuiltinLintDiag::WasmCAbi => ctx.emit_span_lint(lint, span, lints::WasmCAbi),
-        BuiltinLintDiag::IllFormedAttributeInput { suggestions } => ctx.emit_span_lint(
-            lint,
-            span,
+            lints::UnknownMacroVariable { name }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedCrateDependency { extern_crate, local_crate } => {
+            lints::UnusedCrateDependency { extern_crate, local_crate }.decorate_lint(diag)
+        }
+        BuiltinLintDiag::WasmCAbi => lints::WasmCAbi.decorate_lint(diag),
+        BuiltinLintDiag::IllFormedAttributeInput { suggestions } => {
             lints::IllFormedAttributeInput {
                 num_suggestions: suggestions.len(),
                 suggestions: DiagArgValue::StrListSepByAnd(
                     suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(),
                 ),
-            },
-        ),
-        BuiltinLintDiag::InnerAttributeUnstable { is_macro } => ctx.emit_span_lint(
-            lint,
-            span,
-            if is_macro {
-                lints::InnerAttributeUnstable::InnerMacroAttribute
-            } else {
-                lints::InnerAttributeUnstable::CustomInnerAttribute
-            },
-        ),
+            }
+            .decorate_lint(diag)
+        }
+        BuiltinLintDiag::InnerAttributeUnstable { is_macro } => if is_macro {
+            lints::InnerAttributeUnstable::InnerMacroAttribute
+        } else {
+            lints::InnerAttributeUnstable::CustomInnerAttribute
+        }
+        .decorate_lint(diag),
     }
 }