about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-05-21 21:36:09 +0000
committerbors <bors@rust-lang.org>2024-05-21 21:36:09 +0000
commit791adf759cc065316f054961875052d5bc03e16c (patch)
tree17144e778b88c1cc27d2d9b605278b6fe1c9473c
parent39e02f1bd1e53d009da382654139f7c0639172a8 (diff)
parent98dd6c7e8f33966221216200182175672294a719 (diff)
downloadrust-791adf759cc065316f054961875052d5bc03e16c.tar.gz
rust-791adf759cc065316f054961875052d5bc03e16c.zip
Auto merge of #124417 - Xiretza:translate-early-lints, r=fmease
Make early lints translatable

<del>Requires https://github.com/projectfluent/fluent-rs/pull/353.</del> https://github.com/rust-lang/rust/commit/5134a04eaa32b168cf5998a6ec13199356e2e017

r? diagnostics
-rw-r--r--compiler/rustc_ast_passes/messages.ftl5
-rw-r--r--compiler/rustc_ast_passes/src/ast_validation.rs23
-rw-r--r--compiler/rustc_ast_passes/src/errors.rs2
-rw-r--r--compiler/rustc_attr/src/builtin.rs10
-rw-r--r--compiler/rustc_builtin_macros/messages.ftl2
-rw-r--r--compiler/rustc_builtin_macros/src/asm.rs5
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/mod.rs9
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs1
-rw-r--r--compiler/rustc_builtin_macros/src/source_util.rs3
-rw-r--r--compiler/rustc_builtin_macros/src/test_harness.rs3
-rw-r--r--compiler/rustc_builtin_macros/src/util.rs4
-rw-r--r--compiler/rustc_errors/src/diagnostic_impls.rs6
-rw-r--r--compiler/rustc_expand/src/base.rs21
-rw-r--r--compiler/rustc_expand/src/config.rs9
-rw-r--r--compiler/rustc_expand/src/expand.rs6
-rw-r--r--compiler/rustc_expand/src/mbe/macro_check.rs24
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs6
-rw-r--r--compiler/rustc_interface/src/util.rs28
-rw-r--r--compiler/rustc_lint/messages.ftl235
-rw-r--r--compiler/rustc_lint/src/builtin.rs21
-rw-r--r--compiler/rustc_lint/src/context.rs22
-rw-r--r--compiler/rustc_lint/src/context/diagnostics.rs601
-rw-r--r--compiler/rustc_lint/src/context/diagnostics/check_cfg.rs311
-rw-r--r--compiler/rustc_lint/src/early.rs12
-rw-r--r--compiler/rustc_lint/src/lints.rs850
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs116
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs16
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs4
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs12
-rw-r--r--compiler/rustc_macros/src/lib.rs6
-rw-r--r--compiler/rustc_metadata/messages.ftl2
-rw-r--r--compiler/rustc_metadata/src/creader.rs21
-rw-r--r--compiler/rustc_middle/messages.ftl14
-rw-r--r--compiler/rustc_middle/src/middle/stability.rs183
-rw-r--r--compiler/rustc_parse/src/lexer/mod.rs8
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs3
-rw-r--r--compiler/rustc_parse/src/validate_attr.rs32
-rw-r--r--compiler/rustc_resolve/src/check_unused.rs74
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs12
-rw-r--r--compiler/rustc_resolve/src/ident.rs15
-rw-r--r--compiler/rustc_resolve/src/imports.rs22
-rw-r--r--compiler/rustc_resolve/src/late.rs20
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs8
-rw-r--r--compiler/rustc_resolve/src/lib.rs10
-rw-r--r--compiler/rustc_resolve/src/macros.rs59
-rw-r--r--compiler/rustc_session/src/parse.rs20
-rw-r--r--compiler/rustc_session/src/version.rs13
-rw-r--r--tests/ui/cfg/future-compat-crate-attributes-using-cfg_attr.stderr8
-rw-r--r--tests/ui/check-cfg/allow-same-level.stderr2
-rw-r--r--tests/ui/check-cfg/cargo-feature.none.stderr2
-rw-r--r--tests/ui/check-cfg/cargo-feature.some.stderr2
-rw-r--r--tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr2
-rw-r--r--tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr2
-rw-r--r--tests/ui/check-cfg/cfg-value-for-cfg-name.stderr2
-rw-r--r--tests/ui/check-cfg/compact-names.stderr2
-rw-r--r--tests/ui/check-cfg/compact-values.stderr2
-rw-r--r--tests/ui/check-cfg/concat-values.stderr4
-rw-r--r--tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr2
-rw-r--r--tests/ui/check-cfg/exhaustive-names-values.feature.stderr2
-rw-r--r--tests/ui/check-cfg/exhaustive-names-values.full.stderr2
-rw-r--r--tests/ui/check-cfg/exhaustive-names.stderr2
-rw-r--r--tests/ui/check-cfg/mix.stderr4
-rw-r--r--tests/ui/check-cfg/stmt-no-ice.stderr2
-rw-r--r--tests/ui/check-cfg/unexpected-cfg-value.stderr4
-rw-r--r--tests/ui/check-cfg/well-known-names.stderr2
-rw-r--r--tests/ui/check-cfg/well-known-values.stderr32
-rw-r--r--tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs2
-rw-r--r--tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr2
-rw-r--r--tests/ui/lint/anonymous-reexport.stderr2
-rw-r--r--tests/ui/lint/unused/import_remove_line.stderr2
-rw-r--r--tests/ui/lint/unused/lint-unused-imports.rs2
-rw-r--r--tests/ui/lint/unused/lint-unused-imports.stderr2
-rw-r--r--tests/ui/malformed/malformed-regressions.rs6
-rw-r--r--tests/ui/malformed/malformed-regressions.stderr6
-rw-r--r--tests/ui/privacy/issue-46209-private-enum-variant-reexport.rs2
-rw-r--r--tests/ui/privacy/issue-46209-private-enum-variant-reexport.stderr2
-rw-r--r--tests/ui/span/multispan-import-lint.stderr2
-rw-r--r--tests/ui/suggestions/unused-imports.stderr4
-rw-r--r--tests/ui/use/use-nested-groups-unused-imports.rs2
-rw-r--r--tests/ui/use/use-nested-groups-unused-imports.stderr2
80 files changed, 2107 insertions, 900 deletions
diff --git a/compiler/rustc_ast_passes/messages.ftl b/compiler/rustc_ast_passes/messages.ftl
index a3731e94276..31a184fe921 100644
--- a/compiler/rustc_ast_passes/messages.ftl
+++ b/compiler/rustc_ast_passes/messages.ftl
@@ -55,9 +55,6 @@ ast_passes_const_without_body =
 ast_passes_constraint_on_negative_bound =
     associated type constraints not allowed on negative bounds
 
-ast_passes_deprecated_where_clause_location =
-    where clause not allowed here
-
 ast_passes_equality_in_where = equality constraints are not yet supported in `where` clauses
     .label = not supported
     .suggestion = if `{$ident}` is an associated type you're trying to set, use the associated type binding syntax
