about summary refs log tree commit diff
path: root/compiler/rustc_lint/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_lint/src')
-rw-r--r--compiler/rustc_lint/src/array_into_iter.rs2
-rw-r--r--compiler/rustc_lint/src/async_fn_in_trait.rs2
-rw-r--r--compiler/rustc_lint/src/builtin.rs212
-rw-r--r--compiler/rustc_lint/src/context.rs577
-rw-r--r--compiler/rustc_lint/src/context/diagnostics.rs621
-rw-r--r--compiler/rustc_lint/src/deref_into_dyn_supertrait.rs15
-rw-r--r--compiler/rustc_lint/src/drop_forget_useless.rs13
-rw-r--r--compiler/rustc_lint/src/early.rs25
-rw-r--r--compiler/rustc_lint/src/enum_intrinsics_non_enums.rs8
-rw-r--r--compiler/rustc_lint/src/errors.rs27
-rw-r--r--compiler/rustc_lint/src/expect.rs2
-rw-r--r--compiler/rustc_lint/src/for_loops_over_fallibles.rs2
-rw-r--r--compiler/rustc_lint/src/foreign_modules.rs12
-rw-r--r--compiler/rustc_lint/src/hidden_unicode_codepoints.rs2
-rw-r--r--compiler/rustc_lint/src/internal.rs266
-rw-r--r--compiler/rustc_lint/src/invalid_from_utf8.rs4
-rw-r--r--compiler/rustc_lint/src/late.rs11
-rw-r--r--compiler/rustc_lint/src/let_underscore.rs67
-rw-r--r--compiler/rustc_lint/src/levels.rs347
-rw-r--r--compiler/rustc_lint/src/lib.rs42
-rw-r--r--compiler/rustc_lint/src/lints.rs377
-rw-r--r--compiler/rustc_lint/src/map_unit_fn.rs4
-rw-r--r--compiler/rustc_lint/src/methods.rs2
-rw-r--r--compiler/rustc_lint/src/multiple_supertrait_upcastable.rs2
-rw-r--r--compiler/rustc_lint/src/non_ascii_idents.rs69
-rw-r--r--compiler/rustc_lint/src/non_fmt_panic.rs21
-rw-r--r--compiler/rustc_lint/src/non_local_def.rs231
-rw-r--r--compiler/rustc_lint/src/nonstandard_style.rs13
-rw-r--r--compiler/rustc_lint/src/noop_method_call.rs18
-rw-r--r--compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs174
-rw-r--r--compiler/rustc_lint/src/pass_by_value.rs2
-rw-r--r--compiler/rustc_lint/src/ptr_nulls.rs8
-rw-r--r--compiler/rustc_lint/src/redundant_semicolon.rs2
-rw-r--r--compiler/rustc_lint/src/reference_casting.rs93
-rw-r--r--compiler/rustc_lint/src/traits.rs4
-rw-r--r--compiler/rustc_lint/src/types.rs87
-rw-r--r--compiler/rustc_lint/src/unit_bindings.rs2
-rw-r--r--compiler/rustc_lint/src/unused.rs100
38 files changed, 2087 insertions, 1379 deletions
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs
index 814991cd8c9..3a5c585366a 100644
--- a/compiler/rustc_lint/src/array_into_iter.rs
+++ b/compiler/rustc_lint/src/array_into_iter.rs
@@ -132,7 +132,7 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
             } else {
                 None
             };
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 ARRAY_INTO_ITER,
                 call.ident.span,
                 ArrayIntoIterDiag { target, suggestion: call.ident.span, sub },
diff --git a/compiler/rustc_lint/src/async_fn_in_trait.rs b/compiler/rustc_lint/src/async_fn_in_trait.rs
index 51213647361..5f7a3137d52 100644
--- a/compiler/rustc_lint/src/async_fn_in_trait.rs
+++ b/compiler/rustc_lint/src/async_fn_in_trait.rs
@@ -116,7 +116,7 @@ impl<'tcx> LateLintPass<'tcx> for AsyncFnInTrait {
                 def.owner_id.def_id,
                 " + Send",
             );
-            cx.tcx.emit_spanned_lint(
+            cx.tcx.emit_node_span_lint(
                 ASYNC_FN_IN_TRAIT,
                 item.hir_id(),
                 async_span,
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 8afb5f2d32e..d1343e3b4ba 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -44,7 +44,7 @@ use rustc_ast::tokenstream::{TokenStream, TokenTree};
 use rustc_ast::visit::{FnCtxt, FnKind};
 use rustc_ast::{self as ast, *};
 use rustc_ast_pretty::pprust::{self, expr_to_string};
-use rustc_errors::{Applicability, DecorateLint, MultiSpan};
+use rustc_errors::{Applicability, LintDiagnostic, MultiSpan};
 use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
@@ -58,7 +58,7 @@ use rustc_middle::ty::GenericArgKind;
 use rustc_middle::ty::ToPredicate;
 use rustc_middle::ty::TypeVisitableExt;
 use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
-use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason};
+use rustc_session::lint::{BuiltinLintDiag, FutureIncompatibilityReason};
 use rustc_span::edition::Edition;
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
@@ -72,7 +72,7 @@ use crate::nonstandard_style::{method_context, MethodLateContext};
 
 use std::fmt::Write;
 
-// hardwired lints from librustc_middle
+// hardwired lints from rustc_lint_defs
 pub use rustc_session::lint::builtin::*;
 
 declare_lint! {
@@ -121,7 +121,7 @@ impl EarlyLintPass for WhileTrue {
                 "{}loop",
                 label.map_or_else(String::new, |label| format!("{}: ", label.ident,))
             );
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 WHILE_TRUE,
                 condition_span,
                 BuiltinWhileTrue { suggestion: condition_span, replace },
@@ -162,7 +162,7 @@ impl BoxPointers {
             if let GenericArgKind::Type(leaf_ty) = leaf.unpack()
                 && leaf_ty.is_box()
             {
-                cx.emit_spanned_lint(BOX_POINTERS, span, BuiltinBoxPointers { ty });
+                cx.emit_span_lint(BOX_POINTERS, span, BuiltinBoxPointers { ty });
             }
         }
     }
@@ -265,7 +265,7 @@ impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns {
                     if cx.tcx.find_field_index(ident, variant)
                         == Some(cx.typeck_results().field_index(fieldpat.hir_id))
                     {
-                        cx.emit_spanned_lint(
+                        cx.emit_span_lint(
                             NON_SHORTHAND_FIELD_PATTERNS,
                             fieldpat.span,
                             BuiltinNonShorthandFieldPatterns {
@@ -327,14 +327,14 @@ impl UnsafeCode {
         &self,
         cx: &EarlyContext<'_>,
         span: Span,
-        decorate: impl for<'a> DecorateLint<'a, ()>,
+        decorate: impl for<'a> LintDiagnostic<'a, ()>,
     ) {
         // This comes from a macro that has `#[allow_internal_unsafe]`.
         if span.allows_unsafe() {
             return;
         }
 
-        cx.emit_spanned_lint(UNSAFE_CODE, span, decorate);
+        cx.emit_span_lint(UNSAFE_CODE, span, decorate);
     }
 }
 
@@ -393,6 +393,10 @@ impl EarlyLintPass for UnsafeCode {
                 }
             }
 
+            ast::ItemKind::GlobalAsm(..) => {
+                self.report_unsafe(cx, it.span, BuiltinUnsafe::GlobalAsm);
+            }
+
             _ => {}
         }
     }
@@ -509,7 +513,7 @@ impl MissingDoc {
         let attrs = cx.tcx.hir().attrs(cx.tcx.local_def_id_to_hir_id(def_id));
         let has_doc = attrs.iter().any(has_doc);
         if !has_doc {
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 MISSING_DOCS,
                 cx.tcx.def_span(def_id),
                 BuiltinMissingDoc { article, desc },
@@ -710,7 +714,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
         )
         .is_ok()
         {
-            cx.emit_spanned_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, BuiltinMissingCopyImpl);
+            cx.emit_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, BuiltinMissingCopyImpl);
         }
     }
 }
@@ -795,7 +799,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
             .next()
             .is_some();
         if !has_impl {
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 MISSING_DEBUG_IMPLEMENTATIONS,
                 item.span,
                 BuiltinMissingDebugImpl { tcx: cx.tcx, def_id: debug },
@@ -874,7 +878,7 @@ impl EarlyLintPass for AnonymousParameters {
                         } else {
                             ("<type>", Applicability::HasPlaceholders)
                         };
-                        cx.emit_spanned_lint(
+                        cx.emit_span_lint(
                             ANONYMOUS_PARAMETERS,
                             arg.pat.span,
                             BuiltinAnonymousParams { suggestion: (arg.pat.span, appl), ty_snip },
@@ -921,7 +925,7 @@ impl EarlyLintPass for DeprecatedAttr {
                             BuiltinDeprecatedAttrLinkSuggestion::Default { suggestion: attr.span }
                         }
                     };
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         DEPRECATED,
                         attr.span,
                         BuiltinDeprecatedAttrLink { name, reason, link, suggestion },
@@ -931,7 +935,7 @@ impl EarlyLintPass for DeprecatedAttr {
             }
         }
         if attr.has_name(sym::no_start) || attr.has_name(sym::crate_id) {
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 DEPRECATED,
                 attr.span,
                 BuiltinDeprecatedAttrUsed {
@@ -973,7 +977,7 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &
                     BuiltinUnusedDocCommentSub::BlockHelp
                 }
             };
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 UNUSED_DOC_COMMENTS,
                 span,
                 BuiltinUnusedDocComment { kind: node_kind, label: node_span, sub },
@@ -985,7 +989,7 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &
 impl EarlyLintPass for UnusedDocComment {
     fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
         let kind = match stmt.kind {
-            ast::StmtKind::Local(..) => "statements",
+            ast::StmtKind::Let(..) => "statements",
             // Disabled pending discussion in #78306
             ast::StmtKind::Item(..) => return,
             // expressions will be reported by `check_expr`.
@@ -1107,7 +1111,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
                 match param.kind {
                     GenericParamKind::Lifetime { .. } => {}
                     GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
-                        cx.emit_spanned_lint(
+                        cx.emit_span_lint(
                             NO_MANGLE_GENERIC_ITEMS,
                             span,
                             BuiltinNoMangleGeneric { suggestion: no_mangle_attr.span },
@@ -1138,7 +1142,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
 
                     // Const items do not refer to a particular location in memory, and therefore
                     // don't have anything to attach a symbol to
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         NO_MANGLE_CONST_ITEMS,
                         it.span,
                         BuiltinConstNoMangle { suggestion },
@@ -1201,7 +1205,7 @@ impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
             get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind()))
         {
             if from_mutbl < to_mutbl {
-                cx.emit_spanned_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes);
+                cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes);
             }
         }
 
@@ -1227,16 +1231,36 @@ impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
         }
 
         fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool {
-            cx.tcx.is_intrinsic(def_id) && cx.tcx.item_name(def_id) == sym::transmute
+            cx.tcx.is_intrinsic(def_id, sym::transmute)
         }
     }
 }
 
 declare_lint! {
-    /// The `unstable_features` is deprecated and should no longer be used.
+    /// The `unstable_features` lint detects uses of `#![feature]`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(unstable_features)]
+    /// #![feature(test)]
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// In larger nightly-based projects which
+    ///
+    /// * consist of a multitude of crates where a subset of crates has to compile on
+    ///   stable either unconditionally or depending on a `cfg` flag to for example
+    ///   allow stable users to depend on them,
+    /// * don't use nightly for experimental features but for, e.g., unstable options only,
+    ///
+    /// this lint may come in handy to enforce policies of these kinds.
     UNSTABLE_FEATURES,
     Allow,
-    "enabling unstable features (deprecated. do not use)"
+    "enabling unstable features"
 }
 
 declare_lint_pass!(
@@ -1246,11 +1270,11 @@ declare_lint_pass!(
 
 impl<'tcx> LateLintPass<'tcx> for UnstableFeatures {
     fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
-        if attr.has_name(sym::feature) {
-            if let Some(items) = attr.meta_item_list() {
-                for item in items {
-                    cx.emit_spanned_lint(UNSTABLE_FEATURES, item.span(), BuiltinUnstableFeatures);
-                }
+        if attr.has_name(sym::feature)
+            && let Some(items) = attr.meta_item_list()
+        {
+            for item in items {
+                cx.emit_span_lint(UNSTABLE_FEATURES, item.span(), BuiltinUnstableFeatures);
             }
         }
     }
@@ -1303,13 +1327,10 @@ impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller {
             // Now, check if the function has the `#[track_caller]` attribute
             && let Some(attr) = cx.tcx.get_attr(def_id, sym::track_caller)
         {
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 UNGATED_ASYNC_FN_TRACK_CALLER,
                 attr.span,
-                BuiltinUngatedAsyncFnTrackCaller {
-                    label: span,
-                    parse_sess: &cx.tcx.sess.parse_sess,
-                },
+                BuiltinUngatedAsyncFnTrackCaller { label: span, session: &cx.tcx.sess },
             );
         }
     }
@@ -1372,7 +1393,7 @@ impl UnreachablePub {
                 applicability = Applicability::MaybeIncorrect;
             }
             let def_span = cx.tcx.def_span(def_id);
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 UNREACHABLE_PUB,
                 def_span,
                 BuiltinUnreachablePub {
@@ -1399,8 +1420,7 @@ impl<'tcx> LateLintPass<'tcx> for UnreachablePub {
     }
 
     fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) {
-        let map = cx.tcx.hir();
-        if matches!(map.get_parent(field.hir_id), Node::Variant(_)) {
+        if matches!(cx.tcx.parent_hir_node(field.hir_id), Node::Variant(_)) {
             return;
         }
         self.perform_lint(cx, "field", field.def_id, field.vis_span, false);
@@ -1501,7 +1521,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
                 suggested_changing_assoc_types = true;
                 SuggestChangingAssocTypes { ty: hir_ty }
             });
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 TYPE_ALIAS_BOUNDS,
                 where_spans,
                 BuiltinTypeAliasWhereClause {
@@ -1517,7 +1537,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
                 suggested_changing_assoc_types = true;
                 SuggestChangingAssocTypes { ty: hir_ty }
             });
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 TYPE_ALIAS_BOUNDS,
                 inline_spans,
                 BuiltinTypeAliasGenericBounds { suggestion, sub },
@@ -1526,32 +1546,6 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
     }
 }
 