@@ -80,8 +77,6 @@ ast_passes_extern_types_cannot = `type`s inside `extern` blocks cannot have {$de
     .suggestion = remove the {$remove_descr}
     .label = `extern` block begins here
 
-ast_passes_extern_without_abi = extern declarations without an explicit ABI are deprecated
-
 ast_passes_feature_on_non_nightly = `#![feature]` may not be used on the {$channel} release channel
     .suggestion = remove the attribute
     .stable_since = the feature `{$name}` has been stable since `{$since}` and no longer requires an attribute to enable
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index 9d07683f8d6..c57be3cdf35 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -27,7 +27,6 @@ use std::ops::{Deref, DerefMut};
 use thin_vec::thin_vec;
 
 use crate::errors;
-use crate::fluent_generated as fluent;
 
 /// Is `self` allowed semantically as the first parameter in an `FnDecl`?
 enum SelfSemantic {
@@ -766,11 +765,10 @@ impl<'a> AstValidator<'a> {
             .span_to_snippet(span)
             .is_ok_and(|snippet| !snippet.starts_with("#["))
         {
-            self.lint_buffer.buffer_lint_with_diagnostic(
+            self.lint_buffer.buffer_lint(
                 MISSING_ABI,
                 id,
                 span,
-                fluent::ast_passes_extern_without_abi,
                 BuiltinLintDiag::MissingAbi(span, abi::Abi::FALLBACK),
             )
         }
@@ -1428,17 +1426,15 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
             Self::check_decl_no_pat(&sig.decl, |span, ident, mut_ident| {
                 if mut_ident && matches!(ctxt, FnCtxt::Assoc(_)) {
                     if let Some(ident) = ident {
-                        let msg = match ctxt {
-                            FnCtxt::Foreign => fluent::ast_passes_pattern_in_foreign,
-                            _ => fluent::ast_passes_pattern_in_bodiless,
-                        };
-                        let diag = BuiltinLintDiag::PatternsInFnsWithoutBody(span, ident);
-                        self.lint_buffer.buffer_lint_with_diagnostic(
+                        self.lint_buffer.buffer_lint(
                             PATTERNS_IN_FNS_WITHOUT_BODY,
                             id,
                             span,
-                            msg,
-                            diag,
+                            BuiltinLintDiag::PatternsInFnsWithoutBody {
+                                span,
+                                ident,
+                                is_foreign: matches!(ctxt, FnCtxt::Foreign),
+                            },
                         )
                     }
                 } else {
@@ -1510,12 +1506,11 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
                     Some((right, snippet))
                 }
             };
-            self.lint_buffer.buffer_lint_with_diagnostic(
+            self.lint_buffer.buffer_lint(
                 DEPRECATED_WHERE_CLAUSE_LOCATION,
                 item.id,
                 err.span,
-                fluent::ast_passes_deprecated_where_clause_location,
-                BuiltinLintDiag::DeprecatedWhereclauseLocation(sugg),
+                BuiltinLintDiag::DeprecatedWhereclauseLocation(err.span, sugg),
             );
         }
 
diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs
index 25a125f8393..a95a7bdaf6d 100644
--- a/compiler/rustc_ast_passes/src/errors.rs
+++ b/compiler/rustc_ast_passes/src/errors.rs
@@ -669,6 +669,7 @@ pub struct ConstAndCVariadic {
 
 #[derive(Diagnostic)]
 #[diag(ast_passes_pattern_in_foreign, code = E0130)]
+// FIXME: deduplicate with rustc_lint (`BuiltinLintDiag::PatternsInFnsWithoutBody`)
 pub struct PatternInForeign {
     #[primary_span]
     #[label]
@@ -677,6 +678,7 @@ pub struct PatternInForeign {
 
 #[derive(Diagnostic)]
 #[diag(ast_passes_pattern_in_bodiless, code = E0642)]
+// FIXME: deduplicate with rustc_lint (`BuiltinLintDiag::PatternsInFnsWithoutBody`)
 pub struct PatternInBodiless {
     #[primary_span]
     #[label]
diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs
index c08bf287733..5113c5adc8f 100644
--- a/compiler/rustc_attr/src/builtin.rs
+++ b/compiler/rustc_attr/src/builtin.rs
@@ -528,15 +528,10 @@ pub fn cfg_matches(
         try_gate_cfg(cfg.name, cfg.span, sess, features);
         match sess.psess.check_config.expecteds.get(&cfg.name) {
             Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
-                sess.psess.buffer_lint_with_diagnostic(
+                sess.psess.buffer_lint(
                     UNEXPECTED_CFGS,
                     cfg.span,
                     lint_node_id,
-                    if let Some(value) = cfg.value {
-                        format!("unexpected `cfg` condition value: `{value}`")
-                    } else {
-                        format!("unexpected `cfg` condition value: (none)")
-                    },
                     BuiltinLintDiag::UnexpectedCfgValue(
                         (cfg.name, cfg.name_span),
                         cfg.value.map(|v| (v, cfg.value_span.unwrap())),
@@ -544,11 +539,10 @@ pub fn cfg_matches(
                 );
             }
             None if sess.psess.check_config.exhaustive_names => {
-                sess.psess.buffer_lint_with_diagnostic(
+                sess.psess.buffer_lint(
                     UNEXPECTED_CFGS,
                     cfg.span,
                     lint_node_id,
-                    format!("unexpected `cfg` condition name: `{}`", cfg.name),
                     BuiltinLintDiag::UnexpectedCfgName(
                         (cfg.name, cfg.name_span),
                         cfg.value.map(|v| (v, cfg.value_span.unwrap())),
diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl
index 0f158990319..a3d6a1c7360 100644
--- a/compiler/rustc_builtin_macros/messages.ftl
+++ b/compiler/rustc_builtin_macros/messages.ftl
@@ -247,5 +247,3 @@ builtin_macros_unexpected_lit = expected path to a trait, found literal
     .label = not a trait
     .str_lit = try using `#[derive({$sym})]`
     .other = for example, write `#[derive(Debug)]` for `Debug`
-
-builtin_macros_unnameable_test_items = cannot test inner items
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs
index 49b1b8cf992..1a7961bf70c 100644
--- a/compiler/rustc_builtin_macros/src/asm.rs
+++ b/compiler/rustc_builtin_macros/src/asm.rs
@@ -1,6 +1,7 @@
 use crate::errors;
 use crate::util::expr_to_spanned_string;
 use ast::token::IdentIsRaw;
+use lint::BuiltinLintDiag;
 use rustc_ast as ast;
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, Delimiter};
@@ -513,7 +514,7 @@ fn expand_preparsed_asm(
                     lint::builtin::BAD_ASM_STYLE,
                     find_span(".intel_syntax"),
                     ecx.current_expansion.lint_node_id,
-                    "avoid using `.intel_syntax`, Intel syntax is the default",
+                    BuiltinLintDiag::AvoidUsingIntelSyntax,
                 );
             }
             if template_str.contains(".att_syntax") {
@@ -521,7 +522,7 @@ fn expand_preparsed_asm(
                     lint::builtin::BAD_ASM_STYLE,
                     find_span(".att_syntax"),
                     ecx.current_expansion.lint_node_id,
-                    "avoid using `.att_syntax`, prefer using `options(att_syntax)` instead",
+                    BuiltinLintDiag::AvoidUsingAttSyntax,
                 );
             }
         }
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
index 46949f731aa..217fa5ff9f1 100644
--- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
@@ -1621,14 +1621,13 @@ impl<'a> TraitDef<'a> {
                         };
 
                         if let Some(ty) = exception {
-                            cx.sess.psess.buffer_lint_with_diagnostic(
+                            cx.sess.psess.buffer_lint(
                                 BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE,
                                 sp,
                                 ast::CRATE_NODE_ID,
-                                format!(
-                                    "{ty} slice in a packed struct that derives a built-in trait"
-                                ),
-                                rustc_lint_defs::BuiltinLintDiag::ByteSliceInPackedStructWithDerive,
+                                rustc_lint_defs::BuiltinLintDiag::ByteSliceInPackedStructWithDerive {
+                                    ty: ty.to_string(),
+                                },
                             );
                         } else {
                             // Wrap the expression in `{...}`, causing a copy.
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index a5fc74f1d66..5cb0407bd59 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -556,7 +556,6 @@ fn make_format_args(
             let arg_name = args.explicit_args()[index].kind.ident().unwrap();
             ecx.buffered_early_lint.push(BufferedEarlyLint {
                 span: arg_name.span.into(),
-                msg: format!("named argument `{}` is not used by name", arg_name.name).into(),
                 node_id: rustc_ast::CRATE_NODE_ID,
                 lint_id: LintId::of(NAMED_ARGUMENTS_USED_POSITIONALLY),
                 diagnostic: BuiltinLintDiag::NamedArgumentUsedPositionally {
diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs
index 47b2ee975ca..29e991525a9 100644
--- a/compiler/rustc_builtin_macros/src/source_util.rs
+++ b/compiler/rustc_builtin_macros/src/source_util.rs
@@ -11,6 +11,7 @@ use rustc_expand::base::{
     resolve_path, DummyResult, ExpandResult, ExtCtxt, MacEager, MacResult, MacroExpanderResult,
 };
 use rustc_expand::module::DirOwnership;
+use rustc_lint_defs::BuiltinLintDiag;
 use rustc_parse::new_parser_from_file;
 use rustc_parse::parser::{ForceCollect, Parser};
 use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
@@ -147,7 +148,7 @@ pub(crate) fn expand_include<'cx>(
                     INCOMPLETE_INCLUDE,
                     self.p.token.span,
                     self.node_id,
-                    "include macro expected single expression in source",
+                    BuiltinLintDiag::IncompleteInclude,
                 );
             }
             Some(expr)
diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs
index 8cf431482ff..38ac2f15fe7 100644
--- a/compiler/rustc_builtin_macros/src/test_harness.rs
+++ b/compiler/rustc_builtin_macros/src/test_harness.rs
@@ -9,6 +9,7 @@ use rustc_ast::{attr, ModKind};
 use rustc_expand::base::{ExtCtxt, ResolverExpand};
 use rustc_expand::expand::{AstFragment, ExpansionConfig};
 use rustc_feature::Features;
+use rustc_lint_defs::BuiltinLintDiag;
 use rustc_session::lint::builtin::UNNAMEABLE_TEST_ITEMS;
 use rustc_session::Session;
 use rustc_span::hygiene::{AstPass, SyntaxContext, Transparency};
@@ -163,7 +164,7 @@ impl<'a> Visitor<'a> for InnerItemLinter<'_> {
                 UNNAMEABLE_TEST_ITEMS,
                 attr.span,
                 i.id,
-                crate::fluent_generated::builtin_macros_unnameable_test_items,
+                BuiltinLintDiag::UnnameableTestItems,
             );
         }
     }
diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs
index 8dc7bc14ec3..652e34268ea 100644
--- a/compiler/rustc_builtin_macros/src/util.rs
+++ b/compiler/rustc_builtin_macros/src/util.rs
@@ -5,7 +5,7 @@ use rustc_errors::{Applicability, Diag, ErrorGuaranteed};
 use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt};
 use rustc_expand::expand::AstFragment;
 use rustc_feature::AttributeTemplate;
-use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES;
+use rustc_lint_defs::{builtin::DUPLICATE_MACRO_ATTRIBUTES, BuiltinLintDiag};
 use rustc_parse::{parser, validate_attr};
 use rustc_session::errors::report_lit_error;
 use rustc_span::{BytePos, Span, Symbol};
@@ -46,7 +46,7 @@ pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable,
                 DUPLICATE_MACRO_ATTRIBUTES,
                 attr.span,
                 ecx.current_expansion.lint_node_id,
-                "duplicated attribute",
+                BuiltinLintDiag::DuplicateMacroAttribute,
             );
         }
     }
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index 4bf7dccab92..662c8a7b8be 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -282,6 +282,12 @@ impl IntoDiagArg for ClosureKind {
     }
 }
 
+impl IntoDiagArg for hir::def::Namespace {
+    fn into_diag_arg(self) -> DiagArgValue {
+        DiagArgValue::Str(Cow::Borrowed(self.descr()))
+    }
+}
+
 #[derive(Clone)]
 pub struct DiagSymbolList(Vec<Symbol>);
 
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 12868a66605..91af8758e51 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -1364,18 +1364,15 @@ fn pretty_printing_compatibility_hack(item: &Item, sess: &Session) -> bool {
                             };
 
                             if crate_matches {
-                                // FIXME: make this translatable
-                                #[allow(rustc::untranslatable_diagnostic)]
-                                sess.psess.buffer_lint_with_diagnostic(
-                                        PROC_MACRO_BACK_COMPAT,
-                                        item.ident.span,
-                                        ast::CRATE_NODE_ID,
-                                        "using an old version of `rental`",
-                                        BuiltinLintDiag::ProcMacroBackCompat(
-                                        "older versions of the `rental` crate will stop compiling in future versions of Rust; \
-                                        please update to `rental` v0.5.6, or switch to one of the `rental` alternatives".to_string()
-                                        )
-                                    );
+                                sess.psess.buffer_lint(
+                                    PROC_MACRO_BACK_COMPAT,
+                                    item.ident.span,
+                                    ast::CRATE_NODE_ID,
+                                    BuiltinLintDiag::ProcMacroBackCompat {
+                                        crate_name: "rental".to_string(),
+                                        fixed_version: "0.5.6".to_string(),
+                                    },
+                                );
                                 return true;
                             }
                         }
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 35f0d8abffc..badfa6d3aa3 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -14,6 +14,7 @@ use rustc_attr as attr;
 use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
 use rustc_feature::Features;
 use rustc_feature::{ACCEPTED_FEATURES, REMOVED_FEATURES, UNSTABLE_FEATURES};
+use rustc_lint_defs::BuiltinLintDiag;
 use rustc_parse::validate_attr;
 use rustc_session::parse::feature_err;
 use rustc_session::Session;
@@ -248,7 +249,6 @@ impl<'a> StripUnconfigured<'a> {
     /// Gives a compiler warning when the `cfg_attr` contains no attributes and
     /// is in the original source file. Gives a compiler error if the syntax of
     /// the attribute is incorrect.
-    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     pub(crate) fn expand_cfg_attr(&self, attr: &Attribute, recursive: bool) -> Vec<Attribute> {
         let Some((cfg_predicate, expanded_attrs)) =
             rustc_parse::parse_cfg_attr(attr, &self.sess.psess)
@@ -262,7 +262,7 @@ impl<'a> StripUnconfigured<'a> {
                 rustc_lint_defs::builtin::UNUSED_ATTRIBUTES,
                 attr.span,
                 ast::CRATE_NODE_ID,
-                "`#[cfg_attr]` does not expand to any attributes",
+                BuiltinLintDiag::CfgAttrNoAttributes,
             );
         }
 
@@ -283,7 +283,6 @@ impl<'a> StripUnconfigured<'a> {
         }
     }
 
-    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn expand_cfg_attr_item(
         &self,
         attr: &Attribute,
@@ -346,7 +345,7 @@ impl<'a> StripUnconfigured<'a> {
                 rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
                 attr.span,
                 ast::CRATE_NODE_ID,
-                "`crate_type` within an `#![cfg_attr] attribute is deprecated`",
+                BuiltinLintDiag::CrateTypeInCfgAttr,
             );
         }
         if attr.has_name(sym::crate_name) {
@@ -354,7 +353,7 @@ impl<'a> StripUnconfigured<'a> {
                 rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
                 attr.span,
                 ast::CRATE_NODE_ID,
-                "`crate_name` within an `#![cfg_attr] attribute is deprecated`",
+                BuiltinLintDiag::CrateNameInCfgAttr,
             );
         }
         attr
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index a049ac251e1..d8f0f221189 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -1799,11 +1799,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
             }
 
             if attr.is_doc_comment() {
-                self.cx.sess.psess.buffer_lint_with_diagnostic(
+                self.cx.sess.psess.buffer_lint(
                     UNUSED_DOC_COMMENTS,
                     current_span,
                     self.cx.current_expansion.lint_node_id,
-                    "unused doc comment",
                     BuiltinLintDiag::UnusedDocComment(attr.span),
                 );
             } else if rustc_attr::is_builtin_attr(attr) {
@@ -1811,11 +1810,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                 // `#[cfg]` and `#[cfg_attr]` are special - they are
                 // eagerly evaluated.
                 if attr_name != sym::cfg && attr_name != sym::cfg_attr {
-                    self.cx.sess.psess.buffer_lint_with_diagnostic(
+                    self.cx.sess.psess.buffer_lint(
                         UNUSED_ATTRIBUTES,
                         attr.span,
                         self.cx.current_expansion.lint_node_id,
-                        format!("unused attribute `{attr_name}`"),
                         BuiltinLintDiag::UnusedBuiltinAttribute {
                             attr_name,
                             macro_name: pprust::path_to_string(&call.path),
diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs
index dce8e0c36ed..72dbbde54b3 100644
--- a/compiler/rustc_expand/src/mbe/macro_check.rs
+++ b/compiler/rustc_expand/src/mbe/macro_check.rs
@@ -110,7 +110,8 @@ use crate::mbe::{KleeneToken, TokenTree};
 use rustc_ast::token::{Delimiter, IdentIsRaw, Token, TokenKind};
 use rustc_ast::{NodeId, DUMMY_NODE_ID};
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{DiagMessage, MultiSpan};
+use rustc_errors::MultiSpan;
+use rustc_lint_defs::BuiltinLintDiag;
 use rustc_session::lint::builtin::{META_VARIABLE_MISUSE, MISSING_FRAGMENT_SPECIFIER};
 use rustc_session::parse::ParseSess;
 use rustc_span::symbol::kw;
@@ -252,7 +253,7 @@ fn check_binders(
                 // 1. The meta-variable is already bound in the current LHS: This is an error.
                 let mut span = MultiSpan::from_span(span);
                 span.push_span_label(prev_info.span, "previous declaration");
-                buffer_lint(psess, span, node_id, "duplicate matcher binding");
+                buffer_lint(psess, span, node_id, BuiltinLintDiag::DuplicateMatcherBinding);
             } else if get_binder_info(macros, binders, name).is_none() {
                 // 2. The meta-variable is free: This is a binder.
                 binders.insert(name, BinderInfo { span, ops: ops.into() });
@@ -271,7 +272,7 @@ fn check_binders(
                     MISSING_FRAGMENT_SPECIFIER,
                     span,
                     node_id,
-                    "missing fragment specifier",
+                    BuiltinLintDiag::MissingFragmentSpecifier,
                 );
             }
             if !macros.is_empty() {
@@ -595,7 +596,7 @@ fn check_ops_is_prefix(
             return;
         }
     }
-    buffer_lint(psess, span.into(), node_id, format!("unknown macro variable `{name}`"));
+    buffer_lint(psess, span.into(), node_id, BuiltinLintDiag::UnknownMacroVariable(name));
 }
 
 /// Returns whether `binder_ops` is a prefix of `occurrence_ops`.
@@ -628,8 +629,7 @@ fn ops_is_prefix(
         if i >= occurrence_ops.len() {
             let mut span = MultiSpan::from_span(span);
             span.push_span_label(binder.span, "expected repetition");
-            let message = format!("variable '{name}' is still repeating at this depth");
-            buffer_lint(psess, span, node_id, message);
+            buffer_lint(psess, span, node_id, BuiltinLintDiag::MetaVariableStillRepeating(name));
             return;
         }
         let occurrence = &occurrence_ops[i];
@@ -637,21 +637,15 @@ fn ops_is_prefix(
             let mut span = MultiSpan::from_span(span);
             span.push_span_label(binder.span, "expected repetition");
             span.push_span_label(occurrence.span, "conflicting repetition");
-            let message = "meta-variable repeats with different Kleene operator";
-            buffer_lint(psess, span, node_id, message);
+            buffer_lint(psess, span, node_id, BuiltinLintDiag::MetaVariableWrongOperator);
             return;
         }
     }
 }
 
-fn buffer_lint(
-    psess: &ParseSess,
-    span: MultiSpan,
-    node_id: NodeId,
-    message: impl Into<DiagMessage>,
-) {
+fn buffer_lint(psess: &ParseSess, span: MultiSpan, node_id: NodeId, diag: BuiltinLintDiag) {
     // Macros loaded from other crates have dummy node ids.
     if node_id != DUMMY_NODE_ID {
-        psess.buffer_lint(META_VARIABLE_MISUSE, span, node_id, message);
+        psess.buffer_lint(META_VARIABLE_MISUSE, span, node_id, diag);
     }
 }
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index 8f18055f838..d99ecb61085 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -79,11 +79,10 @@ impl<'a> ParserAnyMacro<'a> {
         // but `m!()` is allowed in expression positions (cf. issue #34706).
         if kind == AstFragmentKind::Expr && parser.token == token::Semi {
             if is_local {
-                parser.psess.buffer_lint_with_diagnostic(
+                parser.psess.buffer_lint(
                     SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
                     parser.token.span,
                     lint_node_id,
-                    "trailing semicolon in macro used in expression position",
                     BuiltinLintDiag::TrailingMacro(is_trailing_mac, macro_ident),
                 );
             }
@@ -1154,11 +1153,10 @@ fn check_matcher_core<'tt>(
                             name,
                             Some(NonterminalKind::PatParam { inferred: false }),
                         ));
-                        sess.psess.buffer_lint_with_diagnostic(
+                        sess.psess.buffer_lint(
                             RUST_2021_INCOMPATIBLE_OR_PATTERNS,
                             span,
                             ast::CRATE_NODE_ID,
-                            "the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro",
                             BuiltinLintDiag::OrPatternsBackCompat(span, suggestion),
                         );
                     }
diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs
index ce2382b9501..987e48a1a76 100644
--- a/compiler/rustc_interface/src/util.rs
+++ b/compiler/rustc_interface/src/util.rs
@@ -399,31 +399,17 @@ pub(crate) fn check_attr_crate_type(
 
                 if let ast::MetaItemKind::NameValue(spanned) = a.meta_kind().unwrap() {
                     let span = spanned.span;
-                    let lev_candidate = find_best_match_for_name(
+                    let candidate = find_best_match_for_name(
                         &CRATE_TYPES.iter().map(|(k, _)| *k).collect::<Vec<_>>(),
                         n,
                         None,
                     );
-                    if let Some(candidate) = lev_candidate {
-                        lint_buffer.buffer_lint_with_diagnostic(
-                            lint::builtin::UNKNOWN_CRATE_TYPES,
-                            ast::CRATE_NODE_ID,
-                            span,
-                            "invalid `crate_type` value",
-                            BuiltinLintDiag::UnknownCrateTypes(
-                                span,
-                                "did you mean".to_string(),
-                                format!("\"{candidate}\""),
-                            ),
-                        );
-                    } else {
-                        lint_buffer.buffer_lint(
-                            lint::builtin::UNKNOWN_CRATE_TYPES,
-                            ast::CRATE_NODE_ID,
-                            span,
-                            "invalid `crate_type` value",
-                        );
-                    }
+                    lint_buffer.buffer_lint(
+                        lint::builtin::UNKNOWN_CRATE_TYPES,
+                        ast::CRATE_NODE_ID,
+                        span,
+                        BuiltinLintDiag::UnknownCrateTypes { span, candidate },
+                    );
                 }
             } else {
                 // This is here mainly to check for using a macro, such as
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index cf9d089ff62..6f6480a4964 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -1,7 +1,20 @@
+lint_abs_path_with_module = absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition
+    .suggestion = use `crate`
+
+lint_ambiguous_glob_reexport = ambiguous glob re-exports
+    .label_first_reexport = the name `{$name}` in the {$namespace} namespace is first re-exported here
+    .label_duplicate_reexport = but the name `{$name}` in the {$namespace} namespace is also re-exported here
+
 lint_ambiguous_wide_pointer_comparisons = ambiguous wide pointer comparison, the comparison includes metadata which may not be expected
     .addr_metadata_suggestion = use explicit `std::ptr::eq` method to compare metadata and addresses
     .addr_suggestion = use `std::ptr::addr_eq` or untyped pointers to only compare their addresses
 
+lint_associated_const_elided_lifetime = {$elided ->
+        [true] `&` without an explicit lifetime name cannot be used here
+        *[false] `'_` cannot be used here
+    }
+    .suggestion = use the `'static` lifetime
+
 lint_async_fn_in_trait = use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
     .note = you can suppress this lint if you plan to use the trait only in your own code, or do not care about auto traits like `Send` on the `Future`
     .suggestion = you can alternatively desugar to a normal `fn` that returns `impl Future` and add any desired bounds such as `Send`, but these cannot be relaxed without a breaking API change
@@ -19,10 +32,19 @@ lint_atomic_ordering_load = atomic loads cannot have `Release` or `AcqRel` order
 lint_atomic_ordering_store = atomic stores cannot have `Acquire` or `AcqRel` ordering
     .help = consider using ordering modes `Release`, `SeqCst` or `Relaxed`
 
+lint_avoid_att_syntax =
+    avoid using `.att_syntax`, prefer using `options(att_syntax)` instead
+
+lint_avoid_intel_syntax =
+    avoid using `.intel_syntax`, Intel syntax is the default
+
 lint_bad_attribute_argument = bad attribute argument
 
 lint_bad_opt_access = {$msg}
 
+lint_break_with_label_and_loop = this labeled break expression is easy to confuse with an unlabeled break with a labeled value expression
+    .suggestion = wrap this expression in parentheses
+
 lint_builtin_allow_internal_unsafe =
     `allow_internal_unsafe` allows defining macros using unsafe without triggering the `unsafe_code` lint at their call site
 
@@ -30,6 +52,8 @@ lint_builtin_anonymous_params = anonymous parameters are deprecated and will be
     .suggestion = try naming the parameter or explicitly ignoring it
 
 lint_builtin_asm_labels = avoid using named labels in inline assembly
+    .help = only local labels of the form `<number>:` should be used in inline asm
+    .note = see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information
 
 lint_builtin_box_pointers = type uses owned (Box type) pointers: {$ty}
 
@@ -154,6 +178,12 @@ lint_builtin_unused_doc_comment = unused doc comment
 lint_builtin_while_true = denote infinite loops with `loop {"{"} ... {"}"}`
     .suggestion = use `loop`
 
+lint_byte_slice_in_packed_struct_with_derive = {$ty} slice in a packed struct that derives a built-in trait
+    .help = consider implementing the trait by hand, or remove the `packed` attribute
+
+lint_cfg_attr_no_attributes =
+    `#[cfg_attr]` does not expand to any attributes
+
 lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}`
 
 lint_command_line_source = `forbid` lint level was set on command line
@@ -162,12 +192,20 @@ lint_confusable_identifier_pair = found both `{$existing_sym}` and `{$sym}` as i
     .current_use = this identifier can be confused with `{$existing_sym}`
     .other_use = other identifier used here
 
+lint_crate_name_in_cfg_attr_deprecated =
+    `crate_name` within an `#![cfg_attr]` attribute is deprecated
+
+lint_crate_type_in_cfg_attr_deprecated =
+    `crate_type` within an `#![cfg_attr]` attribute is deprecated
+
 lint_cstring_ptr = getting the inner pointer of a temporary `CString`
     .as_ptr_label = this pointer will be invalid
     .unwrap_label = this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime
     .note = pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned
     .help = for more information, see https://doc.rust-lang.org/reference/destructors.html
 
+lint_custom_inner_attribute_unstable = custom inner attributes are unstable
+
 lint_default_hash_types = prefer `{$preferred}` over `{$used}`, it has better performance
     .note = a `use rustc_data_structures::fx::{$preferred}` may be necessary
 
@@ -178,6 +216,11 @@ lint_deprecated_lint_name =
     .suggestion = change it to
     .help = change it to {$replace}
 
+lint_deprecated_where_clause_location = where clause not allowed here
+    .note = see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information
+    .suggestion_move_to_end = move it to the end of the type declaration
+    .suggestion_remove_where = remove this `where`
+
 lint_diag_out_of_impl =
     diagnostics should only be created in `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls
 
@@ -195,6 +238,11 @@ lint_dropping_references = calls to `std::mem::drop` with a reference instead of
     .label = argument has type `{$arg_ty}`
     .note = use `let _ = ...` to ignore the expression or result
 
+lint_duplicate_macro_attribute =
+    duplicated attribute
+
+lint_duplicate_matcher_binding = duplicate matcher binding
+
 lint_enum_intrinsics_mem_discriminant =
     the return value of `mem::discriminant` is unspecified when called with a non-enum type
     .note = the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `{$ty_param}`, which is not an enum.
@@ -207,6 +255,13 @@ lint_expectation = this lint expectation is unfulfilled
     .note = the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
     .rationale = {$rationale}
 
+lint_extern_crate_not_idiomatic = `extern crate` is not idiomatic in the new edition
+    .suggestion = convert it to a `use`
+
+lint_extern_without_abi = extern declarations without an explicit ABI are deprecated
+    .label = ABI should be specified here
+    .help = the default ABI is {$default_abi}
+
 lint_for_loops_over_fallibles =
     for loop over {$article} `{$ty}`. This is more readably written as an `if let` statement
     .suggestion = consider using `if let` to clear intent
@@ -221,6 +276,12 @@ lint_forgetting_references = calls to `std::mem::forget` with a reference instea
     .label = argument has type `{$arg_ty}`
     .note = use `let _ = ...` to ignore the expression or result
 
+lint_hidden_glob_reexport = private item shadows public glob re-export
+    .note_glob_reexport = the name `{$name}` in the {$namespace} namespace is supposed to be publicly re-exported here
+    .note_private_item = but the private item here shadows it
+
+lint_hidden_lifetime_parameters = hidden lifetime parameters in types are deprecated
+
 lint_hidden_unicode_codepoints = unicode codepoint changing visible direction of text present in {$label}
     .label = this {$label} contains {$count ->
         [one] an invisible
@@ -262,6 +323,11 @@ lint_identifier_uncommon_codepoints = identifier contains {$codepoints_len ->
 
 lint_ignored_unless_crate_specified = {$level}({$name}) is ignored unless specified at crate level
 
+lint_ill_formed_attribute_input = {$num_suggestions ->
+        [1] attribute must be of the form {$suggestions}
+        *[other] valid forms for the attribute are {$suggestions}
+    }
+
 lint_impl_trait_overcaptures = `{$self_ty}` will capture more lifetimes than possibly intended in edition 2024
     .note = specifically, {$num_captured ->
         [one] this lifetime is
@@ -332,6 +398,14 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re
 lint_improper_ctypes_union_layout_reason = this union has unspecified layout
 lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive
 
+lint_incomplete_include =
+    include macro expected single expression in source
+
+lint_inner_macro_attribute_unstable = inner macro attributes are unstable
+
+lint_invalid_crate_type_value = invalid `crate_type` value
+    .suggestion = did you mean
+
 # FIXME: we should ordinalize $valid_up_to when we add support for doing so
 lint_invalid_from_utf8_checked = calls to `{$method}` with a invalid literal always return an error
     .label = the literal was valid UTF-8 up to the {$valid_up_to} bytes
@@ -360,9 +434,22 @@ lint_invalid_reference_casting_note_book = for more information, visit <https://
 
 lint_invalid_reference_casting_note_ty_has_interior_mutability = even for types with interior mutability, the only legal way to obtain a mutable pointer from a shared reference is through `UnsafeCell::get`
 
+lint_legacy_derive_helpers = derive helper attribute is used before it is introduced
+    .label = the attribute is introduced here
+
 lint_lintpass_by_hand = implementing `LintPass` by hand
     .help = try using `declare_lint_pass!` or `impl_lint_pass!` instead
 
+lint_macro_expanded_macro_exports_accessed_by_absolute_paths = macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths
+    .note = the macro is defined here
+
+lint_macro_is_private = macro `{$ident}` is private
+
+lint_macro_rule_never_used = rule #{$n} of macro `{$name}` is never used
+
+lint_macro_use_deprecated =
+    deprecated `#[macro_use]` attribute used to import macros should be replaced at use sites with a `use` item to import the macro instead
+
 lint_malformed_attribute = malformed lint attribute input
 
 lint_map_unit_fn = `Iterator::map` call that discard the iterator's values
@@ -372,6 +459,12 @@ lint_map_unit_fn = `Iterator::map` call that discard the iterator's values
     .map_label = after this call to map, the resulting iterator is `impl Iterator<Item = ()>`, which means the only information carried by the iterator is the number of items
     .suggestion = you might have meant to use `Iterator::for_each`
 
+lint_metavariable_still_repeating = variable '{$name}' is still repeating at this depth
+
+lint_metavariable_wrong_operator = meta-variable repeats with different Kleene operator
+
+lint_missing_fragment_specifier = missing fragment specifier
+
 lint_mixed_script_confusables =
     the usage of Script Group `{$set}` in this crate consists solely of mixed script confusables
     .includes_note = the usage includes {$includes}
@@ -379,6 +472,11 @@ lint_mixed_script_confusables =
 
 lint_multiple_supertrait_upcastable = `{$ident}` is object-safe and has multiple supertraits
 
+lint_named_argument_used_positionally = named argument `{$named_arg_name}` is not used by name
+    .label_named_arg = this named argument is referred to by position in formatting string
+    .label_position_arg = this formatting argument uses named argument `{$named_arg_name}` by position
+    .suggestion = use the named argument by name to avoid ambiguity
+
 lint_node_source = `forbid` level set here
     .note = {$reason}
 
@@ -490,6 +588,9 @@ lint_opaque_hidden_inferred_bound = opaque type `{$ty}` does not satisfy its ass
 
 lint_opaque_hidden_inferred_bound_sugg = add this bound
 
+lint_or_patterns_back_compat = the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro
+    .suggestion = use pat_param to preserve semantics
+
 lint_overflowing_bin_hex = literal out of range for `{$ty}`
     .negative_note = the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}`
     .negative_becomes_note = and the value `-{$lit}` will become `{$actually}{$ty}`
@@ -519,6 +620,21 @@ lint_path_statement_drop = path statement drops value
 
 lint_path_statement_no_effect = path statement with no effect
 
+lint_pattern_in_bodiless = patterns aren't allowed in functions without bodies
+    .label = pattern not allowed in function without body
+
+lint_pattern_in_foreign = patterns aren't allowed in foreign function declarations
+    .label = pattern not allowed in foreign function
+
+lint_private_extern_crate_reexport =
+    extern crate `{$ident}` is private, and cannot be re-exported (error E0365), consider declaring with `pub`
+
+lint_proc_macro_back_compat = using an old version of `{$crate_name}`
+    .note = older versions of the `{$crate_name}` crate will stop compiling in future versions of Rust; please update to `{$crate_name}` v{$fixed_version}, or switch to one of the `{$crate_name}` alternatives
+
+lint_proc_macro_derive_resolution_fallback = cannot find {$ns} `{$ident}` in this scope
+    .label = names from parent modules are not accessible without an explicit import
+
 lint_ptr_null_checks_fn_ptr = function pointers are not nullable, so checking them for null will always return false
     .help = wrap the function pointer inside an `Option` and use `Option::is_none` to check for null pointer value
     .label = expression has type `{$orig_ty}`
@@ -540,6 +656,16 @@ lint_reason_must_be_string_literal = reason must be a string literal
 
 lint_reason_must_come_last = reason in lint attribute must come last
 
+lint_redundant_import = the item `{$ident}` is imported redundantly
+    .label_imported_here = the item `{ident}` is already imported here
+    .label_defined_here = the item `{ident}` is already defined here
+    .label_imported_prelude = the item `{ident}` is already imported by the extern prelude
+    .label_defined_prelude = the item `{ident}` is already defined by the extern prelude
+
+lint_redundant_import_visibility = glob import doesn't reexport anything with visibility `{$import_vis}` because no imported item is public enough
+    .note = the most public imported item is `{$max_vis}`
+    .help = reduce the glob import's visibility or increase visibility of imported items
+
 lint_redundant_semicolons =
     unnecessary trailing {$multiple ->
         [true] semicolons
@@ -550,6 +676,8 @@ lint_redundant_semicolons =
         *[false] this semicolon
     }
 
+lint_remove_mut_from_pattern = remove `mut` from the parameter
+
 lint_removed_lint = lint `{$name}` has been removed: {$reason}
 
 lint_renamed_lint = lint `{$name}` has been renamed to `{$replace}`
@@ -558,6 +686,10 @@ lint_renamed_lint = lint `{$name}` has been renamed to `{$replace}`
 
 lint_requested_level = requested on the command line with `{$level} {$lint_name}`
 
+lint_reserved_prefix = prefix `{$prefix}` is unknown
+    .label = unknown prefix
+    .suggestion = insert whitespace here to avoid this being parsed as a prefix in Rust 2021
+
 lint_shadowed_into_iter =
     this method call resolves to `<&{$target} as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<{$target} as IntoIterator>::into_iter` in Rust {$edition}
     .use_iter_suggestion = use `.iter()` instead of `.into_iter()` to avoid ambiguity
@@ -565,6 +697,11 @@ lint_shadowed_into_iter =
     .use_explicit_into_iter_suggestion =
         or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
 
+lint_single_use_lifetime = lifetime parameter `{$ident}` only used once
+    .label_param = this lifetime...
+    .label_use = ...is used only here
+    .suggestion = elide the single-use lifetime
+
 lint_span_use_eq_ctxt = use `.eq_ctxt()` instead of `.ctxt() == .ctxt()`
 
 lint_supertrait_as_deref_target = this `Deref` implementation is covered by an implicit supertrait coercion
@@ -578,6 +715,10 @@ lint_suspicious_double_ref_clone =
 lint_suspicious_double_ref_deref =
     using `.deref()` on a double reference, which returns `{$ty}` instead of dereferencing the inner type
 
+lint_trailing_semi_macro = trailing semicolon in macro used in expression position
+    .note1 = macro invocations at the end of a block are treated as expressions
+    .note2 = to ignore the value produced by the macro, add a semicolon after the invocation of `{$name}`
+
 lint_ty_qualified = usage of qualified `ty::{$ty}`
     .suggestion = try importing it and using it unqualified
 
@@ -591,12 +732,64 @@ lint_undropped_manually_drops = calls to `std::mem::drop` with `std::mem::Manual
     .label = argument has type `{$arg_ty}`
     .suggestion = use `std::mem::ManuallyDrop::into_inner` to get the inner value
 
+lint_unexpected_cfg_add_build_rs_println = or consider adding `{$build_rs_println}` to the top of the `build.rs`
+lint_unexpected_cfg_add_cargo_feature = consider using a Cargo feature instead
+lint_unexpected_cfg_add_cargo_toml_lint_cfg = or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:{$cargo_toml_lint_cfg}
+lint_unexpected_cfg_add_cmdline_arg = to expect this configuration use `{$cmdline_arg}`
+lint_unexpected_cfg_define_features = consider defining some features in `Cargo.toml`
+lint_unexpected_cfg_doc_cargo = see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
+lint_unexpected_cfg_doc_rustc = see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
+
+lint_unexpected_cfg_name = unexpected `cfg` condition name: `{$name}`
+lint_unexpected_cfg_name_expected_names = expected names are: {$possibilities}{$and_more ->
+        [0] {""}
+        *[other] {" "}and {$and_more} more
+    }
+lint_unexpected_cfg_name_expected_values = expected values for `{$best_match}` are: {$possibilities}
+lint_unexpected_cfg_name_similar_name = there is a config with a similar name
+lint_unexpected_cfg_name_similar_name_different_values = there is a config with a similar name and different values
+lint_unexpected_cfg_name_similar_name_no_value = there is a config with a similar name and no value
+lint_unexpected_cfg_name_similar_name_value = there is a config with a similar name and value
+lint_unexpected_cfg_name_with_similar_value = found config with similar value
+
+lint_unexpected_cfg_value = unexpected `cfg` condition value: {$has_value ->
+        [true] `{$value}`
+        *[false] (none)
+    }
+lint_unexpected_cfg_value_add_feature = consider adding `{$value}` as a feature in `Cargo.toml`
+lint_unexpected_cfg_value_expected_values = expected values for `{$name}` are: {$have_none_possibility ->
+        [true] {"(none), "}
+        *[false] {""}
+    }{$possibilities}{$and_more ->
+        [0] {""}
+        *[other] {" "}and {$and_more} more
+    }
+lint_unexpected_cfg_value_no_expected_value = no expected value for `{$name}`
+lint_unexpected_cfg_value_no_expected_values = no expected values for `{$name}`
+lint_unexpected_cfg_value_remove_condition = remove the condition
+lint_unexpected_cfg_value_remove_value = remove the value
+lint_unexpected_cfg_value_similar_name = there is a expected value with a similar name
+lint_unexpected_cfg_value_specify_value = specify a config value
+
 lint_ungated_async_fn_track_caller = `#[track_caller]` on async functions is a no-op
      .label = this function will not propagate the caller location
 
+lint_unicode_text_flow = unicode codepoint changing visible direction of text present in comment
+    .label = {$num_codepoints ->
+            [1] this comment contains an invisible unicode text flow control codepoint
+            *[other] this comment contains invisible unicode text flow control codepoints
+        }
+    .note = these kind of unicode codepoints change the way text flows on applications that support them, but can cause confusion because they change the order of characters on the screen
+    .suggestion = if their presence wasn't intentional, you can remove them
+    .label_comment_char = {$c_debug}
+
+
 lint_unit_bindings = binding has unit type `()`
     .label = this pattern is inferred to be the unit type `()`
 
+lint_unknown_diagnostic_attribute = unknown diagnostic attribute
+lint_unknown_diagnostic_attribute_typo_sugg = an attribute with a similar name exists
+
 lint_unknown_gated_lint =
     unknown lint: `{$name}`
     .note = the `{$name}` lint is unstable
@@ -612,9 +805,16 @@ lint_unknown_lint =
         *[false] did you mean: `{$replace}`
     }
 
+lint_unknown_macro_variable = unknown macro variable `{$name}`
+
 lint_unknown_tool_in_scoped_lint = unknown tool name `{$tool_name}` found in scoped lint: `{$tool_name}::{$lint_name}`
     .help = add `#![register_tool({$tool_name})]` to the crate root
 
+lint_unnameable_test_items = cannot test inner items
+
+lint_unnecessary_qualification = unnecessary qualification
+    .suggestion = remove the unnecessary path segments
+
 lint_unsupported_group = `{$lint_group}` lint group is not supported with ´--force-warn´
 
 lint_untranslatable_diag = diagnostics should be created using translatable messages
@@ -622,6 +822,9 @@ lint_untranslatable_diag = diagnostics should be created using translatable mess
 lint_unused_allocation = unnecessary allocation, use `&` instead
 lint_unused_allocation_mut = unnecessary allocation, use `&mut` instead
 
+lint_unused_builtin_attribute = unused attribute `{$attr_name}`
+    .note = the built-in attribute `{$attr_name}` will be ignored, since it's applied to the macro invocation `{$macro_name}`
+
 lint_unused_closure =
     unused {$pre}{$count ->
         [one] closure
@@ -638,14 +841,43 @@ lint_unused_coroutine =
     }{$post} that must be used
     .note = coroutines are lazy and do nothing unless resumed
 
+lint_unused_crate_dependency = external crate `{$extern_crate}` unused in `{$local_crate}`: remove the dependency or add `use {$extern_crate} as _;`
+
 lint_unused_def = unused {$pre}`{$def}`{$post} that must be used
     .suggestion = use `let _ = ...` to ignore the resulting value
 
 lint_unused_delim = unnecessary {$delim} around {$item}
     .suggestion = remove these {$delim}
 
+lint_unused_doc_comment = unused doc comment
+    .label = rustdoc does not generate documentation for macro invocations
+    .help = to document an item produced by a macro, the macro must produce the documentation as part of its expansion
+
+lint_unused_extern_crate = unused extern crate
+    .suggestion = remove it
+
 lint_unused_import_braces = braces around {$node} is unnecessary
 
+lint_unused_imports = {$num_snippets ->
+        [one] unused import: {$span_snippets}
+        *[other] unused imports: {$span_snippets}
+    }
+    .suggestion_remove_whole_use = remove the whole `use` item
+    .suggestion_remove_imports = {$num_to_remove ->
+            [one] remove the unused import
+            *[other] remove the unused imports
+        }
+    .help = if this is a test module, consider adding a `#[cfg(test)]` to the containing module
+
+lint_unused_label = unused label
+
+lint_unused_lifetime = lifetime parameter `{$ident}` never used
+    .suggestion = elide the unused lifetime
+
+lint_unused_macro_definition = unused macro definition: `{$name}`
+
+lint_unused_macro_use = unused `#[macro_use]` import
+
 lint_unused_op = unused {$op} that must be used
     .label = the {$op} produces a value
     .suggestion = use `let _ = ...` to ignore the resulting value
@@ -654,3 +886,6 @@ lint_unused_result = unused result of type `{$ty}`
 
 lint_variant_size_differences =
     enum variant is more than three times larger ({$largest} bytes) than the next largest
+
+lint_wasm_c_abi =
+    older versions of the `wasm-bindgen` crate will be incompatible with future versions of Rust; please update to `wasm-bindgen` v0.2.88
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 6b9f9d1531e..0f059bceae7 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -30,10 +30,10 @@ use crate::{
         BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures,
         BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
         BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
-        BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns,
-        BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasGenericBounds,
-        BuiltinTypeAliasGenericBoundsSuggestion, BuiltinTypeAliasWhereClause,
-        BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
+        BuiltinMutablesTransmutes, BuiltinNamedAsmLabel, BuiltinNoMangleGeneric,
+        BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds,
+        BuiltinTypeAliasGenericBounds, BuiltinTypeAliasGenericBoundsSuggestion,
+        BuiltinTypeAliasWhereClause, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
         BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe,
         BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
         BuiltinWhileTrue, SuggestChangingAssocTypes,
@@ -60,7 +60,7 @@ use rustc_middle::ty::GenericArgKind;
 use rustc_middle::ty::TypeVisitableExt;
 use rustc_middle::ty::Upcast;
 use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
-use rustc_session::lint::{BuiltinLintDiag, FutureIncompatibilityReason};
+use rustc_session::lint::FutureIncompatibilityReason;
 use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
 use rustc_span::edition::Edition;
 use rustc_span::source_map::Spanned;
@@ -2882,16 +2882,7 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels {
                     let target_spans: MultiSpan =
                         if spans.len() > 0 { spans.into() } else { (*template_span).into() };
 
-                    cx.span_lint_with_diagnostics(
-                            NAMED_ASM_LABELS,
-                            Some(target_spans),
-                            fluent::lint_builtin_asm_labels,
-                            |_| {},
-                            BuiltinLintDiag::NamedAsmLabel(
-                                "only local labels of the form `<number>:` should be used in inline asm"
-                                    .to_string(),
-                            ),
-                        );
+                    cx.emit_span_lint(NAMED_ASM_LABELS, target_spans, BuiltinNamedAsmLabel);
                 }
             }
         }
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 62ba9ef5c11..deeb3ae090c 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -527,30 +527,24 @@ pub struct EarlyContext<'a> {
     pub buffered: LintBuffer,
 }
 
-pub trait LintContext {
-    fn sess(&self) -> &Session;
-
+impl EarlyContext<'_> {
     /// Emit a lint at the appropriate level, with an optional associated span and an existing
     /// diagnostic.
     ///
     /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
     #[rustc_lint_diagnostics]
-    fn span_lint_with_diagnostics(
+    pub fn span_lint_with_diagnostics(
         &self,
         lint: &'static Lint,
-        span: Option<impl Into<MultiSpan>>,
-        msg: impl Into<DiagMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
+        span: MultiSpan,
         diagnostic: BuiltinLintDiag,
     ) {
-        // We first generate a blank diagnostic.
-        self.opt_span_lint(lint, span, msg, |db| {
-            // Now, set up surrounding context.
-            diagnostics::builtin(self.sess(), diagnostic, db);
-            // Rewrap `db`, and pass control to the user.
-            decorate(db)
-        });
+        diagnostics::emit_buffered_lint(self, lint, span, diagnostic)
     }
+}
+
+pub trait LintContext {
+    fn sess(&self) -> &Session;
 
     // FIXME: These methods should not take an Into<MultiSpan> -- instead, callers should need to
     // set the span in their `decorate` function (preferably using set_span).
diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs
index 5ad3ff71a6d..236eeee6152 100644
--- a/compiler/rustc_lint/src/context/diagnostics.rs
+++ b/compiler/rustc_lint/src/context/diagnostics.rs
@@ -1,58 +1,57 @@
 #![allow(rustc::diagnostic_outside_of_impl)]
 #![allow(rustc::untranslatable_diagnostic)]
 
+use std::borrow::Cow;
+
 use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
-use rustc_errors::{elided_lifetime_in_path_suggestion, Diag};
-use rustc_errors::{Applicability, SuggestionStyle};
+use rustc_errors::Applicability;
+use rustc_errors::{elided_lifetime_in_path_suggestion, DiagArgValue, MultiSpan};
 use rustc_middle::middle::stability;
-use rustc_session::lint::BuiltinLintDiag;
-use rustc_session::Session;
+use rustc_session::lint::{BuiltinLintDiag, Lint};
 use rustc_span::BytePos;
 
+use crate::{lints, EarlyContext, LintContext as _};
+
 mod check_cfg;
 
-pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Diag<'_, ()>) {
+pub(super) fn emit_buffered_lint(
+    ctx: &EarlyContext<'_>,
+    lint: &'static Lint,
+    span: MultiSpan,
+    diagnostic: BuiltinLintDiag,
+) {
+    let sess = ctx.sess();
     match diagnostic {
-        BuiltinLintDiag::UnicodeTextFlow(span, content) => {
+        BuiltinLintDiag::UnicodeTextFlow(comment_span, content) => {
             let spans: Vec<_> = content
                 .char_indices()
                 .filter_map(|(i, c)| {
                     TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| {
-                        let lo = span.lo() + BytePos(2 + i as u32);
-                        (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32)))
+                        let lo = comment_span.lo() + BytePos(2 + i as u32);
+                        (c, comment_span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32)))
                     })
                 })
                 .collect();
-            let (an, s) = match spans.len() {
-                1 => ("an ", ""),
-                _ => ("", "s"),
-            };
-            diag.span_label(
+            let characters = spans
+                .iter()
+                .map(|&(c, span)| lints::UnicodeCharNoteSub { span, c_debug: format!("{c:?}") })
+                .collect();
+            let suggestions = (!spans.is_empty()).then_some(lints::UnicodeTextFlowSuggestion {
+                spans: spans.iter().map(|(_c, span)| *span).collect(),
+            });
+            ctx.emit_span_lint(
+                lint,
                 span,
-                format!(
-                    "this comment contains {an}invisible unicode text flow control codepoint{s}",
-                ),
-            );
-            for (c, span) in &spans {
-                diag.span_label(*span, format!("{c:?}"));
-            }
-            diag.note(
-                "these kind of unicode codepoints change the way text flows on \
-                         applications that support them, but can cause confusion because they \
-                         change the order of characters on the screen",
-            );
-            if !spans.is_empty() {
-                diag.multipart_suggestion_with_style(
-                    "if their presence wasn't intentional, you can remove them",
-                    spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(),
-                    Applicability::MachineApplicable,
-                    SuggestionStyle::HideCodeAlways,
-                );
-            }
-        }
-        BuiltinLintDiag::Normal => (),
-        BuiltinLintDiag::AbsPathWithModule(span) => {
-            let (sugg, app) = match sess.source_map().span_to_snippet(span) {
+                lints::UnicodeTextFlow {
+                    comment_span,
+                    characters,
+                    suggestions,
+                    num_codepoints: spans.len(),
+                },
+            )
+        }
+        BuiltinLintDiag::AbsPathWithModule(mod_span) => {
+            let (replacement, applicability) = match sess.source_map().span_to_snippet(mod_span) {
                 Ok(ref s) => {
                     // FIXME(Manishearth) ideally the emitting code
                     // can tell us whether or not this is global
@@ -62,160 +61,207 @@ pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Di
                 }
                 Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders),
             };
-            diag.span_suggestion(span, "use `crate`", sugg, app);
-        }
-        BuiltinLintDiag::ProcMacroDeriveResolutionFallback(span) => {
-            diag.span_label(
+            ctx.emit_span_lint(
+                lint,
                 span,
-                "names from parent modules are not accessible without an explicit import",
+                lints::AbsPathWithModule {
+                    sugg: lints::AbsPathWithModuleSugg {
+                        span: mod_span,
+                        applicability,
+                        replacement,
+                    },
+                },
             );
         }
-        BuiltinLintDiag::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => {
-            diag.span_note(span_def, "the macro is defined here");
-        }
+        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 },
+            ),
         BuiltinLintDiag::ElidedLifetimesInPaths(n, path_span, incl_angl_brckt, insertion_span) => {
-            diag.subdiagnostic(
-                sess.dcx(),
-                elided_lifetime_in_path_suggestion(
-                    sess.source_map(),
-                    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,
+                    ),
+                },
             );
         }
-        BuiltinLintDiag::UnknownCrateTypes(span, note, sugg) => {
-            diag.span_suggestion(span, note, sugg, Applicability::MaybeIncorrect);
-        }
-        BuiltinLintDiag::UnusedImports(message, replaces, in_test_module) => {
-            if !replaces.is_empty() {
-                diag.tool_only_multipart_suggestion(
-                    message,
-                    replaces,
-                    Applicability::MachineApplicable,
-                );
-            }
+        BuiltinLintDiag::UnknownCrateTypes { span, candidate } => {
+            let sugg = candidate.map(|candidate| lints::UnknownCrateTypesSub { span, candidate });
+            ctx.emit_span_lint(lint, span, lints::UnknownCrateTypes { sugg });
+        }
+        BuiltinLintDiag::UnusedImports {
+            remove_whole_use,
+            num_to_remove,
+            remove_spans,
+            test_module_span,
+            span_snippets,
+        } => {
+            let sugg = if remove_whole_use {
+                lints::UnusedImportsSugg::RemoveWholeUse { span: remove_spans[0] }
+            } else {
+                lints::UnusedImportsSugg::RemoveImports { remove_spans, num_to_remove }
+            };
+            let test_module_span =
+                test_module_span.map(|span| sess.source_map().guess_head_span(span));
 
-            if let Some(span) = in_test_module {
-                diag.span_help(
-                    sess.source_map().guess_head_span(span),
-                    "if this is a test module, consider adding a `#[cfg(test)]` to the containing module",
-                );
-            }
+            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(),
+                    ),
+                },
+            );
         }
         BuiltinLintDiag::RedundantImport(spans, ident) => {
-            for (span, is_imported) in spans {
-                let introduced = if is_imported { "imported" } else { "defined" };
-                let span_msg = if span.is_dummy() { "by the extern prelude" } else { "here" };
-                diag.span_label(
-                    span,
-                    format!("the item `{ident}` is already {introduced} {span_msg}"),
-                );
-            }
-        }
-        BuiltinLintDiag::DeprecatedMacro(suggestion, span) => {
-            stability::deprecation_suggestion(diag, "macro", suggestion, span)
-        }
-        BuiltinLintDiag::UnusedDocComment(span) => {
-            diag.span_label(span, "rustdoc does not generate documentation for macro invocations");
-            diag.help("to document an item produced by a macro, \
-                                  the macro must produce the documentation as part of its expansion");
-        }
-        BuiltinLintDiag::PatternsInFnsWithoutBody(span, ident) => {
-            diag.span_suggestion(
+            let subs = spans
+                .into_iter()
+                .map(|(span, is_imported)| {
+                    (match (span.is_dummy(), is_imported) {
+                        (false, true) => lints::RedundantImportSub::ImportedHere,
+                        (false, false) => lints::RedundantImportSub::DefinedHere,
+                        (true, true) => lints::RedundantImportSub::ImportedPrelude,
+                        (true, false) => lints::RedundantImportSub::DefinedPrelude,
+                    })(span)
+                })
+                .collect();
+            ctx.emit_span_lint(lint, span, lints::RedundantImport { subs, ident });
+        }
+        BuiltinLintDiag::DeprecatedMacro {
+            suggestion,
+            suggestion_span,
+            note,
+            path,
+            since_kind,
+        } => {
+            let sub = suggestion.map(|suggestion| stability::DeprecationSuggestion {
+                span: suggestion_span,
+                kind: "macro".to_owned(),
+                suggestion,
+            });
+            ctx.emit_span_lint(
+                lint,
                 span,
-                "remove `mut` from the parameter",
-                ident,
-                Applicability::MachineApplicable,
+                stability::Deprecated { sub, kind: "macro".to_owned(), path, note, since_kind },
             );
         }
-        BuiltinLintDiag::MissingAbi(span, default_abi) => {
-            diag.span_label(span, "ABI should be specified here");
-            diag.help(format!("the default ABI is {}", default_abi.name()));
+        BuiltinLintDiag::UnusedDocComment(attr_span) => {
+            ctx.emit_span_lint(lint, span, lints::UnusedDocComment { span: attr_span });
         }
-        BuiltinLintDiag::LegacyDeriveHelpers(span) => {
-            diag.span_label(span, "the attribute is introduced here");
+        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 }
+                },
+            );
         }
-        BuiltinLintDiag::ProcMacroBackCompat(note) => {
-            diag.note(note);
+        BuiltinLintDiag::MissingAbi(label_span, default_abi) => {
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::MissingAbi { span: label_span, default_abi: default_abi.name() },
+            );
         }
-        BuiltinLintDiag::OrPatternsBackCompat(span, suggestion) => {
-            diag.span_suggestion(
+        BuiltinLintDiag::LegacyDeriveHelpers(label_span) => {
+            ctx.emit_span_lint(lint, span, lints::LegacyDeriveHelpers { span: label_span });
+        }
+        BuiltinLintDiag::ProcMacroBackCompat { crate_name, fixed_version } => {
+            ctx.emit_span_lint(
+                lint,
                 span,
-                "use pat_param to preserve semantics",
-                suggestion,
-                Applicability::MachineApplicable,
+                lints::ProcMacroBackCompat { crate_name, fixed_version },
+            );
+        }
+        BuiltinLintDiag::OrPatternsBackCompat(suggestion_span, suggestion) => {
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::OrPatternsBackCompat { span: suggestion_span, suggestion },
             );
         }
-        BuiltinLintDiag::ReservedPrefix(span) => {
-            diag.span_label(span, "unknown prefix");
-            diag.span_suggestion_verbose(
-                span.shrink_to_hi(),
-                "insert whitespace here to avoid this being parsed as a prefix in Rust 2021",
-                " ",
-                Applicability::MachineApplicable,
+        BuiltinLintDiag::ReservedPrefix(label_span, prefix) => {
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::ReservedPrefix {
+                    label: label_span,
+                    suggestion: label_span.shrink_to_hi(),
+                    prefix,
+                },
             );
         }
         BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => {
-            diag.span_note(
-                        invoc_span,
-                        format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`")
-                    );
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name },
+            );
         }
         BuiltinLintDiag::TrailingMacro(is_trailing, name) => {
-            if is_trailing {
-                diag.note("macro invocations at the end of a block are treated as expressions");
-                diag.note(format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`"));
-            }
-        }
-        BuiltinLintDiag::BreakWithLabelAndLoop(span) => {
-            diag.multipart_suggestion(
-                "wrap this expression in parentheses",
-                vec![
-                    (span.shrink_to_lo(), "(".to_string()),
-                    (span.shrink_to_hi(), ")".to_string()),
-                ],
-                Applicability::MachineApplicable,
-            );
+            ctx.emit_span_lint(lint, span, lints::TrailingMacro { is_trailing, name });
         }
-        BuiltinLintDiag::NamedAsmLabel(help) => {
-            diag.help(help);
-            diag.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information");
+        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(),
+                    },
+                },
+            );
         }
         BuiltinLintDiag::UnexpectedCfgName(name, value) => {
-            check_cfg::unexpected_cfg_name(sess, diag, name, value)
+            ctx.emit_span_lint(lint, span, check_cfg::unexpected_cfg_name(sess, name, value));
         }
         BuiltinLintDiag::UnexpectedCfgValue(name, value) => {
-            check_cfg::unexpected_cfg_value(sess, diag, name, value)
-        }
-        BuiltinLintDiag::DeprecatedWhereclauseLocation(sugg) => {
-            let left_sp = diag.span.primary_span().unwrap();
-            match sugg {
-                Some((right_sp, sugg)) => diag.multipart_suggestion(
-                    "move it to the end of the type declaration",
-                    vec![(left_sp, String::new()), (right_sp, sugg)],
-                    Applicability::MachineApplicable,
-                ),
-                None => diag.span_suggestion(
-                    left_sp,
-                    "remove this `where`",
-                    "",
-                    Applicability::MachineApplicable,
-                ),
+            ctx.emit_span_lint(lint, span, check_cfg::unexpected_cfg_value(sess, name, value));
+        }
+        BuiltinLintDiag::DeprecatedWhereclauseLocation(left_sp, sugg) => {
+            let suggestion = match sugg {
+                Some((right_sp, sugg)) => lints::DeprecatedWhereClauseLocationSugg::MoveToEnd {
+                    left: left_sp,
+                    right: right_sp,
+                    sugg,
+                },
+                None => lints::DeprecatedWhereClauseLocationSugg::RemoveWhere { span: left_sp },
             };
-            diag.note("see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information");
+            ctx.emit_span_lint(lint, span, lints::DeprecatedWhereClauseLocation { suggestion });
         }
         BuiltinLintDiag::SingleUseLifetime {
             param_span,
             use_span: Some((use_span, elide)),
             deletion_span,
+            ident,
         } => {
             debug!(?param_span, ?use_span, ?deletion_span);
-            diag.span_label(param_span, "this lifetime...");
-            diag.span_label(use_span, "...is used only here");
-            if let Some(deletion_span) = deletion_span {
-                let msg = "elide the single-use lifetime";
+            let suggestion = if let Some(deletion_span) = deletion_span {
                 let (use_span, replace_lt) = if elide {
                     let use_span = sess.source_map().span_extend_while_whitespace(use_span);
                     (use_span, String::new())
@@ -226,24 +272,22 @@ pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Di
 
                 // issue 107998 for the case such as a wrong function pointer type
                 // `deletion_span` is empty and there is no need to report lifetime uses here
-                let suggestions = if deletion_span.is_empty() {
-                    vec![(use_span, replace_lt)]
-                } else {
-                    vec![(deletion_span, String::new()), (use_span, replace_lt)]
-                };
-                diag.multipart_suggestion(msg, suggestions, Applicability::MachineApplicable);
-            }
+                let deletion_span =
+                    if deletion_span.is_empty() { None } else { Some(deletion_span) };
+                Some(lints::SingleUseLifetimeSugg { deletion_span, use_span, replace_lt })
+            } else {
+                None
+            };
+
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::SingleUseLifetime { suggestion, param_span, use_span, ident },
+            );
         }
-        BuiltinLintDiag::SingleUseLifetime { param_span: _, use_span: None, deletion_span } => {
+        BuiltinLintDiag::SingleUseLifetime { use_span: None, deletion_span, ident, .. } => {
             debug!(?deletion_span);
-            if let Some(deletion_span) = deletion_span {
-                diag.span_suggestion(
-                    deletion_span,
-                    "elide the unused lifetime",
-                    "",
-                    Applicability::MachineApplicable,
-                );
-            }
+            ctx.emit_span_lint(lint, span, lints::UnusedLifetime { deletion_span, ident });
         }
         BuiltinLintDiag::NamedArgumentUsedPositionally {
             position_sp_to_replace,
@@ -252,19 +296,12 @@ pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Di
             named_arg_name,
             is_formatting_arg,
         } => {
-            diag.span_label(
-                named_arg_sp,
-                "this named argument is referred to by position in formatting string",
-            );
-            if let Some(positional_arg_for_msg) = position_sp_for_msg {
-                let msg = format!(
-                    "this formatting argument uses named argument `{named_arg_name}` by position"
-                );
-                diag.span_label(positional_arg_for_msg, msg);
-            }
-
-            if let Some(positional_arg_to_replace) = position_sp_to_replace {
-                let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name };
+            let (suggestion, name) = if let Some(positional_arg_to_replace) = position_sp_to_replace
+            {
+                let mut name = named_arg_name.clone();
+                if is_formatting_arg {
+                    name.push('$')
+                };
                 let span_to_replace = if let Ok(positional_arg_content) =
                     sess.source_map().span_to_snippet(positional_arg_to_replace)
                     && positional_arg_content.starts_with(':')
@@ -273,31 +310,40 @@ pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Di
                 } else {
                     positional_arg_to_replace
                 };
-                diag.span_suggestion_verbose(
-                    span_to_replace,
-                    "use the named argument by name to avoid ambiguity",
+                (Some(span_to_replace), name)
+            } else {
+                (None, String::new())
+            };
+
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::NamedArgumentUsedPositionally {
+                    named_arg_sp,
+                    position_label_sp: position_sp_for_msg,
+                    suggestion,
                     name,
-                    Applicability::MaybeIncorrect,
-                );
-            }
+                    named_arg_name,
+                },
+            )
         }
-        BuiltinLintDiag::ByteSliceInPackedStructWithDerive => {
-            diag.help("consider implementing the trait by hand, or remove the `packed` attribute");
+        BuiltinLintDiag::ByteSliceInPackedStructWithDerive { ty } => {
+            ctx.emit_span_lint(lint, span, lints::ByteSliceInPackedStructWithDerive { ty })
         }
         BuiltinLintDiag::UnusedExternCrate { removal_span } => {
-            diag.span_suggestion(removal_span, "remove it", "", Applicability::MachineApplicable);
+            ctx.emit_span_lint(lint, span, lints::UnusedExternCrate { removal_span })
         }
         BuiltinLintDiag::ExternCrateNotIdiomatic { vis_span, ident_span } => {
             let suggestion_span = vis_span.between(ident_span);
-            diag.span_suggestion_verbose(
-                suggestion_span,
-                "convert it to a `use`",
-                if vis_span.is_empty() { "use " } else { " use " },
-                Applicability::MachineApplicable,
+            let code = if vis_span.is_empty() { "use " } else { " use " };
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::ExternCrateNotIdiomatic { span: suggestion_span, code },
             );
         }
         BuiltinLintDiag::AmbiguousGlobImports { diag: ambiguity } => {
-            rustc_errors::report_ambiguity_error(diag, ambiguity);
+            ctx.emit_span_lint(lint, span, lints::AmbiguousGlobImports { ambiguity });
         }
         BuiltinLintDiag::AmbiguousGlobReexports {
             name,
@@ -305,15 +351,15 @@ pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Di
             first_reexport_span,
             duplicate_reexport_span,
         } => {
-            diag.span_label(
-                first_reexport_span,
-                format!("the name `{name}` in the {namespace} namespace is first re-exported here"),
-            );
-            diag.span_label(
-                duplicate_reexport_span,
-                format!(
-                    "but the name `{name}` in the {namespace} namespace is also re-exported here"
-                ),
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::AmbiguousGlobReexports {
+                    first_reexport: first_reexport_span,
+                    duplicate_reexport: duplicate_reexport_span,
+                    name,
+                    namespace,
+                },
             );
         }
         BuiltinLintDiag::HiddenGlobReexports {
@@ -322,38 +368,127 @@ pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Di
             glob_reexport_span,
             private_item_span,
         } => {
-            diag.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here"));
-            diag.span_note(private_item_span, "but the private item here shadows it".to_owned());
-        }
-        BuiltinLintDiag::UnusedQualifications { removal_span } => {
-            diag.span_suggestion_verbose(
-                removal_span,
-                "remove the unnecessary path segments",
-                "",
-                Applicability::MachineApplicable,
+            ctx.emit_span_lint(
+                lint,
+                span,
+                lints::HiddenGlobReexports {
+                    glob_reexport: glob_reexport_span,
+                    private_item: private_item_span,
+
+                    name,
+                    namespace,
+                },
             );
         }
-        BuiltinLintDiag::AssociatedConstElidedLifetime { elided, span } => {
-            diag.span_suggestion_verbose(
-                if elided { span.shrink_to_hi() } else { span },
-                "use the `'static` lifetime",
-                if elided { "'static " } else { "'static" },
-                Applicability::MachineApplicable,
-            );
+        BuiltinLintDiag::UnusedQualifications { removal_span } => {
+            ctx.emit_span_lint(lint, span, lints::UnusedQualifications { removal_span });
         }
-        BuiltinLintDiag::RedundantImportVisibility { max_vis, span } => {
-            diag.span_note(span, format!("the most public imported item is `{max_vis}`"));
-            diag.help(
-                "reduce the glob import's visibility or increase visibility of imported items",
+        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 },
             );
         }
-        BuiltinLintDiag::MaybeTypo { span, name } => {
-            diag.span_suggestion_verbose(
+        BuiltinLintDiag::RedundantImportVisibility { max_vis, span: vis_span, import_vis } => {
+            ctx.emit_span_lint(
+                lint,
                 span,
-                "an attribute with a similar name exists",
-                name,
-                Applicability::MachineApplicable,
+                lints::RedundantImportVisibility { span: vis_span, help: (), max_vis, import_vis },
             );
         }
+        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 });
+        }
+        BuiltinLintDiag::MacroUseDeprecated => {
+            ctx.emit_span_lint(lint, span, lints::MacroUseDeprecated)
+        }
+        BuiltinLintDiag::UnusedMacroUse => ctx.emit_span_lint(lint, span, lints::UnusedMacroUse),
+        BuiltinLintDiag::PrivateExternCrateReexport(ident) => {
+            ctx.emit_span_lint(lint, span, lints::PrivateExternCrateReexport { ident })
+        }
+        BuiltinLintDiag::UnusedLabel => ctx.emit_span_lint(lint, span, lints::UnusedLabel),
+        BuiltinLintDiag::MacroIsPrivate(ident) => {
+            ctx.emit_span_lint(lint, span, lints::MacroIsPrivate { ident })
+        }
+        BuiltinLintDiag::UnusedMacroDefinition(name) => {
+            ctx.emit_span_lint(lint, span, lints::UnusedMacroDefinition { name })
+        }
+        BuiltinLintDiag::MacroRuleNeverUsed(n, name) => {
+            ctx.emit_span_lint(lint, span, lints::MacroRuleNeverUsed { n: n + 1, name })
+        }
+        BuiltinLintDiag::UnstableFeature(msg) => {
+            ctx.emit_span_lint(lint, span, lints::UnstableFeature { msg })
+        }
+        BuiltinLintDiag::AvoidUsingIntelSyntax => {
+            ctx.emit_span_lint(lint, span, lints::AvoidIntelSyntax)
+        }
+        BuiltinLintDiag::AvoidUsingAttSyntax => {
+            ctx.emit_span_lint(lint, span, lints::AvoidAttSyntax)
+        }
+        BuiltinLintDiag::IncompleteInclude => {
+            ctx.emit_span_lint(lint, span, lints::IncompleteInclude)
+        }
+        BuiltinLintDiag::UnnameableTestItems => {
+            ctx.emit_span_lint(lint, span, lints::UnnameableTestItems)
+        }
+        BuiltinLintDiag::DuplicateMacroAttribute => {
+            ctx.emit_span_lint(lint, span, lints::DuplicateMacroAttribute)
+        }
+        BuiltinLintDiag::CfgAttrNoAttributes => {
+            ctx.emit_span_lint(lint, span, lints::CfgAttrNoAttributes)
+        }
+        BuiltinLintDiag::CrateTypeInCfgAttr => {
+            ctx.emit_span_lint(lint, span, lints::CrateTypeInCfgAttr)
+        }
+        BuiltinLintDiag::CrateNameInCfgAttr => {
+            ctx.emit_span_lint(lint, span, lints::CrateNameInCfgAttr)
+        }
+        BuiltinLintDiag::MissingFragmentSpecifier => {
+            ctx.emit_span_lint(lint, span, lints::MissingFragmentSpecifier)
+        }
+        BuiltinLintDiag::MetaVariableStillRepeating(name) => {
+            ctx.emit_span_lint(lint, span, lints::MetaVariableStillRepeating { name })
+        }
+        BuiltinLintDiag::MetaVariableWrongOperator => {
+            ctx.emit_span_lint(lint, span, lints::MetaVariableWrongOperator)
+        }
+        BuiltinLintDiag::DuplicateMatcherBinding => {
+            ctx.emit_span_lint(lint, span, lints::DuplicateMatcherBinding)
+        }
+        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::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
+            },
+        ),
     }
 }
diff --git a/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs b/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs
index 3fa04ab75f8..020ca1753cf 100644
--- a/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs
+++ b/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs
@@ -1,44 +1,27 @@
-use rustc_errors::{Applicability, Diag};
 use rustc_middle::bug;
 use rustc_session::{config::ExpectedValues, Session};
 use rustc_span::edit_distance::find_best_match_for_name;
 use rustc_span::{sym, Span, Symbol};
 
+use crate::lints;
+
 const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35;
 
-fn check_cfg_expected_note(
+fn sort_and_truncate_possibilities(
     sess: &Session,
-    possibilities: &[Symbol],
-    type_: &str,
-    name: Option<Symbol>,
-    suffix: &str,
-) -> String {
-    use std::fmt::Write;
-
+    mut possibilities: Vec<Symbol>,
+) -> (Vec<Symbol>, usize) {
     let n_possibilities = if sess.opts.unstable_opts.check_cfg_all_expected {
         possibilities.len()
     } else {
         std::cmp::min(possibilities.len(), MAX_CHECK_CFG_NAMES_OR_VALUES)
     };
 
-    let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
-    possibilities.sort();
+    possibilities.sort_by(|s1, s2| s1.as_str().cmp(s2.as_str()));
 
     let and_more = possibilities.len().saturating_sub(n_possibilities);
-    let possibilities = possibilities[..n_possibilities].join("`, `");
-
-    let mut note = String::with_capacity(50 + possibilities.len());
-
-    write!(&mut note, "expected {type_}").unwrap();
-    if let Some(name) = name {
-        write!(&mut note, " for `{name}`").unwrap();
-    }
-    write!(&mut note, " are: {suffix}`{possibilities}`").unwrap();
-    if and_more > 0 {
-        write!(&mut note, " and {and_more} more").unwrap();
-    }
-
-    note
+    possibilities.truncate(n_possibilities);
+    (possibilities, and_more)
 }
 
 enum EscapeQuotes {
@@ -61,10 +44,9 @@ fn to_check_cfg_arg(name: Symbol, value: Option<Symbol>, quotes: EscapeQuotes) -
 
 pub(super) fn unexpected_cfg_name(
     sess: &Session,
-    diag: &mut Diag<'_, ()>,
     (name, name_span): (Symbol, Span),
     value: Option<(Symbol, Span)>,
-) {
+) -> lints::UnexpectedCfgName {
     #[allow(rustc::potential_query_instability)]
     let possibilities: Vec<Symbol> = sess.psess.check_config.expecteds.keys().copied().collect();
 
@@ -87,114 +69,122 @@ pub(super) fn unexpected_cfg_name(
     let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
     let mut is_feature_cfg = name == sym::feature;
 
-    if is_feature_cfg && is_from_cargo {
-        diag.help("consider defining some features in `Cargo.toml`");
+    let code_sugg = if is_feature_cfg && is_from_cargo {
+        lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures
     // Suggest the most probable if we found one
     } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
+        is_feature_cfg |= best_match == sym::feature;
+
         if let Some(ExpectedValues::Some(best_match_values)) =
             sess.psess.check_config.expecteds.get(&best_match)
         {
             // We will soon sort, so the initial order does not matter.
             #[allow(rustc::potential_query_instability)]
-            let mut possibilities =
-                best_match_values.iter().flatten().map(Symbol::as_str).collect::<Vec<_>>();
-            possibilities.sort();
+            let mut possibilities = best_match_values.iter().flatten().collect::<Vec<_>>();
+            possibilities.sort_by_key(|s| s.as_str());
+
+            let get_possibilities_sub = || {
+                if !possibilities.is_empty() {
+                    let possibilities =
+                        possibilities.iter().copied().cloned().collect::<Vec<_>>().into();
+                    Some(lints::unexpected_cfg_name::ExpectedValues { best_match, possibilities })
+                } else {
+                    None
+                }
+            };
 
-            let mut should_print_possibilities = true;
             if let Some((value, value_span)) = value {
                 if best_match_values.contains(&Some(value)) {
-                    diag.span_suggestion(
-                        name_span,
-                        "there is a config with a similar name and value",
-                        best_match,
-                        Applicability::MaybeIncorrect,
-                    );
-                    should_print_possibilities = false;
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue {
+                        span: name_span,
+                        code: best_match.to_string(),
+                    }
                 } else if best_match_values.contains(&None) {
-                    diag.span_suggestion(
-                        name_span.to(value_span),
-                        "there is a config with a similar name and no value",
-                        best_match,
-                        Applicability::MaybeIncorrect,
-                    );
-                    should_print_possibilities = false;
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameNoValue {
+                        span: name_span.to(value_span),
+                        code: best_match.to_string(),
+                    }
                 } else if let Some(first_value) = possibilities.first() {
-                    diag.span_suggestion(
-                        name_span.to(value_span),
-                        "there is a config with a similar name and different values",
-                        format!("{best_match} = \"{first_value}\""),
-                        Applicability::MaybeIncorrect,
-                    );
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
+                        span: name_span.to(value_span),
+                        code: format!("{best_match} = \"{first_value}\""),
+                        expected: get_possibilities_sub(),
+                    }
                 } else {
-                    diag.span_suggestion(
-                        name_span.to(value_span),
-                        "there is a config with a similar name and different values",
-                        best_match,
-                        Applicability::MaybeIncorrect,
-                    );
-                };
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
+                        span: name_span.to(value_span),
+                        code: best_match.to_string(),
+                        expected: get_possibilities_sub(),
+                    }
+                }
             } else {
-                diag.span_suggestion(
-                    name_span,
-                    "there is a config with a similar name",
-                    best_match,
-                    Applicability::MaybeIncorrect,
-                );
-            }
-
-            if !possibilities.is_empty() && should_print_possibilities {
-                let possibilities = possibilities.join("`, `");
-                diag.help(format!("expected values for `{best_match}` are: `{possibilities}`"));
+                lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
+                    span: name_span,
+                    code: best_match.to_string(),
+                    expected: get_possibilities_sub(),
+                }
             }
         } else {
-            diag.span_suggestion(
-                name_span,
-                "there is a config with a similar name",
-                best_match,
-                Applicability::MaybeIncorrect,
-            );
+            lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
+                span: name_span,
+                code: best_match.to_string(),
+                expected: None,
+            }
         }
-
-        is_feature_cfg |= best_match == sym::feature;
     } else {
-        if !names_possibilities.is_empty() && names_possibilities.len() <= 3 {
+        let similar_values = if !names_possibilities.is_empty() && names_possibilities.len() <= 3 {
             names_possibilities.sort();
-            for cfg_name in names_possibilities.iter() {
-                diag.span_suggestion(
-                    name_span,
-                    "found config with similar value",
-                    format!("{cfg_name} = \"{name}\""),
-                    Applicability::MaybeIncorrect,
-                );
-            }
-        }
-        if !possibilities.is_empty() {
-            diag.help_once(check_cfg_expected_note(sess, &possibilities, "names", None, ""));
+            names_possibilities
+                .iter()
+                .map(|cfg_name| lints::unexpected_cfg_name::FoundWithSimilarValue {
+                    span: name_span,
+                    code: format!("{cfg_name} = \"{name}\""),
+                })
+                .collect()
+        } else {
+            vec![]
+        };
+        let expected_names = if !possibilities.is_empty() {
+            let (possibilities, and_more) = sort_and_truncate_possibilities(sess, possibilities);
+            Some(lints::unexpected_cfg_name::ExpectedNames {
+                possibilities: possibilities.into(),
+                and_more,
+            })
+        } else {
+            None
+        };
+        lints::unexpected_cfg_name::CodeSuggestion::SimilarValues {
+            with_similar_values: similar_values,
+            expected_names,
         }
-    }
+    };
 
     let inst = |escape_quotes| to_check_cfg_arg(name, value.map(|(v, _s)| v), escape_quotes);
 
-    if is_from_cargo {
-        if !is_feature_cfg {
-            diag.help(format!("consider using a Cargo feature instead"));
-            diag.help(format!("or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{}'] }}", inst(EscapeQuotes::No)));
-            diag.help(format!("or consider adding `println!(\"cargo::rustc-check-cfg={}\");` to the top of the `build.rs`", inst(EscapeQuotes::Yes)));
-        }
-        diag.note("see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration");
+    let invocation_help = if is_from_cargo {
+        let sub = if !is_feature_cfg {
+            Some(lints::UnexpectedCfgCargoHelp::new(
+                &inst(EscapeQuotes::No),
+                &inst(EscapeQuotes::Yes),
+            ))
+        } else {
+            None
+        };
+        lints::unexpected_cfg_name::InvocationHelp::Cargo { sub }
     } else {
-        let inst = inst(EscapeQuotes::No);
-        diag.help(format!("to expect this configuration use `--check-cfg={inst}`",));
-        diag.note("see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration");
-    }
+        lints::unexpected_cfg_name::InvocationHelp::Rustc(lints::UnexpectedCfgRustcHelp::new(
+            &inst(EscapeQuotes::No),
+        ))
+    };
+
+    lints::UnexpectedCfgName { code_sugg, invocation_help, name }
 }
 
 pub(super) fn unexpected_cfg_value(
     sess: &Session,
-    diag: &mut Diag<'_, ()>,
     (name, name_span): (Symbol, Span),
     value: Option<(Symbol, Span)>,
-) {
+) -> lints::UnexpectedCfgValue {
     let Some(ExpectedValues::Some(values)) = &sess.psess.check_config.expecteds.get(&name) else {
         bug!(
             "it shouldn't be possible to have a diagnostic on a value whose name is not in values"
@@ -214,53 +204,53 @@ pub(super) fn unexpected_cfg_value(
 
     // Show the full list if all possible values for a given name, but don't do it
     // for names as the possibilities could be very long
-    if !possibilities.is_empty() {
-        diag.note(check_cfg_expected_note(
-            sess,
-            &possibilities,
-            "values",
-            Some(name),
-            if have_none_possibility { "(none), " } else { "" },
-        ));
+    let code_sugg = if !possibilities.is_empty() {
+        let expected_values = {
+            let (possibilities, and_more) =
+                sort_and_truncate_possibilities(sess, possibilities.clone());
+            lints::unexpected_cfg_value::ExpectedValues {
+                name,
+                have_none_possibility,
+                possibilities: possibilities.into(),
+                and_more,
+            }
+        };
 
-        if let Some((value, value_span)) = value {
+        let suggestion = if let Some((value, value_span)) = value {
             // Suggest the most probable if we found one
             if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
-                diag.span_suggestion(
-                    value_span,
-                    "there is a expected value with a similar name",
-                    format!("\"{best_match}\""),
-                    Applicability::MaybeIncorrect,
-                );
+                Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
+                    span: value_span,
+                    best_match,
+                })
+            } else {
+                None
             }
         } else if let &[first_possibility] = &possibilities[..] {
-            diag.span_suggestion(
-                name_span.shrink_to_hi(),
-                "specify a config value",
-                format!(" = \"{first_possibility}\""),
-                Applicability::MaybeIncorrect,
-            );
-        }
+            Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
+                span: name_span.shrink_to_hi(),
+                first_possibility,
+            })
+        } else {
+            None
+        };
+
+        lints::unexpected_cfg_value::CodeSuggestion::ChangeValue { expected_values, suggestion }
     } else if have_none_possibility {
-        diag.note(format!("no expected value for `{name}`"));
-        if let Some((_value, value_span)) = value {
-            diag.span_suggestion(
-                name_span.shrink_to_hi().to(value_span),
-                "remove the value",
-                "",
-                Applicability::MaybeIncorrect,
-            );
-        }
+        let suggestion =
+            value.map(|(_value, value_span)| lints::unexpected_cfg_value::RemoveValueSuggestion {
+                span: name_span.shrink_to_hi().to(value_span),
+            });
+        lints::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name }
     } else {
-        diag.note(format!("no expected values for `{name}`"));
-
-        let sp = if let Some((_value, value_span)) = value {
+        let span = if let Some((_value, value_span)) = value {
             name_span.to(value_span)
         } else {
             name_span
         };
-        diag.span_suggestion(sp, "remove the condition", "", Applicability::MaybeIncorrect);
-    }
+        let suggestion = lints::unexpected_cfg_value::RemoveConditionSuggestion { span };
+        lints::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
+    };
 
     // We don't want to suggest adding values to well known names
     // since those are defined by rustc it-self. Users can still