-declare_lint_pass!(
-    /// Lint constants that are erroneous.
-    /// Without this lint, we might not get any diagnostic if the constant is
-    /// unused within this crate, even though downstream crates can't use it
-    /// without producing an error.
-    UnusedBrokenConst => []
-);
-
-impl<'tcx> LateLintPass<'tcx> for UnusedBrokenConst {
-    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
-        match it.kind {
-            hir::ItemKind::Const(_, _, body_id) => {
-                let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id();
-                // trigger the query once for all constants since that will already report the errors
-                // FIXME(generic_const_items): Does this work properly with generic const items?
-                cx.tcx.ensure().const_eval_poly(def_id);
-            }
-            hir::ItemKind::Static(_, _, body_id) => {
-                let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id();
-                cx.tcx.ensure().eval_static_initializer(def_id);
-            }
-            _ => {}
-        }
-    }
-}
-
 declare_lint! {
     /// The `trivial_bounds` lint detects trait bounds that don't depend on
     /// any type parameters.
@@ -1616,7 +1610,7 @@ impl<'tcx> LateLintPass<'tcx> for TrivialConstraints {
                     | ClauseKind::ConstEvaluatable(..)  => continue,
                 };
                 if predicate.is_global() {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         TRIVIAL_BOUNDS,
                         span,
                         BuiltinTrivialBounds { predicate_kind_name, predicate },
@@ -1734,7 +1728,7 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
                         replace,
                     });
                 } else {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
                         pat.span,
                         BuiltinEllipsisInclusiveRangePatternsLint::Parenthesise {
@@ -1752,7 +1746,7 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
                         replace: replace.to_string(),
                     });
                 } else {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
                         join,
                         BuiltinEllipsisInclusiveRangePatternsLint::NonParenthesise {
@@ -1831,7 +1825,7 @@ impl KeywordIdents {
             match tt {
                 // Only report non-raw idents.
                 TokenTree::Token(token, _) => {
-                    if let Some((ident, false)) = token.ident() {
+                    if let Some((ident, token::IdentIsRaw::No)) = token.ident() {
                         self.check_ident_token(cx, UnderMacro(true), ident);
                     }
                 }
@@ -1874,11 +1868,11 @@ impl KeywordIdents {
         };
 
         // Don't lint `r#foo`.
-        if cx.sess().parse_sess.raw_identifier_spans.contains(ident.span) {
+        if cx.sess().psess.raw_identifier_spans.contains(ident.span) {
             return;
         }
 
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             KEYWORD_IDENTS,
             ident.span,
             BuiltinKeywordIdents { kw: ident, next: next_edition, suggestion: ident.span },
@@ -2186,7 +2180,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
                 lint_spans.sort_unstable();
                 lint_spans.dedup();
 
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     EXPLICIT_OUTLIVES_REQUIREMENTS,
                     lint_spans.clone(),
                     BuiltinExplicitOutlives {
@@ -2273,13 +2267,13 @@ impl EarlyLintPass for IncompleteInternalFeatures {
                     let help =
                         HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp);
 
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         INCOMPLETE_FEATURES,
                         span,
                         BuiltinIncompleteFeatures { name, note, help },
                     );
                 } else {
-                    cx.emit_spanned_lint(INTERNAL_FEATURES, span, BuiltinInternalFeatures { name });
+                    cx.emit_span_lint(INTERNAL_FEATURES, span, BuiltinInternalFeatures { name });
                 }
             });
     }
@@ -2595,7 +2589,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
                     InitKind::Uninit => fluent::lint_builtin_unpermitted_type_init_uninit,
                 };
                 let sub = BuiltinUnpermittedTypeInitSub { err };
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     INVALID_VALUE,
                     expr.span,
                     BuiltinUnpermittedTypeInit {
@@ -2683,7 +2677,7 @@ impl<'tcx> LateLintPass<'tcx> for DerefNullPtr {
 
         if let rustc_hir::ExprKind::Unary(rustc_hir::UnOp::Deref, expr_deref) = expr.kind {
             if is_null_ptr(cx, expr_deref) {
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     DEREF_NULLPTR,
                     expr.span,
                     BuiltinDerefNullptr { label: expr.span },
@@ -2734,10 +2728,13 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels {
     #[allow(rustc::diagnostic_outside_of_impl)]
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
         if let hir::Expr {
-            kind: hir::ExprKind::InlineAsm(hir::InlineAsm { template_strs, .. }),
+            kind: hir::ExprKind::InlineAsm(hir::InlineAsm { template_strs, options, .. }),
             ..
         } = expr
         {
+            // asm with `options(raw)` does not do replacement with `{` and `}`.
+            let raw = options.contains(InlineAsmOptions::RAW);
+
             for (template_sym, template_snippet, template_span) in template_strs.iter() {
                 let template_str = template_sym.as_str();
                 let find_label_span = |needle: &str| -> Option<Span> {
@@ -2763,24 +2760,57 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels {
                 for statement in statements {
                     // If there's a comment, trim it from the statement
                     let statement = statement.find("//").map_or(statement, |idx| &statement[..idx]);
+
+                    // In this loop, if there is ever a non-label, no labels can come after it.
                     let mut start_idx = 0;
-                    for (idx, _) in statement.match_indices(':') {
+                    'label_loop: for (idx, _) in statement.match_indices(':') {
                         let possible_label = statement[start_idx..idx].trim();
                         let mut chars = possible_label.chars();
-                        let Some(c) = chars.next() else {
-                            // Empty string means a leading ':' in this section, which is not a label
-                            break;
+
+                        let Some(start) = chars.next() else {
+                            // Empty string means a leading ':' in this section, which is not a label.
+                            break 'label_loop;
                         };
-                        // A label starts with an alphabetic character or . or _ and continues with alphanumeric characters, _, or $
-                        if (c.is_alphabetic() || matches!(c, '.' | '_'))
-                            && chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '$'))
-                        {
-                            found_labels.push(possible_label);
-                        } else {
-                            // If we encounter a non-label, there cannot be any further labels, so stop checking
-                            break;
+
+                        // Whether a { bracket has been seen and its } hasn't been found yet.
+                        let mut in_bracket = false;
+
+                        // A label starts with an ASCII alphabetic character or . or _
+                        // A label can also start with a format arg, if it's not a raw asm block.
+                        if !raw && start == '{' {
+                            in_bracket = true;
+                        } else if !(start.is_ascii_alphabetic() || matches!(start, '.' | '_')) {
+                            break 'label_loop;
+                        }
+
+                        // Labels continue with ASCII alphanumeric characters, _, or $
+                        for c in chars {
+                            // Inside a template format arg, any character is permitted for the puproses of label detection
+                            // because we assume that it can be replaced with some other valid label string later.
+                            // `options(raw)` asm blocks cannot have format args, so they are excluded from this special case.
+                            if !raw && in_bracket {
+                                if c == '{' {
+                                    // Nested brackets are not allowed in format args, this cannot be a label.
+                                    break 'label_loop;
+                                }
+
+                                if c == '}' {
+                                    // The end of the format arg.
+                                    in_bracket = false;
+                                }
+                            } else if !raw && c == '{' {
+                                // Start of a format arg.
+                                in_bracket = true;
+                            } else {
+                                if !(c.is_ascii_alphanumeric() || matches!(c, '_' | '$')) {
+                                    // The potential label had an invalid character inside it, it cannot be a label.
+                                    break 'label_loop;
+                                }
+                            }
                         }
 
+                        // If all characters passed the label checks, this is likely a label.
+                        found_labels.push(possible_label);
                         start_idx = idx + 1;
                     }
                 }
@@ -2796,12 +2826,12 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels {
                     let target_spans: MultiSpan =
                         if spans.len() > 0 { spans.into() } else { (*template_span).into() };
 
-                    cx.lookup_with_diagnostics(
+                    cx.span_lint_with_diagnostics(
                             NAMED_ASM_LABELS,
                             Some(target_spans),
                             fluent::lint_builtin_asm_labels,
                             |_| {},
-                            BuiltinLintDiagnostics::NamedAsmLabel(
+                            BuiltinLintDiag::NamedAsmLabel(
                                 "only local labels of the form `<number>:` should be used in inline asm"
                                     .to_string(),
                             ),
@@ -2872,12 +2902,12 @@ impl EarlyLintPass for SpecialModuleName {
                 }
 
                 match item.ident.name.as_str() {
-                    "lib" => cx.emit_spanned_lint(
+                    "lib" => cx.emit_span_lint(
                         SPECIAL_MODULE_NAME,
                         item.span,
                         BuiltinSpecialModuleNameUsed::Lib,
                     ),
-                    "main" => cx.emit_spanned_lint(
+                    "main" => cx.emit_span_lint(
                         SPECIAL_MODULE_NAME,
                         item.span,
                         BuiltinSpecialModuleNameUsed::Main,
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 40b70ba4e04..8c0e5b17fd8 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -18,34 +18,33 @@ use self::TargetLint::*;
 
 use crate::levels::LintLevelsBuilder;
 use crate::passes::{EarlyLintPassObject, LateLintPassObject};
-use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
-use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sync;
-use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder, DiagnosticMessage};
-use rustc_errors::{Applicability, DecorateLint, MultiSpan, SuggestionStyle};
+use rustc_data_structures::unord::UnordMap;
+use rustc_errors::{Diag, DiagMessage, LintDiagnostic, MultiSpan};
 use rustc_feature::Features;
 use rustc_hir as hir;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::{CrateNum, DefId};
 use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
 use rustc_middle::middle::privacy::EffectiveVisibilities;
-use rustc_middle::middle::stability;
 use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
 use rustc_middle::ty::print::{with_no_trimmed_paths, PrintError};
 use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, TyCtxt};
-use rustc_session::config::ExpectedValues;
-use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
+use rustc_session::lint::{BuiltinLintDiag, LintExpectationId};
 use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
 use rustc_session::{LintStoreMarker, Session};
-use rustc_span::edit_distance::find_best_match_for_name;
+use rustc_span::edit_distance::find_best_match_for_names;
 use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::{BytePos, Span};
+use rustc_span::Span;
 use rustc_target::abi;
 
 use std::cell::Cell;
 use std::iter;
 use std::slice;
 
+mod diagnostics;
+
 type EarlyLintPassFactory = dyn Fn() -> EarlyLintPassObject + sync::DynSend + sync::DynSync;
 type LateLintPassFactory =
     dyn for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + sync::DynSend + sync::DynSync;
@@ -71,10 +70,10 @@ pub struct LintStore {
     pub late_module_passes: Vec<Box<LateLintPassFactory>>,
 
     /// Lints indexed by name.
-    by_name: FxHashMap<String, TargetLint>,
+    by_name: UnordMap<String, TargetLint>,
 
     /// Map of registered lint groups to what lints they expand to.
-    lint_groups: FxHashMap<&'static str, LintGroup>,
+    lint_groups: FxIndexMap<&'static str, LintGroup>,
 }
 
 impl LintStoreMarker for LintStore {}
@@ -119,7 +118,7 @@ struct LintGroup {
 pub enum CheckLintNameResult<'a> {
     Ok(&'a [LintId]),
     /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name.
-    NoLint(Option<Symbol>),
+    NoLint(Option<(Symbol, bool)>),
     /// The lint refers to a tool that has not been registered.
     NoTool,
     /// The lint has been renamed to a new name.
@@ -154,8 +153,6 @@ impl LintStore {
     pub fn get_lint_groups<'t>(
         &'t self,
     ) -> impl Iterator<Item = (&'static str, Vec<LintId>, bool)> + 't {
-        // This function is not used in a way which observes the order of lints.
-        #[allow(rustc::potential_query_instability)]
         self.lint_groups
             .iter()
             .filter(|(_, LintGroup { depr, .. })| {
@@ -341,7 +338,7 @@ impl LintStore {
     }
 
     /// Checks the name of a lint for its existence, and whether it was
-    /// renamed or removed. Generates a DiagnosticBuilder containing a
+    /// renamed or removed. Generates a `Diag` containing a
     /// warning for renamed and removed lints. This is over both lint
     /// names from attributes and those passed on the command line. Since
     /// it emits non-fatal warnings and there are *two* lint passes that
@@ -376,10 +373,11 @@ impl LintStore {
                     None => {
                         // 1. The tool is currently running, so this lint really doesn't exist.
                         // FIXME: should this handle tools that never register a lint, like rustfmt?
-                        debug!("lints={:?}", self.by_name.keys().collect::<Vec<_>>());
+                        debug!("lints={:?}", self.by_name);
                         let tool_prefix = format!("{tool_name}::");
+
                         return if self.by_name.keys().any(|lint| lint.starts_with(&tool_prefix)) {
-                            self.no_lint_suggestion(&complete_name)
+                            self.no_lint_suggestion(&complete_name, tool_name.as_str())
                         } else {
                             // 2. The tool isn't currently running, so no lints will be registered.
                             // To avoid giving a false positive, ignore all unknown lints.
@@ -421,17 +419,20 @@ impl LintStore {
         }
     }
 
-    fn no_lint_suggestion(&self, lint_name: &str) -> CheckLintNameResult<'_> {
+    fn no_lint_suggestion(&self, lint_name: &str, tool_name: &str) -> CheckLintNameResult<'_> {
         let name_lower = lint_name.to_lowercase();
 
         if lint_name.chars().any(char::is_uppercase) && self.find_lints(&name_lower).is_ok() {
             // First check if the lint name is (partly) in upper case instead of lower case...
-            return CheckLintNameResult::NoLint(Some(Symbol::intern(&name_lower)));
+            return CheckLintNameResult::NoLint(Some((Symbol::intern(&name_lower), false)));
         }
+
         // ...if not, search for lints with a similar name
         // Note: find_best_match_for_name depends on the sort order of its input vector.
         // To ensure deterministic output, sort elements of the lint_groups hash map.
         // Also, never suggest deprecated lint groups.
+        // We will soon sort, so the initial order does not matter.
+        #[allow(rustc::potential_query_instability)]
         let mut groups: Vec<_> = self
             .lint_groups
             .iter()
@@ -441,7 +442,16 @@ impl LintStore {
         let groups = groups.iter().map(|k| Symbol::intern(k));
         let lints = self.lints.iter().map(|l| Symbol::intern(&l.name_lower()));
         let names: Vec<Symbol> = groups.chain(lints).collect();
-        let suggestion = find_best_match_for_name(&names, Symbol::intern(&name_lower), None);
+        let mut lookups = vec![Symbol::intern(&name_lower)];
+        if let Some(stripped) = name_lower.split("::").last() {
+            lookups.push(Symbol::intern(stripped));
+        }
+        let res = find_best_match_for_names(&names, &lookups, None);
+        let is_rustc = res.map_or_else(
+            || false,
+            |s| name_lower.contains("::") && !s.as_str().starts_with(tool_name),
+        );
+        let suggestion = res.map(|s| (s, is_rustc));
         CheckLintNameResult::NoLint(suggestion)
     }
 
@@ -454,7 +464,7 @@ impl LintStore {
         match self.by_name.get(&complete_name) {
             None => match self.lint_groups.get(&*complete_name) {
                 // Now we are sure, that this lint exists nowhere
-                None => self.no_lint_suggestion(lint_name),
+                None => self.no_lint_suggestion(lint_name, tool_name),
                 Some(LintGroup { lint_ids, depr, .. }) => {
                     // Reaching this would be weird, but let's cover this case anyway
                     if let Some(LintAlias { name, silent }) = depr {
@@ -520,458 +530,20 @@ pub trait LintContext {
     /// Emit a lint at the appropriate level, with an optional associated span and an existing
     /// diagnostic.
     ///
-    /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
     #[rustc_lint_diagnostics]
-    fn lookup_with_diagnostics(
+    fn span_lint_with_diagnostics(
         &self,
         lint: &'static Lint,
         span: Option<impl Into<MultiSpan>>,
-        msg: impl Into<DiagnosticMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>),
-        diagnostic: BuiltinLintDiagnostics,
+        msg: impl Into<DiagMessage>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
+        diagnostic: BuiltinLintDiag,
     ) {
         // We first generate a blank diagnostic.
-        self.lookup(lint, span, msg,|db| {
+        self.opt_span_lint(lint, span, msg, |db| {
             // Now, set up surrounding context.
-            let sess = self.sess();
-            match diagnostic {
-                BuiltinLintDiagnostics::UnicodeTextFlow(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)))
-                            })
-                        })
-                        .collect();
-                    let (an, s) = match spans.len() {
-                        1 => ("an ", ""),
-                        _ => ("", "s"),
-                    };
-                    db.span_label(span, format!(
-                        "this comment contains {an}invisible unicode text flow control codepoint{s}",
-                    ));
-                    for (c, span) in &spans {
-                        db.span_label(*span, format!("{c:?}"));
-                    }
-                    db.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() {
-                        db.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,
-                        );
-                    }
-                },
-                BuiltinLintDiagnostics::Normal => (),
-                BuiltinLintDiagnostics::AbsPathWithModule(span) => {
-                    let (sugg, app) = match sess.source_map().span_to_snippet(span) {
-                        Ok(ref s) => {
-                            // FIXME(Manishearth) ideally the emitting code
-                            // can tell us whether or not this is global
-                            let opt_colon =
-                                if s.trim_start().starts_with("::") { "" } else { "::" };
-
-                            (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable)
-                        }
-                        Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders),
-                    };
-                    db.span_suggestion(span, "use `crate`", sugg, app);
-                }
-                BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => {
-                    db.span_label(
-                        span,
-                        "names from parent modules are not accessible without an explicit import",
-                    );
-                }
-                BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths(
-                    span_def,
-                ) => {
-                    db.span_note(span_def, "the macro is defined here");
-                }
-                BuiltinLintDiagnostics::ElidedLifetimesInPaths(
-                    n,
-                    path_span,
-                    incl_angl_brckt,
-                    insertion_span,
-                ) => {
-                    add_elided_lifetime_in_path_suggestion(
-                        sess.source_map(),
-                        db,
-                        n,
-                        path_span,
-                        incl_angl_brckt,
-                        insertion_span,
-                    );
-                }
-                BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => {
-                    db.span_suggestion(span, note, sugg, Applicability::MaybeIncorrect);
-                }
-                BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => {
-                    if !replaces.is_empty() {
-                        db.tool_only_multipart_suggestion(
-                            message,
-                            replaces,
-                            Applicability::MachineApplicable,
-                        );
-                    }
-
-                    if let Some(span) = in_test_module {
-                        db.span_help(
-                            self.sess().source_map().guess_head_span(span),
-                            "consider adding a `#[cfg(test)]` to the containing module",
-                        );
-                    }
-                }
-                BuiltinLintDiagnostics::RedundantImport(spans, ident) => {
-                    for (span, is_imported) in spans {
-                        let introduced = if is_imported { "imported" } else { "defined" };
-                        db.span_label(
-                            span,
-                            format!("the item `{ident}` is already {introduced} here"),
-                        );
-                    }
-                }
-                BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
-                    stability::deprecation_suggestion(db, "macro", suggestion, span)
-                }
-                BuiltinLintDiagnostics::UnusedDocComment(span) => {
-                    db.span_label(span, "rustdoc does not generate documentation for macro invocations");
-                    db.help("to document an item produced by a macro, \
-                                  the macro must produce the documentation as part of its expansion");
-                }
-                BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => {
-                    db.span_suggestion(span, "remove `mut` from the parameter", ident, Applicability::MachineApplicable);
-                }
-                BuiltinLintDiagnostics::MissingAbi(span, default_abi) => {
-                    db.span_label(span, "ABI should be specified here");
-                    db.help(format!("the default ABI is {}", default_abi.name()));
-                }
-                BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => {
-                    db.span_label(span, "the attribute is introduced here");
-                }
-                BuiltinLintDiagnostics::ProcMacroBackCompat(note) => {
-                    db.note(note);
-                }
-                BuiltinLintDiagnostics::OrPatternsBackCompat(span,suggestion) => {
-                    db.span_suggestion(span, "use pat_param to preserve semantics", suggestion, Applicability::MachineApplicable);
-                }
-                BuiltinLintDiagnostics::ReservedPrefix(span) => {
-                    db.span_label(span, "unknown prefix");
-                    db.span_suggestion_verbose(
-                        span.shrink_to_hi(),
-                        "insert whitespace here to avoid this being parsed as a prefix in Rust 2021",
-                        " ",
-                        Applicability::MachineApplicable,
-                    );
-                }
-                BuiltinLintDiagnostics::UnusedBuiltinAttribute {
-                    attr_name,
-                    macro_name,
-                    invoc_span
-                } => {
-                    db.span_note(
-                        invoc_span,
-                        format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`")
-                    );
-                }
-                BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => {
-                    if is_trailing {
-                        db.note("macro invocations at the end of a block are treated as expressions");
-                        db.note(format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`"));
-                    }
-                }
-                BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => {
-                    db.multipart_suggestion(
-                        "wrap this expression in parentheses",
-                        vec![(span.shrink_to_lo(), "(".to_string()),
-                             (span.shrink_to_hi(), ")".to_string())],
-                        Applicability::MachineApplicable
-                    );
-                }
-                BuiltinLintDiagnostics::NamedAsmLabel(help) => {
-                    db.help(help);
-                    db.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");
-                },
-                BuiltinLintDiagnostics::UnexpectedCfgName((name, name_span), value) => {
-                    let possibilities: Vec<Symbol> = sess.parse_sess.check_config.expecteds.keys().copied().collect();
-                    let is_from_cargo = std::env::var_os("CARGO").is_some();
-                    let mut is_feature_cfg = name == sym::feature;
-
-                    if is_feature_cfg && is_from_cargo {
-                        db.help("consider defining some features in `Cargo.toml`");
-                    // Suggest the most probable if we found one
-                    } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
-                        if let Some(ExpectedValues::Some(best_match_values)) =
-                            sess.parse_sess.check_config.expecteds.get(&best_match) {
-                            let mut possibilities = best_match_values.iter()
-                                .flatten()
-                                .map(Symbol::as_str)
-                                .collect::<Vec<_>>();
-                            possibilities.sort();
-
-                            let mut should_print_possibilities = true;
-                            if let Some((value, value_span)) = value {
-                                if best_match_values.contains(&Some(value)) {
-                                    db.span_suggestion(name_span, "there is a config with a similar name and value", best_match, Applicability::MaybeIncorrect);
-                                    should_print_possibilities = false;
-                                } else if best_match_values.contains(&None) {
-                                    db.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;
-                                } else if let Some(first_value) = possibilities.first() {
-                                    db.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);
-                                } else {
-                                    db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", best_match, Applicability::MaybeIncorrect);
-                                };
-                            } else {
-                                db.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("`, `");
-                                db.help(format!("expected values for `{best_match}` are: `{possibilities}`"));
-                            }
-                        } else {
-                            db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect);
-                        }
-
-                        is_feature_cfg |= best_match == sym::feature;
-                    } else if !possibilities.is_empty() {
-                        let mut possibilities = possibilities.iter()
-                            .map(Symbol::as_str)
-                            .collect::<Vec<_>>();
-                        possibilities.sort();
-                        let possibilities = possibilities.join("`, `");
-
-                        // The list of expected names can be long (even by default) and
-                        // so the diagnostic produced can take a lot of space. To avoid
-                        // cloging the user output we only want to print that diagnostic
-                        // once.
-                        db.help_once(format!("expected names are: `{possibilities}`"));
-                    }
-
-                    let inst = if let Some((value, _value_span)) = value {
-                        let pre = if is_from_cargo { "\\" } else { "" };
-                        format!("cfg({name}, values({pre}\"{value}{pre}\"))")
-                    } else {
-                        format!("cfg({name})")
-                    };
-
-                    if is_from_cargo {
-                        if !is_feature_cfg {
-                            db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`"));
-                        }
-                        db.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration");
-                    } else {
-                        db.help(format!("to expect this configuration use `--check-cfg={inst}`"));
-                        db.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration");
-                    }
-                },
-                BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => {
-                    let Some(ExpectedValues::Some(values)) = &sess.parse_sess.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");
-                    };
-                    let mut have_none_possibility = false;
-                    let possibilities: Vec<Symbol> = values.iter()
-                        .inspect(|a| have_none_possibility |= a.is_none())
-                        .copied()
-                        .flatten()
-                        .collect();
-                    let is_from_cargo = std::env::var_os("CARGO").is_some();
-
-                    // 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() {
-                        {
-                            let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
-                            possibilities.sort();
-
-                            let possibilities = possibilities.join("`, `");
-                            let none = if have_none_possibility { "(none), " } else { "" };
-
-                            db.note(format!("expected values for `{name}` are: {none}`{possibilities}`"));
-                        }
-
-                        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) {
-                                db.span_suggestion(value_span, "there is a expected value with a similar name", format!("\"{best_match}\""), Applicability::MaybeIncorrect);
-
-                            }
-                        } else if name == sym::feature && is_from_cargo {
-                            db.help(format!("consider defining `{name}` as feature in `Cargo.toml`"));
-                        } else if let &[first_possibility] = &possibilities[..] {
-                            db.span_suggestion(name_span.shrink_to_hi(), "specify a config value", format!(" = \"{first_possibility}\""), Applicability::MaybeIncorrect);
-                        }
-                    } else if have_none_possibility {
-                        db.note(format!("no expected value for `{name}`"));
-                        if let Some((_value, value_span)) = value {
-                            db.span_suggestion(name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect);
-                        }
-                    }
-
-                    let inst = if let Some((value, _value_span)) = value {
-                        let pre = if is_from_cargo { "\\" } else { "" };
-                        format!("cfg({name}, values({pre}\"{value}{pre}\"))")
-                    } else {
-                        format!("cfg({name})")
-                    };
-
-                    if is_from_cargo {
-                        if name == sym::feature {
-                            if let Some((value, _value_span)) = value {
-                                db.help(format!("consider adding `{value}` as a feature in `Cargo.toml`"));
-                            }
-                        } else {
-                            db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`"));
-                        }
-                        db.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration");
-                    } else {
-                        db.help(format!("to expect this configuration use `--check-cfg={inst}`"));
-                        db.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration");
-                    }
-                },
-                BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => {
-                    db.multipart_suggestion(
-                        "move it to the end of the type declaration",
-                        vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)],
-                        Applicability::MachineApplicable,
-                    );
-                    db.note(
-                        "see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information",
-                    );
-                },
-                BuiltinLintDiagnostics::SingleUseLifetime {
-                    param_span,
-                    use_span: Some((use_span, elide)),
-                    deletion_span,
-                } => {
-                    debug!(?param_span, ?use_span, ?deletion_span);
-                    db.span_label(param_span, "this lifetime...");
-                    db.span_label(use_span, "...is used only here");
-                    if let Some(deletion_span) = deletion_span {
-                        let msg = "elide the single-use lifetime";
-                        let (use_span, replace_lt) = if elide {
-                            let use_span = sess.source_map().span_extend_while(
-                                use_span,
-                                char::is_whitespace,
-                            ).unwrap_or(use_span);
-                            (use_span, String::new())
-                        } else {
-                            (use_span, "'_".to_owned())
-                        };
-                        debug!(?deletion_span, ?use_span);
-
-                        // 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)]
-                        };
-                        db.multipart_suggestion(
-                            msg,
-                            suggestions,
-                            Applicability::MachineApplicable,
-                        );
-                    }
-                },
-                BuiltinLintDiagnostics::SingleUseLifetime {
-                    param_span: _,
-                    use_span: None,
-                    deletion_span,
-                } => {
-                    debug!(?deletion_span);
-                    if let Some(deletion_span) = deletion_span {
-                        db.span_suggestion(
-                            deletion_span,
-                            "elide the unused lifetime",
-                            "",
-                            Applicability::MachineApplicable,
-                        );
-                    }
-                },
-                BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => {
-                    db.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");
-                        db.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 span_to_replace = if let Ok(positional_arg_content) =
-                            self.sess().source_map().span_to_snippet(positional_arg_to_replace) && positional_arg_content.starts_with(':') {
-                            positional_arg_to_replace.shrink_to_lo()
-                        } else {
-                            positional_arg_to_replace
-                        };
-                        db.span_suggestion_verbose(
-                            span_to_replace,
-                            "use the named argument by name to avoid ambiguity",
-                            name,
-                            Applicability::MaybeIncorrect,
-                        );
-                    }
-                }
-                BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive => {
-                    db.help("consider implementing the trait by hand, or remove the `packed` attribute");
-                }
-                BuiltinLintDiagnostics::UnusedExternCrate { removal_span }=> {
-                    db.span_suggestion(
-                        removal_span,
-                        "remove it",
-                        "",
-                        Applicability::MachineApplicable,
-                    );
-                }
-                BuiltinLintDiagnostics::ExternCrateNotIdiomatic { vis_span, ident_span }=> {
-                    let suggestion_span = vis_span.between(ident_span);
-                    db.span_suggestion_verbose(
-                        suggestion_span,
-                        "convert it to a `use`",
-                        if vis_span.is_empty() { "use " } else { " use " },
-                        Applicability::MachineApplicable,
-                    );
-                }
-                BuiltinLintDiagnostics::AmbiguousGlobImports { diag } => {
-                    rustc_errors::report_ambiguity_error(db, diag);
-                }
-                BuiltinLintDiagnostics::AmbiguousGlobReexports { name, namespace, first_reexport_span, duplicate_reexport_span } => {
-                    db.span_label(first_reexport_span, format!("the name `{name}` in the {namespace} namespace is first re-exported here"));
-                    db.span_label(duplicate_reexport_span, format!("but the name `{name}` in the {namespace} namespace is also re-exported here"));
-                }
-                BuiltinLintDiagnostics::HiddenGlobReexports { name, namespace, glob_reexport_span, private_item_span } => {
-                    db.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here"));
-                    db.span_note(private_item_span, "but the private item here shadows it".to_owned());
-                }
-                BuiltinLintDiagnostics::UnusedQualifications { removal_span } => {
-                    db.span_suggestion_verbose(
-                        removal_span,
-                        "remove the unnecessary path segments",
-                        "",
-                        Applicability::MachineApplicable
-                    );
-                }
-                BuiltinLintDiagnostics::AssociatedConstElidedLifetime { elided, span } => {
-                    db.span_suggestion_verbose(
-                        if elided { span.shrink_to_hi() } else { span },
-                        "use the `'static` lifetime",
-                        if elided { "'static " } else { "'static" },
-                        Applicability::MachineApplicable
-                    );
-                },
-                BuiltinLintDiagnostics::RedundantImportVisibility { max_vis, span } => {
-                    db.span_note(span, format!("the most public imported item is `{max_vis}`"));
-                    db.help("reduce the glob import's visibility or increase visibility of imported items");
-                }
-            }
+            diagnostics::builtin(self.sess(), diagnostic, db);
             // Rewrap `db`, and pass control to the user.
             decorate(db)
         });
@@ -981,62 +553,62 @@ pub trait LintContext {
     // set the span in their `decorate` function (preferably using set_span).
     /// Emit a lint at the appropriate level, with an optional associated span.
     ///
-    /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
     #[rustc_lint_diagnostics]
-    fn lookup<S: Into<MultiSpan>>(
+    fn opt_span_lint<S: Into<MultiSpan>>(
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        msg: impl Into<DiagnosticMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagMessage>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     );
 
-    /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`,
+    /// Emit a lint at `span` from a lint struct (some type that implements `LintDiagnostic`,
     /// typically generated by `#[derive(LintDiagnostic)]`).
-    fn emit_spanned_lint<S: Into<MultiSpan>>(
+    fn emit_span_lint<S: Into<MultiSpan>>(
         &self,
         lint: &'static Lint,
         span: S,
-        decorator: impl for<'a> DecorateLint<'a, ()>,
+        decorator: impl for<'a> LintDiagnostic<'a, ()>,
     ) {
-        self.lookup(lint, Some(span), decorator.msg(), |diag| {
+        self.opt_span_lint(lint, Some(span), decorator.msg(), |diag| {
             decorator.decorate_lint(diag);
         });
     }
 
     /// Emit a lint at the appropriate level, with an associated span.
     ///
-    /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
     #[rustc_lint_diagnostics]
-    fn struct_span_lint<S: Into<MultiSpan>>(
+    fn span_lint<S: Into<MultiSpan>>(
         &self,
         lint: &'static Lint,
         span: S,
-        msg: impl Into<DiagnosticMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagMessage>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
-        self.lookup(lint, Some(span), msg, decorate);
+        self.opt_span_lint(lint, Some(span), msg, decorate);
     }
 
-    /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically
+    /// Emit a lint from a lint struct (some type that implements `LintDiagnostic`, typically
     /// generated by `#[derive(LintDiagnostic)]`).
-    fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> DecorateLint<'a, ()>) {
-        self.lookup(lint, None as Option<Span>, decorator.msg(), |diag| {
+    fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> LintDiagnostic<'a, ()>) {
+        self.opt_span_lint(lint, None as Option<Span>, decorator.msg(), |diag| {
             decorator.decorate_lint(diag);
         });
     }
 
     /// Emit a lint at the appropriate level, with no associated span.
     ///
-    /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
     #[rustc_lint_diagnostics]
     fn lint(
         &self,
         lint: &'static Lint,
-        msg: impl Into<DiagnosticMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagMessage>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
-        self.lookup(lint, None as Option<Span>, msg, decorate);
+        self.opt_span_lint(lint, None as Option<Span>, msg, decorate);
     }
 
     /// This returns the lint level for the given lint at the current location.
@@ -1049,12 +621,13 @@ pub trait LintContext {
     /// Note that this function should only be called for [`LintExpectationId`]s
     /// retrieved from the current lint pass. Buffered or manually created ids can
     /// cause ICEs.
-    #[rustc_lint_diagnostics]
     fn fulfill_expectation(&self, expectation: LintExpectationId) {
         // We need to make sure that submitted expectation ids are correctly fulfilled suppressed
         // and stored between compilation sessions. To not manually do these steps, we simply create
-        // a dummy diagnostic and emit is as usual, which will be suppressed and stored like a normal
-        // expected lint diagnostic.
+        // a dummy diagnostic and emit it as usual, which will be suppressed and stored like a
+        // normal expected lint diagnostic.
+        #[allow(rustc::diagnostic_outside_of_impl)]
+        #[allow(rustc::untranslatable_diagnostic)]
         self.sess()
             .dcx()
             .struct_expect(
@@ -1069,7 +642,7 @@ impl<'a> EarlyContext<'a> {
     pub(crate) fn new(
         sess: &'a Session,
         features: &'a Features,
-        warn_about_weird_lints: bool,
+        lint_added_lints: bool,
         lint_store: &'a LintStore,
         registered_tools: &'a RegisteredTools,
         buffered: LintBuffer,
@@ -1078,7 +651,7 @@ impl<'a> EarlyContext<'a> {
             builder: LintLevelsBuilder::new(
                 sess,
                 features,
-                warn_about_weird_lints,
+                lint_added_lints,
                 lint_store,
                 registered_tools,
             ),
@@ -1094,18 +667,18 @@ impl<'tcx> LintContext for LateContext<'tcx> {
     }
 
     #[rustc_lint_diagnostics]
-    fn lookup<S: Into<MultiSpan>>(
+    fn opt_span_lint<S: Into<MultiSpan>>(
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        msg: impl Into<DiagnosticMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagMessage>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
         let hir_id = self.last_node_with_lint_attrs;
 
         match span {
-            Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, msg, decorate),
-            None => self.tcx.struct_lint_node(lint, hir_id, msg, decorate),
+            Some(s) => self.tcx.node_span_lint(lint, hir_id, s, msg, decorate),
+            None => self.tcx.node_lint(lint, hir_id, msg, decorate),
         }
     }
 
@@ -1121,14 +694,14 @@ impl LintContext for EarlyContext<'_> {
     }
 
     #[rustc_lint_diagnostics]
-    fn lookup<S: Into<MultiSpan>>(
+    fn opt_span_lint<S: Into<MultiSpan>>(
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        msg: impl Into<DiagnosticMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagMessage>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
-        self.builder.struct_lint(lint, span.map(|s| s.into()), msg, decorate)
+        self.builder.opt_span_lint(lint, span.map(|s| s.into()), msg, decorate)
     }
 
     fn get_lint_level(&self, lint: &'static Lint) -> Level {
@@ -1359,7 +932,7 @@ impl<'tcx> LateContext<'tcx> {
 
         while let hir::ExprKind::Path(ref qpath) = expr.kind
             && let Some(parent_node) = match self.qpath_res(qpath, expr.hir_id) {
-                Res::Local(hir_id) => self.tcx.hir().find_parent(hir_id),
+                Res::Local(hir_id) => Some(self.tcx.parent_hir_node(hir_id)),
                 _ => None,
             }
             && let Some(init) = match parent_node {
@@ -1403,7 +976,7 @@ impl<'tcx> LateContext<'tcx> {
 
         while let hir::ExprKind::Path(ref qpath) = expr.kind
             && let Some(parent_node) = match self.qpath_res(qpath, expr.hir_id) {
-                Res::Local(hir_id) => self.tcx.hir().find_parent(hir_id),
+                Res::Local(hir_id) => Some(self.tcx.parent_hir_node(hir_id)),
                 Res::Def(_, def_id) => self.tcx.hir().get_if_local(def_id),
                 _ => None,
             }
diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs
new file mode 100644
index 00000000000..a0be1c09c9a
--- /dev/null
+++ b/compiler/rustc_lint/src/context/diagnostics.rs
@@ -0,0 +1,621 @@
+#![allow(rustc::diagnostic_outside_of_impl)]
+#![allow(rustc::untranslatable_diagnostic)]
+
+use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
+use rustc_errors::{add_elided_lifetime_in_path_suggestion, Diag};
+use rustc_errors::{Applicability, SuggestionStyle};
+use rustc_middle::middle::stability;
+use rustc_session::config::ExpectedValues;
+use rustc_session::lint::BuiltinLintDiag;
+use rustc_session::Session;
+use rustc_span::edit_distance::find_best_match_for_name;
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::BytePos;
+
+const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35;
+
+fn check_cfg_expected_note(
+    sess: &Session,
+    possibilities: &[Symbol],
+    type_: &str,
+    name: Option<Symbol>,
+    suffix: &str,
+) -> String {
+    use std::fmt::Write;
+
+    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();
+
+    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
+}
+
+pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Diag<'_, ()>) {
+    match diagnostic {
+        BuiltinLintDiag::UnicodeTextFlow(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)))
+                    })
+                })
+                .collect();
+            let (an, s) = match spans.len() {
+                1 => ("an ", ""),
+                _ => ("", "s"),
+            };
+            diag.span_label(
+                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) {
+                Ok(ref s) => {
+                    // FIXME(Manishearth) ideally the emitting code
+                    // can tell us whether or not this is global
+                    let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" };
+
+                    (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable)
+                }
+                Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders),
+            };
+            diag.span_suggestion(span, "use `crate`", sugg, app);
+        }
+        BuiltinLintDiag::ProcMacroDeriveResolutionFallback(span) => {
+            diag.span_label(
+                span,
+                "names from parent modules are not accessible without an explicit import",
+            );
+        }
+        BuiltinLintDiag::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => {
+            diag.span_note(span_def, "the macro is defined here");
+        }
+        BuiltinLintDiag::ElidedLifetimesInPaths(n, path_span, incl_angl_brckt, insertion_span) => {
+            add_elided_lifetime_in_path_suggestion(
+                sess.source_map(),
+                diag,
+                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,
+                );
+            }
+
+            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",
+                );
+            }
+        }
+        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 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(
+                span,
+                "remove `mut` from the parameter",
+                ident,
+                Applicability::MachineApplicable,
+            );
+        }
+        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::LegacyDeriveHelpers(span) => {
+            diag.span_label(span, "the attribute is introduced here");
+        }
+        BuiltinLintDiag::ProcMacroBackCompat(note) => {
+            diag.note(note);
+        }
+        BuiltinLintDiag::OrPatternsBackCompat(span, suggestion) => {
+            diag.span_suggestion(
+                span,
+                "use pat_param to preserve semantics",
+                suggestion,
+                Applicability::MachineApplicable,
+            );
+        }
+        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::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}`")
+                    );
+        }
+        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,
+            );
+        }
+        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::UnexpectedCfgName((name, name_span), value) => {
+            #[allow(rustc::potential_query_instability)]
+            let possibilities: Vec<Symbol> =
+                sess.psess.check_config.expecteds.keys().copied().collect();
+
+            let mut names_possibilities: Vec<_> = if value.is_none() {
+                // We later sort and display all the possibilities, so the order here does not matter.
+                #[allow(rustc::potential_query_instability)]
+                sess.psess
+                    .check_config
+                    .expecteds
+                    .iter()
+                    .filter_map(|(k, v)| match v {
+                        ExpectedValues::Some(v) if v.contains(&Some(name)) => Some(k),
+                        _ => None,
+                    })
+                    .collect()
+            } else {
+                Vec::new()
+            };
+
+            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`");
+            // Suggest the most probable if we found one
+            } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
+                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 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;
+                        } 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;
+                        } 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,
+                            );
+                        } else {
+                            diag.span_suggestion(
+                                name_span.to(value_span),
+                                "there is a config with a similar name and different values",
+                                best_match,
+                                Applicability::MaybeIncorrect,
+                            );
+                        };
+                    } 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}`"
+                        ));
+                    }
+                } else {
+                    diag.span_suggestion(
+                        name_span,
+                        "there is a config with a similar name",
+                        best_match,
+                        Applicability::MaybeIncorrect,
+                    );
+                }
+
+                is_feature_cfg |= best_match == sym::feature;
+            } else {
+                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,
+                        "",
+                    ));
+                }
+            }
+
+            let inst = if let Some((value, _value_span)) = value {
+                let pre = if is_from_cargo { "\\" } else { "" };
+                format!("cfg({name}, values({pre}\"{value}{pre}\"))")
+            } else {
+                format!("cfg({name})")
+            };
+
+            if is_from_cargo {
+                if !is_feature_cfg {
+                    diag.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`"));
+                }
+                diag.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration");
+            } else {
+                diag.help(format!("to expect this configuration use `--check-cfg={inst}`"));
+                diag.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration");
+            }
+        }
+        BuiltinLintDiag::UnexpectedCfgValue((name, name_span), value) => {
+            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"
+                );
+            };
+            let mut have_none_possibility = false;
+            // We later sort possibilities if it is not empty, so the
+            // order here does not matter.
+            #[allow(rustc::potential_query_instability)]
+            let possibilities: Vec<Symbol> = values
+                .iter()
+                .inspect(|a| have_none_possibility |= a.is_none())
+                .copied()
+                .flatten()
+                .collect();
+            let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
+
+            // 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 { "" },
+                ));
+
+                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,
+                        );
+                    }
+                } else if let &[first_possibility] = &possibilities[..] {
+                    diag.span_suggestion(
+                        name_span.shrink_to_hi(),
+                        "specify a config value",
+                        format!(" = \"{first_possibility}\""),
+                        Applicability::MaybeIncorrect,
+                    );
+                }
+            } 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,
+                    );
+                }
+            } else {
+                diag.note(format!("no expected values for `{name}`"));
+
+                let sp = if let Some((_value, value_span)) = value {
+                    name_span.to(value_span)
+                } else {
+                    name_span
+                };
+                diag.span_suggestion(sp, "remove the condition", "", Applicability::MaybeIncorrect);
+            }
+
+            // We don't want to suggest adding values to well known names
+            // since those are defined by rustc it-self. Users can still
+            // do it if they want, but should not encourage them.
+            let is_cfg_a_well_know_name = sess.psess.check_config.well_known_names.contains(&name);
+
+            let inst = if let Some((value, _value_span)) = value {
+                let pre = if is_from_cargo { "\\" } else { "" };
+                format!("cfg({name}, values({pre}\"{value}{pre}\"))")
+            } else {
+                format!("cfg({name})")
+            };
+
+            if is_from_cargo {
+                if name == sym::feature {
+                    if let Some((value, _value_span)) = value {
+                        diag.help(format!(
+                            "consider adding `{value}` as a feature in `Cargo.toml`"
+                        ));
+                    } else {
+                        diag.help("consider defining some features in `Cargo.toml`");
+                    }
+                } else if !is_cfg_a_well_know_name {
+                    diag.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`"));
+                }
+                diag.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration");
+            } else {
+                if !is_cfg_a_well_know_name {
+                    diag.help(format!("to expect this configuration use `--check-cfg={inst}`"));
+                }
+                diag.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration");
+            }
+        }
+        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,
+                ),
+            };
+            diag.note("see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information");
+        }
+        BuiltinLintDiag::SingleUseLifetime {
+            param_span,
+            use_span: Some((use_span, elide)),
+            deletion_span,
+        } => {
+            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 (use_span, replace_lt) = if elide {
+                    let use_span = sess
+                        .source_map()
+                        .span_extend_while(use_span, char::is_whitespace)
+                        .unwrap_or(use_span);
+                    (use_span, String::new())
+                } else {
+                    (use_span, "'_".to_owned())
+                };
+                debug!(?deletion_span, ?use_span);
+
+                // 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);
+            }
+        }
+        BuiltinLintDiag::SingleUseLifetime { param_span: _, use_span: None, deletion_span } => {
+            debug!(?deletion_span);
+            if let Some(deletion_span) = deletion_span {
+                diag.span_suggestion(
+                    deletion_span,
+                    "elide the unused lifetime",
+                    "",
+                    Applicability::MachineApplicable,
+                );
+            }
+        }
+        BuiltinLintDiag::NamedArgumentUsedPositionally {
+            position_sp_to_replace,
+            position_sp_for_msg,
+            named_arg_sp,
+            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 span_to_replace = if let Ok(positional_arg_content) =
+                    sess.source_map().span_to_snippet(positional_arg_to_replace)
+                    && positional_arg_content.starts_with(':')
+                {
+                    positional_arg_to_replace.shrink_to_lo()
+                } else {
+                    positional_arg_to_replace
+                };
+                diag.span_suggestion_verbose(
+                    span_to_replace,
+                    "use the named argument by name to avoid ambiguity",
+                    name,
+                    Applicability::MaybeIncorrect,
+                );
+            }
+        }
+        BuiltinLintDiag::ByteSliceInPackedStructWithDerive => {
+            diag.help("consider implementing the trait by hand, or remove the `packed` attribute");
+        }
+        BuiltinLintDiag::UnusedExternCrate { removal_span } => {
+            diag.span_suggestion(removal_span, "remove it", "", Applicability::MachineApplicable);
+        }
+        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,
+            );
+        }
+        BuiltinLintDiag::AmbiguousGlobImports { diag: ambiguity } => {
+            rustc_errors::report_ambiguity_error(diag, ambiguity);
+        }
+        BuiltinLintDiag::AmbiguousGlobReexports {
+            name,
+            namespace,
+            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"
+                ),
+            );
+        }
+        BuiltinLintDiag::HiddenGlobReexports {
+            name,
+            namespace,
+            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,
+            );
+        }
+        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::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",
+            );
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs
index 98bafc0f263..9bc81d2c46a 100644
--- a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs
+++ b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs
@@ -5,6 +5,7 @@ use crate::{
 
 use rustc_hir as hir;
 use rustc_middle::ty;
+use rustc_session::lint::FutureIncompatibilityReason;
 use rustc_span::sym;
 use rustc_trait_selection::traits::supertraits;
 
@@ -12,6 +13,9 @@ declare_lint! {
     /// The `deref_into_dyn_supertrait` lint is output whenever there is a use of the
     /// `Deref` implementation with a `dyn SuperTrait` type as `Output`.
     ///
+    /// These implementations will become shadowed when the `trait_upcasting` feature is stabilized.
+    /// The `deref` functions will no longer be called implicitly, so there might be behavior change.
+    ///
     /// ### Example
     ///
     /// ```rust,compile_fail
@@ -40,10 +44,15 @@ declare_lint! {
     ///
     /// ### Explanation
     ///
-    /// The implicit dyn upcasting coercion take priority over those `Deref` impls.
+    /// The dyn upcasting coercion feature adds new coercion rules, taking priority
+    /// over certain other coercion rules, which will cause some behavior change.
     pub DEREF_INTO_DYN_SUPERTRAIT,
     Warn,
-    "`Deref` implementation usage with a supertrait trait object for output are shadow by implicit coercion",
+    "`Deref` implementation usage with a supertrait trait object for output might be shadowed in the future",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::FutureReleaseSemanticsChange,
+        reference: "issue #89460 <https://github.com/rust-lang/rust/issues/89460>",
+    };
 }
 
 declare_lint_pass!(DerefIntoDynSupertrait => [DEREF_INTO_DYN_SUPERTRAIT]);
@@ -78,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait {
                 .find_map(|i| (i.ident.name == sym::Target).then_some(i.span))
                 .map(|label| SupertraitAsDerefTargetLabel { label });
             let span = tcx.def_span(item.owner_id.def_id);
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 DEREF_INTO_DYN_SUPERTRAIT,
                 span,
                 SupertraitAsDerefTarget {
diff --git a/compiler/rustc_lint/src/drop_forget_useless.rs b/compiler/rustc_lint/src/drop_forget_useless.rs
index 390a1620a2a..78ac7f9b235 100644
--- a/compiler/rustc_lint/src/drop_forget_useless.rs
+++ b/compiler/rustc_lint/src/drop_forget_useless.rs
@@ -149,28 +149,28 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetUseless {
             let drop_is_single_call_in_arm = is_single_call_in_arm(cx, arg, expr);
             match fn_name {
                 sym::mem_drop if arg_ty.is_ref() && !drop_is_single_call_in_arm => {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         DROPPING_REFERENCES,
                         expr.span,
                         DropRefDiag { arg_ty, label: arg.span },
                     );
                 }
                 sym::mem_forget if arg_ty.is_ref() => {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         FORGETTING_REFERENCES,
                         expr.span,
                         ForgetRefDiag { arg_ty, label: arg.span },
                     );
                 }
                 sym::mem_drop if is_copy && !drop_is_single_call_in_arm => {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         DROPPING_COPY_TYPES,
                         expr.span,
                         DropCopyDiag { arg_ty, label: arg.span },
                     );
                 }
                 sym::mem_forget if is_copy => {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         FORGETTING_COPY_TYPES,
                         expr.span,
                         ForgetCopyDiag { arg_ty, label: arg.span },
@@ -180,7 +180,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetUseless {
                     if let ty::Adt(adt, _) = arg_ty.kind()
                         && adt.is_manually_drop() =>
                 {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         UNDROPPED_MANUALLY_DROPS,
                         expr.span,
                         UndroppedManuallyDropsDiag {
@@ -214,8 +214,7 @@ fn is_single_call_in_arm<'tcx>(
     drop_expr: &'tcx Expr<'_>,
 ) -> bool {
     if arg.can_have_side_effects() {
-        let parent_node = cx.tcx.hir().find_parent(drop_expr.hir_id);
-        if let Some(Node::Arm(Arm { body, .. })) = &parent_node {
+        if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) {
             return body.hir_id == drop_expr.hir_id;
         }
     }
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
index 70af6572246..d78ec8c0dd3 100644
--- a/compiler/rustc_lint/src/early.rs
+++ b/compiler/rustc_lint/src/early.rs
@@ -17,8 +17,8 @@
 use crate::context::{EarlyContext, LintContext, LintStore};
 use crate::passes::{EarlyLintPass, EarlyLintPassObject};
 use rustc_ast::ptr::P;
-use rustc_ast::visit::{self as ast_visit, Visitor};
-use rustc_ast::{self as ast, walk_list, HasAttrs};
+use rustc_ast::visit::{self as ast_visit, walk_list, Visitor};
+use rustc_ast::{self as ast, HasAttrs};
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_feature::Features;
 use rustc_middle::ty::RegisteredTools;
@@ -45,7 +45,13 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
     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.lookup_with_diagnostics(lint_id.lint, Some(span), msg, |_| {}, diagnostic);
+            self.context.span_lint_with_diagnostics(
+                lint_id.lint,
+                Some(span),
+                msg,
+                |_| {},
+                diagnostic,
+            );
         }
     }
 
@@ -425,14 +431,13 @@ pub fn check_ast_node_inner<'a, T: EarlyLintPass>(
     // If not, that means that we somehow buffered a lint for a node id
     // that was not lint-checked (perhaps it doesn't exist?). This is a bug.
     for (id, lints) in cx.context.buffered.map {
-        for early_lint in lints {
-            sess.dcx().span_delayed_bug(
-                early_lint.span,
-                format!(
-                    "failed to process buffered lint here (dummy = {})",
-                    id == ast::DUMMY_NODE_ID
-                ),
+        if !lints.is_empty() {
+            assert!(
+                sess.dcx().has_errors().is_some(),
+                "failed to process buffered lint here (dummy = {})",
+                id == ast::DUMMY_NODE_ID
             );
+            break;
         }
     }
 }
diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
index 05fe64830d1..a67f1b62fb0 100644
--- a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
+++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
@@ -53,7 +53,7 @@ fn enforce_mem_discriminant(
 ) {
     let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0);
     if is_non_enum(ty_param) {
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             ENUM_INTRINSICS_NON_ENUMS,
             expr_span,
             EnumIntrinsicsMemDiscriminate { ty_param, note: args_span },
@@ -64,11 +64,7 @@ fn enforce_mem_discriminant(
 fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) {
     let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0);
     if is_non_enum(ty_param) {
-        cx.emit_spanned_lint(
-            ENUM_INTRINSICS_NON_ENUMS,
-            span,
-            EnumIntrinsicsMemVariant { ty_param },
-        );
+        cx.emit_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, EnumIntrinsicsMemVariant { ty_param });
     }
 }
 
diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs
index eccea35c702..ee99e824a54 100644
--- a/compiler/rustc_lint/src/errors.rs
+++ b/compiler/rustc_lint/src/errors.rs
@@ -1,11 +1,11 @@
 use crate::fluent_generated as fluent;
-use rustc_errors::{AddToDiagnostic, Diagnostic, SubdiagnosticMessage};
+use rustc_errors::{codes::*, Diag, EmissionGuarantee, SubdiagMessageOp, Subdiagnostic};
 use rustc_macros::{Diagnostic, Subdiagnostic};
 use rustc_session::lint::Level;
 use rustc_span::{Span, Symbol};
 
 #[derive(Diagnostic)]
-#[diag(lint_overruled_attribute, code = "E0453")]
+#[diag(lint_overruled_attribute, code = E0453)]
 pub struct OverruledAttribute<'a> {
     #[primary_span]
     pub span: Span,
@@ -23,15 +23,16 @@ pub enum OverruledAttributeSub {
     CommandLineSource,
 }
 
-impl AddToDiagnostic for OverruledAttributeSub {
-    fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
-    where
-        F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
-    {
+impl Subdiagnostic for OverruledAttributeSub {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
         match self {
             OverruledAttributeSub::DefaultSource { id } => {
                 diag.note(fluent::lint_default_source);
-                diag.set_arg("id", id);
+                diag.arg("id", id);
             }
             OverruledAttributeSub::NodeSource { span, reason } => {
                 diag.span_label(span, fluent::lint_node_source);
@@ -48,7 +49,7 @@ impl AddToDiagnostic for OverruledAttributeSub {
 }
 
 #[derive(Diagnostic)]
-#[diag(lint_malformed_attribute, code = "E0452")]
+#[diag(lint_malformed_attribute, code = E0452)]
 pub struct MalformedAttribute {
     #[primary_span]
     pub span: Span,
@@ -67,7 +68,7 @@ pub enum MalformedAttributeSub {
 }
 
 #[derive(Diagnostic)]
-#[diag(lint_unknown_tool_in_scoped_lint, code = "E0710")]
+#[diag(lint_unknown_tool_in_scoped_lint, code = E0710)]
 pub struct UnknownToolInScopedLint {
     #[primary_span]
     pub span: Option<Span>,
@@ -78,7 +79,7 @@ pub struct UnknownToolInScopedLint {
 }
 
 #[derive(Diagnostic)]
-#[diag(lint_builtin_ellipsis_inclusive_range_patterns, code = "E0783")]
+#[diag(lint_builtin_ellipsis_inclusive_range_patterns, code = E0783)]
 pub struct BuiltinEllipsisInclusiveRangePatterns {
     #[primary_span]
     pub span: Span,
@@ -95,13 +96,13 @@ pub struct RequestedLevel<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(lint_unsupported_group, code = "E0602")]
+#[diag(lint_unsupported_group, code = E0602)]
 pub struct UnsupportedGroup {
     pub lint_group: String,
 }
 
 #[derive(Diagnostic)]
-#[diag(lint_check_name_unknown_tool, code = "E0602")]
+#[diag(lint_check_name_unknown_tool, code = E0602)]
 pub struct CheckNameUnknownTool<'a> {
     pub tool_name: Symbol,
     #[subdiagnostic]
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
index 2b6290e8a00..40db765da53 100644
--- a/compiler/rustc_lint/src/expect.rs
+++ b/compiler/rustc_lint/src/expect.rs
@@ -29,7 +29,7 @@ fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) {
             {
                 let rationale = expectation.reason.map(|rationale| ExpectationNote { rationale });
                 let note = expectation.is_unfulfilled_lint_expectations.then_some(());
-                tcx.emit_spanned_lint(
+                tcx.emit_node_span_lint(
                     UNFULFILLED_LINT_EXPECTATIONS,
                     *hir_id,
                     expectation.emission_span,
diff --git a/compiler/rustc_lint/src/for_loops_over_fallibles.rs b/compiler/rustc_lint/src/for_loops_over_fallibles.rs
index ea922785a95..cb7feea16b5 100644
--- a/compiler/rustc_lint/src/for_loops_over_fallibles.rs
+++ b/compiler/rustc_lint/src/for_loops_over_fallibles.rs
@@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles {
             end_span: pat.span.between(arg.span),
         };
 
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             FOR_LOOPS_OVER_FALLIBLES,
             arg.span,
             ForLoopsOverFalliblesDiag { article, ty, sub, question_mark, suggestion },
diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs
index 31d9c0d33fe..b995f38f23c 100644
--- a/compiler/rustc_lint/src/foreign_modules.rs
+++ b/compiler/rustc_lint/src/foreign_modules.rs
@@ -1,5 +1,5 @@
-use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
+use rustc_data_structures::unord::{UnordMap, UnordSet};
 use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_middle::query::Providers;
@@ -72,7 +72,7 @@ struct ClashingExternDeclarations {
     /// the symbol should be reported as a clashing declaration.
     // FIXME: Technically, we could just store a &'tcx str here without issue; however, the
     // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime.
-    seen_decls: FxHashMap<Symbol, hir::OwnerId>,
+    seen_decls: UnordMap<Symbol, hir::OwnerId>,
 }
 
 /// Differentiate between whether the name for an extern decl came from the link_name attribute or
@@ -96,7 +96,7 @@ impl SymbolName {
 
 impl ClashingExternDeclarations {
     pub(crate) fn new() -> Self {
-        ClashingExternDeclarations { seen_decls: FxHashMap::default() }
+        ClashingExternDeclarations { seen_decls: Default::default() }
     }
 
     /// Insert a new foreign item into the seen set. If a symbol with the same name already exists
@@ -161,7 +161,7 @@ impl ClashingExternDeclarations {
                     sub,
                 }
             };
-            tcx.emit_spanned_lint(
+            tcx.emit_node_span_lint(
                 CLASHING_EXTERN_DECLARATIONS,
                 this_fi.hir_id(),
                 mismatch_label,
@@ -209,12 +209,12 @@ fn structurally_same_type<'tcx>(
     b: Ty<'tcx>,
     ckind: types::CItemKind,
 ) -> bool {
-    let mut seen_types = FxHashSet::default();
+    let mut seen_types = UnordSet::default();
     structurally_same_type_impl(&mut seen_types, tcx, param_env, a, b, ckind)
 }
 
 fn structurally_same_type_impl<'tcx>(
-    seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>,
+    seen_types: &mut UnordSet<(Ty<'tcx>, Ty<'tcx>)>,
     tcx: TyCtxt<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
     a: Ty<'tcx>,
diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
index 7c1af6bee1d..054cfe92a3a 100644
--- a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
+++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
@@ -74,7 +74,7 @@ impl HiddenUnicodeCodepoints {
             HiddenUnicodeCodepointsDiagSub::NoEscape { spans }
         };
 
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             TEXT_DIRECTION_CODEPOINT_IN_LITERAL,
             span,
             HiddenUnicodeCodepointsDiag { label, count, span_label: span, labels, sub },
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
index 53d99c7f7f3..153d91ce28c 100644
--- a/compiler/rustc_lint/src/internal.rs
+++ b/compiler/rustc_lint/src/internal.rs
@@ -4,14 +4,13 @@
 use crate::lints::{
     BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword,
     QueryInstability, SpanUseEqCtxtDiag, TyQualified, TykindDiag, TykindKind, UntranslatableDiag,
-    UntranslatableDiagnosticTrivial,
 };
 use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 use rustc_ast as ast;
 use rustc_hir::def::Res;
 use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
 use rustc_hir::{BinOp, BinOpKind, HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
-use rustc_middle::ty;
+use rustc_middle::ty::{self, Ty as MiddleTy};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use rustc_span::symbol::{kw, sym, Symbol};
@@ -43,7 +42,7 @@ impl LateLintPass<'_> for DefaultHashTypes {
             Some(sym::HashSet) => "FxHashSet",
             _ => return,
         };
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             DEFAULT_HASH_TYPES,
             path.span,
             DefaultHashTypesDiag { preferred, used: cx.tcx.item_name(def_id) },
@@ -91,7 +90,7 @@ impl LateLintPass<'_> for QueryStability {
         if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args) {
             let def_id = instance.def_id();
             if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     POTENTIAL_QUERY_INSTABILITY,
                     span,
                     QueryInstability { query: cx.tcx.item_name(def_id) },
@@ -136,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
         {
             let span =
                 path.span.with_hi(segment.args.map_or(segment.ident.span, |a| a.span_ext).hi());
-            cx.emit_spanned_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span });
+            cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span });
         }
     }
 
@@ -144,15 +143,14 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
         match &ty.kind {
             TyKind::Path(QPath::Resolved(_, path)) => {
                 if lint_ty_kind_usage(cx, &path.res) {
-                    let hir = cx.tcx.hir();
-                    let span = match hir.find_parent(ty.hir_id) {
-                        Some(Node::Pat(Pat {
+                    let span = match cx.tcx.parent_hir_node(ty.hir_id) {
+                        Node::Pat(Pat {
                             kind:
                                 PatKind::Path(qpath)
                                 | PatKind::TupleStruct(qpath, ..)
                                 | PatKind::Struct(qpath, ..),
                             ..
-                        })) => {
+                        }) => {
                             if let QPath::TypeRelative(qpath_ty, ..) = qpath
                                 && qpath_ty.hir_id == ty.hir_id
                             {
@@ -161,7 +159,7 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
                                 None
                             }
                         }
-                        Some(Node::Expr(Expr { kind: ExprKind::Path(qpath), .. })) => {
+                        Node::Expr(Expr { kind: ExprKind::Path(qpath), .. }) => {
                             if let QPath::TypeRelative(qpath_ty, ..) = qpath
                                 && qpath_ty.hir_id == ty.hir_id
                             {
@@ -172,7 +170,7 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
                         }
                         // Can't unify these two branches because qpath below is `&&` and above is `&`
                         // and `A | B` paths don't play well together with adjustments, apparently.
-                        Some(Node::Expr(Expr { kind: ExprKind::Struct(qpath, ..), .. })) => {
+                        Node::Expr(Expr { kind: ExprKind::Struct(qpath, ..), .. }) => {
                             if let QPath::TypeRelative(qpath_ty, ..) = qpath
                                 && qpath_ty.hir_id == ty.hir_id
                             {
@@ -186,19 +184,19 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
 
                     match span {
                         Some(span) => {
-                            cx.emit_spanned_lint(
+                            cx.emit_span_lint(
                                 USAGE_OF_TY_TYKIND,
                                 path.span,
                                 TykindKind { suggestion: span },
                             );
                         }
-                        None => cx.emit_spanned_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag),
+                        None => cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag),
                     }
                 } else if !ty.span.from_expansion()
                     && path.segments.len() > 1
                     && let Some(ty) = is_ty_or_ty_ctxt(cx, path)
                 {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         USAGE_OF_QUALIFIED_TY,
                         path.span,
                         TyQualified { ty, suggestion: path.span },
@@ -285,7 +283,7 @@ impl EarlyLintPass for LintPassImpl {
                         && call_site.ctxt().outer_expn_data().kind
                             != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
                     {
-                        cx.emit_spanned_lint(
+                        cx.emit_span_lint(
                             LINT_PASS_IMPL_WITHOUT_MACRO,
                             lint_pass.path.span,
                             LintPassByHand,
@@ -327,7 +325,7 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
                         if is_doc_keyword(keyword) {
                             return;
                         }
-                        cx.emit_spanned_lint(
+                        cx.emit_span_lint(
                             EXISTING_DOC_KEYWORD,
                             attr.span,
                             NonExistentDocKeyword { keyword },
@@ -340,161 +338,151 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
 }
 
 declare_tool_lint! {
-    /// The `untranslatable_diagnostic` lint detects diagnostics created
-    /// without using translatable Fluent strings.
+    /// The `untranslatable_diagnostic` lint detects messages passed to functions with `impl
+    /// Into<{D,Subd}iagMessage` parameters without using translatable Fluent strings.
     ///
-    /// More details on translatable diagnostics can be found [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
+    /// More details on translatable diagnostics can be found
+    /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
     pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
-    Allow,
+    Deny,
     "prevent creation of diagnostics which cannot be translated",
     report_in_external_macro: true
 }
 
 declare_tool_lint! {
-    /// The `diagnostic_outside_of_impl` lint detects diagnostics created manually,
-    /// and inside an `IntoDiagnostic`/`AddToDiagnostic` implementation,
-    /// or a `#[derive(Diagnostic)]`/`#[derive(Subdiagnostic)]` expansion.
+    /// The `diagnostic_outside_of_impl` lint detects calls to functions annotated with
+    /// `#[rustc_lint_diagnostics]` that are outside an `Diagnostic`, `Subdiagnostic`, or
+    /// `LintDiagnostic` impl (either hand-written or derived).
     ///
-    /// More details on diagnostics implementations can be found [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
+    /// More details on diagnostics implementations can be found
+    /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
     pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
-    Allow,
-    "prevent creation of diagnostics outside of `IntoDiagnostic`/`AddToDiagnostic` impls",
-    report_in_external_macro: true
-}
-
-declare_tool_lint! {
-    /// The `untranslatable_diagnostic_trivial` lint detects diagnostics created using only static strings.
-    pub rustc::UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL,
     Deny,
-    "prevent creation of diagnostics which cannot be translated, which use only static strings",
+    "prevent diagnostic creation outside of `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls",
     report_in_external_macro: true
 }
 
-declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL, UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL ]);
+declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL]);
 
 impl LateLintPass<'_> for Diagnostics {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
-        let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return };
-        debug!(?span, ?def_id, ?args);
-        let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args)
+        // Only check function calls and method calls.
+        let (span, def_id, fn_gen_args, call_tys) = match expr.kind {
+            ExprKind::Call(callee, args) => {
+                match cx.typeck_results().node_type(callee.hir_id).kind() {
+                    &ty::FnDef(def_id, fn_gen_args) => {
+                        let call_tys: Vec<_> =
+                            args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect();
+                        (callee.span, def_id, fn_gen_args, call_tys)
+                    }
+                    _ => return, // occurs for fns passed as args
+                }
+            }
+            ExprKind::MethodCall(_segment, _recv, args, _span) => {
+                let Some((span, def_id, fn_gen_args)) = typeck_results_of_method_fn(cx, expr)
+                else {
+                    return;
+                };
+                let mut call_tys: Vec<_> =
+                    args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect();
+                call_tys.insert(0, cx.tcx.types.self_param); // dummy inserted for `self`
+                (span, def_id, fn_gen_args, call_tys)
+            }
+            _ => return,
+        };
+
+        // Is the callee marked with `#[rustc_lint_diagnostics]`?
+        let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, fn_gen_args)
             .ok()
             .flatten()
             .is_some_and(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics));
-        if !has_attr {
-            return;
-        }
 
-        let mut found_parent_with_attr = false;
-        let mut found_impl = false;
-        for (hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
-            if let Some(owner_did) = hir_id.as_owner() {
-                found_parent_with_attr = found_parent_with_attr
-                    || cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics);
+        // Closure: is the type `{D,Subd}iagMessage`?
+        let is_diag_message = |ty: MiddleTy<'_>| {
+            if let Some(adt_def) = ty.ty_adt_def()
+                && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
+                && matches!(name, sym::DiagMessage | sym::SubdiagMessage)
+            {
+                true
+            } else {
+                false
             }
+        };
 
-            debug!(?parent);
-            if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
-                && let Impl { of_trait: Some(of_trait), .. } = impl_
-                && let Some(def_id) = of_trait.trait_def_id()
-                && let Some(name) = cx.tcx.get_diagnostic_name(def_id)
-                && matches!(name, sym::IntoDiagnostic | sym::AddToDiagnostic | sym::DecorateLint)
-            {
-                found_impl = true;
-                break;
+        // Does the callee have one or more `impl Into<{D,Subd}iagMessage>` parameters?
+        let mut impl_into_diagnostic_message_params = vec![];
+        let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
+        let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates;
+        for (i, &param_ty) in fn_sig.inputs().iter().enumerate() {
+            if let ty::Param(p) = param_ty.kind() {
+                // It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`.
+                for pred in predicates.iter() {
+                    if let Some(trait_pred) = pred.as_trait_clause()
+                        && let trait_ref = trait_pred.skip_binder().trait_ref
+                        && trait_ref.self_ty() == param_ty // correct predicate for the param?
+                        && cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id)
+                        && let ty1 = trait_ref.args.type_at(1)
+                        && is_diag_message(ty1)
+                    {
+                        impl_into_diagnostic_message_params.push((i, p.name));
+                    }
+                }
             }
         }
-        debug!(?found_impl);
-        if !found_parent_with_attr && !found_impl {
-            cx.emit_spanned_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
+
+        // Is the callee interesting?
+        if !has_attr && impl_into_diagnostic_message_params.is_empty() {
+            return;
         }
 
-        let mut found_diagnostic_message = false;
-        for ty in args.types() {
-            debug!(?ty);
-            if let Some(adt_def) = ty.ty_adt_def()
-                && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
-                && matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage)
+        // Is the parent method marked with `#[rustc_lint_diagnostics]`?
+        let mut parent_has_attr = false;
+        for (hir_id, _parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
+            if let Some(owner_did) = hir_id.as_owner()
+                && cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics)
             {
-                found_diagnostic_message = true;
+                parent_has_attr = true;
                 break;
             }
         }
-        debug!(?found_diagnostic_message);
-        if !found_parent_with_attr && !found_diagnostic_message {
-            cx.emit_spanned_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag);
-        }
-    }
-}
 
-impl EarlyLintPass for Diagnostics {
-    #[allow(unused_must_use)]
-    fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
-        // Looking for a straight chain of method calls from 'struct_span_err' to 'emit'.
-        let ast::StmtKind::Semi(expr) = &stmt.kind else {
-            return;
-        };
-        let ast::ExprKind::MethodCall(meth) = &expr.kind else {
-            return;
-        };
-        if meth.seg.ident.name != sym::emit || !meth.args.is_empty() {
-            return;
-        }
-        let mut segments = vec![];
-        let mut cur = &meth.receiver;
-        let fake = &[].into();
-        loop {
-            match &cur.kind {
-                ast::ExprKind::Call(func, args) => {
-                    if let ast::ExprKind::Path(_, path) = &func.kind {
-                        segments.push((path.segments.last().unwrap().ident.name, args))
-                    }
-                    break;
-                }
-                ast::ExprKind::MethodCall(method) => {
-                    segments.push((method.seg.ident.name, &method.args));
-                    cur = &method.receiver;
-                }
-                ast::ExprKind::MacCall(mac) => {
-                    segments.push((mac.path.segments.last().unwrap().ident.name, fake));
-                    break;
-                }
-                _ => {
+        // Calls to `#[rustc_lint_diagnostics]`-marked functions should only occur:
+        // - inside an impl of `Diagnostic`, `Subdiagnostic`, or `LintDiagnostic`, or
+        // - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`.
+        //
+        // Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint.
+        if has_attr && !parent_has_attr {
+            let mut is_inside_appropriate_impl = false;
+            for (_hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
+                debug!(?parent);
+                if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
+                    && let Impl { of_trait: Some(of_trait), .. } = impl_
+                    && let Some(def_id) = of_trait.trait_def_id()
+                    && let Some(name) = cx.tcx.get_diagnostic_name(def_id)
+                    && matches!(name, sym::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic)
+                {
+                    is_inside_appropriate_impl = true;
                     break;
                 }
             }
+            debug!(?is_inside_appropriate_impl);
+            if !is_inside_appropriate_impl {
+                cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
+            }
         }
-        segments.reverse();
-        if segments.is_empty() {
-            return;
-        }
-        if segments[0].0.as_str() != "struct_span_err" {
-            return;
-        }
-        if !segments.iter().all(|(name, args)| {
-            let arg = match name.as_str() {
-                "struct_span_err" | "span_note" | "span_label" | "span_help" if args.len() == 2 => {
-                    &args[1]
-                }
-                "note" | "help" if args.len() == 1 => &args[0],
-                _ => {
-                    return false;
-                }
-            };
-            if let ast::ExprKind::Lit(lit) = arg.kind
-                && let ast::token::LitKind::Str = lit.kind
-            {
-                true
-            } else {
-                false
+
+        // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
+        // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
+        // `UNTRANSLATABLE_DIAGNOSTIC` lint.
+        for (param_i, param_i_p_name) in impl_into_diagnostic_message_params {
+            // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
+            let arg_ty = call_tys[param_i];
+            let is_translatable = is_diag_message(arg_ty)
+                || matches!(arg_ty.kind(), ty::Param(p) if p.name == param_i_p_name);
+            if !is_translatable {
+                cx.emit_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag);
             }
-        }) {
-            return;
         }
-        cx.emit_spanned_lint(
-            UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL,
-            stmt.span,
-            UntranslatableDiagnosticTrivial,
-        );
     }
 }
 
@@ -507,7 +495,7 @@ declare_tool_lint! {
     report_in_external_macro: true
 }
 
-declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]);
+declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]);
 
 impl LateLintPass<'_> for BadOptAccess {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
@@ -528,7 +516,7 @@ impl LateLintPass<'_> for BadOptAccess {
                 && let Some(lit) = item.lit()
                 && let ast::LitKind::Str(val, _) = lit.kind
             {
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     BAD_OPT_ACCESS,
                     expr.span,
                     BadOptAccessDiag { msg: val.as_str() },
@@ -549,9 +537,11 @@ declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]);
 
 impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
-        if let ExprKind::Binary(BinOp { node: BinOpKind::Eq, .. }, lhs, rhs) = expr.kind {
+        if let ExprKind::Binary(BinOp { node: BinOpKind::Eq | BinOpKind::Ne, .. }, lhs, rhs) =
+            expr.kind
+        {
             if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) {
-                cx.emit_spanned_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag);
+                cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag);
             }
         }
     }
diff --git a/compiler/rustc_lint/src/invalid_from_utf8.rs b/compiler/rustc_lint/src/invalid_from_utf8.rs
index 0b91b77a9f2..081e3e87530 100644
--- a/compiler/rustc_lint/src/invalid_from_utf8.rs
+++ b/compiler/rustc_lint/src/invalid_from_utf8.rs
@@ -78,7 +78,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 {
                 let valid_up_to = utf8_error.valid_up_to();
                 let is_unchecked_variant = diag_item.as_str().contains("unchecked");
 
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     if is_unchecked_variant {
                         INVALID_FROM_UTF8_UNCHECKED
                     } else {
@@ -111,7 +111,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 {
                         .map(|e| match &e.kind {
                             ExprKind::Lit(Spanned { node: lit, .. }) => match lit {
                                 LitKind::Byte(b) => Some(*b),
-                                LitKind::Int(b, _) => Some(*b as u8),
+                                LitKind::Int(b, _) => Some(b.get() as u8),
                                 _ => None,
                             },
                             _ => None,
diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs
index caa01556591..506716e39a1 100644
--- a/compiler/rustc_lint/src/late.rs
+++ b/compiler/rustc_lint/src/late.rs
@@ -356,7 +356,7 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>(
         cached_typeck_results: Cell::new(None),
         param_env: ty::ParamEnv::empty(),
         effective_visibilities: tcx.effective_visibilities(()),
-        last_node_with_lint_attrs: tcx.local_def_id_to_hir_id(module_def_id.into()),
+        last_node_with_lint_attrs: tcx.local_def_id_to_hir_id(module_def_id),
         generics: None,
         only_module: true,
     };
@@ -364,14 +364,11 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>(
     // Note: `passes` is often empty. In that case, it's faster to run
     // `builtin_lints` directly rather than bundling it up into the
     // `RuntimeCombinedLateLintPass`.
-    let mut passes: Vec<_> = unerased_lint_store(tcx.sess)
-        .late_module_passes
-        .iter()
-        .map(|mk_pass| (mk_pass)(tcx))
-        .collect();
-    if passes.is_empty() {
+    let late_module_passes = &unerased_lint_store(tcx.sess).late_module_passes;
+    if late_module_passes.is_empty() {
         late_lint_mod_inner(tcx, module_def_id, context, builtin_lints);
     } else {
+        let mut passes: Vec<_> = late_module_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect();
         passes.push(Box::new(builtin_lints));
         let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] };
         late_lint_mod_inner(tcx, module_def_id, context, pass);
diff --git a/compiler/rustc_lint/src/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs
index 3eefd1b0e08..de642f373b5 100644
--- a/compiler/rustc_lint/src/let_underscore.rs
+++ b/compiler/rustc_lint/src/let_underscore.rs
@@ -5,7 +5,7 @@ use crate::{
 use rustc_errors::MultiSpan;
 use rustc_hir as hir;
 use rustc_middle::ty;
-use rustc_span::Symbol;
+use rustc_span::{sym, Symbol};
 
 declare_lint! {
     /// The `let_underscore_drop` lint checks for statements which don't bind
@@ -104,47 +104,68 @@ const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
 ];
 
 impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
-        if !matches!(local.pat.kind, hir::PatKind::Wild) {
+        if matches!(local.source, rustc_hir::LocalSource::AsyncFn) {
             return;
         }
-        if let Some(init) = local.init {
-            let init_ty = cx.typeck_results().expr_ty(init);
+
+        let mut top_level = true;
+
+        // We recursively walk through all patterns, so that we can catch cases where the lock is nested in a pattern.
+        // For the basic `let_underscore_drop` lint, we only look at the top level, since there are many legitimate reasons
+        // to bind a sub-pattern to an `_`, if we're only interested in the rest.
+        // But with locks, we prefer having the chance of "false positives" over missing cases, since the effects can be
+        // quite catastrophic.
+        local.pat.walk_always(|pat| {
+            let is_top_level = top_level;
+            top_level = false;
+
+            if !matches!(pat.kind, hir::PatKind::Wild) {
+                return;
+            }
+
+            let ty = cx.typeck_results().pat_ty(pat);
+
             // If the type has a trivial Drop implementation, then it doesn't
             // matter that we drop the value immediately.
-            if !init_ty.needs_drop(cx.tcx, cx.param_env) {
+            if !ty.needs_drop(cx.tcx, cx.param_env) {
                 return;
             }
-            let is_sync_lock = match init_ty.kind() {
+            // Lint for patterns like `mutex.lock()`, which returns `Result<MutexGuard, _>` as well.
+            let potential_lock_type = match ty.kind() {
+                ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => {
+                    args.type_at(0)
+                }
+                _ => ty,
+            };
+            let is_sync_lock = match potential_lock_type.kind() {
                 ty::Adt(adt, _) => SYNC_GUARD_SYMBOLS
                     .iter()
                     .any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())),
                 _ => false,
             };
 
+            let can_use_init = is_top_level.then_some(local.init).flatten();
+
             let sub = NonBindingLetSub {
-                suggestion: local.pat.span,
-                multi_suggestion_start: local.span.until(init.span),
-                multi_suggestion_end: init.span.shrink_to_hi(),
+                suggestion: pat.span,
+                // We can't suggest `drop()` when we're on the top level.
+                drop_fn_start_end: can_use_init
+                    .map(|init| (local.span.until(init.span), init.span.shrink_to_hi())),
+                is_assign_desugar: matches!(local.source, rustc_hir::LocalSource::AssignDesugar(_)),
             };
             if is_sync_lock {
-                let mut span = MultiSpan::from_spans(vec![local.pat.span, init.span]);
+                let mut span = MultiSpan::from_span(pat.span);
                 span.push_span_label(
-                    local.pat.span,
+                    pat.span,
                     "this lock is not assigned to a binding and is immediately dropped".to_string(),
                 );
-                span.push_span_label(
-                    init.span,
-                    "this binding will immediately drop the value assigned to it".to_string(),
-                );
-                cx.emit_spanned_lint(LET_UNDERSCORE_LOCK, span, NonBindingLet::SyncLock { sub });
-            } else {
-                cx.emit_spanned_lint(
-                    LET_UNDERSCORE_DROP,
-                    local.span,
-                    NonBindingLet::DropType { sub },
-                );
+                cx.emit_span_lint(LET_UNDERSCORE_LOCK, span, NonBindingLet::SyncLock { sub });
+            // Only emit let_underscore_drop for top-level `_` patterns.
+            } else if can_use_init.is_some() {
+                cx.emit_span_lint(LET_UNDERSCORE_DROP, local.span, NonBindingLet::DropType { sub });
             }
-        }
+        });
     }
 }
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 17c56f1ca58..95f312a31b3 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -15,8 +15,8 @@ use crate::{
 };
 use rustc_ast as ast;
 use rustc_ast_pretty::pprust;
-use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{DecorateLint, DiagnosticBuilder, DiagnosticMessage, MultiSpan};
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_errors::{Diag, DiagMessage, LintDiagnostic, MultiSpan};
 use rustc_feature::{Features, GateIssue};
 use rustc_hir as hir;
 use rustc_hir::intravisit::{self, Visitor};
@@ -24,7 +24,7 @@ use rustc_hir::HirId;
 use rustc_index::IndexVec;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::lint::{
-    reveal_actual_level, struct_lint_level, LevelAndSource, LintExpectation, LintLevelSource,
+    lint_level, reveal_actual_level, LevelAndSource, LintExpectation, LintLevelSource,
     ShallowLintLevelMap,
 };
 use rustc_middle::query::Providers;
@@ -73,7 +73,7 @@ rustc_index::newtype_index! {
 struct LintSet {
     // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
     // flag.
-    specs: FxHashMap<LintId, LevelAndSource>,
+    specs: FxIndexMap<LintId, LevelAndSource>,
     parent: LintStackIndex,
 }
 
@@ -86,7 +86,7 @@ impl LintLevelSets {
         &self,
         lint: &'static Lint,
         idx: LintStackIndex,
-        aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+        aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
         sess: &Session,
     ) -> LevelAndSource {
         let lint = LintId::of(lint);
@@ -101,13 +101,14 @@ impl LintLevelSets {
         &self,
         id: LintId,
         mut idx: LintStackIndex,
-        aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+        aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
     ) -> (Option<Level>, LintLevelSource) {
-        if let Some(specs) = aux {
-            if let Some(&(level, src)) = specs.get(&id) {
-                return (Some(level), src);
-            }
+        if let Some(specs) = aux
+            && let Some(&(level, src)) = specs.get(&id)
+        {
+            return (Some(level), src);
         }
+
         loop {
             let LintSet { ref specs, parent } = self.list[idx];
             if let Some(&(level, src)) = specs.get(&id) {
@@ -132,10 +133,10 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp
             cur: hir::CRATE_HIR_ID,
             specs: ShallowLintLevelMap::default(),
             expectations: Vec::new(),
-            unstable_to_stable_ids: FxHashMap::default(),
-            empty: FxHashMap::default(),
+            unstable_to_stable_ids: FxIndexMap::default(),
+            empty: FxIndexMap::default(),
         },
-        warn_about_weird_lints: false,
+        lint_added_lints: false,
         store,
         registered_tools: tcx.registered_tools(()),
     };
@@ -161,10 +162,10 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe
             tcx,
             cur: owner.into(),
             specs: ShallowLintLevelMap::default(),
-            empty: FxHashMap::default(),
+            empty: FxIndexMap::default(),
             attrs,
         },
-        warn_about_weird_lints: false,
+        lint_added_lints: false,
         store,
         registered_tools: tcx.registered_tools(()),
     };
@@ -177,11 +178,11 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe
         // There is only something to do if there are attributes at all.
         [] => {}
         // Most of the time, there is only one attribute. Avoid fetching HIR in that case.
-        [(local_id, _)] => levels.add_id(HirId { owner, local_id: *local_id }),
+        &[(local_id, _)] => levels.add_id(HirId { owner, local_id }),
         // Otherwise, we need to visit the attributes in source code order, so we fetch HIR and do
         // a standard visit.
         // FIXME(#102522) Just iterate on attrs once that iteration order matches HIR's.
-        _ => match tcx.hir().owner(owner) {
+        _ => match tcx.hir_owner_node(owner) {
             hir::OwnerNode::Item(item) => levels.visit_item(item),
             hir::OwnerNode::ForeignItem(item) => levels.visit_foreign_item(item),
             hir::OwnerNode::TraitItem(item) => levels.visit_trait_item(item),
@@ -190,6 +191,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe
                 levels.add_id(hir::CRATE_HIR_ID);
                 levels.visit_mod(mod_, mod_.spans.inner_span, hir::CRATE_HIR_ID)
             }
+            hir::OwnerNode::AssocOpaqueTy(..) => unreachable!(),
         },
     }
 
@@ -209,14 +211,14 @@ pub struct TopDown {
 }
 
 pub trait LintLevelsProvider {
-    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource>;
+    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource>;
     fn insert(&mut self, id: LintId, lvl: LevelAndSource);
     fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource;
     fn push_expectation(&mut self, _id: LintExpectationId, _expectation: LintExpectation) {}
 }
 
 impl LintLevelsProvider for TopDown {
-    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> {
         &self.sets.list[self.cur].specs
     }
 
@@ -234,12 +236,12 @@ struct LintLevelQueryMap<'tcx> {
     cur: HirId,
     specs: ShallowLintLevelMap,
     /// Empty hash map to simplify code.
-    empty: FxHashMap<LintId, LevelAndSource>,
+    empty: FxIndexMap<LintId, LevelAndSource>,
     attrs: &'tcx hir::AttributeMap<'tcx>,
 }
 
 impl LintLevelsProvider for LintLevelQueryMap<'_> {
-    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> {
         self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty)
     }
     fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
@@ -257,13 +259,13 @@ struct QueryMapExpectationsWrapper<'tcx> {
     /// Level map for `cur`.
     specs: ShallowLintLevelMap,
     expectations: Vec<(LintExpectationId, LintExpectation)>,
-    unstable_to_stable_ids: FxHashMap<LintExpectationId, LintExpectationId>,
+    unstable_to_stable_ids: FxIndexMap<LintExpectationId, LintExpectationId>,
     /// Empty hash map to simplify code.
-    empty: FxHashMap<LintId, LevelAndSource>,
+    empty: FxIndexMap<LintId, LevelAndSource>,
 }
 
 impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> {
-    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> {
         self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty)
     }
     fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
@@ -451,7 +453,7 @@ pub struct LintLevelsBuilder<'s, P> {
     sess: &'s Session,
     features: &'s Features,
     provider: P,
-    warn_about_weird_lints: bool,
+    lint_added_lints: bool,
     store: &'s LintStore,
     registered_tools: &'s RegisteredTools,
 }
@@ -464,7 +466,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> {
     pub(crate) fn new(
         sess: &'s Session,
         features: &'s Features,
-        warn_about_weird_lints: bool,
+        lint_added_lints: bool,
         store: &'s LintStore,
         registered_tools: &'s RegisteredTools,
     ) -> Self {
@@ -472,7 +474,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> {
             sess,
             features,
             provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE },
-            warn_about_weird_lints,
+            lint_added_lints,
             store,
             registered_tools,
         };
@@ -486,7 +488,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> {
             .provider
             .sets
             .list
-            .push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE });
+            .push(LintSet { specs: FxIndexMap::default(), parent: COMMAND_LINE });
         self.add_command_line();
     }
 
@@ -512,7 +514,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> {
     ) -> BuilderPush {
         let prev = self.provider.cur;
         self.provider.cur =
-            self.provider.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev });
+            self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev });
 
         self.add(attrs, is_crate_node, source_hir_id);
 
@@ -547,7 +549,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
         self.features
     }
 
-    fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> {
         self.provider.current_specs()
     }
 
@@ -582,8 +584,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                 }
                 CheckLintNameResult::NoLint(suggestion) => {
                     let name = lint_name.clone();
-                    let suggestion =
-                        suggestion.map(|replace| UnknownLintSuggestion::WithoutSpan { replace });
+                    let suggestion = suggestion.map(|(replace, from_rustc)| {
+                        UnknownLintSuggestion::WithoutSpan { replace, from_rustc }
+                    });
                     let requested_level = RequestedLevel { level, lint_name };
                     let lint = UnknownLintFromCommandLine { name, suggestion, requested_level };
                     self.emit_lint(UNKNOWN_LINTS, lint);
@@ -642,63 +645,61 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
         //
         // This means that this only errors if we're truly lowering the lint
         // level from forbid.
-        if level != Level::Forbid {
-            if let Level::Forbid = old_level {
-                // Backwards compatibility check:
-                //
-                // We used to not consider `forbid(lint_group)`
-                // as preventing `allow(lint)` for some lint `lint` in
-                // `lint_group`. For now, issue a future-compatibility
-                // warning for this case.
-                let id_name = id.lint.name_lower();
-                let fcw_warning = match old_src {
-                    LintLevelSource::Default => false,
-                    LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
-                    LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
-                };
-                debug!(
-                    "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
-                    fcw_warning,
-                    self.current_specs(),
-                    old_src,
-                    id_name
-                );
-                let sub = match old_src {
-                    LintLevelSource::Default => {
-                        OverruledAttributeSub::DefaultSource { id: id.to_string() }
-                    }
-                    LintLevelSource::Node { span, reason, .. } => {
-                        OverruledAttributeSub::NodeSource { span, reason }
-                    }
-                    LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource,
-                };
-                if !fcw_warning {
-                    self.sess.dcx().emit_err(OverruledAttribute {
-                        span: src.span(),
+        if self.lint_added_lints && level != Level::Forbid && old_level == Level::Forbid {
+            // Backwards compatibility check:
+            //
+            // We used to not consider `forbid(lint_group)`
+            // as preventing `allow(lint)` for some lint `lint` in
+            // `lint_group`. For now, issue a future-compatibility
+            // warning for this case.
+            let id_name = id.lint.name_lower();
+            let fcw_warning = match old_src {
+                LintLevelSource::Default => false,
+                LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
+                LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
+            };
+            debug!(
+                "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
+                fcw_warning,
+                self.current_specs(),
+                old_src,
+                id_name
+            );
+            let sub = match old_src {
+                LintLevelSource::Default => {
+                    OverruledAttributeSub::DefaultSource { id: id.to_string() }
+                }
+                LintLevelSource::Node { span, reason, .. } => {
+                    OverruledAttributeSub::NodeSource { span, reason }
+                }
+                LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource,
+            };
+            if !fcw_warning {
+                self.sess.dcx().emit_err(OverruledAttribute {
+                    span: src.span(),
+                    overruled: src.span(),
+                    lint_level: level.as_str(),
+                    lint_source: src.name(),
+                    sub,
+                });
+            } else {
+                self.emit_span_lint(
+                    FORBIDDEN_LINT_GROUPS,
+                    src.span().into(),
+                    OverruledAttributeLint {
                         overruled: src.span(),
                         lint_level: level.as_str(),
                         lint_source: src.name(),
                         sub,
-                    });
-                } else {
-                    self.emit_spanned_lint(
-                        FORBIDDEN_LINT_GROUPS,
-                        src.span().into(),
-                        OverruledAttributeLint {
-                            overruled: src.span(),
-                            lint_level: level.as_str(),
-                            lint_source: src.name(),
-                            sub,
-                        },
-                    );
-                }
+                    },
+                );
+            }
 
-                // Retain the forbid lint level, unless we are
-                // issuing a FCW. In the FCW case, we want to
-                // respect the new setting.
-                if !fcw_warning {
-                    return;
-                }
+            // Retain the forbid lint level, unless we are
+            // issuing a FCW. In the FCW case, we want to
+            // respect the new setting.
+            if !fcw_warning {
+                return;
             }
         }
 
@@ -723,6 +724,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
         };
     }
 
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn add(&mut self, attrs: &[ast::Attribute], is_crate_node: bool, source_hir_id: Option<HirId>) {
         let sess = self.sess;
         for (attr_index, attr) in attrs.iter().enumerate() {
@@ -768,15 +770,15 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
 
             let Some(mut metas) = attr.meta_item_list() else { continue };
 
-            if metas.is_empty() {
+            // Check whether `metas` is empty, and get its last element.
+            let Some(tail_li) = metas.last() else {
                 // This emits the unused_attributes lint for `#[level()]`
                 continue;
-            }
+            };
 
             // Before processing the lint names, look for a reason (RFC 2383)
             // at the end.
             let mut reason = None;
-            let tail_li = &metas[metas.len() - 1];
             if let Some(item) = tail_li.meta_item() {
                 match item.kind {
                     ast::MetaItemKind::Word => {} // actual lint names handled later
@@ -785,7 +787,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                             if let ast::LitKind::Str(rationale, _) = name_value.kind {
                                 if !self.features.lint_reasons {
                                     feature_err(
-                                        &self.sess.parse_sess,
+                                        &self.sess,
                                         sym::lint_reasons,
                                         item.span,
                                         "lint reasons are experimental",
@@ -832,21 +834,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                 let meta_item = match li {
                     ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item,
                     _ => {
-                        if let Some(item) = li.meta_item() {
-                            if let ast::MetaItemKind::NameValue(_) = item.kind {
-                                if item.path == sym::reason {
-                                    sess.dcx().emit_err(MalformedAttribute {
-                                        span: sp,
-                                        sub: MalformedAttributeSub::ReasonMustComeLast(sp),
-                                    });
-                                    continue;
-                                }
-                            }
-                        }
-                        sess.dcx().emit_err(MalformedAttribute {
-                            span: sp,
-                            sub: MalformedAttributeSub::BadAttributeArgument(sp),
-                        });
+                        let sub = if let Some(item) = li.meta_item()
+                            && let ast::MetaItemKind::NameValue(_) = item.kind
+                            && item.path == sym::reason
+                        {
+                            MalformedAttributeSub::ReasonMustComeLast(sp)
+                        } else {
+                            MalformedAttributeSub::BadAttributeArgument(sp)
+                        };
+
+                        sess.dcx().emit_err(MalformedAttribute { span: sp, sub });
                         continue;
                     }
                 };
@@ -924,7 +921,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                             }
                             Err((Some(ids), ref new_lint_name)) => {
                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
-                                self.emit_spanned_lint(
+                                self.emit_span_lint(
                                     lint,
                                     sp.into(),
                                     DeprecatedLintName {
@@ -968,33 +965,29 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                         continue;
                     }
 
-                    _ if !self.warn_about_weird_lints => {}
+                    _ if !self.lint_added_lints => {}
 
                     CheckLintNameResult::Renamed(ref replace) => {
                         let suggestion =
                             RenamedLintSuggestion::WithSpan { suggestion: sp, replace };
                         let name = tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
                         let lint = RenamedLint { name: name.as_str(), suggestion };
-                        self.emit_spanned_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint);
+                        self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint);
                     }
 
                     CheckLintNameResult::Removed(ref reason) => {
                         let name = tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
                         let lint = RemovedLint { name: name.as_str(), reason };
-                        self.emit_spanned_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint);
+                        self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint);
                     }
 
                     CheckLintNameResult::NoLint(suggestion) => {
-                        let name = if let Some(tool_ident) = tool_ident {
-                            format!("{}::{}", tool_ident.name, name)
-                        } else {
-                            name.to_string()
-                        };
-                        let suggestion = suggestion.map(|replace| {
-                            UnknownLintSuggestion::WithSpan { suggestion: sp, replace }
+                        let name = tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
+                        let suggestion = suggestion.map(|(replace, from_rustc)| {
+                            UnknownLintSuggestion::WithSpan { suggestion: sp, replace, from_rustc }
                         });
                         let lint = UnknownLint { name, suggestion };
-                        self.emit_spanned_lint(UNKNOWN_LINTS, sp.into(), lint);
+                        self.emit_span_lint(UNKNOWN_LINTS, sp.into(), lint);
                     }
                 }
                 // If this lint was renamed, apply the new lint instead of ignoring the attribute.
@@ -1003,33 +996,30 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                 if let CheckLintNameResult::Renamed(new_name) = lint_result {
                     // Ignore any errors or warnings that happen because the new name is inaccurate
                     // NOTE: `new_name` already includes the tool name, so we don't have to add it again.
-                    if let CheckLintNameResult::Ok(ids) =
+                    let CheckLintNameResult::Ok(ids) =
                         self.store.check_lint_name(&new_name, None, self.registered_tools)
-                    {
-                        let src = LintLevelSource::Node {
-                            name: Symbol::intern(&new_name),
-                            span: sp,
-                            reason,
-                        };
-                        for &id in ids {
-                            if self.check_gated_lint(id, attr.span, false) {
-                                self.insert_spec(id, (level, src));
-                            }
-                        }
-                        if let Level::Expect(expect_id) = level {
-                            self.provider.push_expectation(
-                                expect_id,
-                                LintExpectation::new(reason, sp, false, tool_name),
-                            );
-                        }
-                    } else {
+                    else {
                         panic!("renamed lint does not exist: {new_name}");
+                    };
+
+                    let src =
+                        LintLevelSource::Node { name: Symbol::intern(&new_name), span: sp, reason };
+                    for &id in ids {
+                        if self.check_gated_lint(id, attr.span, false) {
+                            self.insert_spec(id, (level, src));
+                        }
+                    }
+                    if let Level::Expect(expect_id) = level {
+                        self.provider.push_expectation(
+                            expect_id,
+                            LintExpectation::new(reason, sp, false, tool_name),
+                        );
                     }
                 }
             }
         }
 
-        if !is_crate_node {
+        if self.lint_added_lints && !is_crate_node {
             for (id, &(level, ref src)) in self.current_specs().iter() {
                 if !id.lint.crate_level_only {
                     continue;
@@ -1040,7 +1030,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                     continue;
                 };
 
-                self.emit_spanned_lint(
+                self.emit_span_lint(
                     UNUSED_ATTRIBUTES,
                     lint_attr_span.into(),
                     IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name },
@@ -1054,37 +1044,46 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
     /// Checks if the lint is gated on a feature that is not enabled.
     ///
     /// Returns `true` if the lint's feature is enabled.
-    // FIXME only emit this once for each attribute, instead of repeating it 4 times for
-    // pre-expansion lints, post-expansion lints, `shallow_lint_levels_on` and `lint_expectations`.
     #[track_caller]
     fn check_gated_lint(&self, lint_id: LintId, span: Span, lint_from_cli: bool) -> bool {
-        if let Some(feature) = lint_id.lint.feature_gate {
-            if !self.features.active(feature) {
-                let lint = builtin::UNKNOWN_LINTS;
-                let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
-                struct_lint_level(
-                    self.sess,
-                    lint,
-                    level,
-                    src,
-                    Some(span.into()),
-                    fluent::lint_unknown_gated_lint,
-                    |lint| {
-                        lint.set_arg("name", lint_id.lint.name_lower());
-                        lint.note(fluent::lint_note);
-                        rustc_session::parse::add_feature_diagnostics_for_issue(
-                            lint,
-                            &self.sess.parse_sess,
-                            feature,
-                            GateIssue::Language,
-                            lint_from_cli,
-                        );
-                    },
-                );
-                return false;
-            }
+        let feature = if let Some(feature) = lint_id.lint.feature_gate
+            && !self.features.active(feature)
+        {
+            // Lint is behind a feature that is not enabled; eventually return false.
+            feature
+        } else {
+            // Lint is ungated or its feature is enabled; exit early.
+            return true;
+        };
+
+        if self.lint_added_lints {
+            let lint = builtin::UNKNOWN_LINTS;
+            let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
+            // FIXME: make this translatable
+            #[allow(rustc::diagnostic_outside_of_impl)]
+            #[allow(rustc::untranslatable_diagnostic)]
+            lint_level(
+                self.sess,
+                lint,
+                level,
+                src,
+                Some(span.into()),
+                fluent::lint_unknown_gated_lint,
+                |lint| {
+                    lint.arg("name", lint_id.lint.name_lower());
+                    lint.note(fluent::lint_note);
+                    rustc_session::parse::add_feature_diagnostics_for_issue(
+                        lint,
+                        &self.sess,
+                        feature,
+                        GateIssue::Language,
+                        lint_from_cli,
+                    );
+                },
+            );
         }
-        true
+
+        false
     }
 
     /// Find the lint level for a lint.
@@ -1095,37 +1094,37 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
     /// Used to emit a lint-related diagnostic based on the current state of
     /// this lint context.
     ///
-    /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
     #[rustc_lint_diagnostics]
     #[track_caller]
-    pub(crate) fn struct_lint(
+    pub(crate) fn opt_span_lint(
         &self,
         lint: &'static Lint,
         span: Option<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
-        decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagMessage>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
     ) {
         let (level, src) = self.lint_level(lint);
-        struct_lint_level(self.sess, lint, level, src, span, msg, decorate)
+        lint_level(self.sess, lint, level, src, span, msg, decorate)
     }
 
     #[track_caller]
-    pub fn emit_spanned_lint(
+    pub fn emit_span_lint(
         &self,
         lint: &'static Lint,
         span: MultiSpan,
-        decorate: impl for<'a> DecorateLint<'a, ()>,
+        decorate: impl for<'a> LintDiagnostic<'a, ()>,
     ) {
         let (level, src) = self.lint_level(lint);
-        struct_lint_level(self.sess, lint, level, src, Some(span), decorate.msg(), |lint| {
+        lint_level(self.sess, lint, level, src, Some(span), decorate.msg(), |lint| {
             decorate.decorate_lint(lint);
         });
     }
 
     #[track_caller]
-    pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> DecorateLint<'a, ()>) {
+    pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) {
         let (level, src) = self.lint_level(lint);
-        struct_lint_level(self.sess, lint, level, src, None, decorate.msg(), |lint| {
+        lint_level(self.sess, lint, level, src, None, decorate.msg(), |lint| {
             decorate.decorate_lint(lint);
         });
     }
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 0fc24e88b3b..250c4adb948 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -25,23 +25,19 @@
 //!
 //! This API is completely unstable and subject to change.
 
-#![allow(rustc::potential_query_instability)]
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
 #![doc(rust_logo)]
 #![feature(rustdoc_internals)]
 #![feature(array_windows)]
 #![feature(box_patterns)]
 #![feature(control_flow_enum)]
+#![feature(extract_if)]
+#![feature(generic_nonzero)]
 #![feature(if_let_guard)]
-#![feature(iter_intersperse)]
 #![feature(iter_order_by)]
 #![feature(let_chains)]
-#![feature(min_specialization)]
-#![feature(never_type)]
+#![feature(trait_upcasting)]
 #![feature(rustc_attrs)]
-#![recursion_limit = "256"]
-#![deny(rustc::untranslatable_diagnostic)]
-#![deny(rustc::diagnostic_outside_of_impl)]
 #![allow(internal_features)]
 
 #[macro_use]
@@ -75,6 +71,7 @@ mod methods;
 mod multiple_supertrait_upcastable;
 mod non_ascii_idents;
 mod non_fmt_panic;
+mod non_local_def;
 mod nonstandard_style;
 mod noop_method_call;
 mod opaque_hidden_inferred_bound;
@@ -110,6 +107,7 @@ use methods::*;
 use multiple_supertrait_upcastable::*;
 use non_ascii_idents::*;
 use non_fmt_panic::NonPanicFmt;
+use non_local_def::*;
 use nonstandard_style::*;
 use noop_method_call::*;
 use opaque_hidden_inferred_bound::*;
@@ -218,8 +216,6 @@ late_lint_methods!(
             ExplicitOutlivesRequirements: ExplicitOutlivesRequirements,
             InvalidValue: InvalidValue,
             DerefNullPtr: DerefNullPtr,
-            // May Depend on constants elsewhere
-            UnusedBrokenConst: UnusedBrokenConst,
             UnstableFeatures: UnstableFeatures,
             UngatedAsyncFnTrackCaller: UngatedAsyncFnTrackCaller,
             ArrayIntoIter: ArrayIntoIter::default(),
@@ -236,6 +232,7 @@ late_lint_methods!(
             MissingDebugImplementations: MissingDebugImplementations,
             MissingDoc: MissingDoc,
             AsyncFnInTrait: AsyncFnInTrait,
+            NonLocalDefinitions: NonLocalDefinitions::default(),
         ]
     ]
 );
@@ -329,6 +326,8 @@ fn register_builtins(store: &mut LintStore) {
     store.register_renamed("disjoint_capture_migration", "rust_2021_incompatible_closure_captures");
     store.register_renamed("or_patterns_back_compat", "rust_2021_incompatible_or_patterns");
     store.register_renamed("non_fmt_panic", "non_fmt_panics");
+    store.register_renamed("unused_tuple_struct_fields", "dead_code");
+    store.register_renamed("static_mut_ref", "static_mut_refs");
 
     // These were moved to tool lints, but rustc still sees them when compiling normally, before
     // tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use
@@ -513,6 +512,30 @@ fn register_builtins(store: &mut LintStore) {
         "converted into hard error, see PR #117984 \
         <https://github.com/rust-lang/rust/pull/117984> for more information",
     );
+    store.register_removed(
+        "coinductive_overlap_in_coherence",
+        "converted into hard error, see PR #118649 \
+         <https://github.com/rust-lang/rust/pull/118649> for more information",
+    );
+    store.register_removed(
+        "illegal_floating_point_literal_pattern",
+        "no longer a warning, float patterns behave the same as `==`",
+    );
+    store.register_removed(
+        "nontrivial_structural_match",
+        "no longer needed, see RFC #3535 \
+         <https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for more information",
+    );
+    store.register_removed(
+        "suspicious_auto_trait_impls",
+        "no longer needed, see #93367 \
+         <https://github.com/rust-lang/rust/issues/93367> for more information",
+    );
+    store.register_removed(
+        "const_patterns_without_partial_eq",
+        "converted into hard error, see RFC #3535 \
+         <https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for more information",
+    );
 }
 
 fn register_internals(store: &mut LintStore) {
@@ -527,7 +550,6 @@ fn register_internals(store: &mut LintStore) {
     store.register_lints(&TyTyKind::get_lints());
     store.register_late_mod_pass(|_| Box::new(TyTyKind));
     store.register_lints(&Diagnostics::get_lints());
-    store.register_early_pass(|| Box::new(Diagnostics));
     store.register_late_mod_pass(|_| Box::new(Diagnostics));
     store.register_lints(&BadOptAccess::get_lints());
     store.register_late_mod_pass(|_| Box::new(BadOptAccess));
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index ca6408bdf3d..1dac2d89c6b 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -1,19 +1,19 @@
-#![allow(rustc::untranslatable_diagnostic)]
 #![allow(rustc::diagnostic_outside_of_impl)]
-use std::num::NonZeroU32;
+#![allow(rustc::untranslatable_diagnostic)]
+use std::num::NonZero;
 
 use crate::errors::RequestedLevel;
 use crate::fluent_generated as fluent;
 use rustc_errors::{
-    AddToDiagnostic, Applicability, DecorateLint, DiagnosticMessage, DiagnosticStyledString,
-    SuggestionStyle,
+    codes::*, Applicability, Diag, DiagMessage, DiagStyledString, EmissionGuarantee,
+    LintDiagnostic, SubdiagMessageOp, Subdiagnostic, SuggestionStyle,
 };
 use rustc_hir::def_id::DefId;
 use rustc_macros::{LintDiagnostic, Subdiagnostic};
 use rustc_middle::ty::{
     inhabitedness::InhabitedPredicate, Clause, PolyExistentialTraitRef, Ty, TyCtxt,
 };
-use rustc_session::parse::ParseSess;
+use rustc_session::Session;
 use rustc_span::{edition::Edition, sym, symbol::Ident, Span, Symbol};
 
 use crate::{
@@ -114,6 +114,9 @@ pub enum BuiltinUnsafe {
     DeclUnsafeMethod,
     #[diag(lint_builtin_impl_unsafe_method)]
     ImplUnsafeMethod,
+    #[diag(lint_builtin_global_asm)]
+    #[note(lint_builtin_global_macro_unsafety)]
+    GlobalAsm,
 }
 
 #[derive(LintDiagnostic)]
@@ -133,12 +136,12 @@ pub struct BuiltinMissingDebugImpl<'a> {
 }
 
 // Needed for def_path_str
-impl<'a> DecorateLint<'a, ()> for BuiltinMissingDebugImpl<'_> {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
-        diag.set_arg("debug", self.tcx.def_path_str(self.def_id));
+impl<'a> LintDiagnostic<'a, ()> for BuiltinMissingDebugImpl<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
+        diag.arg("debug", self.tcx.def_path_str(self.def_id));
     }
 
-    fn msg(&self) -> DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         fluent::lint_builtin_missing_debug_impl
     }
 }
@@ -235,20 +238,20 @@ pub struct BuiltinUnstableFeatures;
 // lint_ungated_async_fn_track_caller
 pub struct BuiltinUngatedAsyncFnTrackCaller<'a> {
     pub label: Span,
-    pub parse_sess: &'a ParseSess,
+    pub session: &'a Session,
 }
 
-impl<'a> DecorateLint<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
+impl<'a> LintDiagnostic<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
         diag.span_label(self.label, fluent::lint_label);
         rustc_session::parse::add_feature_diagnostics(
             diag,
-            self.parse_sess,
+            self.session,
             sym::async_fn_track_caller,
         );
     }
 
-    fn msg(&self) -> DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         fluent::lint_ungated_async_fn_track_caller
     }
 }
@@ -267,23 +270,21 @@ pub struct SuggestChangingAssocTypes<'a, 'b> {
     pub ty: &'a rustc_hir::Ty<'b>,
 }
 
-impl AddToDiagnostic for SuggestChangingAssocTypes<'_, '_> {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
+impl<'a, 'b> Subdiagnostic for SuggestChangingAssocTypes<'a, 'b> {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
         // Access to associates types should use `<T as Bound>::Assoc`, which does not need a
         // bound. Let's see if this type does that.
 
         // We use a HIR visitor to walk the type.
         use rustc_hir::intravisit::{self, Visitor};
-        struct WalkAssocTypes<'a> {
-            err: &'a mut rustc_errors::Diagnostic,
+        struct WalkAssocTypes<'a, 'b, G: EmissionGuarantee> {
+            err: &'a mut Diag<'b, G>,
         }
-        impl Visitor<'_> for WalkAssocTypes<'_> {
+        impl<'a, 'b, G: EmissionGuarantee> Visitor<'_> for WalkAssocTypes<'a, 'b, G> {
             fn visit_qpath(
                 &mut self,
                 qpath: &rustc_hir::QPath<'_>,
@@ -325,14 +326,12 @@ pub struct BuiltinTypeAliasGenericBoundsSuggestion {
     pub suggestions: Vec<(Span, String)>,
 }
 
-impl AddToDiagnostic for BuiltinTypeAliasGenericBoundsSuggestion {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
+impl Subdiagnostic for BuiltinTypeAliasGenericBoundsSuggestion {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
         diag.multipart_suggestion(
             fluent::lint_suggestion,
             self.suggestions,
@@ -413,20 +412,20 @@ pub struct BuiltinIncompleteFeaturesHelp;
 #[derive(Subdiagnostic)]
 #[note(lint_note)]
 pub struct BuiltinFeatureIssueNote {
-    pub n: NonZeroU32,
+    pub n: NonZero<u32>,
 }
 
 pub struct BuiltinUnpermittedTypeInit<'a> {
-    pub msg: DiagnosticMessage,
+    pub msg: DiagMessage,
     pub ty: Ty<'a>,
     pub label: Span,
     pub sub: BuiltinUnpermittedTypeInitSub,
     pub tcx: TyCtxt<'a>,
 }
 
-impl<'a> DecorateLint<'a, ()> for BuiltinUnpermittedTypeInit<'_> {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
-        diag.set_arg("ty", self.ty);
+impl<'a> LintDiagnostic<'a, ()> for BuiltinUnpermittedTypeInit<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.arg("ty", self.ty);
         diag.span_label(self.label, fluent::lint_builtin_unpermitted_type_init_label);
         if let InhabitedPredicate::True = self.ty.inhabited_predicate(self.tcx) {
             // Only suggest late `MaybeUninit::assume_init` initialization if the type is inhabited.
@@ -435,10 +434,10 @@ impl<'a> DecorateLint<'a, ()> for BuiltinUnpermittedTypeInit<'_> {
                 fluent::lint_builtin_unpermitted_type_init_label_suggestion,
             );
         }
-        self.sub.add_to_diagnostic(diag);
+        self.sub.add_to_diag(diag);
     }
 
-    fn msg(&self) -> rustc_errors::DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         self.msg.clone()
     }
 }
@@ -448,14 +447,12 @@ pub struct BuiltinUnpermittedTypeInitSub {
     pub err: InitError,
 }
 
-impl AddToDiagnostic for BuiltinUnpermittedTypeInitSub {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
+impl Subdiagnostic for BuiltinUnpermittedTypeInitSub {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
         let mut err = self.err;
         loop {
             if let Some(span) = err.span {
@@ -505,17 +502,15 @@ pub struct BuiltinClashingExternSub<'a> {
     pub found: Ty<'a>,
 }
 
-impl AddToDiagnostic for BuiltinClashingExternSub<'_> {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
-        let mut expected_str = DiagnosticStyledString::new();
+impl Subdiagnostic for BuiltinClashingExternSub<'_> {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
+        let mut expected_str = DiagStyledString::new();
         expected_str.push(self.expected.fn_sig(self.tcx).to_string(), false);
-        let mut found_str = DiagnosticStyledString::new();
+        let mut found_str = DiagStyledString::new();
         found_str.push(self.found.fn_sig(self.tcx).to_string(), true);
         diag.note_expected_found(&"", expected_str, &"", found_str);
     }
@@ -544,7 +539,6 @@ pub enum BuiltinSpecialModuleNameUsed {
 // deref_into_dyn_supertrait.rs
 #[derive(LintDiagnostic)]
 #[diag(lint_supertrait_as_deref_target)]
-#[help]
 pub struct SupertraitAsDerefTarget<'a> {
     pub self_ty: Ty<'a>,
     pub supertrait_principal: PolyExistentialTraitRef<'a>,
@@ -740,7 +734,7 @@ pub enum InvalidFromUtf8Diag {
 
 // reference_casting.rs
 #[derive(LintDiagnostic)]
-pub enum InvalidReferenceCastingDiag {
+pub enum InvalidReferenceCastingDiag<'tcx> {
     #[diag(lint_invalid_reference_casting_borrow_as_mut)]
     #[note(lint_invalid_reference_casting_note_book)]
     BorrowAsMut {
@@ -757,6 +751,18 @@ pub enum InvalidReferenceCastingDiag {
         #[note(lint_invalid_reference_casting_note_ty_has_interior_mutability)]
         ty_has_interior_mutability: Option<()>,
     },
+    #[diag(lint_invalid_reference_casting_bigger_layout)]
+    #[note(lint_layout)]
+    BiggerLayout {
+        #[label]
+        orig_cast: Option<Span>,
+        #[label(lint_alloc)]
+        alloc: Span,
+        from_ty: Ty<'tcx>,
+        from_size: u64,
+        to_ty: Ty<'tcx>,
+        to_size: u64,
+    },
 }
 
 // hidden_unicode_codepoints.rs
@@ -778,14 +784,12 @@ pub struct HiddenUnicodeCodepointsDiagLabels {
     pub spans: Vec<(char, Span)>,
 }
 
-impl AddToDiagnostic for HiddenUnicodeCodepointsDiagLabels {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
+impl Subdiagnostic for HiddenUnicodeCodepointsDiagLabels {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
         for (c, span) in self.spans {
             diag.span_label(span, format!("{c:?}"));
         }
@@ -798,14 +802,12 @@ pub enum HiddenUnicodeCodepointsDiagSub {
 }
 
 // Used because of multiple multipart_suggestion and note
-impl AddToDiagnostic for HiddenUnicodeCodepointsDiagSub {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
+impl Subdiagnostic for HiddenUnicodeCodepointsDiagSub {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
         match self {
             HiddenUnicodeCodepointsDiagSub::Escape { spans } => {
                 diag.multipart_suggestion_with_style(
@@ -830,7 +832,7 @@ impl AddToDiagnostic for HiddenUnicodeCodepointsDiagSub {
                 // FIXME: in other suggestions we've reversed the inner spans of doc comments. We
                 // should do the same here to provide the same good suggestions as we do for
                 // literals above.
-                diag.set_arg(
+                diag.arg(
                     "escaped",
                     spans
                         .into_iter()
@@ -922,10 +924,6 @@ pub struct DiagOutOfImpl;
 pub struct UntranslatableDiag;
 
 #[derive(LintDiagnostic)]
-#[diag(lint_trivial_untranslatable_diag)]
-pub struct UntranslatableDiagnosticTrivial;
-
-#[derive(LintDiagnostic)]
 #[diag(lint_bad_opt_access)]
 pub struct BadOptAccessDiag<'a> {
     pub msg: &'a str,
@@ -948,32 +946,41 @@ pub enum NonBindingLet {
 
 pub struct NonBindingLetSub {
     pub suggestion: Span,
-    pub multi_suggestion_start: Span,
-    pub multi_suggestion_end: Span,
-}
-
-impl AddToDiagnostic for NonBindingLetSub {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
-        diag.span_suggestion_verbose(
-            self.suggestion,
-            fluent::lint_non_binding_let_suggestion,
-            "_unused",
-            Applicability::MachineApplicable,
-        );
-        diag.multipart_suggestion(
-            fluent::lint_non_binding_let_multi_suggestion,
-            vec![
-                (self.multi_suggestion_start, "drop(".to_string()),
-                (self.multi_suggestion_end, ")".to_string()),
-            ],
-            Applicability::MachineApplicable,
-        );
+    pub drop_fn_start_end: Option<(Span, Span)>,
+    pub is_assign_desugar: bool,
+}
+
+impl Subdiagnostic for NonBindingLetSub {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
+        let can_suggest_binding = self.drop_fn_start_end.is_some() || !self.is_assign_desugar;
+
+        if can_suggest_binding {
+            let prefix = if self.is_assign_desugar { "let " } else { "" };
+            diag.span_suggestion_verbose(
+                self.suggestion,
+                fluent::lint_non_binding_let_suggestion,
+                format!("{prefix}_unused"),
+                Applicability::MachineApplicable,
+            );
+        } else {
+            diag.span_help(self.suggestion, fluent::lint_non_binding_let_suggestion);
+        }
+        if let Some(drop_fn_start_end) = self.drop_fn_start_end {
+            diag.multipart_suggestion(
+                fluent::lint_non_binding_let_multi_suggestion,
+                vec![
+                    (drop_fn_start_end.0, "drop(".to_string()),
+                    (drop_fn_start_end.1, ")".to_string()),
+                ],
+                Applicability::MachineApplicable,
+            );
+        } else {
+            diag.help(fluent::lint_non_binding_let_multi_drop_fn);
+        }
     }
 }
 
@@ -1069,13 +1076,14 @@ pub enum UnknownLintSuggestion {
         #[primary_span]
         suggestion: Span,
         replace: Symbol,
+        from_rustc: bool,
     },
     #[help(lint_help)]
-    WithoutSpan { replace: Symbol },
+    WithoutSpan { replace: Symbol, from_rustc: bool },
 }
 
 #[derive(LintDiagnostic)]
-#[diag(lint_unknown_lint, code = "E0602")]
+#[diag(lint_unknown_lint, code = E0602)]
 pub struct UnknownLintFromCommandLine<'a> {
     pub name: String,
     #[subdiagnostic]
@@ -1117,7 +1125,12 @@ pub struct IdentifierNonAsciiChar;
 
 #[derive(LintDiagnostic)]
 #[diag(lint_identifier_uncommon_codepoints)]
-pub struct IdentifierUncommonCodepoints;
+#[note]
+pub struct IdentifierUncommonCodepoints {
+    pub codepoints: Vec<char>,
+    pub codepoints_len: usize,
+    pub identifier_type: &'static str,
+}
 
 #[derive(LintDiagnostic)]
 #[diag(lint_confusable_identifier_pair)]
@@ -1146,9 +1159,9 @@ pub struct NonFmtPanicUnused {
 }
 
 // Used because of two suggestions based on one Option<Span>
-impl<'a> DecorateLint<'a, ()> for NonFmtPanicUnused {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
-        diag.set_arg("count", self.count);
+impl<'a> LintDiagnostic<'a, ()> for NonFmtPanicUnused {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.arg("count", self.count);
         diag.note(fluent::lint_note);
         if let Some(span) = self.suggestion {
             diag.span_suggestion(
@@ -1166,7 +1179,7 @@ impl<'a> DecorateLint<'a, ()> for NonFmtPanicUnused {
         }
     }
 
-    fn msg(&self) -> rustc_errors::DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         fluent::lint_non_fmt_panic_unused
     }
 }
@@ -1223,14 +1236,12 @@ pub enum NonSnakeCaseDiagSub {
     SuggestionAndNote { span: Span },
 }
 
-impl AddToDiagnostic for NonSnakeCaseDiagSub {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
+impl Subdiagnostic for NonSnakeCaseDiagSub {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
         match self {
             NonSnakeCaseDiagSub::Label { span } => {
                 diag.span_label(span, fluent::lint_label);
@@ -1301,6 +1312,12 @@ pub struct NoopMethodCallDiag<'a> {
     pub trait_: Symbol,
     #[suggestion(code = "", applicability = "machine-applicable")]
     pub label: Span,
+    #[suggestion(
+        lint_derive_suggestion,
+        code = "#[derive(Clone)]\n",
+        applicability = "maybe-incorrect"
+    )]
+    pub suggest_derive: Option<Span>,
 }
 
 #[derive(LintDiagnostic)]
@@ -1315,6 +1332,45 @@ pub struct SuspiciousDoubleRefCloneDiag<'a> {
     pub ty: Ty<'a>,
 }
 
+// non_local_defs.rs
+#[derive(LintDiagnostic)]
+pub enum NonLocalDefinitionsDiag {
+    #[diag(lint_non_local_definitions_impl)]
+    #[help]
+    #[note(lint_non_local)]
+    #[note(lint_exception)]
+    #[note(lint_non_local_definitions_deprecation)]
+    Impl {
+        depth: u32,
+        body_kind_descr: &'static str,
+        body_name: String,
+        #[subdiagnostic]
+        cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>,
+        #[suggestion(lint_const_anon, code = "_", applicability = "machine-applicable")]
+        const_anon: Option<Span>,
+    },
+    #[diag(lint_non_local_definitions_macro_rules)]
+    #[help]
+    #[note(lint_non_local)]
+    #[note(lint_exception)]
+    #[note(lint_non_local_definitions_deprecation)]
+    MacroRules {
+        depth: u32,
+        body_kind_descr: &'static str,
+        body_name: String,
+        #[subdiagnostic]
+        cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>,
+    },
+}
+
+#[derive(Subdiagnostic)]
+#[note(lint_non_local_definitions_cargo_update)]
+pub struct NonLocalDefinitionsCargoUpdateNote {
+    pub macro_kind: &'static str,
+    pub macro_name: Symbol,
+    pub crate_name: Symbol,
+}
+
 // pass_by_value.rs
 #[derive(LintDiagnostic)]
 #[diag(lint_pass_by_value)]
@@ -1341,13 +1397,13 @@ pub struct DropTraitConstraintsDiag<'a> {
 }
 
 // Needed for def_path_str
-impl<'a> DecorateLint<'a, ()> for DropTraitConstraintsDiag<'_> {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
-        diag.set_arg("predicate", self.predicate);
-        diag.set_arg("needs_drop", self.tcx.def_path_str(self.def_id));
+impl<'a> LintDiagnostic<'a, ()> for DropTraitConstraintsDiag<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.arg("predicate", self.predicate);
+        diag.arg("needs_drop", self.tcx.def_path_str(self.def_id));
     }
 
-    fn msg(&self) -> rustc_errors::DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         fluent::lint_drop_trait_constraints
     }
 }
@@ -1358,12 +1414,12 @@ pub struct DropGlue<'a> {
 }
 
 // Needed for def_path_str
-impl<'a> DecorateLint<'a, ()> for DropGlue<'_> {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
-        diag.set_arg("needs_drop", self.tcx.def_path_str(self.def_id));
+impl<'a> LintDiagnostic<'a, ()> for DropGlue<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.arg("needs_drop", self.tcx.def_path_str(self.def_id));
     }
 
-    fn msg(&self) -> rustc_errors::DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         fluent::lint_drop_glue
     }
 }
@@ -1422,14 +1478,12 @@ pub enum OverflowingBinHexSign {
     Negative,
 }
 
-impl AddToDiagnostic for OverflowingBinHexSign {
-    fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F)
-    where
-        F: Fn(
-            &mut rustc_errors::Diagnostic,
-            rustc_errors::SubdiagnosticMessage,
-        ) -> rustc_errors::SubdiagnosticMessage,
-    {
+impl Subdiagnostic for OverflowingBinHexSign {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _f: F,
+    ) {
         match self {
             OverflowingBinHexSign::Positive => {
                 diag.note(fluent::lint_positive_note);
@@ -1571,7 +1625,8 @@ pub enum AmbiguousWidePointerComparisons<'a> {
 #[multipart_suggestion(
     lint_addr_metadata_suggestion,
     style = "verbose",
-    applicability = "machine-applicable"
+    // FIXME(#53934): make machine-applicable again
+    applicability = "maybe-incorrect"
 )]
 pub struct AmbiguousWidePointerComparisonsAddrMetadataSuggestion<'a> {
     pub ne: &'a str,
@@ -1590,7 +1645,8 @@ pub enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> {
     #[multipart_suggestion(
         lint_addr_suggestion,
         style = "verbose",
-        applicability = "machine-applicable"
+        // FIXME(#53934): make machine-applicable again
+        applicability = "maybe-incorrect"
     )]
     AddrEq {
         ne: &'a str,
@@ -1606,7 +1662,8 @@ pub enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> {
     #[multipart_suggestion(
         lint_addr_suggestion,
         style = "verbose",
-        applicability = "machine-applicable"
+        // FIXME(#53934): make machine-applicable again
+        applicability = "maybe-incorrect"
     )]
     Cast {
         deref_left: &'a str,
@@ -1626,16 +1683,16 @@ pub struct ImproperCTypes<'a> {
     pub ty: Ty<'a>,
     pub desc: &'a str,
     pub label: Span,
-    pub help: Option<DiagnosticMessage>,
-    pub note: DiagnosticMessage,
+    pub help: Option<DiagMessage>,
+    pub note: DiagMessage,
     pub span_note: Option<Span>,
 }
 
-// Used because of the complexity of Option<DiagnosticMessage>, DiagnosticMessage, and Option<Span>
-impl<'a> DecorateLint<'a, ()> for ImproperCTypes<'_> {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
-        diag.set_arg("ty", self.ty);
-        diag.set_arg("desc", self.desc);
+// Used because of the complexity of Option<DiagMessage>, DiagMessage, and Option<Span>
+impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.arg("ty", self.ty);
+        diag.arg("desc", self.desc);
         diag.span_label(self.label, fluent::lint_label);
         if let Some(help) = self.help {
             diag.help(help);
@@ -1646,7 +1703,7 @@ impl<'a> DecorateLint<'a, ()> for ImproperCTypes<'_> {
         }
     }
 
-    fn msg(&self) -> rustc_errors::DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         fluent::lint_improper_ctypes
     }
 }
@@ -1775,21 +1832,21 @@ pub enum UnusedDefSuggestion {
 }
 
 // Needed because of def_path_str
-impl<'a> DecorateLint<'a, ()> for UnusedDef<'_, '_> {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
-        diag.set_arg("pre", self.pre);
-        diag.set_arg("post", self.post);
-        diag.set_arg("def", self.cx.tcx.def_path_str(self.def_id));
+impl<'a> LintDiagnostic<'a, ()> for UnusedDef<'_, '_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.arg("pre", self.pre);
+        diag.arg("post", self.post);
+        diag.arg("def", self.cx.tcx.def_path_str(self.def_id));
         // check for #[must_use = "..."]
         if let Some(note) = self.note {
             diag.note(note.to_string());
         }
         if let Some(sugg) = self.suggestion {
-            diag.subdiagnostic(sugg);
+            diag.subdiagnostic(diag.dcx, sugg);
         }
     }
 
-    fn msg(&self) -> rustc_errors::DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         fluent::lint_unused_def
     }
 }
@@ -1858,15 +1915,15 @@ pub struct AsyncFnInTraitDiag {
     pub sugg: Option<Vec<(Span, String)>>,
 }
 
-impl<'a> DecorateLint<'a, ()> for AsyncFnInTraitDiag {
-    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) {
+impl<'a> LintDiagnostic<'a, ()> for AsyncFnInTraitDiag {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
         diag.note(fluent::lint_note);
         if let Some(sugg) = self.sugg {
             diag.multipart_suggestion(fluent::lint_suggestion, sugg, Applicability::MaybeIncorrect);
         }
     }
 
-    fn msg(&self) -> rustc_errors::DiagnosticMessage {
+    fn msg(&self) -> DiagMessage {
         fluent::lint_async_fn_in_trait
     }
 }
diff --git a/compiler/rustc_lint/src/map_unit_fn.rs b/compiler/rustc_lint/src/map_unit_fn.rs
index 7c692fee333..07980cfb6fa 100644
--- a/compiler/rustc_lint/src/map_unit_fn.rs
+++ b/compiler/rustc_lint/src/map_unit_fn.rs
@@ -61,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for MapUnitFn {
                         let fn_ty = cx.tcx.fn_sig(id).skip_binder();
                         let ret_ty = fn_ty.output().skip_binder();
                         if is_unit_type(ret_ty) {
-                            cx.emit_spanned_lint(
+                            cx.emit_span_lint(
                                 MAP_UNIT_FN,
                                 span,
                                 MappingToUnit {
@@ -80,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for MapUnitFn {
                         let cl_ty = subs.as_closure().sig();
                         let ret_ty = cl_ty.output().skip_binder();
                         if is_unit_type(ret_ty) {
-                            cx.emit_spanned_lint(
+                            cx.emit_span_lint(
                                 MAP_UNIT_FN,
                                 span,
                                 MappingToUnit {
diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs
index 5b63b19c53c..9cfdaf0ce2f 100644
--- a/compiler/rustc_lint/src/methods.rs
+++ b/compiler/rustc_lint/src/methods.rs
@@ -57,7 +57,7 @@ fn lint_cstring_as_ptr(
         if cx.tcx.is_diagnostic_item(sym::Result, def.did()) {
             if let ty::Adt(adt, _) = args.type_at(0).kind() {
                 if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did()) {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         TEMPORARY_CSTRING_AS_PTR,
                         as_ptr_span,
                         CStringPtr { as_ptr: as_ptr_span, unwrap: unwrap.span },
diff --git a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
index dfefaf82fd7..69d623b547b 100644
--- a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
+++ b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
@@ -49,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleSupertraitUpcastable {
                 .into_iter()
                 .filter_map(|(pred, _)| pred.as_trait_clause());
             if direct_super_traits_iter.count() > 1 {
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     MULTIPLE_SUPERTRAIT_UPCASTABLE,
                     cx.tcx.def_span(def_id),
                     crate::lints::MultipleSupertraitUpcastable { ident: item.ident },
diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs
index 4f92fcd71c6..79bc78ae55a 100644
--- a/compiler/rustc_lint/src/non_ascii_idents.rs
+++ b/compiler/rustc_lint/src/non_ascii_idents.rs
@@ -4,8 +4,10 @@ use crate::lints::{
 };
 use crate::{EarlyContext, EarlyLintPass, LintContext};
 use rustc_ast as ast;
-use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_data_structures::unord::UnordMap;
 use rustc_span::symbol::Symbol;
+use unicode_security::general_security_profile::IdentifierType;
 
 declare_lint! {
     /// The `non_ascii_idents` lint detects non-ASCII identifiers.
@@ -170,30 +172,71 @@ impl EarlyLintPass for NonAsciiIdents {
         }
 
         let mut has_non_ascii_idents = false;
-        let symbols = cx.sess().parse_sess.symbol_gallery.symbols.lock();
+        let symbols = cx.sess().psess.symbol_gallery.symbols.lock();
 
         // Sort by `Span` so that error messages make sense with respect to the
         // order of identifier locations in the code.
+        // We will soon sort, so the initial order does not matter.
+        #[allow(rustc::potential_query_instability)]
         let mut symbols: Vec<_> = symbols.iter().collect();
         symbols.sort_by_key(|k| k.1);
-
         for (symbol, &sp) in symbols.iter() {
             let symbol_str = symbol.as_str();
             if symbol_str.is_ascii() {
                 continue;
             }
             has_non_ascii_idents = true;
-            cx.emit_spanned_lint(NON_ASCII_IDENTS, sp, IdentifierNonAsciiChar);
+            cx.emit_span_lint(NON_ASCII_IDENTS, sp, IdentifierNonAsciiChar);
             if check_uncommon_codepoints
                 && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed)
             {
-                cx.emit_spanned_lint(UNCOMMON_CODEPOINTS, sp, IdentifierUncommonCodepoints);
+                let mut chars: Vec<_> = symbol_str
+                    .chars()
+                    .map(|c| (c, GeneralSecurityProfile::identifier_type(c)))
+                    .collect();
+
+                for (id_ty, id_ty_descr) in [
+                    (IdentifierType::Exclusion, "Exclusion"),
+                    (IdentifierType::Technical, "Technical"),
+                    (IdentifierType::Limited_Use, "Limited_Use"),
+                    (IdentifierType::Not_NFKC, "Not_NFKC"),
+                ] {
+                    let codepoints: Vec<_> =
+                        chars.extract_if(|(_, ty)| *ty == Some(id_ty)).collect();
+                    if codepoints.is_empty() {
+                        continue;
+                    }
+                    cx.emit_span_lint(
+                        UNCOMMON_CODEPOINTS,
+                        sp,
+                        IdentifierUncommonCodepoints {
+                            codepoints_len: codepoints.len(),
+                            codepoints: codepoints.into_iter().map(|(c, _)| c).collect(),
+                            identifier_type: id_ty_descr,
+                        },
+                    );
+                }
+
+                let remaining = chars
+                    .extract_if(|(c, _)| !GeneralSecurityProfile::identifier_allowed(*c))
+                    .collect::<Vec<_>>();
+                if !remaining.is_empty() {
+                    cx.emit_span_lint(
+                        UNCOMMON_CODEPOINTS,
+                        sp,
+                        IdentifierUncommonCodepoints {
+                            codepoints_len: remaining.len(),
+                            codepoints: remaining.into_iter().map(|(c, _)| c).collect(),
+                            identifier_type: "Restricted",
+                        },
+                    );
+                }
             }
         }
 
         if has_non_ascii_idents && check_confusable_idents {
-            let mut skeleton_map: FxHashMap<Symbol, (Symbol, Span, bool)> =
-                FxHashMap::with_capacity_and_hasher(symbols.len(), Default::default());
+            let mut skeleton_map: UnordMap<Symbol, (Symbol, Span, bool)> =
+                UnordMap::with_capacity(symbols.len());
             let mut skeleton_buf = String::new();
 
             for (&symbol, &sp) in symbols.iter() {
@@ -215,7 +258,7 @@ impl EarlyLintPass for NonAsciiIdents {
                     .entry(skeleton_sym)
                     .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| {
                         if !*existing_is_ascii || !is_ascii {
-                            cx.emit_spanned_lint(
+                            cx.emit_span_lint(
                                 CONFUSABLE_IDENTS,
                                 sp,
                                 ConfusableIdentifierPair {
@@ -246,8 +289,8 @@ impl EarlyLintPass for NonAsciiIdents {
                 Verified,
             }
 
-            let mut script_states: FxHashMap<AugmentedScriptSet, ScriptSetUsage> =
-                FxHashMap::default();
+            let mut script_states: FxIndexMap<AugmentedScriptSet, ScriptSetUsage> =
+                Default::default();
             let latin_augmented_script_set = AugmentedScriptSet::for_char('A');
             script_states.insert(latin_augmented_script_set, ScriptSetUsage::Verified);
 
@@ -287,6 +330,8 @@ impl EarlyLintPass for NonAsciiIdents {
             }
 
             if has_suspicious {
+                // The end result is put in `lint_reports` which is sorted.
+                #[allow(rustc::potential_query_instability)]
                 let verified_augmented_script_sets = script_states
                     .iter()
                     .flat_map(|(k, v)| match v {
@@ -299,6 +344,8 @@ impl EarlyLintPass for NonAsciiIdents {
                 let mut lint_reports: BTreeMap<(Span, Vec<char>), AugmentedScriptSet> =
                     BTreeMap::new();
 
+                // The end result is put in `lint_reports` which is sorted.
+                #[allow(rustc::potential_query_instability)]
                 'outerloop: for (augment_script_set, usage) in script_states {
                     let ScriptSetUsage::Suspicious(mut ch_list, sp) = usage else { continue };
 
@@ -332,7 +379,7 @@ impl EarlyLintPass for NonAsciiIdents {
                         let char_info = format!("'{}' (U+{:04X})", ch, ch as u32);
                         includes += &char_info;
                     }
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         MIXED_SCRIPT_CONFUSABLES,
                         sp,
                         MixedScriptConfusables { set: script_set.to_string(), includes },
diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs
index 9fcd70ba0b5..a2d07fff506 100644
--- a/compiler/rustc_lint/src/non_fmt_panic.rs
+++ b/compiler/rustc_lint/src/non_fmt_panic.rs
@@ -111,17 +111,18 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
     let mut arg_span = arg.span;
     let mut arg_macro = None;
     while !span.contains(arg_span) {
-        let expn = arg_span.ctxt().outer_expn_data();
-        if expn.is_root() {
+        let ctxt = arg_span.ctxt();
+        if ctxt.is_root() {
             break;
         }
+        let expn = ctxt.outer_expn_data();
         arg_macro = expn.macro_def_id;
         arg_span = expn.call_site;
     }
 
     #[allow(rustc::diagnostic_outside_of_impl)]
-    cx.struct_span_lint(NON_FMT_PANICS, arg_span, fluent::lint_non_fmt_panic, |lint| {
-        lint.set_arg("name", symbol);
+    cx.span_lint(NON_FMT_PANICS, arg_span, fluent::lint_non_fmt_panic, |lint| {
+        lint.arg("name", symbol);
         lint.note(fluent::lint_note);
         lint.note(fluent::lint_more_info_note);
         if !is_arg_inside_call(arg_span, span) {
@@ -180,7 +181,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
                     fmt_applicability,
                 );
             } else if suggest_debug {
-                lint.set_arg("ty", ty);
+                lint.arg("ty", ty);
                 lint.span_suggestion_verbose(
                     arg_span.shrink_to_lo(),
                     fluent::lint_debug_suggestion,
@@ -191,7 +192,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
 
             if suggest_panic_any {
                 if let Some((open, close, del)) = find_delimiters(cx, span) {
-                    lint.set_arg("already_suggested", suggest_display || suggest_debug);
+                    lint.arg("already_suggested", suggest_display || suggest_debug);
                     lint.multipart_suggestion(
                         fluent::lint_panic_suggestion,
                         if del == '(' {
@@ -230,7 +231,7 @@ fn check_panic_str<'tcx>(
 
     let fmt_span = arg.span.source_callsite();
 
-    let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) {
+    let (snippet, style) = match cx.sess().psess.source_map().span_to_snippet(fmt_span) {
         Ok(snippet) => {
             // Count the number of `#`s between the `r` and `"`.
             let style = snippet.strip_prefix('r').and_then(|s| s.find('"'));
@@ -250,7 +251,7 @@ fn check_panic_str<'tcx>(
                 .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end)))
                 .collect(),
         };
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             NON_FMT_PANICS,
             arg_spans,
             NonFmtPanicUnused {
@@ -267,7 +268,7 @@ fn check_panic_str<'tcx>(
                     .collect()
             });
         let count = brace_spans.as_ref().map(|v| v.len()).unwrap_or(/* any number >1 */ 2);
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             NON_FMT_PANICS,
             brace_spans.unwrap_or_else(|| vec![span]),
             NonFmtPanicBraces {
@@ -281,7 +282,7 @@ fn check_panic_str<'tcx>(
 /// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
 /// and the type of (opening) delimiter used.
 fn find_delimiters(cx: &LateContext<'_>, span: Span) -> Option<(Span, Span, char)> {
-    let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?;
+    let snippet = cx.sess().psess.source_map().span_to_snippet(span).ok()?;
     let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?;
     let close = snippet.rfind(|c| ")]}".contains(c))?;
     Some((
diff --git a/compiler/rustc_lint/src/non_local_def.rs b/compiler/rustc_lint/src/non_local_def.rs
new file mode 100644
index 00000000000..7c4d92d3ce0
--- /dev/null
+++ b/compiler/rustc_lint/src/non_local_def.rs
@@ -0,0 +1,231 @@
+use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, Path, QPath, TyKind};
+use rustc_span::def_id::{DefId, LOCAL_CRATE};
+use rustc_span::{sym, symbol::kw, ExpnKind, MacroKind};
+
+use crate::lints::{NonLocalDefinitionsCargoUpdateNote, NonLocalDefinitionsDiag};
+use crate::{LateContext, LateLintPass, LintContext};
+
+declare_lint! {
+    /// The `non_local_definitions` lint checks for `impl` blocks and `#[macro_export]`
+    /// macro inside bodies (functions, enum discriminant, ...).
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![warn(non_local_definitions)]
+    /// trait MyTrait {}
+    /// struct MyStruct;
+    ///
+    /// fn foo() {
+    ///     impl MyTrait for MyStruct {}
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Creating non-local definitions go against expectation and can create discrepancies
+    /// in tooling. It should be avoided. It may become deny-by-default in edition 2024
+    /// and higher, see see the tracking issue <https://github.com/rust-lang/rust/issues/120363>.
+    ///
+    /// An `impl` definition is non-local if it is nested inside an item and neither
+    /// the type nor the trait are at the same nesting level as the `impl` block.
+    ///
+    /// All nested bodies (functions, enum discriminant, array length, consts) (expect for
+    /// `const _: Ty = { ... }` in top-level module, which is still undecided) are checked.
+    pub NON_LOCAL_DEFINITIONS,
+    Allow,
+    "checks for non-local definitions",
+    report_in_external_macro
+}
+
+#[derive(Default)]
+pub struct NonLocalDefinitions {
+    body_depth: u32,
+}
+
+impl_lint_pass!(NonLocalDefinitions => [NON_LOCAL_DEFINITIONS]);
+
+// FIXME(Urgau): Figure out how to handle modules nested in bodies.
+// It's currently not handled by the current logic because modules are not bodies.
+// They don't even follow the correct order (check_body -> check_mod -> check_body_post)
+// instead check_mod is called after every body has been handled.
+
+impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
+    fn check_body(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx Body<'tcx>) {
+        self.body_depth += 1;
+    }
+
+    fn check_body_post(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx Body<'tcx>) {
+        self.body_depth -= 1;
+    }
+
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+        if self.body_depth == 0 {
+            return;
+        }
+
+        let parent = cx.tcx.parent(item.owner_id.def_id.into());
+        let parent_def_kind = cx.tcx.def_kind(parent);
+        let parent_opt_item_name = cx.tcx.opt_item_name(parent);
+
+        // Per RFC we (currently) ignore anon-const (`const _: Ty = ...`) in top-level module.
+        if self.body_depth == 1
+            && parent_def_kind == DefKind::Const
+            && parent_opt_item_name == Some(kw::Underscore)
+        {
+            return;
+        }
+
+        let cargo_update = || {
+            let oexpn = item.span.ctxt().outer_expn_data();
+            if let Some(def_id) = oexpn.macro_def_id
+                && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind
+                && def_id.krate != LOCAL_CRATE
+                && rustc_session::utils::was_invoked_from_cargo()
+            {
+                Some(NonLocalDefinitionsCargoUpdateNote {
+                    macro_kind: macro_kind.descr(),
+                    macro_name,
+                    crate_name: cx.tcx.crate_name(def_id.krate),
+                })
+            } else {
+                None
+            }
+        };
+
+        match item.kind {
+            ItemKind::Impl(impl_) => {
+                // The RFC states:
+                //
+                // > An item nested inside an expression-containing item (through any
+                // > level of nesting) may not define an impl Trait for Type unless
+                // > either the **Trait** or the **Type** is also nested inside the
+                // > same expression-containing item.
+                //
+                // To achieve this we get try to get the paths of the _Trait_ and
+                // _Type_, and we look inside thoses paths to try a find in one
+                // of them a type whose parent is the same as the impl definition.
+                //
+                // If that's the case this means that this impl block declaration
+                // is using local items and so we don't lint on it.
+
+                // We also ignore anon-const in item by including the anon-const
+                // parent as well.
+                let parent_parent = if parent_def_kind == DefKind::Const
+                    && parent_opt_item_name == Some(kw::Underscore)
+                {
+                    Some(cx.tcx.parent(parent))
+                } else {
+                    None
+                };
+
+                let self_ty_has_local_parent = match impl_.self_ty.kind {
+                    TyKind::Path(QPath::Resolved(_, ty_path)) => {
+                        path_has_local_parent(ty_path, cx, parent, parent_parent)
+                    }
+                    TyKind::TraitObject([principle_poly_trait_ref, ..], _, _) => {
+                        path_has_local_parent(
+                            principle_poly_trait_ref.trait_ref.path,
+                            cx,
+                            parent,
+                            parent_parent,
+                        )
+                    }
+                    TyKind::TraitObject([], _, _)
+                    | TyKind::InferDelegation(_, _)
+                    | TyKind::Slice(_)
+                    | TyKind::Array(_, _)
+                    | TyKind::Ptr(_)
+                    | TyKind::Ref(_, _)
+                    | TyKind::BareFn(_)
+                    | TyKind::Never
+                    | TyKind::Tup(_)
+                    | TyKind::Path(_)
+                    | TyKind::AnonAdt(_)
+                    | TyKind::OpaqueDef(_, _, _)
+                    | TyKind::Typeof(_)
+                    | TyKind::Infer
+                    | TyKind::Err(_) => false,
+                };
+
+                let of_trait_has_local_parent = impl_
+                    .of_trait
+                    .map(|of_trait| path_has_local_parent(of_trait.path, cx, parent, parent_parent))
+                    .unwrap_or(false);
+
+                // If none of them have a local parent (LOGICAL NOR) this means that
+                // this impl definition is a non-local definition and so we lint on it.
+                if !(self_ty_has_local_parent || of_trait_has_local_parent) {
+                    let const_anon = if self.body_depth == 1
+                        && parent_def_kind == DefKind::Const
+                        && parent_opt_item_name != Some(kw::Underscore)
+                        && let Some(parent) = parent.as_local()
+                        && let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent)
+                        && let ItemKind::Const(ty, _, _) = item.kind
+                        && let TyKind::Tup(&[]) = ty.kind
+                    {
+                        Some(item.ident.span)
+                    } else {
+                        None
+                    };
+
+                    cx.emit_span_lint(
+                        NON_LOCAL_DEFINITIONS,
+                        item.span,
+                        NonLocalDefinitionsDiag::Impl {
+                            depth: self.body_depth,
+                            body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent),
+                            body_name: parent_opt_item_name
+                                .map(|s| s.to_ident_string())
+                                .unwrap_or_else(|| "<unnameable>".to_string()),
+                            cargo_update: cargo_update(),
+                            const_anon,
+                        },
+                    )
+                }
+            }
+            ItemKind::Macro(_macro, MacroKind::Bang)
+                if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) =>
+            {
+                cx.emit_span_lint(
+                    NON_LOCAL_DEFINITIONS,
+                    item.span,
+                    NonLocalDefinitionsDiag::MacroRules {
+                        depth: self.body_depth,
+                        body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent),
+                        body_name: parent_opt_item_name
+                            .map(|s| s.to_ident_string())
+                            .unwrap_or_else(|| "<unnameable>".to_string()),
+                        cargo_update: cargo_update(),
+                    },
+                )
+            }
+            _ => {}
+        }
+    }
+}
+
+/// Given a path and a parent impl def id, this checks if the if parent resolution
+/// def id correspond to the def id of the parent impl definition.
+///
+/// Given this path, we will look at the path (and ignore any generic args):
+///
+/// ```text
+///    std::convert::PartialEq<Foo<Bar>>
+///    ^^^^^^^^^^^^^^^^^^^^^^^
+/// ```
+fn path_has_local_parent(
+    path: &Path<'_>,
+    cx: &LateContext<'_>,
+    impl_parent: DefId,
+    impl_parent_parent: Option<DefId>,
+) -> bool {
+    path.res.opt_def_id().is_some_and(|did| {
+        did.is_local() && {
+            let res_parent = cx.tcx.parent(did);
+            res_parent == impl_parent || Some(res_parent) == impl_parent_parent
+        }
+    })
+}
diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs
index 59f27a88aec..ee863672017 100644
--- a/compiler/rustc_lint/src/nonstandard_style.rs
+++ b/compiler/rustc_lint/src/nonstandard_style.rs
@@ -10,6 +10,7 @@ use rustc_hir::def::{DefKind, Res};
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{GenericParamKind, PatKind};
 use rustc_middle::ty;
+use rustc_session::config::CrateType;
 use rustc_span::def_id::LocalDefId;
 use rustc_span::symbol::{sym, Ident};
 use rustc_span::{BytePos, Span};
@@ -150,7 +151,7 @@ impl NonCamelCaseTypes {
             } else {
                 NonCamelCaseTypeSub::Label { span: ident.span }
             };
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 NON_CAMEL_CASE_TYPES,
                 ident.span,
                 NonCamelCaseType { sort, name, sub },
@@ -320,7 +321,7 @@ impl NonSnakeCase {
             } else {
                 NonSnakeCaseDiagSub::Label { span }
             };
-            cx.emit_spanned_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub });
+            cx.emit_span_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub });
         }
     }
 }
@@ -331,6 +332,10 @@ impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
             return;
         }
 
+        if cx.tcx.crate_types().iter().all(|&crate_type| crate_type == CrateType::Executable) {
+            return;
+        }
+
         let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name {
             Some(Ident::from_str(name))
         } else {
@@ -427,7 +432,7 @@ impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
 
     fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
         if let PatKind::Binding(_, hid, ident, _) = p.kind {
-            if let hir::Node::PatField(field) = cx.tcx.hir().get_parent(hid) {
+            if let hir::Node::PatField(field) = cx.tcx.parent_hir_node(hid) {
                 if !field.is_shorthand {
                     // Only check if a new name has been introduced, to avoid warning
                     // on both the struct definition and this pattern.
@@ -481,7 +486,7 @@ impl NonUpperCaseGlobals {
             } else {
                 NonUpperCaseGlobalSub::Label { span: ident.span }
             };
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 NON_UPPER_CASE_GLOBALS,
                 ident.span,
                 NonUpperCaseGlobal { sort, name, sub },
diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs
index cfbca6efbfa..970d411fb06 100644
--- a/compiler/rustc_lint/src/noop_method_call.rs
+++ b/compiler/rustc_lint/src/noop_method_call.rs
@@ -121,22 +121,32 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
         let orig_ty = expr_ty.peel_refs();
 
         if receiver_ty == expr_ty {
-            cx.emit_spanned_lint(
+            let suggest_derive = match orig_ty.kind() {
+                ty::Adt(def, _) => Some(cx.tcx.def_span(def.did()).shrink_to_lo()),
+                _ => None,
+            };
+            cx.emit_span_lint(
                 NOOP_METHOD_CALL,
                 span,
-                NoopMethodCallDiag { method: call.ident.name, orig_ty, trait_, label: span },
+                NoopMethodCallDiag {
+                    method: call.ident.name,
+                    orig_ty,
+                    trait_,
+                    label: span,
+                    suggest_derive,
+                },
             );
         } else {
             match name {
                 // If `type_of(x) == T` and `x.borrow()` is used to get `&T`,
                 // then that should be allowed
                 sym::noop_method_borrow => return,
-                sym::noop_method_clone => cx.emit_spanned_lint(
+                sym::noop_method_clone => cx.emit_span_lint(
                     SUSPICIOUS_DOUBLE_REF_OP,
                     span,
                     SuspiciousDoubleRefCloneDiag { ty: expr_ty },
                 ),
-                sym::noop_method_deref => cx.emit_spanned_lint(
+                sym::noop_method_deref => cx.emit_span_lint(
                     SUSPICIOUS_DOUBLE_REF_OP,
                     span,
                     SuspiciousDoubleRefDerefDiag { ty: expr_ty },
diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs
index 44b23b8bdc0..cf825be7a55 100644
--- a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs
+++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs
@@ -4,7 +4,7 @@ use rustc_macros::{LintDiagnostic, Subdiagnostic};
 use rustc_middle::ty::{
     self, fold::BottomUpFolder, print::TraitPredPrintModifiersAndPath, Ty, TypeFoldable,
 };
-use rustc_span::Span;
+use rustc_span::{symbol::kw, Span};
 use rustc_trait_selection::traits;
 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
 
@@ -77,90 +77,104 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound {
         for (pred, pred_span) in
             cx.tcx.explicit_item_bounds(def_id).instantiate_identity_iter_copied()
         {
-            let predicate = infcx.instantiate_binder_with_placeholders(pred.kind());
-            let ty::ClauseKind::Projection(proj) = predicate else {
-                continue;
-            };
-            // Only check types, since those are the only things that may
-            // have opaques in them anyways.
-            let Some(proj_term) = proj.term.ty() else { continue };
+            infcx.enter_forall(pred.kind(), |predicate| {
+                let ty::ClauseKind::Projection(proj) = predicate else {
+                    return;
+                };
+                // Only check types, since those are the only things that may
+                // have opaques in them anyways.
+                let Some(proj_term) = proj.term.ty() else { return };
+
+                // HACK: `impl Trait<Assoc = impl Trait2>` from an RPIT is "ok"...
+                if let ty::Alias(ty::Opaque, opaque_ty) = *proj_term.kind()
+                    && cx.tcx.parent(opaque_ty.def_id) == def_id
+                    && matches!(
+                        opaque.origin,
+                        hir::OpaqueTyOrigin::FnReturn(_) | hir::OpaqueTyOrigin::AsyncFn(_)
+                    )
+                {
+                    return;
+                }
 
-            // HACK: `impl Trait<Assoc = impl Trait2>` from an RPIT is "ok"...
-            if let ty::Alias(ty::Opaque, opaque_ty) = *proj_term.kind()
-                && cx.tcx.parent(opaque_ty.def_id) == def_id
-                && matches!(
-                    opaque.origin,
-                    hir::OpaqueTyOrigin::FnReturn(_) | hir::OpaqueTyOrigin::AsyncFn(_)
-                )
-            {
-                continue;
-            }
+                // HACK: `async fn() -> Self` in traits is "ok"...
+                // This is not really that great, but it's similar to why the `-> Self`
+                // return type is well-formed in traits even when `Self` isn't sized.
+                if let ty::Param(param_ty) = *proj_term.kind()
+                    && param_ty.name == kw::SelfUpper
+                    && matches!(opaque.origin, hir::OpaqueTyOrigin::AsyncFn(_))
+                    && opaque.in_trait
+                {
+                    return;
+                }
 
-            let proj_ty =
-                Ty::new_projection(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.args);
-            // For every instance of the projection type in the bounds,
-            // replace them with the term we're assigning to the associated
-            // type in our opaque type.
-            let proj_replacer = &mut BottomUpFolder {
-                tcx: cx.tcx,
-                ty_op: |ty| if ty == proj_ty { proj_term } else { ty },
-                lt_op: |lt| lt,
-                ct_op: |ct| ct,
-            };
-            // For example, in `impl Trait<Assoc = impl Send>`, for all of the bounds on `Assoc`,
-            // e.g. `type Assoc: OtherTrait`, replace `<impl Trait as Trait>::Assoc: OtherTrait`
-            // with `impl Send: OtherTrait`.
-            for (assoc_pred, assoc_pred_span) in cx
-                .tcx
-                .explicit_item_bounds(proj.projection_ty.def_id)
-                .iter_instantiated_copied(cx.tcx, proj.projection_ty.args)
-            {
-                let assoc_pred = assoc_pred.fold_with(proj_replacer);
-                let Ok(assoc_pred) = traits::fully_normalize(
-                    infcx,
-                    traits::ObligationCause::dummy(),
-                    cx.param_env,
-                    assoc_pred,
-                ) else {
-                    continue;
+                let proj_ty =
+                    Ty::new_projection(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.args);
+                // For every instance of the projection type in the bounds,
+                // replace them with the term we're assigning to the associated
+                // type in our opaque type.
+                let proj_replacer = &mut BottomUpFolder {
+                    tcx: cx.tcx,
+                    ty_op: |ty| if ty == proj_ty { proj_term } else { ty },
+                    lt_op: |lt| lt,
+                    ct_op: |ct| ct,
                 };
-                // If that predicate doesn't hold modulo regions (but passed during type-check),
-                // then we must've taken advantage of the hack in `project_and_unify_types` where
-                // we replace opaques with inference vars. Emit a warning!
-                if !infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new(
-                    cx.tcx,
-                    traits::ObligationCause::dummy(),
-                    cx.param_env,
-                    assoc_pred,
-                )) {
-                    // If it's a trait bound and an opaque that doesn't satisfy it,
-                    // then we can emit a suggestion to add the bound.
-                    let add_bound = match (proj_term.kind(), assoc_pred.kind().skip_binder()) {
-                        (
-                            ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }),
-                            ty::ClauseKind::Trait(trait_pred),
-                        ) => Some(AddBound {
-                            suggest_span: cx.tcx.def_span(*def_id).shrink_to_hi(),
-                            trait_ref: trait_pred.print_modifiers_and_trait_path(),
-                        }),
-                        _ => None,
+                // For example, in `impl Trait<Assoc = impl Send>`, for all of the bounds on `Assoc`,
+                // e.g. `type Assoc: OtherTrait`, replace `<impl Trait as Trait>::Assoc: OtherTrait`
+                // with `impl Send: OtherTrait`.
+                for (assoc_pred, assoc_pred_span) in cx
+                    .tcx
+                    .explicit_item_bounds(proj.projection_ty.def_id)
+                    .iter_instantiated_copied(cx.tcx, proj.projection_ty.args)
+                {
+                    let assoc_pred = assoc_pred.fold_with(proj_replacer);
+                    let Ok(assoc_pred) = traits::fully_normalize(
+                        infcx,
+                        traits::ObligationCause::dummy(),
+                        cx.param_env,
+                        assoc_pred,
+                    ) else {
+                        continue;
                     };
-                    cx.emit_spanned_lint(
-                        OPAQUE_HIDDEN_INFERRED_BOUND,
-                        pred_span,
-                        OpaqueHiddenInferredBoundLint {
-                            ty: Ty::new_opaque(
-                                cx.tcx,
-                                def_id,
-                                ty::GenericArgs::identity_for_item(cx.tcx, def_id),
-                            ),
-                            proj_ty: proj_term,
-                            assoc_pred_span,
-                            add_bound,
-                        },
-                    );
+
+                    // If that predicate doesn't hold modulo regions (but passed during type-check),
+                    // then we must've taken advantage of the hack in `project_and_unify_types` where
+                    // we replace opaques with inference vars. Emit a warning!
+                    if !infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new(
+                        cx.tcx,
+                        traits::ObligationCause::dummy(),
+                        cx.param_env,
+                        assoc_pred,
+                    )) {
+                        // If it's a trait bound and an opaque that doesn't satisfy it,
+                        // then we can emit a suggestion to add the bound.
+                        let add_bound = match (proj_term.kind(), assoc_pred.kind().skip_binder()) {
+                            (
+                                ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }),
+                                ty::ClauseKind::Trait(trait_pred),
+                            ) => Some(AddBound {
+                                suggest_span: cx.tcx.def_span(*def_id).shrink_to_hi(),
+                                trait_ref: trait_pred.print_modifiers_and_trait_path(),
+                            }),
+                            _ => None,
+                        };
+
+                        cx.emit_span_lint(
+                            OPAQUE_HIDDEN_INFERRED_BOUND,
+                            pred_span,
+                            OpaqueHiddenInferredBoundLint {
+                                ty: Ty::new_opaque(
+                                    cx.tcx,
+                                    def_id,
+                                    ty::GenericArgs::identity_for_item(cx.tcx, def_id),
+                                ),
+                                proj_ty: proj_term,
+                                assoc_pred_span,
+                                add_bound,
+                            },
+                        );
+                    }
                 }
-            }
+            });
         }
     }
 }
diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs
index fce750c9b55..160d42caa9e 100644
--- a/compiler/rustc_lint/src/pass_by_value.rs
+++ b/compiler/rustc_lint/src/pass_by_value.rs
@@ -29,7 +29,7 @@ impl<'tcx> LateLintPass<'tcx> for PassByValue {
                     }
                 }
                 if let Some(t) = path_for_pass_by_value(cx, inner_ty) {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         PASS_BY_VALUE,
                         ty.span,
                         PassByValueDiag { ty: t, suggestion: ty.span },
diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs
index 4ac8a5ceb85..8038115ef51 100644
--- a/compiler/rustc_lint/src/ptr_nulls.rs
+++ b/compiler/rustc_lint/src/ptr_nulls.rs
@@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
                     )
                     && let Some(diag) = incorrect_check(cx, arg) =>
             {
-                cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
+                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
             }
 
             // Catching:
@@ -110,7 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
                     )
                     && let Some(diag) = incorrect_check(cx, receiver) =>
             {
-                cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
+                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
             }
 
             ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => {
@@ -134,7 +134,7 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
                             && let LitKind::Int(v, _) = spanned.node
                             && v == 0 =>
                     {
-                        cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
+                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
                     }
 
                     // Catching:
@@ -145,7 +145,7 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
                             && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
                             && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) =>
                     {
-                        cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
+                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
                     }
 
                     _ => {}
diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs
index 9a8b14b4907..1681ac2f1e5 100644
--- a/compiler/rustc_lint/src/redundant_semicolon.rs
+++ b/compiler/rustc_lint/src/redundant_semicolon.rs
@@ -47,7 +47,7 @@ fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, boo
             return;
         }
 
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             REDUNDANT_SEMICOLONS,
             span,
             RedundantSemicolonsDiag { multiple, suggestion: span },
diff --git a/compiler/rustc_lint/src/reference_casting.rs b/compiler/rustc_lint/src/reference_casting.rs
index 96290288f07..f386db9d8db 100644
--- a/compiler/rustc_lint/src/reference_casting.rs
+++ b/compiler/rustc_lint/src/reference_casting.rs
@@ -1,6 +1,7 @@
 use rustc_ast::Mutability;
 use rustc_hir::{Expr, ExprKind, UnOp};
-use rustc_middle::ty::{self, TypeAndMut};
+use rustc_middle::ty::layout::LayoutOf as _;
+use rustc_middle::ty::{self, layout::TyAndLayout, TypeAndMut};
 use rustc_span::sym;
 
 use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext};
@@ -38,16 +39,22 @@ declare_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]);
 impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
         if let Some((e, pat)) = borrow_or_assign(cx, expr) {
-            if matches!(pat, PatternKind::Borrow { mutbl: Mutability::Mut } | PatternKind::Assign) {
-                let init = cx.expr_or_init(e);
+            let init = cx.expr_or_init(e);
+            let orig_cast = if init.span != e.span { Some(init.span) } else { None };
+
+            // small cache to avoid recomputing needlesly computing peel_casts of init
+            let mut peel_casts = {
+                let mut peel_casts_cache = None;
+                move || *peel_casts_cache.get_or_insert_with(|| peel_casts(cx, init))
+            };
 
-                let Some(ty_has_interior_mutability) = is_cast_from_ref_to_mut_ptr(cx, init) else {
-                    return;
-                };
-                let orig_cast = if init.span != e.span { Some(init.span) } else { None };
+            if matches!(pat, PatternKind::Borrow { mutbl: Mutability::Mut } | PatternKind::Assign)
+                && let Some(ty_has_interior_mutability) =
+                    is_cast_from_ref_to_mut_ptr(cx, init, &mut peel_casts)
+            {
                 let ty_has_interior_mutability = ty_has_interior_mutability.then_some(());
 
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     INVALID_REFERENCE_CASTING,
                     expr.span,
                     if pat == PatternKind::Assign {
@@ -63,6 +70,23 @@ impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
                     },
                 );
             }
+
+            if let Some((from_ty_layout, to_ty_layout, e_alloc)) =
+                is_cast_to_bigger_memory_layout(cx, init, &mut peel_casts)
+            {
+                cx.emit_span_lint(
+                    INVALID_REFERENCE_CASTING,
+                    expr.span,
+                    InvalidReferenceCastingDiag::BiggerLayout {
+                        orig_cast,
+                        alloc: e_alloc.span,
+                        from_ty: from_ty_layout.ty,
+                        from_size: from_ty_layout.layout.size().bytes(),
+                        to_ty: to_ty_layout.ty,
+                        to_size: to_ty_layout.layout.size().bytes(),
+                    },
+                );
+            }
         }
     }
 }
@@ -124,6 +148,7 @@ fn borrow_or_assign<'tcx>(
 fn is_cast_from_ref_to_mut_ptr<'tcx>(
     cx: &LateContext<'tcx>,
     orig_expr: &'tcx Expr<'tcx>,
+    mut peel_casts: impl FnMut() -> (&'tcx Expr<'tcx>, bool),
 ) -> Option<bool> {
     let end_ty = cx.typeck_results().node_type(orig_expr.hir_id);
 
@@ -132,7 +157,7 @@ fn is_cast_from_ref_to_mut_ptr<'tcx>(
         return None;
     }
 
-    let (e, need_check_freeze) = peel_casts(cx, orig_expr);
+    let (e, need_check_freeze) = peel_casts();
 
     let start_ty = cx.typeck_results().node_type(e.hir_id);
     if let ty::Ref(_, inner_ty, Mutability::Not) = start_ty.kind() {
@@ -151,6 +176,56 @@ fn is_cast_from_ref_to_mut_ptr<'tcx>(
     }
 }
 
+fn is_cast_to_bigger_memory_layout<'tcx>(
+    cx: &LateContext<'tcx>,
+    orig_expr: &'tcx Expr<'tcx>,
+    mut peel_casts: impl FnMut() -> (&'tcx Expr<'tcx>, bool),
+) -> Option<(TyAndLayout<'tcx>, TyAndLayout<'tcx>, Expr<'tcx>)> {
+    let end_ty = cx.typeck_results().node_type(orig_expr.hir_id);
+
+    let ty::RawPtr(TypeAndMut { ty: inner_end_ty, mutbl: _ }) = end_ty.kind() else {
+        return None;
+    };
+
+    let (e, _) = peel_casts();
+    let start_ty = cx.typeck_results().node_type(e.hir_id);
+
+    let ty::Ref(_, inner_start_ty, _) = start_ty.kind() else {
+        return None;
+    };
+
+    // try to find the underlying allocation
+    let e_alloc = cx.expr_or_init(e);
+    let e_alloc =
+        if let ExprKind::AddrOf(_, _, inner_expr) = e_alloc.kind { inner_expr } else { e_alloc };
+    let alloc_ty = cx.typeck_results().node_type(e_alloc.hir_id);
+
+    // if we do not find it we bail out, as this may not be UB
+    // see https://github.com/rust-lang/unsafe-code-guidelines/issues/256
+    if alloc_ty.is_any_ptr() {
+        return None;
+    }
+
+    let from_layout = cx.layout_of(*inner_start_ty).ok()?;
+
+    // if the type isn't sized, we bail out, instead of potentially giving
+    // the user a meaningless warning.
+    if from_layout.is_unsized() {
+        return None;
+    }
+
+    let alloc_layout = cx.layout_of(alloc_ty).ok()?;
+    let to_layout = cx.layout_of(*inner_end_ty).ok()?;
+
+    if to_layout.layout.size() > from_layout.layout.size()
+        && to_layout.layout.size() > alloc_layout.layout.size()
+    {
+        Some((from_layout, to_layout, *e_alloc))
+    } else {
+        None
+    }
+}
+
 fn peel_casts<'tcx>(cx: &LateContext<'tcx>, mut e: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, bool) {
     let mut gone_trough_unsafe_cell_raw_get = false;
 
diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs
index e812493b3dd..789f154eac5 100644
--- a/compiler/rustc_lint/src/traits.rs
+++ b/compiler/rustc_lint/src/traits.rs
@@ -101,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
                     continue;
                 }
                 let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return };
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     DROP_BOUNDS,
                     span,
                     DropTraitConstraintsDiag { predicate, tcx: cx.tcx, def_id },
@@ -116,7 +116,7 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
             let def_id = bound.trait_ref.trait_def_id();
             if cx.tcx.lang_items().drop_trait() == def_id {
                 let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return };
-                cx.emit_spanned_lint(DYN_DROP, bound.span, DropGlue { tcx: cx.tcx, def_id });
+                cx.emit_span_lint(DYN_DROP, bound.span, DropGlue { tcx: cx.tcx, def_id });
             }
         }
     }
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index 6dade43a183..51fe08d4af1 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -14,7 +14,7 @@ use crate::{LateContext, LateLintPass, LintContext};
 use rustc_ast as ast;
 use rustc_attr as attr;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::DiagnosticMessage;
+use rustc_errors::DiagMessage;
 use rustc_hir as hir;
 use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
 use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton};
@@ -29,7 +29,6 @@ use rustc_span::{Span, Symbol};
 use rustc_target::abi::{Abi, Size, WrappingRange};
 use rustc_target::abi::{Integer, TagEncoding, Variants};
 use rustc_target::spec::abi::Abi as SpecAbi;
-use rustc_type_ir::DynKind;
 
 use std::iter;
 use std::ops::ControlFlow;
@@ -201,8 +200,7 @@ fn lint_overflowing_range_endpoint<'tcx>(
     ty: &str,
 ) -> bool {
     // Look past casts to support cases like `0..256 as u8`
-    let (expr, lit_span) = if let Node::Expr(par_expr) =
-        cx.tcx.hir_node(cx.tcx.hir().parent_id(expr.hir_id))
+    let (expr, lit_span) = if let Node::Expr(par_expr) = cx.tcx.parent_hir_node(expr.hir_id)
         && let ExprKind::Cast(_, _) = par_expr.kind
     {
         (par_expr, expr.span)
@@ -212,9 +210,8 @@ fn lint_overflowing_range_endpoint<'tcx>(
 
     // We only want to handle exclusive (`..`) ranges,
     // which are represented as `ExprKind::Struct`.
-    let par_id = cx.tcx.hir().parent_id(expr.hir_id);
-    let Node::ExprField(field) = cx.tcx.hir_node(par_id) else { return false };
-    let Node::Expr(struct_expr) = cx.tcx.hir().get_parent(field.hir_id) else { return false };
+    let Node::ExprField(field) = cx.tcx.parent_hir_node(expr.hir_id) else { return false };
+    let Node::Expr(struct_expr) = cx.tcx.parent_hir_node(field.hir_id) else { return false };
     if !is_range_literal(struct_expr) {
         return false;
     };
@@ -255,7 +252,7 @@ fn lint_overflowing_range_endpoint<'tcx>(
         }
     };
 
-    cx.emit_spanned_lint(
+    cx.emit_span_lint(
         OVERFLOWING_LITERALS,
         struct_expr.span,
         RangeEndpointOutOfRange { ty, sub: sub_sugg },
@@ -372,7 +369,7 @@ fn report_bin_hex_error(
         })
         .flatten();
 
-    cx.emit_spanned_lint(
+    cx.emit_span_lint(
         OVERFLOWING_LITERALS,
         expr.span,
         OverflowingBinHex {
@@ -474,7 +471,7 @@ fn lint_int_literal<'tcx>(
         let help = get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative)
             .map(|suggestion_ty| OverflowingIntHelp { suggestion_ty });
 
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             OVERFLOWING_LITERALS,
             span,
             OverflowingInt { ty: t.name_str(), lit, min, max, help },
@@ -493,16 +490,15 @@ fn lint_uint_literal<'tcx>(
     let lit_val: u128 = match lit.node {
         // _v is u8, within range by definition
         ast::LitKind::Byte(_v) => return,
-        ast::LitKind::Int(v, _) => v,
+        ast::LitKind::Int(v, _) => v.get(),
         _ => bug!(),
     };
     if lit_val < min || lit_val > max {
-        let parent_id = cx.tcx.hir().parent_id(e.hir_id);
-        if let Node::Expr(par_e) = cx.tcx.hir_node(parent_id) {
+        if let Node::Expr(par_e) = cx.tcx.parent_hir_node(e.hir_id) {
             match par_e.kind {
                 hir::ExprKind::Cast(..) => {
                     if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() {
-                        cx.emit_spanned_lint(
+                        cx.emit_span_lint(
                             OVERFLOWING_LITERALS,
                             par_e.span,
                             OnlyCastu8ToChar { span: par_e.span, literal: lit_val },
@@ -529,7 +525,7 @@ fn lint_uint_literal<'tcx>(
             );
             return;
         }
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             OVERFLOWING_LITERALS,
             e.span,
             OverflowingUInt {
@@ -556,7 +552,7 @@ fn lint_literal<'tcx>(
         ty::Int(t) => {
             match lit.node {
                 ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => {
-                    lint_int_literal(cx, type_limits, e, lit, t, v)
+                    lint_int_literal(cx, type_limits, e, lit, t, v.get())
                 }
                 _ => bug!(),
             };
@@ -565,13 +561,16 @@ fn lint_literal<'tcx>(
         ty::Float(t) => {
             let is_infinite = match lit.node {
                 ast::LitKind::Float(v, _) => match t {
+                    // FIXME(f16_f128): add this check once we have library support
+                    ty::FloatTy::F16 => Ok(false),
                     ty::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite),
                     ty::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite),
+                    ty::FloatTy::F128 => Ok(false),
                 },
                 _ => bug!(),
             };
             if is_infinite == Ok(true) {
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     OVERFLOWING_LITERALS,
                     e.span,
                     OverflowingLiteral {
@@ -655,7 +654,7 @@ fn lint_nan<'tcx>(
         _ => return,
     };
 
-    cx.emit_spanned_lint(INVALID_NAN_COMPARISONS, e.span, lint);
+    cx.emit_span_lint(INVALID_NAN_COMPARISONS, e.span, lint);
 }
 
 fn lint_wide_pointer<'tcx>(
@@ -675,7 +674,7 @@ fn lint_wide_pointer<'tcx>(
         }
         match ty.kind() {
             ty::RawPtr(TypeAndMut { mutbl: _, ty }) => (!ty.is_sized(cx.tcx, cx.param_env))
-                .then(|| (refs, matches!(ty.kind(), ty::Dynamic(_, _, DynKind::Dyn)))),
+                .then(|| (refs, matches!(ty.kind(), ty::Dynamic(_, _, ty::Dyn)))),
             _ => None,
         }
     };
@@ -701,7 +700,7 @@ fn lint_wide_pointer<'tcx>(
     let (Some(l_span), Some(r_span)) =
         (l.span.find_ancestor_inside(e.span), r.span.find_ancestor_inside(e.span))
     else {
-        return cx.emit_spanned_lint(
+        return cx.emit_span_lint(
             AMBIGUOUS_WIDE_POINTER_COMPARISONS,
             e.span,
             AmbiguousWidePointerComparisons::Spanless,
@@ -719,7 +718,7 @@ fn lint_wide_pointer<'tcx>(
     let deref_left = &*"*".repeat(l_ty_refs);
     let deref_right = &*"*".repeat(r_ty_refs);
 
-    cx.emit_spanned_lint(
+    cx.emit_span_lint(
         AMBIGUOUS_WIDE_POINTER_COMPARISONS,
         e.span,
         AmbiguousWidePointerComparisons::Spanful {
@@ -771,7 +770,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
             hir::ExprKind::Binary(binop, ref l, ref r) => {
                 if is_comparison(binop) {
                     if !check_limits(cx, binop, l, r) {
-                        cx.emit_spanned_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
+                        cx.emit_span_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
                     } else {
                         lint_nan(cx, e, binop, l, r);
                         lint_wide_pointer(cx, e, binop.node, l, r);
@@ -843,7 +842,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
                             ast::LitKind::Int(
                                 v,
                                 ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed,
-                            ) => v as i128,
+                            ) => v.get() as i128,
                             _ => return true,
                         },
                         _ => bug!(),
@@ -854,7 +853,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
                     let (min, max): (u128, u128) = uint_ty_range(uint_ty);
                     let lit_val: u128 = match lit.kind {
                         hir::ExprKind::Lit(li) => match li.node {
-                            ast::LitKind::Int(v, _) => v,
+                            ast::LitKind::Int(v, _) => v.get(),
                             _ => return true,
                         },
                         _ => bug!(),
@@ -961,7 +960,7 @@ struct ImproperCTypesVisitor<'a, 'tcx> {
 enum FfiResult<'tcx> {
     FfiSafe,
     FfiPhantom(Ty<'tcx>),
-    FfiUnsafe { ty: Ty<'tcx>, reason: DiagnosticMessage, help: Option<DiagnosticMessage> },
+    FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option<DiagMessage> },
 }
 
 pub(crate) fn nonnull_optimization_guaranteed<'tcx>(
@@ -1436,6 +1435,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
             | ty::Bound(..)
             | ty::Error(_)
             | ty::Closure(..)
+            | ty::CoroutineClosure(..)
             | ty::Coroutine(..)
             | ty::CoroutineWitness(..)
             | ty::Placeholder(..)
@@ -1447,8 +1447,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         &mut self,
         ty: Ty<'tcx>,
         sp: Span,
-        note: DiagnosticMessage,
-        help: Option<DiagnosticMessage>,
+        note: DiagMessage,
+        help: Option<DiagMessage>,
     ) {
         let lint = match self.mode {
             CItemKind::Declaration => IMPROPER_CTYPES,
@@ -1465,7 +1465,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         } else {
             None
         };
-        self.cx.emit_spanned_lint(
+        self.cx.emit_span_lint(
             lint,
             sp,
             ImproperCTypes { ty, desc, label: sp, help, note, span_note },
@@ -1475,9 +1475,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
     fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
         struct ProhibitOpaqueTypes;
         impl<'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for ProhibitOpaqueTypes {
-            type BreakTy = Ty<'tcx>;
+            type Result = ControlFlow<Ty<'tcx>>;
 
-            fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+            fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
                 if !ty.has_opaque_types() {
                     return ControlFlow::Continue(());
                 }
@@ -1590,10 +1590,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
     }
 
     fn is_internal_abi(&self, abi: SpecAbi) -> bool {
-        matches!(
-            abi,
-            SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic | SpecAbi::PlatformIntrinsic
-        )
+        matches!(abi, SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic)
     }
 
     /// Find any fn-ptr types with external ABIs in `ty`.
@@ -1624,9 +1621,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         }
 
         impl<'vis, 'a, 'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'vis, 'a, 'tcx> {
-            type BreakTy = Ty<'tcx>;
+            type Result = ControlFlow<Ty<'tcx>>;
 
-            fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+            fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
                 if let ty::FnPtr(sig) = ty.kind()
                     && !self.visitor.is_internal_abi(sig.abi())
                 {
@@ -1793,7 +1790,7 @@ impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {
             // We only warn if the largest variant is at least thrice as large as
             // the second-largest.
             if largest > slargest * 3 && slargest > 0 {
-                cx.emit_spanned_lint(
+                cx.emit_span_lint(
                     VARIANT_SIZE_DIFFERENCES,
                     enum_definition.variants[largest_index].span,
                     VariantSizeDifferencesDiag { largest },
@@ -1914,17 +1911,9 @@ impl InvalidAtomicOrdering {
             && (ordering == invalid_ordering || ordering == sym::AcqRel)
         {
             if method == sym::load {
-                cx.emit_spanned_lint(
-                    INVALID_ATOMIC_ORDERING,
-                    ordering_arg.span,
-                    AtomicOrderingLoad,
-                );
+                cx.emit_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, AtomicOrderingLoad);
             } else {
-                cx.emit_spanned_lint(
-                    INVALID_ATOMIC_ORDERING,
-                    ordering_arg.span,
-                    AtomicOrderingStore,
-                );
+                cx.emit_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, AtomicOrderingStore);
             };
         }
     }
@@ -1936,7 +1925,7 @@ impl InvalidAtomicOrdering {
             && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::fence | sym::compiler_fence))
             && Self::match_ordering(cx, &args[0]) == Some(sym::Relaxed)
         {
-            cx.emit_spanned_lint(INVALID_ATOMIC_ORDERING, args[0].span, AtomicOrderingFence);
+            cx.emit_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, AtomicOrderingFence);
         }
     }
 
@@ -1958,7 +1947,7 @@ impl InvalidAtomicOrdering {
         let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return };
 
         if matches!(fail_ordering, sym::Release | sym::AcqRel) {
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 INVALID_ATOMIC_ORDERING,
                 fail_order_arg.span,
                 InvalidAtomicOrderingDiag { method, fail_order_arg_span: fail_order_arg.span },
diff --git a/compiler/rustc_lint/src/unit_bindings.rs b/compiler/rustc_lint/src/unit_bindings.rs
index c80889f3ae7..b74430d8fa0 100644
--- a/compiler/rustc_lint/src/unit_bindings.rs
+++ b/compiler/rustc_lint/src/unit_bindings.rs
@@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for UnitBindings {
             && !matches!(init.kind, hir::ExprKind::Tup([]))
             && !matches!(local.pat.kind, hir::PatKind::Tuple([], ..))
         {
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 UNIT_BINDINGS,
                 local.span,
                 UnitBindingsDiag { label: local.pat.span },
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index 0386f2ec56c..3e10879e241 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -20,6 +20,7 @@ use rustc_span::symbol::Symbol;
 use rustc_span::symbol::{kw, sym};
 use rustc_span::{BytePos, Span};
 use std::iter;
+use std::ops::ControlFlow;
 
 declare_lint! {
     /// The `unused_must_use` lint detects unused result of a type flagged as
@@ -182,7 +183,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
         let mut op_warned = false;
 
         if let Some(must_use_op) = must_use_op {
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 UNUSED_MUST_USE,
                 expr.span,
                 UnusedOp {
@@ -202,7 +203,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
         }
 
         if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) {
-            cx.emit_spanned_lint(UNUSED_RESULTS, s.span, UnusedResult { ty });
+            cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty });
         }
 
         fn check_fn_must_use(
@@ -355,7 +356,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
                     Some(len) => is_ty_must_use(cx, ty, expr, span)
                         .map(|inner| MustUsePath::Array(Box::new(inner), len)),
                 },
-                ty::Closure(..) => Some(MustUsePath::Closure(span)),
+                ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)),
                 ty::Coroutine(def_id, ..) => {
                     // async fn should be treated as "implementor of `Future`"
                     let must_use = if cx.tcx.coroutine_is_async(def_id) {
@@ -494,21 +495,21 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
                     );
                 }
                 MustUsePath::Closure(span) => {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         UNUSED_MUST_USE,
                         *span,
                         UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post },
                     );
                 }
                 MustUsePath::Coroutine(span) => {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         UNUSED_MUST_USE,
                         *span,
                         UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post },
                     );
                 }
                 MustUsePath::Def(span, def_id, reason) => {
-                    cx.emit_spanned_lint(
+                    cx.emit_span_lint(
                         UNUSED_MUST_USE,
                         *span,
                         UnusedDef {
@@ -568,9 +569,9 @@ impl<'tcx> LateLintPass<'tcx> for PathStatements {
                     } else {
                         PathStatementDropSub::Help { span: s.span }
                     };
-                    cx.emit_spanned_lint(PATH_STATEMENTS, s.span, PathStatementDrop { sub })
+                    cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementDrop { sub })
                 } else {
-                    cx.emit_spanned_lint(PATH_STATEMENTS, s.span, PathStatementNoEffect);
+                    cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementNoEffect);
                 }
             }
         }
@@ -651,9 +652,11 @@ trait UnusedDelimLint {
 
     fn is_expr_delims_necessary(
         inner: &ast::Expr,
+        ctx: UnusedDelimsCtx,
         followed_by_block: bool,
-        followed_by_else: bool,
     ) -> bool {
+        let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse;
+
         if followed_by_else {
             match inner.kind {
                 ast::ExprKind::Binary(op, ..) if op.node.is_lazy() => return true,
@@ -662,6 +665,13 @@ trait UnusedDelimLint {
             }
         }
 
+        // Check it's range in LetScrutineeExpr
+        if let ast::ExprKind::Range(..) = inner.kind
+            && matches!(ctx, UnusedDelimsCtx::LetScrutineeExpr)
+        {
+            return true;
+        }
+
         // Check if LHS needs parens to prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`.
         {
             let mut innermost = inner;
@@ -744,21 +754,18 @@ trait UnusedDelimLint {
         // fn f(){(print!(รก
         // ```
         use rustc_ast::visit::{walk_expr, Visitor};
-        struct ErrExprVisitor {
-            has_error: bool,
-        }
+        struct ErrExprVisitor;
         impl<'ast> Visitor<'ast> for ErrExprVisitor {
-            fn visit_expr(&mut self, expr: &'ast ast::Expr) {
-                if let ExprKind::Err = expr.kind {
-                    self.has_error = true;
-                    return;
+            type Result = ControlFlow<()>;
+            fn visit_expr(&mut self, expr: &'ast ast::Expr) -> ControlFlow<()> {
+                if let ExprKind::Err(_) = expr.kind {
+                    ControlFlow::Break(())
+                } else {
+                    walk_expr(self, expr)
                 }
-                walk_expr(self, expr)
             }
         }
-        let mut visitor = ErrExprVisitor { has_error: false };
-        visitor.visit_expr(value);
-        if visitor.has_error {
+        if ErrExprVisitor.visit_expr(value).is_break() {
             return;
         }
         let spans = match value.kind {
@@ -824,7 +831,7 @@ trait UnusedDelimLint {
                 end_replace: hi_replace,
             }
         });
-        cx.emit_spanned_lint(
+        cx.emit_span_lint(
             self.lint(),
             primary_span,
             UnusedDelim { delim: Self::DELIM_STR, item: msg, suggestion },
@@ -907,7 +914,7 @@ trait UnusedDelimLint {
 
     fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
         match s.kind {
-            StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
+            StmtKind::Let(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
                 if let Some((init, els)) = local.kind.init_else_opt() {
                     let ctx = match els {
                         None => UnusedDelimsCtx::AssignedValue,
@@ -1007,8 +1014,7 @@ impl UnusedDelimLint for UnusedParens {
     ) {
         match value.kind {
             ast::ExprKind::Paren(ref inner) => {
-                let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse;
-                if !Self::is_expr_delims_necessary(inner, followed_by_block, followed_by_else)
+                if !Self::is_expr_delims_necessary(inner, ctx, followed_by_block)
                     && value.attrs.is_empty()
                     && !value.span.from_expansion()
                     && (ctx != UnusedDelimsCtx::LetScrutineeExpr
@@ -1071,17 +1077,31 @@ impl UnusedParens {
             self.emit_unused_delims(cx, value.span, spans, "pattern", keep_space, false);
         }
     }
+
+    fn cast_followed_by_lt(&self, expr: &ast::Expr) -> Option<ast::NodeId> {
+        if let ExprKind::Binary(op, lhs, _rhs) = &expr.kind
+            && (op.node == ast::BinOpKind::Lt || op.node == ast::BinOpKind::Shl)
+        {
+            let mut cur = lhs;
+            while let ExprKind::Binary(_, _, rhs) = &cur.kind {
+                cur = rhs;
+            }
+
+            if let ExprKind::Cast(_, ty) = &cur.kind
+                && let ast::TyKind::Paren(_) = &ty.kind
+            {
+                return Some(ty.id);
+            }
+        }
+        None
+    }
 }
 
 impl EarlyLintPass for UnusedParens {
     #[inline]
     fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
-        if let ExprKind::Binary(op, lhs, _rhs) = &e.kind
-            && (op.node == ast::BinOpKind::Lt || op.node == ast::BinOpKind::Shl)
-            && let ExprKind::Cast(_expr, ty) = &lhs.kind
-            && let ast::TyKind::Paren(_) = &ty.kind
-        {
-            self.parens_in_cast_in_lt.push(ty.id);
+        if let Some(ty_id) = self.cast_followed_by_lt(e) {
+            self.parens_in_cast_in_lt.push(ty_id);
         }
 
         match e.kind {
@@ -1133,17 +1153,13 @@ impl EarlyLintPass for UnusedParens {
     }
 
     fn check_expr_post(&mut self, _cx: &EarlyContext<'_>, e: &ast::Expr) {
-        if let ExprKind::Binary(op, lhs, _rhs) = &e.kind
-            && (op.node == ast::BinOpKind::Lt || op.node == ast::BinOpKind::Shl)
-            && let ExprKind::Cast(_expr, ty) = &lhs.kind
-            && let ast::TyKind::Paren(_) = &ty.kind
-        {
+        if let Some(ty_id) = self.cast_followed_by_lt(e) {
             let id = self
                 .parens_in_cast_in_lt
                 .pop()
                 .expect("check_expr and check_expr_post must balance");
             assert_eq!(
-                id, ty.id,
+                id, ty_id,
                 "check_expr, check_ty, and check_expr_post are called, in that order, by the visitor"
             );
         }
@@ -1156,7 +1172,7 @@ impl EarlyLintPass for UnusedParens {
             // Do not lint on `(..)` as that will result in the other arms being useless.
             Paren(_)
             // The other cases do not contain sub-patterns.
-            | Wild | Never | Rest | Lit(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) => {},
+            | Wild | Never | Rest | Lit(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) | Err(_) => {},
             // These are list-like patterns; parens can always be removed.
             TupleStruct(_, _, ps) | Tuple(ps) | Slice(ps) | Or(ps) => for p in ps {
                 self.check_unused_parens_pat(cx, p, false, false, keep_space);
@@ -1173,7 +1189,7 @@ impl EarlyLintPass for UnusedParens {
     }
 
     fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
-        if let StmtKind::Local(ref local) = s.kind {
+        if let StmtKind::Let(ref local) = s.kind {
             self.check_unused_parens_pat(cx, &local.pat, true, false, (true, false));
         }
 
@@ -1324,7 +1340,7 @@ impl UnusedDelimLint for UnusedBraces {
                 // FIXME(const_generics): handle paths when #67075 is fixed.
                 if let [stmt] = inner.stmts.as_slice() {
                     if let ast::StmtKind::Expr(ref expr) = stmt.kind {
-                        if !Self::is_expr_delims_necessary(expr, followed_by_block, false)
+                        if !Self::is_expr_delims_necessary(expr, ctx, followed_by_block)
                             && (ctx != UnusedDelimsCtx::AnonConst
                                 || (matches!(expr.kind, ast::ExprKind::Lit(_))
                                     && !expr.span.from_expansion()))
@@ -1497,7 +1513,7 @@ impl UnusedImportBraces {
                 ast::UseTreeKind::Nested(_) => return,
             };
 
-            cx.emit_spanned_lint(
+            cx.emit_span_lint(
                 UNUSED_IMPORT_BRACES,
                 item.span,
                 UnusedImportBracesDiag { node: node_name },
@@ -1554,10 +1570,10 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAllocation {
             if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind {
                 match m {
                     adjustment::AutoBorrowMutability::Not => {
-                        cx.emit_spanned_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationDiag);
+                        cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationDiag);
                     }
                     adjustment::AutoBorrowMutability::Mut { .. } => {
-                        cx.emit_spanned_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationMutDiag);
+                        cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationMutDiag);
                     }
                 };
             }