@@ -269,24 +259,35 @@ pub(super) fn unexpected_cfg_value(
 
     let inst = |escape_quotes| to_check_cfg_arg(name, value.map(|(v, _s)| v), escape_quotes);
 
-    if is_from_cargo {
-        if name == sym::feature {
+    let invocation_help = if is_from_cargo {
+        let help = if name == sym::feature {
             if let Some((value, _value_span)) = value {
-                diag.help(format!("consider adding `{value}` as a feature in `Cargo.toml`"));
+                Some(lints::unexpected_cfg_value::CargoHelp::AddFeature { value })
             } else {
-                diag.help("consider defining some features in `Cargo.toml`");
+                Some(lints::unexpected_cfg_value::CargoHelp::DefineFeatures)
             }
         } else if !is_cfg_a_well_know_name {
-            diag.help(format!("consider using a Cargo feature instead"));
-            diag.help(format!("or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{}'] }}", inst(EscapeQuotes::No)));
-            diag.help(format!("or consider adding `println!(\"cargo::rustc-check-cfg={}\");` to the top of the `build.rs`", inst(EscapeQuotes::Yes)));
-        }
-        diag.note("see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration");
+            Some(lints::unexpected_cfg_value::CargoHelp::Other(lints::UnexpectedCfgCargoHelp::new(
+                &inst(EscapeQuotes::No),
+                &inst(EscapeQuotes::Yes),
+            )))
+        } else {
+            None
+        };
+        lints::unexpected_cfg_value::InvocationHelp::Cargo(help)
     } else {
-        if !is_cfg_a_well_know_name {
-            let inst = inst(EscapeQuotes::No);
-            diag.help(format!("to expect this configuration use `--check-cfg={inst}`",));
-        }
-        diag.note("see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration");
+        let help = if !is_cfg_a_well_know_name {
+            Some(lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
+        } else {
+            None
+        };
+        lints::unexpected_cfg_value::InvocationHelp::Rustc(help)
+    };
+
+    lints::UnexpectedCfgValue {
+        code_sugg,
+        invocation_help,
+        has_value: value.is_some(),
+        value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
     }
 }
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
index 3f627baf770..736c7a11069 100644
--- a/compiler/rustc_lint/src/early.rs
+++ b/compiler/rustc_lint/src/early.rs
@@ -14,7 +14,7 @@
 //! upon. As the ast is traversed, this keeps track of the current lint level
 //! for all lint attributes.
 
-use crate::context::{EarlyContext, LintContext, LintStore};
+use crate::context::{EarlyContext, LintStore};
 use crate::passes::{EarlyLintPass, EarlyLintPassObject};
 use rustc_ast::ptr::P;
 use rustc_ast::visit::{self as ast_visit, walk_list, Visitor};
@@ -44,14 +44,8 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
     #[allow(rustc::diagnostic_outside_of_impl)]
     fn inlined_check_id(&mut self, id: ast::NodeId) {
         for early_lint in self.context.buffered.take(id) {
-            let BufferedEarlyLint { span, msg, node_id: _, lint_id, diagnostic } = early_lint;
-            self.context.span_lint_with_diagnostics(
-                lint_id.lint,
-                Some(span),
-                msg,
-                |_| {},
-                diagnostic,
-            );
+            let BufferedEarlyLint { span, node_id: _, lint_id, diagnostic } = early_lint;
+            self.context.span_lint_with_diagnostics(lint_id.lint, span, diagnostic);
         }
     }
 
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index bc0c8cf85d8..3bd6faca379 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -5,16 +5,22 @@ use std::num::NonZero;
 use crate::errors::RequestedLevel;
 use crate::fluent_generated as fluent;
 use rustc_errors::{
-    codes::*, Applicability, Diag, DiagMessage, DiagStyledString, EmissionGuarantee,
-    LintDiagnostic, SubdiagMessageOp, Subdiagnostic, SuggestionStyle,
+    codes::*, Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString,
+    ElidedLifetimeInPathSubdiag, EmissionGuarantee, LintDiagnostic, SubdiagMessageOp,
+    Subdiagnostic, SuggestionStyle,
 };
-use rustc_hir::def_id::DefId;
+use rustc_hir::{def::Namespace, def_id::DefId};
 use rustc_macros::{LintDiagnostic, Subdiagnostic};
 use rustc_middle::ty::{
     inhabitedness::InhabitedPredicate, Clause, PolyExistentialTraitRef, Ty, TyCtxt,
 };
-use rustc_session::Session;
-use rustc_span::{edition::Edition, sym, symbol::Ident, Span, Symbol};
+use rustc_session::{lint::AmbiguityErrorDiag, Session};
+use rustc_span::{
+    edition::Edition,
+    sym,
+    symbol::{Ident, MacroRulesNormalizedIdent},
+    Span, Symbol,
+};
 
 use crate::{
     builtin::InitError, builtin::TypeAliasBounds, errors::OverruledAttributeSub, LateContext,
@@ -1947,3 +1953,837 @@ pub struct UnitBindingsDiag {
     #[label]
     pub label: Span,
 }
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_asm_labels)]
+#[help]
+#[note]
+pub struct BuiltinNamedAsmLabel;
+
+#[derive(Subdiagnostic)]
+#[help(lint_unexpected_cfg_add_cargo_feature)]
+#[help(lint_unexpected_cfg_add_cargo_toml_lint_cfg)]
+#[help(lint_unexpected_cfg_add_build_rs_println)]
+pub struct UnexpectedCfgCargoHelp {
+    pub build_rs_println: String,
+    pub cargo_toml_lint_cfg: String,
+}
+
+impl UnexpectedCfgCargoHelp {
+    pub fn new(unescaped: &str, escaped: &str) -> Self {
+        Self {
+            cargo_toml_lint_cfg: format!(
+                "\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{unescaped}'] }}",
+            ),
+            build_rs_println: format!("println!(\"cargo::rustc-check-cfg={escaped}\");",),
+        }
+    }
+}
+
+#[derive(Subdiagnostic)]
+#[help(lint_unexpected_cfg_add_cmdline_arg)]
+pub struct UnexpectedCfgRustcHelp {
+    pub cmdline_arg: String,
+}
+
+impl UnexpectedCfgRustcHelp {
+    pub fn new(unescaped: &str) -> Self {
+        Self { cmdline_arg: format!("--check-cfg={unescaped}") }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unexpected_cfg_name)]
+pub struct UnexpectedCfgName {
+    #[subdiagnostic]
+    pub code_sugg: unexpected_cfg_name::CodeSuggestion,
+    #[subdiagnostic]
+    pub invocation_help: unexpected_cfg_name::InvocationHelp,
+
+    pub name: Symbol,
+}
+
+pub mod unexpected_cfg_name {
+    use rustc_errors::DiagSymbolList;
+    use rustc_macros::Subdiagnostic;
+    use rustc_span::{Span, Symbol};
+
+    #[derive(Subdiagnostic)]
+    pub enum CodeSuggestion {
+        #[help(lint_unexpected_cfg_define_features)]
+        DefineFeatures,
+        #[suggestion(
+            lint_unexpected_cfg_name_similar_name_value,
+            applicability = "maybe-incorrect",
+            code = "{code}"
+        )]
+        SimilarNameAndValue {
+            #[primary_span]
+            span: Span,
+            code: String,
+        },
+        #[suggestion(
+            lint_unexpected_cfg_name_similar_name_no_value,
+            applicability = "maybe-incorrect",
+            code = "{code}"
+        )]
+        SimilarNameNoValue {
+            #[primary_span]
+            span: Span,
+            code: String,
+        },
+        #[suggestion(
+            lint_unexpected_cfg_name_similar_name_different_values,
+            applicability = "maybe-incorrect",
+            code = "{code}"
+        )]
+        SimilarNameDifferentValues {
+            #[primary_span]
+            span: Span,
+            code: String,
+            #[subdiagnostic]
+            expected: Option<ExpectedValues>,
+        },
+        #[suggestion(
+            lint_unexpected_cfg_name_similar_name,
+            applicability = "maybe-incorrect",
+            code = "{code}"
+        )]
+        SimilarName {
+            #[primary_span]
+            span: Span,
+            code: String,
+            #[subdiagnostic]
+            expected: Option<ExpectedValues>,
+        },
+        SimilarValues {
+            #[subdiagnostic]
+            with_similar_values: Vec<FoundWithSimilarValue>,
+            #[subdiagnostic]
+            expected_names: Option<ExpectedNames>,
+        },
+    }
+
+    #[derive(Subdiagnostic)]
+    #[help(lint_unexpected_cfg_name_expected_values)]
+    pub struct ExpectedValues {
+        pub best_match: Symbol,
+        pub possibilities: DiagSymbolList,
+    }
+
+    #[derive(Subdiagnostic)]
+    #[suggestion(
+        lint_unexpected_cfg_name_with_similar_value,
+        applicability = "maybe-incorrect",
+        code = "{code}"
+    )]
+    pub struct FoundWithSimilarValue {
+        #[primary_span]
+        pub span: Span,
+        pub code: String,
+    }
+
+    #[derive(Subdiagnostic)]
+    #[help_once(lint_unexpected_cfg_name_expected_names)]
+    pub struct ExpectedNames {
+        pub possibilities: DiagSymbolList,
+        pub and_more: usize,
+    }
+
+    #[derive(Subdiagnostic)]
+    pub enum InvocationHelp {
+        #[note(lint_unexpected_cfg_doc_cargo)]
+        Cargo {
+            #[subdiagnostic]
+            sub: Option<super::UnexpectedCfgCargoHelp>,
+        },
+        #[note(lint_unexpected_cfg_doc_rustc)]
+        Rustc(#[subdiagnostic] super::UnexpectedCfgRustcHelp),
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unexpected_cfg_value)]
+pub struct UnexpectedCfgValue {
+    #[subdiagnostic]
+    pub code_sugg: unexpected_cfg_value::CodeSuggestion,
+    #[subdiagnostic]
+    pub invocation_help: unexpected_cfg_value::InvocationHelp,
+
+    pub has_value: bool,
+    pub value: String,
+}
+
+pub mod unexpected_cfg_value {
+    use rustc_errors::DiagSymbolList;
+    use rustc_macros::Subdiagnostic;
+    use rustc_span::{Span, Symbol};
+
+    #[derive(Subdiagnostic)]
+    pub enum CodeSuggestion {
+        ChangeValue {
+            #[subdiagnostic]
+            expected_values: ExpectedValues,
+            #[subdiagnostic]
+            suggestion: Option<ChangeValueSuggestion>,
+        },
+        #[note(lint_unexpected_cfg_value_no_expected_value)]
+        RemoveValue {
+            #[subdiagnostic]
+            suggestion: Option<RemoveValueSuggestion>,
+
+            name: Symbol,
+        },
+        #[note(lint_unexpected_cfg_value_no_expected_values)]
+        RemoveCondition {
+            #[subdiagnostic]
+            suggestion: RemoveConditionSuggestion,
+
+            name: Symbol,
+        },
+    }
+
+    #[derive(Subdiagnostic)]
+    pub enum ChangeValueSuggestion {
+        #[suggestion(
+            lint_unexpected_cfg_value_similar_name,
+            code = r#""{best_match}""#,
+            applicability = "maybe-incorrect"
+        )]
+        SimilarName {
+            #[primary_span]
+            span: Span,
+            best_match: Symbol,
+        },
+        #[suggestion(
+            lint_unexpected_cfg_value_specify_value,
+            code = r#" = "{first_possibility}""#,
+            applicability = "maybe-incorrect"
+        )]
+        SpecifyValue {
+            #[primary_span]
+            span: Span,
+            first_possibility: Symbol,
+        },
+    }
+
+    #[derive(Subdiagnostic)]
+    #[suggestion(
+        lint_unexpected_cfg_value_remove_value,
+        code = "",
+        applicability = "maybe-incorrect"
+    )]
+    pub struct RemoveValueSuggestion {
+        #[primary_span]
+        pub span: Span,
+    }
+
+    #[derive(Subdiagnostic)]
+    #[suggestion(
+        lint_unexpected_cfg_value_remove_condition,
+        code = "",
+        applicability = "maybe-incorrect"
+    )]
+    pub struct RemoveConditionSuggestion {
+        #[primary_span]
+        pub span: Span,
+    }
+
+    #[derive(Subdiagnostic)]
+    #[note(lint_unexpected_cfg_value_expected_values)]
+    pub struct ExpectedValues {
+        pub name: Symbol,
+        pub have_none_possibility: bool,
+        pub possibilities: DiagSymbolList,
+        pub and_more: usize,
+    }
+
+    #[derive(Subdiagnostic)]
+    pub enum InvocationHelp {
+        #[note(lint_unexpected_cfg_doc_cargo)]
+        Cargo(#[subdiagnostic] Option<CargoHelp>),
+        #[note(lint_unexpected_cfg_doc_rustc)]
+        Rustc(#[subdiagnostic] Option<super::UnexpectedCfgRustcHelp>),
+    }
+
+    #[derive(Subdiagnostic)]
+    pub enum CargoHelp {
+        #[help(lint_unexpected_cfg_value_add_feature)]
+        AddFeature {
+            value: Symbol,
+        },
+        #[help(lint_unexpected_cfg_define_features)]
+        DefineFeatures,
+        Other(#[subdiagnostic] super::UnexpectedCfgCargoHelp),
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_use_deprecated)]
+pub struct MacroUseDeprecated;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_macro_use)]
+pub struct UnusedMacroUse;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_private_extern_crate_reexport)]
+pub struct PrivateExternCrateReexport {
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_label)]
+pub struct UnusedLabel;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_is_private)]
+pub struct MacroIsPrivate {
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_macro_definition)]
+pub struct UnusedMacroDefinition {
+    pub name: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_rule_never_used)]
+pub struct MacroRuleNeverUsed {
+    pub n: usize,
+    pub name: Symbol,
+}
+
+pub struct UnstableFeature {
+    pub msg: DiagMessage,
+}
+
+impl<'a> LintDiagnostic<'a, ()> for UnstableFeature {
+    fn decorate_lint<'b>(self, _diag: &'b mut Diag<'a, ()>) {}
+
+    fn msg(&self) -> DiagMessage {
+        self.msg.clone()
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_avoid_intel_syntax)]
+pub struct AvoidIntelSyntax;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_avoid_att_syntax)]
+pub struct AvoidAttSyntax;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_incomplete_include)]
+pub struct IncompleteInclude;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unnameable_test_items)]
+pub struct UnnameableTestItems;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_duplicate_macro_attribute)]
+pub struct DuplicateMacroAttribute;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_cfg_attr_no_attributes)]
+pub struct CfgAttrNoAttributes;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_crate_type_in_cfg_attr_deprecated)]
+pub struct CrateTypeInCfgAttr;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_crate_name_in_cfg_attr_deprecated)]
+pub struct CrateNameInCfgAttr;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_missing_fragment_specifier)]
+pub struct MissingFragmentSpecifier;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_metavariable_still_repeating)]
+pub struct MetaVariableStillRepeating {
+    pub name: MacroRulesNormalizedIdent,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_metavariable_wrong_operator)]
+pub struct MetaVariableWrongOperator;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_duplicate_matcher_binding)]
+pub struct DuplicateMatcherBinding;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unknown_macro_variable)]
+pub struct UnknownMacroVariable {
+    pub name: MacroRulesNormalizedIdent,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_crate_dependency)]
+pub struct UnusedCrateDependency {
+    pub extern_crate: Symbol,
+    pub local_crate: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_wasm_c_abi)]
+pub struct WasmCAbi;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_ill_formed_attribute_input)]
+pub struct IllFormedAttributeInput {
+    pub num_suggestions: usize,
+    pub suggestions: DiagArgValue,
+}
+
+#[derive(LintDiagnostic)]
+pub enum InnerAttributeUnstable {
+    #[diag(lint_inner_macro_attribute_unstable)]
+    InnerMacroAttribute,
+    #[diag(lint_custom_inner_attribute_unstable)]
+    CustomInnerAttribute,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unknown_diagnostic_attribute)]
+pub struct UnknownDiagnosticAttribute {
+    #[subdiagnostic]
+    pub typo: Option<UnknownDiagnosticAttributeTypoSugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    lint_unknown_diagnostic_attribute_typo_sugg,
+    style = "verbose",
+    code = "{typo_name}",
+    applicability = "machine-applicable"
+)]
+pub struct UnknownDiagnosticAttributeTypoSugg {
+    #[primary_span]
+    pub span: Span,
+    pub typo_name: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unicode_text_flow)]
+#[note]
+pub struct UnicodeTextFlow {
+    #[label]
+    pub comment_span: Span,
+    #[subdiagnostic]
+    pub characters: Vec<UnicodeCharNoteSub>,
+    #[subdiagnostic]
+    pub suggestions: Option<UnicodeTextFlowSuggestion>,
+
+    pub num_codepoints: usize,
+}
+
+#[derive(Subdiagnostic)]
+#[label(lint_label_comment_char)]
+pub struct UnicodeCharNoteSub {
+    #[primary_span]
+    pub span: Span,
+    pub c_debug: String,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable", style = "hidden")]
+pub struct UnicodeTextFlowSuggestion {
+    #[suggestion_part(code = "")]
+    pub spans: Vec<Span>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_abs_path_with_module)]
+pub struct AbsPathWithModule {
+    #[subdiagnostic]
+    pub sugg: AbsPathWithModuleSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(lint_suggestion, code = "{replacement}")]
+pub struct AbsPathWithModuleSugg {
+    #[primary_span]
+    pub span: Span,
+    #[applicability]
+    pub applicability: Applicability,
+    pub replacement: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_proc_macro_derive_resolution_fallback)]
+pub struct ProcMacroDeriveResolutionFallback {
+    #[label]
+    pub span: Span,
+    pub ns: Namespace,
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_expanded_macro_exports_accessed_by_absolute_paths)]
+pub struct MacroExpandedMacroExportsAccessedByAbsolutePaths {
+    #[note]
+    pub definition: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_hidden_lifetime_parameters)]
+pub struct ElidedLifetimesInPaths {
+    #[subdiagnostic]
+    pub subdiag: ElidedLifetimeInPathSubdiag,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_invalid_crate_type_value)]
+pub struct UnknownCrateTypes {
+    #[subdiagnostic]
+    pub sugg: Option<UnknownCrateTypesSub>,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(lint_suggestion, code = r#""{candidate}""#, applicability = "maybe-incorrect")]
+pub struct UnknownCrateTypesSub {
+    #[primary_span]
+    pub span: Span,
+    pub candidate: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_imports)]
+pub struct UnusedImports {
+    #[subdiagnostic]
+    pub sugg: UnusedImportsSugg,
+    #[help]
+    pub test_module_span: Option<Span>,
+
+    pub span_snippets: DiagArgValue,
+    pub num_snippets: usize,
+}
+
+#[derive(Subdiagnostic)]
+pub enum UnusedImportsSugg {
+    #[suggestion(
+        lint_suggestion_remove_whole_use,
+        applicability = "machine-applicable",
+        code = "",
+        style = "tool-only"
+    )]
+    RemoveWholeUse {
+        #[primary_span]
+        span: Span,
+    },
+    #[multipart_suggestion(
+        lint_suggestion_remove_imports,
+        applicability = "machine-applicable",
+        style = "tool-only"
+    )]
+    RemoveImports {
+        #[suggestion_part(code = "")]
+        remove_spans: Vec<Span>,
+        num_to_remove: usize,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_redundant_import)]
+pub struct RedundantImport {
+    #[subdiagnostic]
+    pub subs: Vec<RedundantImportSub>,
+
+    pub ident: Ident,
+}
+
+#[derive(Subdiagnostic)]
+pub enum RedundantImportSub {
+    #[label(lint_label_imported_here)]
+    ImportedHere(#[primary_span] Span),
+    #[label(lint_label_defined_here)]
+    DefinedHere(#[primary_span] Span),
+    #[label(lint_label_imported_prelude)]
+    ImportedPrelude(#[primary_span] Span),
+    #[label(lint_label_defined_prelude)]
+    DefinedPrelude(#[primary_span] Span),
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_doc_comment)]
+#[help]
+pub struct UnusedDocComment {
+    #[label]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+pub enum PatternsInFnsWithoutBody {
+    #[diag(lint_pattern_in_foreign)]
+    Foreign {
+        #[subdiagnostic]
+        sub: PatternsInFnsWithoutBodySub,
+    },
+    #[diag(lint_pattern_in_bodiless)]
+    Bodiless {
+        #[subdiagnostic]
+        sub: PatternsInFnsWithoutBodySub,
+    },
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(lint_remove_mut_from_pattern, code = "{ident}", applicability = "machine-applicable")]
+pub struct PatternsInFnsWithoutBodySub {
+    #[primary_span]
+    pub span: Span,
+
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_extern_without_abi)]
+#[help]
+pub struct MissingAbi {
+    #[label]
+    pub span: Span,
+
+    pub default_abi: &'static str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_legacy_derive_helpers)]
+pub struct LegacyDeriveHelpers {
+    #[label]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_proc_macro_back_compat)]
+#[note]
+pub struct ProcMacroBackCompat {
+    pub crate_name: String,
+    pub fixed_version: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_or_patterns_back_compat)]
+pub struct OrPatternsBackCompat {
+    #[suggestion(code = "{suggestion}", applicability = "machine-applicable")]
+    pub span: Span,
+    pub suggestion: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_reserved_prefix)]
+pub struct ReservedPrefix {
+    #[label]
+    pub label: Span,
+    #[suggestion(code = " ", applicability = "machine-applicable")]
+    pub suggestion: Span,
+
+    pub prefix: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_builtin_attribute)]
+pub struct UnusedBuiltinAttribute {
+    #[note]
+    pub invoc_span: Span,
+
+    pub attr_name: Symbol,
+    pub macro_name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_trailing_semi_macro)]
+pub struct TrailingMacro {
+    #[note(lint_note1)]
+    #[note(lint_note2)]
+    pub is_trailing: bool,
+
+    pub name: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_break_with_label_and_loop)]
+pub struct BreakWithLabelAndLoop {
+    #[subdiagnostic]
+    pub sub: BreakWithLabelAndLoopSub,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
+pub struct BreakWithLabelAndLoopSub {
+    #[suggestion_part(code = "(")]
+    pub left: Span,
+    #[suggestion_part(code = ")")]
+    pub right: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_deprecated_where_clause_location)]
+#[note]
+pub struct DeprecatedWhereClauseLocation {
+    #[subdiagnostic]
+    pub suggestion: DeprecatedWhereClauseLocationSugg,
+}
+
+#[derive(Subdiagnostic)]
+pub enum DeprecatedWhereClauseLocationSugg {
+    #[multipart_suggestion(lint_suggestion_move_to_end, applicability = "machine-applicable")]
+    MoveToEnd {
+        #[suggestion_part(code = "")]
+        left: Span,
+        #[suggestion_part(code = "{sugg}")]
+        right: Span,
+
+        sugg: String,
+    },
+    #[suggestion(lint_suggestion_remove_where, code = "", applicability = "machine-applicable")]
+    RemoveWhere {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_single_use_lifetime)]
+pub struct SingleUseLifetime {
+    #[label(lint_label_param)]
+    pub param_span: Span,
+    #[label(lint_label_use)]
+    pub use_span: Span,
+    #[subdiagnostic]
+    pub suggestion: Option<SingleUseLifetimeSugg>,
+
+    pub ident: Ident,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
+pub struct SingleUseLifetimeSugg {
+    #[suggestion_part(code = "")]
+    pub deletion_span: Option<Span>,
+    #[suggestion_part(code = "{replace_lt}")]
+    pub use_span: Span,
+
+    pub replace_lt: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_lifetime)]
+pub struct UnusedLifetime {
+    #[suggestion(code = "", applicability = "machine-applicable")]
+    pub deletion_span: Option<Span>,
+
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_named_argument_used_positionally)]
+pub struct NamedArgumentUsedPositionally {
+    #[label(lint_label_named_arg)]
+    pub named_arg_sp: Span,
+    #[label(lint_label_position_arg)]
+    pub position_label_sp: Option<Span>,
+    #[suggestion(style = "verbose", code = "{name}", applicability = "maybe-incorrect")]
+    pub suggestion: Option<Span>,
+
+    pub name: String,
+    pub named_arg_name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_byte_slice_in_packed_struct_with_derive)]
+#[help]
+pub struct ByteSliceInPackedStructWithDerive {
+    // FIXME: make this translatable
+    pub ty: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_extern_crate)]
+pub struct UnusedExternCrate {
+    #[suggestion(code = "", applicability = "machine-applicable")]
+    pub removal_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_extern_crate_not_idiomatic)]
+pub struct ExternCrateNotIdiomatic {
+    #[suggestion(style = "verbose", code = "{code}", applicability = "machine-applicable")]
+    pub span: Span,
+
+    pub code: &'static str,
+}
+
+// FIXME: make this translatable
+pub struct AmbiguousGlobImports {
+    pub ambiguity: AmbiguityErrorDiag,
+}
+
+impl<'a, G: EmissionGuarantee> LintDiagnostic<'a, G> for AmbiguousGlobImports {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) {
+        rustc_errors::report_ambiguity_error(diag, self.ambiguity);
+    }
+
+    fn msg(&self) -> DiagMessage {
+        DiagMessage::Str(self.ambiguity.msg.clone().into())
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_ambiguous_glob_reexport)]
+pub struct AmbiguousGlobReexports {
+    #[label(lint_label_first_reexport)]
+    pub first_reexport: Span,
+    #[label(lint_label_duplicate_reexport)]
+    pub duplicate_reexport: Span,
+
+    pub name: String,
+    // FIXME: make this translatable
+    pub namespace: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_hidden_glob_reexport)]
+pub struct HiddenGlobReexports {
+    #[note(lint_note_glob_reexport)]
+    pub glob_reexport: Span,
+    #[note(lint_note_private_item)]
+    pub private_item: Span,
+
+    pub name: String,
+    // FIXME: make this translatable
+    pub namespace: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unnecessary_qualification)]
+pub struct UnusedQualifications {
+    #[suggestion(style = "verbose", code = "", applicability = "machine-applicable")]
+    pub removal_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_associated_const_elided_lifetime)]
+pub struct AssociatedConstElidedLifetime {
+    #[suggestion(style = "verbose", code = "{code}", applicability = "machine-applicable")]
+    pub span: Span,
+
+    pub code: &'static str,
+    pub elided: bool,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_redundant_import_visibility)]
+pub struct RedundantImportVisibility {
+    #[note]
+    pub span: Span,
+    #[help]
+    pub help: (),
+
+    pub import_vis: String,
+    pub max_vis: String,
+}
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index e06e3e9b805..1941b0b1264 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -6,10 +6,12 @@ use rustc_data_structures::stable_hasher::{
     HashStable, StableCompare, StableHasher, ToStableHashKey,
 };
 use rustc_error_messages::{DiagMessage, MultiSpan};
+use rustc_hir::def::Namespace;
 use rustc_hir::HashStableContext;
 use rustc_hir::HirId;
 use rustc_macros::{Decodable, Encodable, HashStable_Generic};
 use rustc_span::edition::Edition;
+use rustc_span::symbol::MacroRulesNormalizedIdent;
 use rustc_span::{sym, symbol::Ident, Span, Symbol};
 use rustc_target::spec::abi::Abi;
 
@@ -565,19 +567,44 @@ pub struct AmbiguityErrorDiag {
     pub b2_help_msgs: Vec<String>,
 }
 
+#[derive(Debug, Clone)]
+pub enum DeprecatedSinceKind {
+    InEffect,
+    InFuture,
+    InVersion(String),
+}
+
 // This could be a closure, but then implementing derive trait
 // becomes hacky (and it gets allocated).
 #[derive(Debug)]
 pub enum BuiltinLintDiag {
-    Normal,
     AbsPathWithModule(Span),
-    ProcMacroDeriveResolutionFallback(Span),
+    ProcMacroDeriveResolutionFallback {
+        span: Span,
+        ns: Namespace,
+        ident: Ident,
+    },
     MacroExpandedMacroExportsAccessedByAbsolutePaths(Span),
     ElidedLifetimesInPaths(usize, Span, bool, Span),
-    UnknownCrateTypes(Span, String, String),
-    UnusedImports(String, Vec<(Span, String)>, Option<Span>),
+    UnknownCrateTypes {
+        span: Span,
+        candidate: Option<Symbol>,
+    },
+    UnusedImports {
+        remove_whole_use: bool,
+        num_to_remove: usize,
+        remove_spans: Vec<Span>,
+        test_module_span: Option<Span>,
+        span_snippets: Vec<String>,
+    },
     RedundantImport(Vec<(Span, bool)>, Ident),
-    DeprecatedMacro(Option<Symbol>, Span),
+    DeprecatedMacro {
+        suggestion: Option<Symbol>,
+        suggestion_span: Span,
+        note: Option<Symbol>,
+        path: String,
+        since_kind: DeprecatedSinceKind,
+    },
     MissingAbi(Span, Abi),
     UnusedDocComment(Span),
     UnusedBuiltinAttribute {
@@ -585,18 +612,24 @@ pub enum BuiltinLintDiag {
         macro_name: String,
         invoc_span: Span,
     },
-    PatternsInFnsWithoutBody(Span, Ident),
+    PatternsInFnsWithoutBody {
+        span: Span,
+        ident: Ident,
+        is_foreign: bool,
+    },
     LegacyDeriveHelpers(Span),
-    ProcMacroBackCompat(String),
+    ProcMacroBackCompat {
+        crate_name: String,
+        fixed_version: String,
+    },
     OrPatternsBackCompat(Span, String),
-    ReservedPrefix(Span),
+    ReservedPrefix(Span, String),
     TrailingMacro(bool, Ident),
     BreakWithLabelAndLoop(Span),
-    NamedAsmLabel(String),
     UnicodeTextFlow(Span, String),
     UnexpectedCfgName((Symbol, Span), Option<(Symbol, Span)>),
     UnexpectedCfgValue((Symbol, Span), Option<(Symbol, Span)>),
-    DeprecatedWhereclauseLocation(Option<(Span, String)>),
+    DeprecatedWhereclauseLocation(Span, Option<(Span, String)>),
     SingleUseLifetime {
         /// Span of the parameter which declares this lifetime.
         param_span: Span,
@@ -606,6 +639,7 @@ pub enum BuiltinLintDiag {
         /// Span of the single use, or None if the lifetime is never used.
         /// If true, the lifetime will be fully elided.
         use_span: Option<(Span, bool)>,
+        ident: Ident,
     },
     NamedArgumentUsedPositionally {
         /// Span where the named argument is used by position and will be replaced with the named
@@ -620,7 +654,10 @@ pub enum BuiltinLintDiag {
         /// Indicates if the named argument is used as a width/precision for formatting
         is_formatting_arg: bool,
     },
-    ByteSliceInPackedStructWithDerive,
+    ByteSliceInPackedStructWithDerive {
+        // FIXME: enum of byte/string
+        ty: String,
+    },
     UnusedExternCrate {
         removal_span: Span,
     },
@@ -662,10 +699,43 @@ pub enum BuiltinLintDiag {
     RedundantImportVisibility {
         span: Span,
         max_vis: String,
+        import_vis: String,
     },
-    MaybeTypo {
+    UnknownDiagnosticAttribute {
         span: Span,
-        name: Symbol,
+        typo_name: Option<Symbol>,
+    },
+    MacroUseDeprecated,
+    UnusedMacroUse,
+    PrivateExternCrateReexport(Ident),
+    UnusedLabel,
+    MacroIsPrivate(Ident),
+    UnusedMacroDefinition(Symbol),
+    MacroRuleNeverUsed(usize, Symbol),
+    UnstableFeature(DiagMessage),
+    AvoidUsingIntelSyntax,
+    AvoidUsingAttSyntax,
+    IncompleteInclude,
+    UnnameableTestItems,
+    DuplicateMacroAttribute,
+    CfgAttrNoAttributes,
+    CrateTypeInCfgAttr,
+    CrateNameInCfgAttr,
+    MissingFragmentSpecifier,
+    MetaVariableStillRepeating(MacroRulesNormalizedIdent),
+    MetaVariableWrongOperator,
+    DuplicateMatcherBinding,
+    UnknownMacroVariable(MacroRulesNormalizedIdent),
+    UnusedCrateDependency {
+        extern_crate: Symbol,
+        local_crate: Symbol,
+    },
+    WasmCAbi,
+    IllFormedAttributeInput {
+        suggestions: Vec<String>,
+    },
+    InnerAttributeUnstable {
+        is_macro: bool,
     },
 }
 
@@ -676,9 +746,6 @@ pub struct BufferedEarlyLint {
     /// The span of code that we are linting on.
     pub span: MultiSpan,
 
-    /// The lint message.
-    pub msg: DiagMessage,
-
     /// The `NodeId` of the AST node that generated the lint.
     pub node_id: NodeId,
 
@@ -706,12 +773,10 @@ impl LintBuffer {
         lint: &'static Lint,
         node_id: NodeId,
         span: MultiSpan,
-        msg: impl Into<DiagMessage>,
         diagnostic: BuiltinLintDiag,
     ) {
         let lint_id = LintId::of(lint);
-        let msg = msg.into();
-        self.add_early_lint(BufferedEarlyLint { lint_id, node_id, span, msg, diagnostic });
+        self.add_early_lint(BufferedEarlyLint { lint_id, node_id, span, diagnostic });
     }
 
     pub fn take(&mut self, id: NodeId) -> Vec<BufferedEarlyLint> {
@@ -724,20 +789,9 @@ impl LintBuffer {
         lint: &'static Lint,
         id: NodeId,
         sp: impl Into<MultiSpan>,
-        msg: impl Into<DiagMessage>,
-    ) {
-        self.add_lint(lint, id, sp.into(), msg, BuiltinLintDiag::Normal)
-    }
-
-    pub fn buffer_lint_with_diagnostic(
-        &mut self,
-        lint: &'static Lint,
-        id: NodeId,
-        sp: impl Into<MultiSpan>,
-        msg: impl Into<DiagMessage>,
         diagnostic: BuiltinLintDiag,
     ) {
-        self.add_lint(lint, id, sp.into(), msg, diagnostic)
+        self.add_lint(lint, id, sp.into(), diagnostic)
     }
 }
 
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index ae481efb263..38d4a5ee61c 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -158,7 +158,9 @@ impl DiagnosticDeriveVariantBuilder {
         let slug = subdiag.slug.unwrap_or_else(|| match subdiag.kind {
             SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
             SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
+            SubdiagnosticKind::NoteOnce => parse_quote! { _subdiag::note_once },
             SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
+            SubdiagnosticKind::HelpOnce => parse_quote! { _subdiag::help_once },
             SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn },
             SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion },
             SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
@@ -233,9 +235,11 @@ impl DiagnosticDeriveVariantBuilder {
         };
         let fn_ident = format_ident!("{}", subdiag);
         match subdiag {
-            SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
-                Ok(self.add_subdiagnostic(&fn_ident, slug))
-            }
+            SubdiagnosticKind::Note
+            | SubdiagnosticKind::NoteOnce
+            | SubdiagnosticKind::Help
+            | SubdiagnosticKind::HelpOnce
+            | SubdiagnosticKind::Warn => Ok(self.add_subdiagnostic(&fn_ident, slug)),
             SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
                 throw_invalid_attr!(attr, |diag| diag
                     .help("`#[label]` and `#[suggestion]` can only be applied to fields"));
@@ -347,7 +351,11 @@ impl DiagnosticDeriveVariantBuilder {
                 report_error_if_not_applied_to_span(attr, &info)?;
                 Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
             }
-            SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
+            SubdiagnosticKind::Note
+            | SubdiagnosticKind::NoteOnce
+            | SubdiagnosticKind::Help
+            | SubdiagnosticKind::HelpOnce
+            | SubdiagnosticKind::Warn => {
                 let inner = info.ty.inner_type();
                 if type_matches_path(inner, &["rustc_span", "Span"])
                     || type_matches_path(inner, &["rustc_span", "MultiSpan"])
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 45236771bce..69014f39925 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -510,11 +510,11 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
             .map(|binding| self.generate_field_attr_code(binding, kind_stats))
             .collect();
 
-        if kind_slugs.is_empty() {
+        if kind_slugs.is_empty() && !self.has_subdiagnostic {
             if self.is_enum {
                 // It's okay for a variant to not be a subdiagnostic at all..
                 return Ok(quote! {});
-            } else if !self.has_subdiagnostic {
+            } else {
                 // ..but structs should always be _something_.
                 throw_span_err!(
                     self.variant.ast().ident.span().unwrap(),
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index 4684306e235..05a5a32514b 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -575,8 +575,12 @@ pub(super) enum SubdiagnosticKind {
     Label,
     /// `#[note(...)]`
     Note,
+    /// `#[note_once(...)]`
+    NoteOnce,
     /// `#[help(...)]`
     Help,
+    /// `#[help_once(...)]`
+    HelpOnce,
     /// `#[warning(...)]`
     Warn,
     /// `#[suggestion{,_short,_hidden,_verbose}]`
@@ -624,7 +628,9 @@ impl SubdiagnosticVariant {
         let mut kind = match name {
             "label" => SubdiagnosticKind::Label,
             "note" => SubdiagnosticKind::Note,
+            "note_once" => SubdiagnosticKind::NoteOnce,
             "help" => SubdiagnosticKind::Help,
+            "help_once" => SubdiagnosticKind::HelpOnce,
             "warning" => SubdiagnosticKind::Warn,
             _ => {
                 // Recover old `#[(multipart_)suggestion_*]` syntaxes
@@ -682,7 +688,9 @@ impl SubdiagnosticVariant {
                 match kind {
                     SubdiagnosticKind::Label
                     | SubdiagnosticKind::Note
+                    | SubdiagnosticKind::NoteOnce
                     | SubdiagnosticKind::Help
+                    | SubdiagnosticKind::HelpOnce
                     | SubdiagnosticKind::Warn
                     | SubdiagnosticKind::MultipartSuggestion { .. } => {
                         return Ok(Some(SubdiagnosticVariant { kind, slug: None, no_span: false }));
@@ -836,7 +844,9 @@ impl SubdiagnosticVariant {
             }
             SubdiagnosticKind::Label
             | SubdiagnosticKind::Note
+            | SubdiagnosticKind::NoteOnce
             | SubdiagnosticKind::Help
+            | SubdiagnosticKind::HelpOnce
             | SubdiagnosticKind::Warn => {}
         }
 
@@ -849,7 +859,9 @@ impl quote::IdentFragment for SubdiagnosticKind {
         match self {
             SubdiagnosticKind::Label => write!(f, "label"),
             SubdiagnosticKind::Note => write!(f, "note"),
+            SubdiagnosticKind::NoteOnce => write!(f, "note_once"),
             SubdiagnosticKind::Help => write!(f, "help"),
+            SubdiagnosticKind::HelpOnce => write!(f, "help_once"),
             SubdiagnosticKind::Warn => write!(f, "warn"),
             SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"),
             SubdiagnosticKind::MultipartSuggestion { .. } => {
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index c7b7eadbd9d..de9c916b4f0 100644
--- a/compiler/rustc_macros/src/lib.rs
+++ b/compiler/rustc_macros/src/lib.rs
@@ -108,7 +108,9 @@ decl_derive!(
         // struct attributes
         diag,
         help,
+        help_once,
         note,
+        note_once,
         warning,
         // field attributes
         skip_arg,
@@ -125,7 +127,9 @@ decl_derive!(
         // struct attributes
         diag,
         help,
+        help_once,
         note,
+        note_once,
         warning,
         // field attributes
         skip_arg,
@@ -142,7 +146,9 @@ decl_derive!(
         // struct/variant attributes
         label,
         help,
+        help_once,
         note,
+        note_once,
         warning,
         subdiagnostic,
         suggestion,
diff --git a/compiler/rustc_metadata/messages.ftl b/compiler/rustc_metadata/messages.ftl
index 3d0846ae6de..2f5dfad265c 100644
--- a/compiler/rustc_metadata/messages.ftl
+++ b/compiler/rustc_metadata/messages.ftl
@@ -284,8 +284,6 @@ metadata_unsupported_abi =
 metadata_unsupported_abi_i686 =
     ABI not supported by `#[link(kind = "raw-dylib")]` on i686
 
-metadata_wasm_c_abi =
-    older versions of the `wasm-bindgen` crate will be incompatible with future versions of Rust; please update to `wasm-bindgen` v0.2.88
 metadata_wasm_import_form =
     wasm import module must be of the form `wasm_import_module = "string"`
 
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index e3205fc1d30..be1a73ef0a7 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -20,7 +20,7 @@ use rustc_middle::bug;
 use rustc_middle::ty::{TyCtxt, TyCtxtFeed};
 use rustc_session::config::{self, CrateType, ExternLocation};
 use rustc_session::cstore::{CrateDepKind, CrateSource, ExternCrate, ExternCrateSource};
-use rustc_session::lint;
+use rustc_session::lint::{self, BuiltinLintDiag};
 use rustc_session::output::validate_crate_name;
 use rustc_session::search_paths::PathKind;
 use rustc_span::edition::Edition;
@@ -975,15 +975,14 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> {
             }
 
             self.sess.psess.buffer_lint(
-                    lint::builtin::UNUSED_CRATE_DEPENDENCIES,
-                    span,
-                    ast::CRATE_NODE_ID,
-                    format!(
-                        "external crate `{}` unused in `{}`: remove the dependency or add `use {} as _;`",
-                        name,
-                        self.tcx.crate_name(LOCAL_CRATE),
-                        name),
-                );
+                lint::builtin::UNUSED_CRATE_DEPENDENCIES,
+                span,
+                ast::CRATE_NODE_ID,
+                BuiltinLintDiag::UnusedCrateDependency {
+                    extern_crate: name_interned,
+                    local_crate: self.tcx.crate_name(LOCAL_CRATE),
+                },
+            );
         }
     }
 
@@ -1020,7 +1019,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> {
                 lint::builtin::WASM_C_ABI,
                 span,
                 ast::CRATE_NODE_ID,
-                crate::fluent_generated::metadata_wasm_c_abi,
+                BuiltinLintDiag::WasmCAbi,
             );
         }
     }
diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl
index 27d555d7e26..f4d619329eb 100644
--- a/compiler/rustc_middle/messages.ftl
+++ b/compiler/rustc_middle/messages.ftl
@@ -50,6 +50,20 @@ middle_const_not_used_in_type_alias =
 middle_cycle =
     a cycle occurred during layout computation
 
+middle_deprecated = use of deprecated {$kind} `{$path}`{$has_note ->
+        [true] : {$note}
+        *[other] {""}
+    }
+middle_deprecated_in_future = use of {$kind} `{$path}` that will be deprecated in a future Rust version{$has_note ->
+        [true] : {$note}
+        *[other] {""}
+    }
+middle_deprecated_in_version = use of {$kind} `{$path}` that will be deprecated in future version {$version}{$has_note ->
+        [true] : {$note}
+        *[other] {""}
+    }
+middle_deprecated_suggestion = replace the use of the deprecated {$kind}
+
 middle_drop_check_overflow =
     overflow while adding drop-check rules for {$ty}
     .note = overflowed on {$overflow_ty}
diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs
index 67bd53f53da..e5df05763b0 100644
--- a/compiler/rustc_middle/src/middle/stability.rs
+++ b/compiler/rustc_middle/src/middle/stability.rs
@@ -9,15 +9,15 @@ use rustc_attr::{
     self as attr, ConstStability, DefaultBodyStability, DeprecatedSince, Deprecation, Stability,
 };
 use rustc_data_structures::unord::UnordMap;
-use rustc_errors::{Applicability, Diag};
+use rustc_errors::{Applicability, Diag, EmissionGuarantee};
 use rustc_feature::GateIssue;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap};
 use rustc_hir::{self as hir, HirId};
-use rustc_macros::{Decodable, Encodable, HashStable};
+use rustc_macros::{Decodable, Encodable, HashStable, Subdiagnostic};
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
-use rustc_session::lint::{BuiltinLintDiag, Level, Lint, LintBuffer};
+use rustc_session::lint::{BuiltinLintDiag, DeprecatedSinceKind, Level, Lint, LintBuffer};
 use rustc_session::parse::feature_err_issue;
 use rustc_session::Session;
 use rustc_span::symbol::{sym, Symbol};
@@ -125,90 +125,107 @@ pub fn report_unstable(
     }
 }
 
-pub fn deprecation_suggestion(
-    diag: &mut Diag<'_, ()>,
-    kind: &str,
-    suggestion: Option<Symbol>,
-    span: Span,
-) {
-    if let Some(suggestion) = suggestion {
-        diag.span_suggestion_verbose(
-            span,
-            format!("replace the use of the deprecated {kind}"),
-            suggestion,
-            Applicability::MachineApplicable,
-        );
-    }
-}
-
 fn deprecation_lint(is_in_effect: bool) -> &'static Lint {
     if is_in_effect { DEPRECATED } else { DEPRECATED_IN_FUTURE }
 }
 
-fn deprecation_message(
-    is_in_effect: bool,
-    since: DeprecatedSince,
-    note: Option<Symbol>,
-    kind: &str,
-    path: &str,
-) -> String {
-    let message = if is_in_effect {
-        format!("use of deprecated {kind} `{path}`")
+#[derive(Subdiagnostic)]
+#[suggestion(
+    middle_deprecated_suggestion,
+    code = "{suggestion}",
+    style = "verbose",
+    applicability = "machine-applicable"
+)]
+pub struct DeprecationSuggestion {
+    #[primary_span]
+    pub span: Span,
+
+    pub kind: String,
+    pub suggestion: Symbol,
+}
+
+pub struct Deprecated {
+    pub sub: Option<DeprecationSuggestion>,
+
+    // FIXME: make this translatable
+    pub kind: String,
+    pub path: String,
+    pub note: Option<Symbol>,
+    pub since_kind: DeprecatedSinceKind,
+}
+
+impl<'a, G: EmissionGuarantee> rustc_errors::LintDiagnostic<'a, G> for Deprecated {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) {
+        diag.arg("kind", self.kind);
+        diag.arg("path", self.path);
+        if let DeprecatedSinceKind::InVersion(version) = self.since_kind {
+            diag.arg("version", version);
+        }
+        if let Some(note) = self.note {
+            diag.arg("has_note", true);
+            diag.arg("note", note);
+        } else {
+            diag.arg("has_note", false);
+        }
+        if let Some(sub) = self.sub {
+            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 {
+    if is_in_effect {
+        DeprecatedSinceKind::InEffect
     } else {
         match since {
-            DeprecatedSince::RustcVersion(version) => format!(
-                "use of {kind} `{path}` that will be deprecated in future version {version}"
-            ),
-            DeprecatedSince::Future => {
-                format!("use of {kind} `{path}` that will be deprecated in a future Rust version")
+            DeprecatedSince::RustcVersion(version) => {
+                DeprecatedSinceKind::InVersion(version.to_string())
             }
+            DeprecatedSince::Future => DeprecatedSinceKind::InFuture,
             DeprecatedSince::NonStandard(_)
             | DeprecatedSince::Unspecified
             | DeprecatedSince::Err => {
                 unreachable!("this deprecation is always in effect; {since:?}")
             }
         }
-    };
-
-    match note {
-        Some(reason) => format!("{message}: {reason}"),
-        None => message,
     }
 }
 
-pub fn deprecation_message_and_lint(
-    depr: &Deprecation,
-    kind: &str,
-    path: &str,
-) -> (String, &'static Lint) {
-    let is_in_effect = depr.is_in_effect();
-    (
-        deprecation_message(is_in_effect, depr.since, depr.note, kind, path),
-        deprecation_lint(is_in_effect),
-    )
-}
-
-pub fn early_report_deprecation(
+pub fn early_report_macro_deprecation(
     lint_buffer: &mut LintBuffer,
-    message: String,
-    suggestion: Option<Symbol>,
-    lint: &'static Lint,
+    depr: &Deprecation,
     span: Span,
     node_id: NodeId,
+    path: String,
 ) {
     if span.in_derive_expansion() {
         return;
     }
 
-    let diag = BuiltinLintDiag::DeprecatedMacro(suggestion, span);
-    lint_buffer.buffer_lint_with_diagnostic(lint, node_id, span, message, diag);
+    let is_in_effect = depr.is_in_effect();
+    let diag = BuiltinLintDiag::DeprecatedMacro {
+        suggestion: depr.suggestion,
+        suggestion_span: span,
+        note: depr.note,
+        path,
+        since_kind: deprecated_since_kind(is_in_effect, depr.since.clone()),
+    };
+    lint_buffer.buffer_lint(deprecation_lint(is_in_effect), node_id, span, diag);
 }
 
 fn late_report_deprecation(
     tcx: TyCtxt<'_>,
-    message: String,
-    suggestion: Option<Symbol>,
-    lint: &'static Lint,
+    depr: &Deprecation,
     span: Span,
     method_span: Option<Span>,
     hir_id: HirId,
@@ -217,13 +234,26 @@ fn late_report_deprecation(
     if span.in_derive_expansion() {
         return;
     }
+
+    let def_path = with_no_trimmed_paths!(tcx.def_path_str(def_id));
+    let def_kind = tcx.def_descr(def_id);
+    let is_in_effect = depr.is_in_effect();
+
     let method_span = method_span.unwrap_or(span);
-    tcx.node_span_lint(lint, hir_id, method_span, message, |diag| {
-        if let hir::Node::Expr(_) = tcx.hir_node(hir_id) {
-            let kind = tcx.def_descr(def_id);
-            deprecation_suggestion(diag, kind, suggestion, method_span);
-        }
-    });
+    let suggestion =
+        if let hir::Node::Expr(_) = tcx.hir_node(hir_id) { depr.suggestion } else { None };
+    let diag = Deprecated {
+        sub: suggestion.map(|suggestion| DeprecationSuggestion {
+            span: method_span,
+            kind: def_kind.to_owned(),
+            suggestion,
+        }),
+        kind: def_kind.to_owned(),
+        path: def_path,
+        note: depr.note,
+        since_kind: deprecated_since_kind(is_in_effect, depr.since),
+    };
+    tcx.emit_node_span_lint(deprecation_lint(is_in_effect), hir_id, method_span, diag);
 }
 
 /// Result of `TyCtxt::eval_stability`.
@@ -352,28 +382,9 @@ impl<'tcx> TyCtxt<'tcx> {
                     // Calculating message for lint involves calling `self.def_path_str`.
                     // Which by default to calculate visible path will invoke expensive `visible_parent_map` query.
                     // So we skip message calculation altogether, if lint is allowed.
-                    let is_in_effect = depr_attr.is_in_effect();
-                    let lint = deprecation_lint(is_in_effect);
+                    let lint = deprecation_lint(depr_attr.is_in_effect());
                     if self.lint_level_at_node(lint, id).0 != Level::Allow {
-                        let def_path = with_no_trimmed_paths!(self.def_path_str(def_id));
-                        let def_kind = self.def_descr(def_id);
-
-                        late_report_deprecation(
-                            self,
-                            deprecation_message(
-                                is_in_effect,
-                                depr_attr.since,
-                                depr_attr.note,
-                                def_kind,
-                                &def_path,
-                            ),
-                            depr_attr.suggestion,
-                            lint,
-                            span,
-                            method_span,
-                            id,
-                            def_id,
-                        );
+                        late_report_deprecation(self, depr_attr, span, method_span, id, def_id);
                     }
                 }
             };
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index d2d200a91af..6eb8bed6a8c 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -370,11 +370,10 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
         let content = self.str_from(content_start);
         if contains_text_flow_control_chars(content) {
             let span = self.mk_sp(start, self.pos);
-            self.psess.buffer_lint_with_diagnostic(
+            self.psess.buffer_lint(
                 TEXT_DIRECTION_CODEPOINT_IN_COMMENT,
                 span,
                 ast::CRATE_NODE_ID,
-                "unicode codepoint changing visible direction of text present in comment",
                 BuiltinLintDiag::UnicodeTextFlow(span, content.to_string()),
             );
         }
@@ -723,12 +722,11 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
             self.dcx().emit_err(errors::UnknownPrefix { span: prefix_span, prefix, sugg });
         } else {
             // Before Rust 2021, only emit a lint for migration.
-            self.psess.buffer_lint_with_diagnostic(
+            self.psess.buffer_lint(
                 RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
                 prefix_span,
                 ast::CRATE_NODE_ID,
-                format!("prefix `{prefix}` is unknown"),
-                BuiltinLintDiag::ReservedPrefix(prefix_span),
+                BuiltinLintDiag::ReservedPrefix(prefix_span, prefix.to_string()),
             );
         }
     }
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index d2d21624150..fd3f63a04de 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -1913,11 +1913,10 @@ impl<'a> Parser<'a> {
                             | ExprKind::Block(_, None)
                     )
                 {
-                    self.psess.buffer_lint_with_diagnostic(
+                    self.psess.buffer_lint(
                         BREAK_WITH_LABEL_AND_LOOP,
                         lo.to(expr.span),
                         ast::CRATE_NODE_ID,
-                        "this labeled break expression is easy to confuse with an unlabeled break with a labeled value expression",
                         BuiltinLintDiag::BreakWithLabelAndLoop(expr.span),
                     );
                 }
diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs
index f88edf29dce..b91ef1ae1f3 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -10,6 +10,7 @@ use rustc_errors::{Applicability, FatalError, PResult};
 use rustc_feature::{AttributeTemplate, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
 use rustc_session::errors::report_lit_error;
 use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
+use rustc_session::lint::BuiltinLintDiag;
 use rustc_session::parse::ParseSess;
 use rustc_span::{sym, Span, Symbol};
 
@@ -176,37 +177,26 @@ fn emit_malformed_attribute(
     };
 
     let error_msg = format!("malformed `{name}` attribute input");
-    let mut msg = "attribute must be of the form ".to_owned();
     let mut suggestions = vec![];
-    let mut first = true;
     let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
     if template.word {
-        first = false;
-        let code = format!("#{inner}[{name}]");
-        msg.push_str(&format!("`{code}`"));
-        suggestions.push(code);
+        suggestions.push(format!("#{inner}[{name}]"));
     }
     if let Some(descr) = template.list {
-        if !first {
-            msg.push_str(" or ");
-        }
-        first = false;
-        let code = format!("#{inner}[{name}({descr})]");
-        msg.push_str(&format!("`{code}`"));
-        suggestions.push(code);
+        suggestions.push(format!("#{inner}[{name}({descr})]"));
     }
     if let Some(descr) = template.name_value_str {
-        if !first {
-            msg.push_str(" or ");
-        }
-        let code = format!("#{inner}[{name} = \"{descr}\"]");
-        msg.push_str(&format!("`{code}`"));
-        suggestions.push(code);
+        suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
     }
-    suggestions.sort();
     if should_warn(name) {
-        psess.buffer_lint(ILL_FORMED_ATTRIBUTE_INPUT, span, ast::CRATE_NODE_ID, msg);
+        psess.buffer_lint(
+            ILL_FORMED_ATTRIBUTE_INPUT,
+            span,
+            ast::CRATE_NODE_ID,
+            BuiltinLintDiag::IllFormedAttributeInput { suggestions: suggestions.clone() },
+        );
     } else {
+        suggestions.sort();
         psess
             .dcx
             .struct_span_err(span, error_msg)
diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs
index 180e7f6def3..fc3669fecc2 100644
--- a/compiler/rustc_resolve/src/check_unused.rs
+++ b/compiler/rustc_resolve/src/check_unused.rs
@@ -32,7 +32,7 @@ use rustc_ast as ast;
 use rustc_ast::visit::{self, Visitor};
 use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
 use rustc_data_structures::unord::UnordSet;
-use rustc_errors::{pluralize, MultiSpan};
+use rustc_errors::MultiSpan;
 use rustc_hir::def::{DefKind, Res};
 use rustc_session::lint::builtin::{MACRO_USE_EXTERN_CRATE, UNUSED_EXTERN_CRATES};
 use rustc_session::lint::builtin::{UNUSED_IMPORTS, UNUSED_QUALIFICATIONS};
@@ -151,11 +151,10 @@ impl<'a, 'b, 'tcx> UnusedImportCheckVisitor<'a, 'b, 'tcx> {
             // We do this in any edition.
             if warn_if_unused {
                 if let Some(&span) = maybe_unused_extern_crates.get(&extern_crate.id) {
-                    self.r.lint_buffer.buffer_lint_with_diagnostic(
+                    self.r.lint_buffer.buffer_lint(
                         UNUSED_EXTERN_CRATES,
                         extern_crate.id,
                         span,
-                        "unused extern crate",
                         BuiltinLintDiag::UnusedExternCrate {
                             removal_span: extern_crate.span_with_attributes,
                         },
@@ -204,11 +203,10 @@ impl<'a, 'b, 'tcx> UnusedImportCheckVisitor<'a, 'b, 'tcx> {
                 .span
                 .find_ancestor_inside(extern_crate.span)
                 .unwrap_or(extern_crate.ident.span);
-            self.r.lint_buffer.buffer_lint_with_diagnostic(
+            self.r.lint_buffer.buffer_lint(
                 UNUSED_EXTERN_CRATES,
                 extern_crate.id,
                 extern_crate.span,
-                "`extern crate` is not idiomatic in the new edition",
                 BuiltinLintDiag::ExternCrateNotIdiomatic { vis_span, ident_span },
             );
         }
@@ -394,10 +392,7 @@ impl Resolver<'_, '_> {
                                 MACRO_USE_EXTERN_CRATE,
                                 import.root_id,
                                 import.span,
-                                "deprecated `#[macro_use]` attribute used to \
-                                import macros should be replaced at use sites \
-                                with a `use` item to import the macro \
-                                instead",
+                                BuiltinLintDiag::MacroUseDeprecated,
                             );
                         }
                     }
@@ -414,8 +409,12 @@ impl Resolver<'_, '_> {
                     }
                 }
                 ImportKind::MacroUse { .. } => {
-                    let msg = "unused `#[macro_use]` import";
-                    self.lint_buffer.buffer_lint(UNUSED_IMPORTS, import.root_id, import.span, msg);
+                    self.lint_buffer.buffer_lint(
+                        UNUSED_IMPORTS,
+                        import.root_id,
+                        import.span,
+                        BuiltinLintDiag::UnusedMacroUse,
+                    );
                 }
                 _ => {}
             }
@@ -434,20 +433,12 @@ impl Resolver<'_, '_> {
         visitor.report_unused_extern_crate_items(maybe_unused_extern_crates);
 
         for unused in visitor.unused_imports.values() {
-            let mut fixes = Vec::new();
-            let spans = match calc_unused_spans(unused, &unused.use_tree, unused.use_tree_id) {
-                UnusedSpanResult::Used => continue,
-                UnusedSpanResult::Unused { spans, remove } => {
-                    fixes.push((remove, String::new()));
-                    spans
-                }
-                UnusedSpanResult::PartialUnused { spans, remove } => {
-                    for fix in &remove {
-                        fixes.push((*fix, String::new()));
-                    }
-                    spans
-                }
-            };
+            let (spans, remove_spans) =
+                match calc_unused_spans(unused, &unused.use_tree, unused.use_tree_id) {
+                    UnusedSpanResult::Used => continue,
+                    UnusedSpanResult::Unused { spans, remove } => (spans, vec![remove]),
+                    UnusedSpanResult::PartialUnused { spans, remove } => (spans, remove),
+                };
 
             let ms = MultiSpan::from_spans(spans);
 
@@ -459,23 +450,8 @@ impl Resolver<'_, '_> {
                 .collect::<Vec<String>>();
             span_snippets.sort();
 
-            let msg = format!(
-                "unused import{}{}",
-                pluralize!(ms.primary_spans().len()),
-                if !span_snippets.is_empty() {
-                    format!(": {}", span_snippets.join(", "))
-                } else {
-                    String::new()
-                }
-            );
-
-            let fix_msg = if fixes.len() == 1 && fixes[0].0 == unused.item_span {
-                "remove the whole `use` item"
-            } else if ms.primary_spans().len() > 1 {
-                "remove the unused imports"
-            } else {
-                "remove the unused import"
-            };
+            let remove_whole_use = remove_spans.len() == 1 && remove_spans[0] == unused.item_span;
+            let num_to_remove = ms.primary_spans().len();
 
             // If we are in the `--test` mode, suppress a help that adds the `#[cfg(test)]`
             // attribute; however, if not, suggest adding the attribute. There is no way to
@@ -501,12 +477,17 @@ impl Resolver<'_, '_> {
                 }
             };
 
-            visitor.r.lint_buffer.buffer_lint_with_diagnostic(
+            visitor.r.lint_buffer.buffer_lint(
                 UNUSED_IMPORTS,
                 unused.use_tree_id,
                 ms,
-                msg,
-                BuiltinLintDiag::UnusedImports(fix_msg.into(), fixes, test_module_span),
+                BuiltinLintDiag::UnusedImports {
+                    remove_whole_use,
+                    num_to_remove,
+                    remove_spans,
+                    test_module_span,
+                    span_snippets,
+                },
             );
         }
 
@@ -552,11 +533,10 @@ impl Resolver<'_, '_> {
                 continue;
             }
 
-            self.lint_buffer.buffer_lint_with_diagnostic(
+            self.lint_buffer.buffer_lint(
                 UNUSED_QUALIFICATIONS,
                 unn_qua.node_id,
                 unn_qua.path_span,
-                "unnecessary qualification",
                 BuiltinLintDiag::UnusedQualifications { removal_span: unn_qua.removal_span },
             );
         }
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index b28312fa473..856cfbc01e8 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -128,13 +128,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
         self.report_with_use_injections(krate);
 
         for &(span_use, span_def) in &self.macro_expanded_macro_export_errors {
-            let msg = "macro-expanded `macro_export` macros from the current crate \
-                       cannot be referred to by absolute paths";
-            self.lint_buffer.buffer_lint_with_diagnostic(
+            self.lint_buffer.buffer_lint(
                 MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS,
                 CRATE_NODE_ID,
                 span_use,
-                msg,
                 BuiltinLintDiag::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def),
             );
         }
@@ -145,11 +142,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                 let NameBindingKind::Import { import, .. } = ambiguity_error.b1.0.kind else {
                     unreachable!()
                 };
-                self.lint_buffer.buffer_lint_with_diagnostic(
+                self.lint_buffer.buffer_lint(
                     AMBIGUOUS_GLOB_IMPORTS,
                     import.root_id,
                     ambiguity_error.ident.span,
-                    diag.msg.to_string(),
                     BuiltinLintDiag::AmbiguousGlobImports { diag },
                 );
             } else {
@@ -526,12 +522,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
         }
 
         let diag = BuiltinLintDiag::AbsPathWithModule(root_span);
-        self.lint_buffer.buffer_lint_with_diagnostic(
+        self.lint_buffer.buffer_lint(
             ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE,
             node_id,
             root_span,
-            "absolute paths must start with `self`, `super`, \
-             `crate`, or an external crate name in the 2018 edition",
             diag,
         );
     }
diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs
index f88725830f1..57db765c07e 100644
--- a/compiler/rustc_resolve/src/ident.rs
+++ b/compiler/rustc_resolve/src/ident.rs
@@ -524,18 +524,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                         match binding {
                             Ok(binding) => {
                                 if let Some(lint_id) = derive_fallback_lint_id {
-                                    this.lint_buffer.buffer_lint_with_diagnostic(
+                                    this.lint_buffer.buffer_lint(
                                         PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
                                         lint_id,
                                         orig_ident.span,
-                                        format!(
-                                            "cannot find {} `{}` in this scope",
-                                            ns.descr(),
-                                            ident
-                                        ),
-                                        BuiltinLintDiag::ProcMacroDeriveResolutionFallback(
-                                            orig_ident.span,
-                                        ),
+                                        BuiltinLintDiag::ProcMacroDeriveResolutionFallback {
+                                            span: orig_ident.span,
+                                            ns,
+                                            ident,
+                                        },
                                     );
                                 }
                                 let misc_flags = if module == this.graph_root {
diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs
index f53bcb0e9d0..51b87c5a9b0 100644
--- a/compiler/rustc_resolve/src/imports.rs
+++ b/compiler/rustc_resolve/src/imports.rs
@@ -619,11 +619,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                         && binding.res() != Res::Err
                         && exported_ambiguities.contains(&binding)
                     {
-                        self.lint_buffer.buffer_lint_with_diagnostic(
+                        self.lint_buffer.buffer_lint(
                             AMBIGUOUS_GLOB_REEXPORTS,
                             import.root_id,
                             import.root_span,
-                            "ambiguous glob re-exports",
                             BuiltinLintDiag::AmbiguousGlobReexports {
                                 name: key.ident.to_string(),
                                 namespace: key.ns.descr().to_string(),
@@ -655,11 +654,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                             && glob_binding.vis.is_public()
                             && !binding.vis.is_public()
                         {
-                            self.lint_buffer.buffer_lint_with_diagnostic(
+                            self.lint_buffer.buffer_lint(
                                 HIDDEN_GLOB_REEXPORTS,
                                 binding_id,
                                 binding.span,
-                                "private item shadows public glob re-export",
                                 BuiltinLintDiag::HiddenGlobReexports {
                                     name: key.ident.name.to_string(),
                                     namespace: key.ns.descr().to_owned(),
@@ -1015,17 +1013,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                         && !max_vis.is_at_least(import_vis, self.tcx)
                     {
                         let def_id = self.local_def_id(id);
-                        let msg = format!(
-                            "glob import doesn't reexport anything with visibility `{}` because no imported item is public enough",
-                            import_vis.to_string(def_id, self.tcx)
-                        );
-                        self.lint_buffer.buffer_lint_with_diagnostic(
+                        self.lint_buffer.buffer_lint(
                             UNUSED_IMPORTS,
                             id,
                             import.span,
-                            msg,
                             BuiltinLintDiag::RedundantImportVisibility {
                                 max_vis: max_vis.to_string(def_id, self.tcx),
+                                import_vis: import_vis.to_string(def_id, self.tcx),
                                 span: import.span,
                             },
                         );
@@ -1252,16 +1246,11 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
         if !any_successful_reexport {
             let (ns, binding) = reexport_error.unwrap();
             if pub_use_of_private_extern_crate_hack(import, binding) {
-                let msg = format!(
-                    "extern crate `{ident}` is private, and cannot be \
-                                   re-exported (error E0365), consider declaring with \
-                                   `pub`"
-                );
                 self.lint_buffer.buffer_lint(
                     PUB_USE_OF_PRIVATE_EXTERN_CRATE,
                     import_id,
                     import.span,
-                    msg,
+                    BuiltinLintDiag::PrivateExternCrateReexport(ident),
                 );
             } else {
                 if ns == TypeNS {
@@ -1397,7 +1386,6 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                 UNUSED_IMPORTS,
                 id,
                 import.span,
-                format!("the item `{source}` is imported redundantly"),
                 BuiltinLintDiag::RedundantImport(redundant_spans, source),
             );
             */
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index 0f585aafdd5..d1d0e336cfe 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -24,7 +24,7 @@ use rustc_middle::middle::resolve_bound_vars::Set1;
 use rustc_middle::ty::DelegationFnSig;
 use rustc_middle::{bug, span_bug};
 use rustc_session::config::{CrateType, ResolveDocLinks};
-use rustc_session::lint;
+use rustc_session::lint::{self, BuiltinLintDiag};
 use rustc_session::parse::feature_err;
 use rustc_span::source_map::{respan, Spanned};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
@@ -1675,16 +1675,10 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
                     return;
                 }
                 LifetimeRibKind::AnonymousWarn(node_id) => {
-                    let msg = if elided {
-                        "`&` without an explicit lifetime name cannot be used here"
-                    } else {
-                        "`'_` cannot be used here"
-                    };
-                    self.r.lint_buffer.buffer_lint_with_diagnostic(
+                    self.r.lint_buffer.buffer_lint(
                         lint::builtin::ELIDED_LIFETIMES_IN_ASSOCIATED_CONSTANT,
                         node_id,
                         lifetime.ident.span,
-                        msg,
                         lint::BuiltinLintDiag::AssociatedConstElidedLifetime {
                             elided,
                             span: lifetime.ident.span,
@@ -1966,11 +1960,10 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
             }
 
             if should_lint {
-                self.r.lint_buffer.buffer_lint_with_diagnostic(
+                self.r.lint_buffer.buffer_lint(
                     lint::builtin::ELIDED_LIFETIMES_IN_PATHS,
                     segment_id,
                     elided_lifetime_span,
-                    "hidden lifetime parameters in types are deprecated",
                     lint::BuiltinLintDiag::ElidedLifetimesInPaths(
                         expected_lifetimes,
                         path_span,
@@ -4822,7 +4815,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
         late_resolution_visitor.resolve_doc_links(&krate.attrs, MaybeExported::Ok(CRATE_NODE_ID));
         visit::walk_crate(&mut late_resolution_visitor, krate);
         for (id, span) in late_resolution_visitor.diag_metadata.unused_labels.iter() {
-            self.lint_buffer.buffer_lint(lint::builtin::UNUSED_LABELS, *id, *span, "unused label");
+            self.lint_buffer.buffer_lint(
+                lint::builtin::UNUSED_LABELS,
+                *id,
+                *span,
+                BuiltinLintDiag::UnusedLabel,
+            );
         }
     }
 }
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index 980f43b0806..9daa22f89d2 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -2652,15 +2652,15 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
                     let deletion_span =
                         if param.bounds.is_empty() { deletion_span() } else { None };
 
-                    self.r.lint_buffer.buffer_lint_with_diagnostic(
+                    self.r.lint_buffer.buffer_lint(
                         lint::builtin::SINGLE_USE_LIFETIMES,
                         param.id,
                         param.ident.span,
-                        format!("lifetime parameter `{}` only used once", param.ident),
                         lint::BuiltinLintDiag::SingleUseLifetime {
                             param_span: param.ident.span,
                             use_span: Some((use_span, elidable)),
                             deletion_span,
+                            ident: param.ident,
                         },
                     );
                 }
@@ -2670,15 +2670,15 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
 
                     // if the lifetime originates from expanded code, we won't be able to remove it #104432
                     if deletion_span.is_some_and(|sp| !sp.in_derive_expansion()) {
-                        self.r.lint_buffer.buffer_lint_with_diagnostic(
+                        self.r.lint_buffer.buffer_lint(
                             lint::builtin::UNUSED_LIFETIMES,
                             param.id,
                             param.ident.span,
-                            format!("lifetime parameter `{}` never used", param.ident),
                             lint::BuiltinLintDiag::SingleUseLifetime {
                                 param_span: param.ident.span,
                                 use_span: None,
                                 deletion_span,
+                                ident: param.ident,
                             },
                         );
                     }
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 01bcfec4bdc..f4c5ad8f672 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -51,7 +51,7 @@ use rustc_middle::ty::{self, DelegationFnSig, Feed, MainDefinition, RegisteredTo
 use rustc_middle::ty::{ResolverGlobalCtxt, ResolverOutputs, TyCtxt, TyCtxtFeed};
 use rustc_query_system::ich::StableHashingContext;
 use rustc_session::lint::builtin::PRIVATE_MACRO_USE;
-use rustc_session::lint::LintBuffer;
+use rustc_session::lint::{BuiltinLintDiag, LintBuffer};
 use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind, SyntaxContext, Transparency};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::{Span, DUMMY_SP};
@@ -1860,8 +1860,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
         }
         if let NameBindingKind::Import { import, binding } = used_binding.kind {
             if let ImportKind::MacroUse { warn_private: true } = import.kind {
-                let msg = format!("macro `{ident}` is private");
-                self.lint_buffer().buffer_lint(PRIVATE_MACRO_USE, import.root_id, ident.span, msg);
+                self.lint_buffer().buffer_lint(
+                    PRIVATE_MACRO_USE,
+                    import.root_id,
+                    ident.span,
+                    BuiltinLintDiag::MacroIsPrivate(ident),
+                );
             }
             // Avoid marking `extern crate` items that refer to a name from extern prelude,
             // but not introduce it, as used if they are accessed from lexical scope.
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index f8d245f94e5..092eb1ce2ee 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -315,7 +315,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
                 UNUSED_MACROS,
                 node_id,
                 ident.span,
-                format!("unused macro definition: `{}`", ident.name),
+                BuiltinLintDiag::UnusedMacroDefinition(ident.name),
             );
         }
         for (&(def_id, arm_i), &(ident, rule_span)) in self.unused_macro_rules.iter() {
@@ -328,7 +328,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
                 UNUSED_MACRO_RULES,
                 node_id,
                 rule_span,
-                format!("rule #{} of macro `{}` is never used", arm_i + 1, ident.name),
+                BuiltinLintDiag::MacroRuleNeverUsed(arm_i, ident.name),
             );
         }
     }
@@ -552,14 +552,25 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
 
         // We are trying to avoid reporting this error if other related errors were reported.
         if res != Res::Err && inner_attr && !self.tcx.features().custom_inner_attributes {
-            let msg = match res {
-                Res::Def(..) => "inner macro attributes are unstable",
-                Res::NonMacroAttr(..) => "custom inner attributes are unstable",
+            let is_macro = match res {
+                Res::Def(..) => true,
+                Res::NonMacroAttr(..) => false,
                 _ => unreachable!(),
             };
             if soft_custom_inner_attributes_gate {
-                self.tcx.sess.psess.buffer_lint(SOFT_UNSTABLE, path.span, node_id, msg);
+                self.tcx.sess.psess.buffer_lint(
+                    SOFT_UNSTABLE,
+                    path.span,
+                    node_id,
+                    BuiltinLintDiag::InnerAttributeUnstable { is_macro },
+                );
             } else {
+                // FIXME: deduplicate with rustc_lint (`BuiltinLintDiag::InnerAttributeUnstable`)
+                let msg = if is_macro {
+                    "inner macro attributes are unstable"
+                } else {
+                    "custom inner attributes are unstable"
+                };
                 feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span, msg).emit();
             }
         }
@@ -572,17 +583,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
             let distance =
                 edit_distance(attribute.ident.name.as_str(), sym::on_unimplemented.as_str(), 5);
 
-            let help = if distance.is_some() {
-                BuiltinLintDiag::MaybeTypo { span: attribute.span(), name: sym::on_unimplemented }
-            } else {
-                BuiltinLintDiag::Normal
-            };
-            self.tcx.sess.psess.buffer_lint_with_diagnostic(
+            let typo_name = distance.map(|_| sym::on_unimplemented);
+
+            self.tcx.sess.psess.buffer_lint(
                 UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                 attribute.span(),
                 node_id,
-                "unknown diagnostic attribute",
-                help,
+                BuiltinLintDiag::UnknownDiagnosticAttribute { span: attribute.span(), typo_name },
             );
         }
 
@@ -782,11 +789,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                             .invocation_parents
                             .get(&parent_scope.expansion)
                             .map_or(ast::CRATE_NODE_ID, |id| self.def_id_to_node_id[id.0]);
-                        self.lint_buffer.buffer_lint_with_diagnostic(
+                        self.lint_buffer.buffer_lint(
                             LEGACY_DERIVE_HELPERS,
                             node_id,
                             ident.span,
-                            "derive helper attribute is used before it is introduced",
                             BuiltinLintDiag::LegacyDeriveHelpers(binding.span),
                         );
                     }
@@ -836,8 +842,17 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
                 let allowed_by_implication = implied_by.is_some_and(|feature| is_allowed(feature));
                 if !is_allowed(feature) && !allowed_by_implication {
                     let lint_buffer = &mut self.lint_buffer;
-                    let soft_handler =
-                        |lint, span, msg: String| lint_buffer.buffer_lint(lint, node_id, span, msg);
+                    let soft_handler = |lint, span, msg: String| {
+                        lint_buffer.buffer_lint(
+                            lint,
+                            node_id,
+                            span,
+                            BuiltinLintDiag::UnstableFeature(
+                                // FIXME make this translatable
+                                msg.into(),
+                            ),
+                        )
+                    };
                     stability::report_unstable(
                         self.tcx.sess,
                         feature,
@@ -853,14 +868,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
         }
         if let Some(depr) = &ext.deprecation {
             let path = pprust::path_to_string(path);
-            let (message, lint) = stability::deprecation_message_and_lint(depr, "macro", &path);
-            stability::early_report_deprecation(
+            stability::early_report_macro_deprecation(
                 &mut self.lint_buffer,
-                message,
-                depr.suggestion,
-                lint,
+                depr,
                 span,
                 node_id,
+                path,
             );
         }
     }
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index f6053f43fbd..df07f81bc45 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -305,32 +305,12 @@ impl ParseSess {
         lint: &'static Lint,
         span: impl Into<MultiSpan>,
         node_id: NodeId,
-        msg: impl Into<DiagMessage>,
-    ) {
-        self.buffered_lints.with_lock(|buffered_lints| {
-            buffered_lints.push(BufferedEarlyLint {
-                span: span.into(),
-                node_id,
-                msg: msg.into(),
-                lint_id: LintId::of(lint),
-                diagnostic: BuiltinLintDiag::Normal,
-            });
-        });
-    }
-
-    pub fn buffer_lint_with_diagnostic(
-        &self,
-        lint: &'static Lint,
-        span: impl Into<MultiSpan>,
-        node_id: NodeId,
-        msg: impl Into<DiagMessage>,
         diagnostic: BuiltinLintDiag,
     ) {
         self.buffered_lints.with_lock(|buffered_lints| {
             buffered_lints.push(BufferedEarlyLint {
                 span: span.into(),
                 node_id,
-                msg: msg.into(),
                 lint_id: LintId::of(lint),
                 diagnostic,
             });
diff --git a/compiler/rustc_session/src/version.rs b/compiler/rustc_session/src/version.rs
index 39e4541349e..e244c77f7f9 100644
--- a/compiler/rustc_session/src/version.rs
+++ b/compiler/rustc_session/src/version.rs
@@ -1,5 +1,10 @@
 use rustc_macros::{current_rustc_version, Decodable, Encodable, HashStable_Generic};
-use std::fmt::{self, Display};
+use std::{
+    borrow::Cow,
+    fmt::{self, Display},
+};
+
+use rustc_errors::IntoDiagArg;
 
 #[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[derive(HashStable_Generic)]
@@ -18,3 +23,9 @@ impl Display for RustcVersion {
         write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
     }
 }
+
+impl IntoDiagArg for RustcVersion {
+    fn into_diag_arg(self) -> rustc_errors::DiagArgValue {
+        rustc_errors::DiagArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
diff --git a/tests/ui/cfg/future-compat-crate-attributes-using-cfg_attr.stderr b/tests/ui/cfg/future-compat-crate-attributes-using-cfg_attr.stderr
index 75df314e0ba..82b2d7d1b1d 100644
--- a/tests/ui/cfg/future-compat-crate-attributes-using-cfg_attr.stderr
+++ b/tests/ui/cfg/future-compat-crate-attributes-using-cfg_attr.stderr
@@ -1,4 +1,4 @@
-error: `crate_type` within an `#![cfg_attr] attribute is deprecated`
+error: `crate_type` within an `#![cfg_attr]` attribute is deprecated
   --> $DIR/future-compat-crate-attributes-using-cfg_attr.rs:4:18
    |
 LL | #![cfg_attr(foo, crate_type="bin")]
@@ -8,7 +8,7 @@ LL | #![cfg_attr(foo, crate_type="bin")]
    = note: for more information, see issue #91632 <https://github.com/rust-lang/rust/issues/91632>
    = note: `#[deny(deprecated_cfg_attr_crate_type_name)]` on by default
 
-error: `crate_name` within an `#![cfg_attr] attribute is deprecated`
+error: `crate_name` within an `#![cfg_attr]` attribute is deprecated
   --> $DIR/future-compat-crate-attributes-using-cfg_attr.rs:9:18
    |
 LL | #![cfg_attr(foo, crate_name="bar")]
@@ -17,7 +17,7 @@ LL | #![cfg_attr(foo, crate_name="bar")]
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #91632 <https://github.com/rust-lang/rust/issues/91632>
 
-error: `crate_type` within an `#![cfg_attr] attribute is deprecated`
+error: `crate_type` within an `#![cfg_attr]` attribute is deprecated
   --> $DIR/future-compat-crate-attributes-using-cfg_attr.rs:4:18
    |
 LL | #![cfg_attr(foo, crate_type="bin")]
@@ -27,7 +27,7 @@ LL | #![cfg_attr(foo, crate_type="bin")]
    = note: for more information, see issue #91632 <https://github.com/rust-lang/rust/issues/91632>
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
-error: `crate_name` within an `#![cfg_attr] attribute is deprecated`
+error: `crate_name` within an `#![cfg_attr]` attribute is deprecated
   --> $DIR/future-compat-crate-attributes-using-cfg_attr.rs:9:18
    |
 LL | #![cfg_attr(foo, crate_name="bar")]
diff --git a/tests/ui/check-cfg/allow-same-level.stderr b/tests/ui/check-cfg/allow-same-level.stderr
index ae4c1605f01..b311a80c8fd 100644
--- a/tests/ui/check-cfg/allow-same-level.stderr
+++ b/tests/ui/check-cfg/allow-same-level.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `FALSE`
 LL | #[cfg(FALSE)]
    |       ^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(FALSE)`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/cargo-feature.none.stderr b/tests/ui/check-cfg/cargo-feature.none.stderr
index a940eda4689..9d3117ed54d 100644
--- a/tests/ui/check-cfg/cargo-feature.none.stderr
+++ b/tests/ui/check-cfg/cargo-feature.none.stderr
@@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable`
 LL | #[cfg(tokio_unstable)]
    |       ^^^^^^^^^^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: consider using a Cargo feature instead
    = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
             [lints.rust]
diff --git a/tests/ui/check-cfg/cargo-feature.some.stderr b/tests/ui/check-cfg/cargo-feature.some.stderr
index 70d3182eec8..14e24cb1429 100644
--- a/tests/ui/check-cfg/cargo-feature.some.stderr
+++ b/tests/ui/check-cfg/cargo-feature.some.stderr
@@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable`
 LL | #[cfg(tokio_unstable)]
    |       ^^^^^^^^^^^^^^
    |
-   = help: expected names are: `CONFIG_NVME`, `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `CONFIG_NVME`, `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: consider using a Cargo feature instead
    = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
             [lints.rust]
diff --git a/tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr b/tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr
index 4975129802b..08bd43832ea 100644
--- a/tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr
+++ b/tests/ui/check-cfg/cfg-value-for-cfg-name-duplicate.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `value`
 LL | #[cfg(value)]
    |       ^^^^^
    |
-   = help: expected names are: `bar`, `bee`, `clippy`, `cow`, `debug_assertions`, `doc`, `doctest`, `foo`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `bar`, `bee`, `clippy`, `cow`, `debug_assertions`, `doc`, `doctest`, `foo`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(value)`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr b/tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr
index 6404556f46c..6db1144eada 100644
--- a/tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr
+++ b/tests/ui/check-cfg/cfg-value-for-cfg-name-multiple.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_value`
 LL | #[cfg(my_value)]
    |       ^^^^^^^^
    |
-   = help: expected names are: `bar`, `clippy`, `debug_assertions`, `doc`, `doctest`, `foo`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `bar`, `clippy`, `debug_assertions`, `doc`, `doctest`, `foo`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(my_value)`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/cfg-value-for-cfg-name.stderr b/tests/ui/check-cfg/cfg-value-for-cfg-name.stderr
index bda1a601410..a5f8176343a 100644
--- a/tests/ui/check-cfg/cfg-value-for-cfg-name.stderr
+++ b/tests/ui/check-cfg/cfg-value-for-cfg-name.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `linux`
 LL | #[cfg(linux)]
    |       ^^^^^ help: found config with similar value: `target_os = "linux"`
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(linux)`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/compact-names.stderr b/tests/ui/check-cfg/compact-names.stderr
index 079be8d08c2..6fecdb52362 100644
--- a/tests/ui/check-cfg/compact-names.stderr
+++ b/tests/ui/check-cfg/compact-names.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `target_architecture`
 LL | #[cfg(target(os = "linux", architecture = "arm"))]
    |                            ^^^^^^^^^^^^^^^^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(target_architecture, values("arm"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/compact-values.stderr b/tests/ui/check-cfg/compact-values.stderr
index 4fe921dd24b..c8d14f8c02a 100644
--- a/tests/ui/check-cfg/compact-values.stderr
+++ b/tests/ui/check-cfg/compact-values.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition value: `X`
 LL | #[cfg(target(os = "linux", pointer_width = "X"))]
    |                            ^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_pointer_width` are: `16`, `32`, `64`
+   = note: expected values for `target_pointer_width` are: `16`, `32`, and `64`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
 
diff --git a/tests/ui/check-cfg/concat-values.stderr b/tests/ui/check-cfg/concat-values.stderr
index a508c397661..ec740952f57 100644
--- a/tests/ui/check-cfg/concat-values.stderr
+++ b/tests/ui/check-cfg/concat-values.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition value: (none)
 LL | #[cfg(my_cfg)]
    |       ^^^^^^
    |
-   = note: expected values for `my_cfg` are: `bar`, `foo`
+   = note: expected values for `my_cfg` are: `bar` and `foo`
    = help: to expect this configuration use `--check-cfg=cfg(my_cfg)`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
@@ -15,7 +15,7 @@ warning: unexpected `cfg` condition value: `unk`
 LL | #[cfg(my_cfg = "unk")]
    |       ^^^^^^^^^^^^^^
    |
-   = note: expected values for `my_cfg` are: `bar`, `foo`
+   = note: expected values for `my_cfg` are: `bar` and `foo`
    = help: to expect this configuration use `--check-cfg=cfg(my_cfg, values("unk"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
diff --git a/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr b/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr
index 755373d7b77..2497864e87e 100644
--- a/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr
+++ b/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
 LL | #[cfg(unknown_key = "value")]
    |       ^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/exhaustive-names-values.feature.stderr b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr
index 6344739ae76..a7d4c6d4df6 100644
--- a/tests/ui/check-cfg/exhaustive-names-values.feature.stderr
+++ b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
 LL | #[cfg(unknown_key = "value")]
    |       ^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/exhaustive-names-values.full.stderr b/tests/ui/check-cfg/exhaustive-names-values.full.stderr
index 6344739ae76..a7d4c6d4df6 100644
--- a/tests/ui/check-cfg/exhaustive-names-values.full.stderr
+++ b/tests/ui/check-cfg/exhaustive-names-values.full.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
 LL | #[cfg(unknown_key = "value")]
    |       ^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/exhaustive-names.stderr b/tests/ui/check-cfg/exhaustive-names.stderr
index 605825cd4e5..7ac3241db5f 100644
--- a/tests/ui/check-cfg/exhaustive-names.stderr
+++ b/tests/ui/check-cfg/exhaustive-names.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
 LL | #[cfg(unknown_key = "value")]
    |       ^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/mix.stderr b/tests/ui/check-cfg/mix.stderr
index 54591699145..8c1bf5a1160 100644
--- a/tests/ui/check-cfg/mix.stderr
+++ b/tests/ui/check-cfg/mix.stderr
@@ -44,7 +44,7 @@ warning: unexpected `cfg` condition name: `uu`
 LL | #[cfg_attr(uu, test)]
    |            ^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(uu)`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
@@ -251,7 +251,7 @@ warning: unexpected `cfg` condition value: `zebra`
 LL |     cfg!(target_feature = "zebra");
    |          ^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_feature` are: `10e60`, `2e3`, `3e3r1`, `3e3r2`, `3e3r3`, `3e7`, `7e10`, `a`, `aclass`, `adx`, `aes`, `altivec`, `alu32`, `atomics`, `avx`, `avx2`, `avx512bf16`, `avx512bitalg`, `avx512bw`, `avx512cd`, `avx512dq`, `avx512er`, `avx512f`, `avx512fp16`, `avx512ifma`, `avx512pf`, `avx512vbmi`, `avx512vbmi2`, `avx512vl`, `avx512vnni`, `avx512vp2intersect`, `avx512vpopcntdq`, `bf16`, `bmi1`, `bmi2` and 188 more
+   = note: expected values for `target_feature` are: `10e60`, `2e3`, `3e3r1`, `3e3r2`, `3e3r3`, `3e7`, `7e10`, `a`, `aclass`, `adx`, `aes`, `altivec`, `alu32`, `atomics`, `avx`, `avx2`, `avx512bf16`, `avx512bitalg`, `avx512bw`, `avx512cd`, `avx512dq`, `avx512er`, `avx512f`, `avx512fp16`, `avx512ifma`, `avx512pf`, `avx512vbmi`, `avx512vbmi2`, `avx512vl`, `avx512vnni`, `avx512vp2intersect`, `avx512vpopcntdq`, `bf16`, `bmi1`, and `bmi2` and 188 more
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: 27 warnings emitted
diff --git a/tests/ui/check-cfg/stmt-no-ice.stderr b/tests/ui/check-cfg/stmt-no-ice.stderr
index ab0deae428d..e8b61d808fe 100644
--- a/tests/ui/check-cfg/stmt-no-ice.stderr
+++ b/tests/ui/check-cfg/stmt-no-ice.stderr
@@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `crossbeam_loom`
 LL |     #[cfg(crossbeam_loom)]
    |           ^^^^^^^^^^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(crossbeam_loom)`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
diff --git a/tests/ui/check-cfg/unexpected-cfg-value.stderr b/tests/ui/check-cfg/unexpected-cfg-value.stderr
index efcf65bb707..0fa0dde41b6 100644
--- a/tests/ui/check-cfg/unexpected-cfg-value.stderr
+++ b/tests/ui/check-cfg/unexpected-cfg-value.stderr
@@ -6,7 +6,7 @@ LL | #[cfg(feature = "sedre")]
    |                 |
    |                 help: there is a expected value with a similar name: `"serde"`
    |
-   = note: expected values for `feature` are: `full`, `serde`
+   = note: expected values for `feature` are: `full` and `serde`
    = help: to expect this configuration use `--check-cfg=cfg(feature, values("sedre"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
    = note: `#[warn(unexpected_cfgs)]` on by default
@@ -17,7 +17,7 @@ warning: unexpected `cfg` condition value: `rand`
 LL | #[cfg(feature = "rand")]
    |       ^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `feature` are: `full`, `serde`
+   = note: expected values for `feature` are: `full` and `serde`
    = help: to expect this configuration use `--check-cfg=cfg(feature, values("rand"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
diff --git a/tests/ui/check-cfg/well-known-names.stderr b/tests/ui/check-cfg/well-known-names.stderr
index 2ce6fe80ef2..41130210df1 100644
--- a/tests/ui/check-cfg/well-known-names.stderr
+++ b/tests/ui/check-cfg/well-known-names.stderr
@@ -18,7 +18,7 @@ warning: unexpected `cfg` condition name: `features`
 LL | #[cfg(features = "foo")]
    |       ^^^^^^^^^^^^^^^^
    |
-   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, `windows`
+   = help: expected names are: `clippy`, `debug_assertions`, `doc`, `doctest`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `rustfmt`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `ub_checks`, `unix`, and `windows`
    = help: to expect this configuration use `--check-cfg=cfg(features, values("foo"))`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
diff --git a/tests/ui/check-cfg/well-known-values.stderr b/tests/ui/check-cfg/well-known-values.stderr
index db4a5fd49e6..c13446bb9f8 100644
--- a/tests/ui/check-cfg/well-known-values.stderr
+++ b/tests/ui/check-cfg/well-known-values.stderr
@@ -71,7 +71,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     panic = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `panic` are: `abort`, `unwind`
+   = note: expected values for `panic` are: `abort` and `unwind`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -91,7 +91,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     relocation_model = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `relocation_model` are: `dynamic-no-pic`, `pic`, `pie`, `ropi`, `ropi-rwpi`, `rwpi`, `static`
+   = note: expected values for `relocation_model` are: `dynamic-no-pic`, `pic`, `pie`, `ropi`, `ropi-rwpi`, `rwpi`, and `static`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -111,7 +111,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     sanitize = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `sanitize` are: `address`, `cfi`, `dataflow`, `hwaddress`, `kcfi`, `kernel-address`, `leak`, `memory`, `memtag`, `safestack`, `shadow-call-stack`, `thread`
+   = note: expected values for `sanitize` are: `address`, `cfi`, `dataflow`, `hwaddress`, `kcfi`, `kernel-address`, `leak`, `memory`, `memtag`, `safestack`, `shadow-call-stack`, and `thread`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -120,7 +120,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_abi = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_abi` are: ``, `abi64`, `abiv2`, `abiv2hf`, `eabi`, `eabihf`, `elf`, `fortanix`, `ilp32`, `llvm`, `macabi`, `sim`, `softfloat`, `spe`, `uwp`, `vec-extabi`, `x32`
+   = note: expected values for `target_abi` are: ``, `abi64`, `abiv2`, `abiv2hf`, `eabi`, `eabihf`, `elf`, `fortanix`, `ilp32`, `llvm`, `macabi`, `sim`, `softfloat`, `spe`, `uwp`, `vec-extabi`, and `x32`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -129,7 +129,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_arch = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_arch` are: `aarch64`, `arm`, `arm64ec`, `avr`, `bpf`, `csky`, `hexagon`, `loongarch64`, `m68k`, `mips`, `mips32r6`, `mips64`, `mips64r6`, `msp430`, `nvptx64`, `powerpc`, `powerpc64`, `riscv32`, `riscv64`, `s390x`, `sparc`, `sparc64`, `wasm32`, `wasm64`, `x86`, `x86_64`
+   = note: expected values for `target_arch` are: `aarch64`, `arm`, `arm64ec`, `avr`, `bpf`, `csky`, `hexagon`, `loongarch64`, `m68k`, `mips`, `mips32r6`, `mips64`, `mips64r6`, `msp430`, `nvptx64`, `powerpc`, `powerpc64`, `riscv32`, `riscv64`, `s390x`, `sparc`, `sparc64`, `wasm32`, `wasm64`, `x86`, and `x86_64`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -138,7 +138,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_endian = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_endian` are: `big`, `little`
+   = note: expected values for `target_endian` are: `big` and `little`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -147,7 +147,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_env = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_env` are: ``, `gnu`, `msvc`, `musl`, `newlib`, `nto70`, `nto71`, `ohos`, `p2`, `psx`, `relibc`, `sgx`, `uclibc`
+   = note: expected values for `target_env` are: ``, `gnu`, `msvc`, `musl`, `newlib`, `nto70`, `nto71`, `ohos`, `p2`, `psx`, `relibc`, `sgx`, and `uclibc`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -156,7 +156,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_family = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_family` are: `unix`, `wasm`, `windows`
+   = note: expected values for `target_family` are: `unix`, `wasm`, and `windows`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -165,7 +165,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_feature = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_feature` are: `10e60`, `2e3`, `3e3r1`, `3e3r2`, `3e3r3`, `3e7`, `7e10`, `a`, `aclass`, `adx`, `aes`, `altivec`, `alu32`, `atomics`, `avx`, `avx2`, `avx512bf16`, `avx512bitalg`, `avx512bw`, `avx512cd`, `avx512dq`, `avx512er`, `avx512f`, `avx512fp16`, `avx512ifma`, `avx512pf`, `avx512vbmi`, `avx512vbmi2`, `avx512vl`, `avx512vnni`, `avx512vp2intersect`, `avx512vpopcntdq`, `bf16`, `bmi1`, `bmi2`, `bti`, `bulk-memory`, `c`, `cache`, `cmpxchg16b`, `crc`, `crt-static`, `d`, `d32`, `dit`, `doloop`, `dotprod`, `dpb`, `dpb2`, `dsp`, `dsp1e2`, `dspe60`, `e`, `e1`, `e2`, `edsp`, `elrw`, `ermsb`, `exception-handling`, `extended-const`, `f`, `f16c`, `f32mm`, `f64mm`, `fcma`, `fdivdu`, `fhm`, `flagm`, `float1e2`, `float1e3`, `float3e4`, `float7e60`, `floate1`, `fma`, `fp-armv8`, `fp16`, `fp64`, `fpuv2_df`, `fpuv2_sf`, `fpuv3_df`, `fpuv3_hf`, `fpuv3_hi`, `fpuv3_sf`, `frecipe`, `frintts`, `fxsr`, `gfni`, `hard-float`, `hard-float-abi`, `hard-tp`, `high-registers`, `hvx`, `hvx-length128b`, `hwdiv`, `i8mm`, `jsconv`, `lahfsahf`, `lasx`, `lbt`, `lor`, `lse`, `lsx`, `lvz`, `lzcnt`, `m`, `mclass`, `movbe`, `mp`, `mp1e2`, `msa`, `mte`, `multivalue`, `mutable-globals`, `neon`, `nontrapping-fptoint`, `nvic`, `paca`, `pacg`, `pan`, `pclmulqdq`, `pmuv3`, `popcnt`, `power10-vector`, `power8-altivec`, `power8-vector`, `power9-altivec`, `power9-vector`, `prfchw`, `rand`, `ras`, `rclass`, `rcpc`, `rcpc2`, `rdm`, `rdrand`, `rdseed`, `reference-types`, `relax`, `relaxed-simd`, `rtm`, `sb`, `sha`, `sha2`, `sha3`, `sign-ext`, `simd128`, `sm4`, `spe`, `ssbs`, `sse`, `sse2`, `sse3`, `sse4.1`, `sse4.2`, `sse4a`, `ssse3`, `sve`, `sve2`, `sve2-aes`, `sve2-bitperm`, `sve2-sha3`, `sve2-sm4`, `tbm`, `thumb-mode`, `thumb2`, `tme`, `trust`, `trustzone`, `ual`, `unaligned-scalar-mem`, `v`, `v5te`, `v6`, `v6k`, `v6t2`, `v7`, `v8`, `v8.1a`, `v8.2a`, `v8.3a`, `v8.4a`, `v8.5a`, `v8.6a`, `v8.7a`, `vaes`, `vdsp2e60f`, `vdspv1`, `vdspv2`, `vfp2`, `vfp3`, `vfp4`, `vh`, `virt`, `virtualization`, `vpclmulqdq`, `vsx`, `xsave`, `xsavec`, `xsaveopt`, `xsaves`, `zba`, `zbb`, `zbc`, `zbkb`, `zbkc`, `zbkx`, `zbs`, `zdinx`, `zfh`, `zfhmin`, `zfinx`, `zhinx`, `zhinxmin`, `zk`, `zkn`, `zknd`, `zkne`, `zknh`, `zkr`, `zks`, `zksed`, `zksh`, `zkt`
+   = note: expected values for `target_feature` are: `10e60`, `2e3`, `3e3r1`, `3e3r2`, `3e3r3`, `3e7`, `7e10`, `a`, `aclass`, `adx`, `aes`, `altivec`, `alu32`, `atomics`, `avx`, `avx2`, `avx512bf16`, `avx512bitalg`, `avx512bw`, `avx512cd`, `avx512dq`, `avx512er`, `avx512f`, `avx512fp16`, `avx512ifma`, `avx512pf`, `avx512vbmi`, `avx512vbmi2`, `avx512vl`, `avx512vnni`, `avx512vp2intersect`, `avx512vpopcntdq`, `bf16`, `bmi1`, `bmi2`, `bti`, `bulk-memory`, `c`, `cache`, `cmpxchg16b`, `crc`, `crt-static`, `d`, `d32`, `dit`, `doloop`, `dotprod`, `dpb`, `dpb2`, `dsp`, `dsp1e2`, `dspe60`, `e`, `e1`, `e2`, `edsp`, `elrw`, `ermsb`, `exception-handling`, `extended-const`, `f`, `f16c`, `f32mm`, `f64mm`, `fcma`, `fdivdu`, `fhm`, `flagm`, `float1e2`, `float1e3`, `float3e4`, `float7e60`, `floate1`, `fma`, `fp-armv8`, `fp16`, `fp64`, `fpuv2_df`, `fpuv2_sf`, `fpuv3_df`, `fpuv3_hf`, `fpuv3_hi`, `fpuv3_sf`, `frecipe`, `frintts`, `fxsr`, `gfni`, `hard-float`, `hard-float-abi`, `hard-tp`, `high-registers`, `hvx`, `hvx-length128b`, `hwdiv`, `i8mm`, `jsconv`, `lahfsahf`, `lasx`, `lbt`, `lor`, `lse`, `lsx`, `lvz`, `lzcnt`, `m`, `mclass`, `movbe`, `mp`, `mp1e2`, `msa`, `mte`, `multivalue`, `mutable-globals`, `neon`, `nontrapping-fptoint`, `nvic`, `paca`, `pacg`, `pan`, `pclmulqdq`, `pmuv3`, `popcnt`, `power10-vector`, `power8-altivec`, `power8-vector`, `power9-altivec`, `power9-vector`, `prfchw`, `rand`, `ras`, `rclass`, `rcpc`, `rcpc2`, `rdm`, `rdrand`, `rdseed`, `reference-types`, `relax`, `relaxed-simd`, `rtm`, `sb`, `sha`, `sha2`, `sha3`, `sign-ext`, `simd128`, `sm4`, `spe`, `ssbs`, `sse`, `sse2`, `sse3`, `sse4.1`, `sse4.2`, `sse4a`, `ssse3`, `sve`, `sve2`, `sve2-aes`, `sve2-bitperm`, `sve2-sha3`, `sve2-sm4`, `tbm`, `thumb-mode`, `thumb2`, `tme`, `trust`, `trustzone`, `ual`, `unaligned-scalar-mem`, `v`, `v5te`, `v6`, `v6k`, `v6t2`, `v7`, `v8`, `v8.1a`, `v8.2a`, `v8.3a`, `v8.4a`, `v8.5a`, `v8.6a`, `v8.7a`, `vaes`, `vdsp2e60f`, `vdspv1`, `vdspv2`, `vfp2`, `vfp3`, `vfp4`, `vh`, `virt`, `virtualization`, `vpclmulqdq`, `vsx`, `xsave`, `xsavec`, `xsaveopt`, `xsaves`, `zba`, `zbb`, `zbc`, `zbkb`, `zbkc`, `zbkx`, `zbs`, `zdinx`, `zfh`, `zfhmin`, `zfinx`, `zhinx`, `zhinxmin`, `zk`, `zkn`, `zknd`, `zkne`, `zknh`, `zkr`, `zks`, `zksed`, `zksh`, and `zkt`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -174,7 +174,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_has_atomic = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_has_atomic` are: (none), `128`, `16`, `32`, `64`, `8`, `ptr`
+   = note: expected values for `target_has_atomic` are: (none), `128`, `16`, `32`, `64`, `8`, and `ptr`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -183,7 +183,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_has_atomic_equal_alignment = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_has_atomic_equal_alignment` are: (none), `128`, `16`, `32`, `64`, `8`, `ptr`
+   = note: expected values for `target_has_atomic_equal_alignment` are: (none), `128`, `16`, `32`, `64`, `8`, and `ptr`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -192,7 +192,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_has_atomic_load_store = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_has_atomic_load_store` are: (none), `128`, `16`, `32`, `64`, `8`, `ptr`
+   = note: expected values for `target_has_atomic_load_store` are: (none), `128`, `16`, `32`, `64`, `8`, and `ptr`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -201,7 +201,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_os = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, `zkvm`
+   = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -210,7 +210,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_pointer_width = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_pointer_width` are: `16`, `32`, `64`
+   = note: expected values for `target_pointer_width` are: `16`, `32`, and `64`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -230,7 +230,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
 LL |     target_vendor = "_UNEXPECTED_VALUE",
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: expected values for `target_vendor` are: `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, `wrs`
+   = note: expected values for `target_vendor` are: `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`
@@ -285,7 +285,7 @@ LL | #[cfg(target_os = "linuz")] // testing that we suggest `linux`
    |                   |
    |                   help: there is a expected value with a similar name: `"linux"`
    |
-   = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, `zkvm`
+   = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm`
    = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
 
 warning: 29 warnings emitted
diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs
index bd3b69c2b4a..ca60bc62b51 100644
--- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs
+++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.rs
@@ -45,7 +45,7 @@ mod inline {
     //~| NOTE not a function or closure
 
     #[inline = "2100"] fn f() { }
-    //~^ ERROR attribute must be of the form
+    //~^ ERROR valid forms for the attribute are
     //~| WARN this was previously accepted
     //~| NOTE #[deny(ill_formed_attribute_input)]` on by default
     //~| NOTE for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr
index 89fa2abffc2..ac2bf78157d 100644
--- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr
+++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr
@@ -7,7 +7,7 @@ LL | #![rustc_main]
    = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable
    = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
 
-error: attribute must be of the form `#[inline]` or `#[inline(always|never)]`
+error: valid forms for the attribute are `#[inline]` and `#[inline(always|never)]`
   --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:47:5
    |
 LL |     #[inline = "2100"] fn f() { }
diff --git a/tests/ui/lint/anonymous-reexport.stderr b/tests/ui/lint/anonymous-reexport.stderr
index e3854a5459e..c4ee18fe8da 100644
--- a/tests/ui/lint/anonymous-reexport.stderr
+++ b/tests/ui/lint/anonymous-reexport.stderr
@@ -34,7 +34,7 @@ error: unused import: `Bar as _`
 LL | pub use self::my_mod::{Bar as _, Foo as _};
    |                        ^^^^^^^^
 
-error: unused imports: `Bar as _`, `TyBar as _`
+error: unused imports: `Bar as _` and `TyBar as _`
   --> $DIR/anonymous-reexport.rs:17:24
    |
 LL | pub use self::my_mod::{Bar as _, TyBar as _};
diff --git a/tests/ui/lint/unused/import_remove_line.stderr b/tests/ui/lint/unused/import_remove_line.stderr
index 0e8c5de3558..b2a16669a93 100644
--- a/tests/ui/lint/unused/import_remove_line.stderr
+++ b/tests/ui/lint/unused/import_remove_line.stderr
@@ -1,4 +1,4 @@
-warning: unused imports: `Duration`, `Instant`
+warning: unused imports: `Duration` and `Instant`
   --> $DIR/import_remove_line.rs:7:17
    |
 LL | use std::time::{Duration, Instant};
diff --git a/tests/ui/lint/unused/lint-unused-imports.rs b/tests/ui/lint/unused/lint-unused-imports.rs
index 88f2baa5da9..710fb7a5ed1 100644
--- a/tests/ui/lint/unused/lint-unused-imports.rs
+++ b/tests/ui/lint/unused/lint-unused-imports.rs
@@ -10,7 +10,7 @@ use std::fmt::{};
 
 // Should get errors for both 'Some' and 'None'
 use std::option::Option::{Some, None};
-//~^ ERROR unused imports: `None`, `Some`
+//~^ ERROR unused imports: `None` and `Some`
 
 use test::A;       //~ ERROR unused import: `test::A`
 // Be sure that if we just bring some methods into scope that they're also
diff --git a/tests/ui/lint/unused/lint-unused-imports.stderr b/tests/ui/lint/unused/lint-unused-imports.stderr
index 07684a84a64..a848fb31eba 100644
--- a/tests/ui/lint/unused/lint-unused-imports.stderr
+++ b/tests/ui/lint/unused/lint-unused-imports.stderr
@@ -10,7 +10,7 @@ note: the lint level is defined here
 LL | #![deny(unused_imports)]
    |         ^^^^^^^^^^^^^^
 
-error: unused imports: `None`, `Some`
+error: unused imports: `None` and `Some`
   --> $DIR/lint-unused-imports.rs:12:27
    |
 LL | use std::option::Option::{Some, None};
diff --git a/tests/ui/malformed/malformed-regressions.rs b/tests/ui/malformed/malformed-regressions.rs
index ac1444bbaef..f0a7aac59c1 100644
--- a/tests/ui/malformed/malformed-regressions.rs
+++ b/tests/ui/malformed/malformed-regressions.rs
@@ -1,8 +1,8 @@
-#[doc] //~ ERROR attribute must be of the form
+#[doc] //~ ERROR valid forms for the attribute are
 //~^ WARN this was previously accepted
-#[ignore()] //~ ERROR attribute must be of the form
+#[ignore()] //~ ERROR valid forms for the attribute are
 //~^ WARN this was previously accepted
-#[inline = ""] //~ ERROR attribute must be of the form
+#[inline = ""] //~ ERROR valid forms for the attribute are
 //~^ WARN this was previously accepted
 #[link] //~ ERROR attribute must be of the form
 //~^ WARN this was previously accepted
diff --git a/tests/ui/malformed/malformed-regressions.stderr b/tests/ui/malformed/malformed-regressions.stderr
index 9bfbe7ebafd..e1dbdb9ab3c 100644
--- a/tests/ui/malformed/malformed-regressions.stderr
+++ b/tests/ui/malformed/malformed-regressions.stderr
@@ -1,4 +1,4 @@
-error: attribute must be of the form `#[doc(hidden|inline|...)]` or `#[doc = "string"]`
+error: valid forms for the attribute are `#[doc(hidden|inline|...)]` and `#[doc = "string"]`
   --> $DIR/malformed-regressions.rs:1:1
    |
 LL | #[doc]
@@ -8,7 +8,7 @@ LL | #[doc]
    = note: for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
    = note: `#[deny(ill_formed_attribute_input)]` on by default
 
-error: attribute must be of the form `#[ignore]` or `#[ignore = "reason"]`
+error: valid forms for the attribute are `#[ignore]` and `#[ignore = "reason"]`
   --> $DIR/malformed-regressions.rs:3:1
    |
 LL | #[ignore()]
@@ -17,7 +17,7 @@ LL | #[ignore()]
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
 
-error: attribute must be of the form `#[inline]` or `#[inline(always|never)]`
+error: valid forms for the attribute are `#[inline]` and `#[inline(always|never)]`
   --> $DIR/malformed-regressions.rs:5:1
    |
 LL | #[inline = ""]
diff --git a/tests/ui/privacy/issue-46209-private-enum-variant-reexport.rs b/tests/ui/privacy/issue-46209-private-enum-variant-reexport.rs
index 653dcab5771..796ba4d2332 100644
--- a/tests/ui/privacy/issue-46209-private-enum-variant-reexport.rs
+++ b/tests/ui/privacy/issue-46209-private-enum-variant-reexport.rs
@@ -6,7 +6,7 @@ mod rank {
     pub use self::Lieutenant::{JuniorGrade, Full};
     //~^ ERROR `JuniorGrade` is private, and cannot be re-exported
     //~| ERROR `Full` is private, and cannot be re-exported
-    //~| ERROR unused imports: `Full`, `JuniorGrade`
+    //~| ERROR unused imports: `Full` and `JuniorGrade`
     pub use self::PettyOfficer::*;
     //~^ ERROR glob import doesn't reexport anything
     //~| ERROR unused import: `self::PettyOfficer::*`
diff --git a/tests/ui/privacy/issue-46209-private-enum-variant-reexport.stderr b/tests/ui/privacy/issue-46209-private-enum-variant-reexport.stderr
index 93a39edbb82..f1701d547a6 100644
--- a/tests/ui/privacy/issue-46209-private-enum-variant-reexport.stderr
+++ b/tests/ui/privacy/issue-46209-private-enum-variant-reexport.stderr
@@ -46,7 +46,7 @@ error: unused import: `self::Professor::*`
 LL |     pub use self::Professor::*;
    |             ^^^^^^^^^^^^^^^^^^
 
-error: unused imports: `Full`, `JuniorGrade`
+error: unused imports: `Full` and `JuniorGrade`
   --> $DIR/issue-46209-private-enum-variant-reexport.rs:6:32
    |
 LL |     pub use self::Lieutenant::{JuniorGrade, Full};
diff --git a/tests/ui/span/multispan-import-lint.stderr b/tests/ui/span/multispan-import-lint.stderr
index 4a955d1b31f..a4ea1af237b 100644
--- a/tests/ui/span/multispan-import-lint.stderr
+++ b/tests/ui/span/multispan-import-lint.stderr
@@ -1,4 +1,4 @@
-warning: unused imports: `Eq`, `Ord`, `PartialEq`, `PartialOrd`
+warning: unused imports: `Eq`, `Ord`, `PartialEq`, and `PartialOrd`
   --> $DIR/multispan-import-lint.rs:5:16
    |
 LL | use std::cmp::{Eq, Ord, min, PartialEq, PartialOrd};
diff --git a/tests/ui/suggestions/unused-imports.stderr b/tests/ui/suggestions/unused-imports.stderr
index bf112608da7..31edb4e5ec2 100644
--- a/tests/ui/suggestions/unused-imports.stderr
+++ b/tests/ui/suggestions/unused-imports.stderr
@@ -1,4 +1,4 @@
-warning: unused imports: `A`, `C`
+warning: unused imports: `A` and `C`
   --> $DIR/unused-imports.rs:22:14
    |
 LL | use nested::{A, B, C};
@@ -10,7 +10,7 @@ note: the lint level is defined here
 LL | #![warn(unused_imports)]
    |         ^^^^^^^^^^^^^^
 
-warning: unused imports: `D`, `E`, `G`
+warning: unused imports: `D`, `E`, and `G`
   --> $DIR/unused-imports.rs:26:5
    |
 LL |     D,
diff --git a/tests/ui/use/use-nested-groups-unused-imports.rs b/tests/ui/use/use-nested-groups-unused-imports.rs
index ca6b8ba94d1..0c8ae558a59 100644
--- a/tests/ui/use/use-nested-groups-unused-imports.rs
+++ b/tests/ui/use/use-nested-groups-unused-imports.rs
@@ -13,7 +13,7 @@ mod foo {
 }
 
 use foo::{Foo, bar::{baz::{}, foobar::*}, *};
-    //~^ ERROR unused imports: `*`, `Foo`, `baz::{}`, `foobar::*`
+    //~^ ERROR unused imports: `*`, `Foo`, `baz::{}`, and `foobar::*`
 use foo::bar::baz::{*, *};
     //~^ ERROR unused import: `*`
 use foo::{};
diff --git a/tests/ui/use/use-nested-groups-unused-imports.stderr b/tests/ui/use/use-nested-groups-unused-imports.stderr
index 6610f8ecd4a..dd39a852772 100644
--- a/tests/ui/use/use-nested-groups-unused-imports.stderr
+++ b/tests/ui/use/use-nested-groups-unused-imports.stderr
@@ -1,4 +1,4 @@
-error: unused imports: `*`, `Foo`, `baz::{}`, `foobar::*`
+error: unused imports: `*`, `Foo`, `baz::{}`, and `foobar::*`
   --> $DIR/use-nested-groups-unused-imports.rs:15:11
    |
 LL | use foo::{Foo, bar::{baz::{}, foobar::*}, *};