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/async_closures.rs129
-rw-r--r--compiler/rustc_lint/src/async_fn_in_trait.rs128
-rw-r--r--compiler/rustc_lint/src/builtin.rs3003
-rw-r--r--compiler/rustc_lint/src/context.rs1023
-rw-r--r--compiler/rustc_lint/src/context/diagnostics.rs441
-rw-r--r--compiler/rustc_lint/src/context/diagnostics/check_cfg.rs305
-rw-r--r--compiler/rustc_lint/src/deref_into_dyn_supertrait.rs106
-rw-r--r--compiler/rustc_lint/src/drop_forget_useless.rs249
-rw-r--r--compiler/rustc_lint/src/early.rs438
-rw-r--r--compiler/rustc_lint/src/enum_intrinsics_non_enums.rs84
-rw-r--r--compiler/rustc_lint/src/errors.rs110
-rw-r--r--compiler/rustc_lint/src/expect.rs38
-rw-r--r--compiler/rustc_lint/src/for_loops_over_fallibles.rs180
-rw-r--r--compiler/rustc_lint/src/foreign_modules.rs403
-rw-r--r--compiler/rustc_lint/src/hidden_unicode_codepoints.rs115
-rw-r--r--compiler/rustc_lint/src/impl_trait_overcaptures.rs418
-rw-r--r--compiler/rustc_lint/src/internal.rs560
-rw-r--r--compiler/rustc_lint/src/invalid_from_utf8.rs132
-rw-r--r--compiler/rustc_lint/src/late.rs455
-rw-r--r--compiler/rustc_lint/src/let_underscore.rs172
-rw-r--r--compiler/rustc_lint/src/levels.rs1125
-rw-r--r--compiler/rustc_lint/src/lib.rs607
-rw-r--r--compiler/rustc_lint/src/lints.rs2945
-rw-r--r--compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs155
-rw-r--r--compiler/rustc_lint/src/map_unit_fn.rs128
-rw-r--r--compiler/rustc_lint/src/methods.rs70
-rw-r--r--compiler/rustc_lint/src/multiple_supertrait_upcastable.rs60
-rw-r--r--compiler/rustc_lint/src/non_ascii_idents.rs392
-rw-r--r--compiler/rustc_lint/src/non_fmt_panic.rs335
-rw-r--r--compiler/rustc_lint/src/non_local_def.rs545
-rw-r--r--compiler/rustc_lint/src/nonstandard_style.rs554
-rw-r--r--compiler/rustc_lint/src/nonstandard_style/tests.rs21
-rw-r--r--compiler/rustc_lint/src/noop_method_call.rs161
-rw-r--r--compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs209
-rw-r--r--compiler/rustc_lint/src/pass_by_value.rs93
-rw-r--r--compiler/rustc_lint/src/passes.rs241
-rw-r--r--compiler/rustc_lint/src/ptr_nulls.rs157
-rw-r--r--compiler/rustc_lint/src/redundant_semicolon.rs57
-rw-r--r--compiler/rustc_lint/src/reference_casting.rs281
-rw-r--r--compiler/rustc_lint/src/shadowed_into_iter.rs157
-rw-r--r--compiler/rustc_lint/src/tests.rs26
-rw-r--r--compiler/rustc_lint/src/traits.rs124
-rw-r--r--compiler/rustc_lint/src/types.rs2059
-rw-r--r--compiler/rustc_lint/src/unit_bindings.rs72
-rw-r--r--compiler/rustc_lint/src/unused.rs1623
45 files changed, 20686 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/async_closures.rs b/compiler/rustc_lint/src/async_closures.rs
new file mode 100644
index 00000000000..33cc5738262
--- /dev/null
+++ b/compiler/rustc_lint/src/async_closures.rs
@@ -0,0 +1,129 @@
+use rustc_hir as hir;
+use rustc_macros::{LintDiagnostic, Subdiagnostic};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::Span;
+
+use crate::{LateContext, LateLintPass};
+
+declare_lint! {
+    /// The `closure_returning_async_block` lint detects cases where users
+    /// write a closure that returns an async block.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![warn(closure_returning_async_block)]
+    /// let c = |x: &str| async {};
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Using an async closure is preferable over a closure that returns an
+    /// async block, since async closures are less restrictive in how its
+    /// captures are allowed to be used.
+    ///
+    /// For example, this code does not work with a closure returning an async
+    /// block:
+    ///
+    /// ```rust,compile_fail
+    /// async fn callback(x: &str) {}
+    ///
+    /// let captured_str = String::new();
+    /// let c = move || async {
+    ///     callback(&captured_str).await;
+    /// };
+    /// ```
+    ///
+    /// But it does work with async closures:
+    ///
+    /// ```rust
+    /// #![feature(async_closure)]
+    ///
+    /// async fn callback(x: &str) {}
+    ///
+    /// let captured_str = String::new();
+    /// let c = async move || {
+    ///     callback(&captured_str).await;
+    /// };
+    /// ```
+    pub CLOSURE_RETURNING_ASYNC_BLOCK,
+    Allow,
+    "closure that returns `async {}` could be rewritten as an async closure",
+    @feature_gate = async_closure;
+}
+
+declare_lint_pass!(
+    /// Lint for potential usages of async closures and async fn trait bounds.
+    AsyncClosureUsage => [CLOSURE_RETURNING_ASYNC_BLOCK]
+);
+
+impl<'tcx> LateLintPass<'tcx> for AsyncClosureUsage {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+        let hir::ExprKind::Closure(&hir::Closure {
+            body,
+            kind: hir::ClosureKind::Closure,
+            fn_decl_span,
+            ..
+        }) = expr.kind
+        else {
+            return;
+        };
+
+        let mut body = cx.tcx.hir().body(body).value;
+
+        // Only peel blocks that have no expressions.
+        while let hir::ExprKind::Block(&hir::Block { stmts: [], expr: Some(tail), .. }, None) =
+            body.kind
+        {
+            body = tail;
+        }
+
+        let hir::ExprKind::Closure(&hir::Closure {
+            kind:
+                hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
+                    hir::CoroutineDesugaring::Async,
+                    hir::CoroutineSource::Block,
+                )),
+            fn_decl_span: async_decl_span,
+            ..
+        }) = body.kind
+        else {
+            return;
+        };
+
+        let deletion_span = cx.tcx.sess.source_map().span_extend_while_whitespace(async_decl_span);
+
+        cx.tcx.emit_node_span_lint(
+            CLOSURE_RETURNING_ASYNC_BLOCK,
+            expr.hir_id,
+            fn_decl_span,
+            ClosureReturningAsyncBlock {
+                async_decl_span,
+                sugg: AsyncClosureSugg {
+                    deletion_span,
+                    insertion_span: fn_decl_span.shrink_to_lo(),
+                },
+            },
+        );
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_closure_returning_async_block)]
+struct ClosureReturningAsyncBlock {
+    #[label]
+    async_decl_span: Span,
+    #[subdiagnostic]
+    sugg: AsyncClosureSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")]
+struct AsyncClosureSugg {
+    #[suggestion_part(code = "")]
+    deletion_span: Span,
+    #[suggestion_part(code = "async ")]
+    insertion_span: Span,
+}
diff --git a/compiler/rustc_lint/src/async_fn_in_trait.rs b/compiler/rustc_lint/src/async_fn_in_trait.rs
new file mode 100644
index 00000000000..6daee95dda6
--- /dev/null
+++ b/compiler/rustc_lint/src/async_fn_in_trait.rs
@@ -0,0 +1,128 @@
+use crate::lints::AsyncFnInTraitDiag;
+use crate::LateContext;
+use crate::LateLintPass;
+use rustc_hir as hir;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_trait_selection::error_reporting::traits::suggestions::suggest_desugaring_async_fn_to_impl_future_in_trait;
+
+declare_lint! {
+    /// The `async_fn_in_trait` lint detects use of `async fn` in the
+    /// definition of a publicly-reachable trait.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// pub trait Trait {
+    ///     async fn method(&self);
+    /// }
+    /// # fn main() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// When `async fn` is used in a trait definition, the trait does not
+    /// promise that the opaque [`Future`] returned by the associated function
+    /// or method will implement any [auto traits] such as [`Send`]. This may
+    /// be surprising and may make the associated functions or methods on the
+    /// trait less useful than intended. On traits exposed publicly from a
+    /// crate, this may affect downstream crates whose authors cannot alter
+    /// the trait definition.
+    ///
+    /// For example, this code is invalid:
+    ///
+    /// ```rust,compile_fail
+    /// pub trait Trait {
+    ///     async fn method(&self) {}
+    /// }
+    ///
+    /// fn test<T: Trait>(x: T) {
+    ///     fn spawn<T: Send>(_: T) {}
+    ///     spawn(x.method()); // Not OK.
+    /// }
+    /// ```
+    ///
+    /// This lint exists to warn authors of publicly-reachable traits that
+    /// they may want to consider desugaring the `async fn` to a normal `fn`
+    /// that returns an opaque `impl Future<..> + Send` type.
+    ///
+    /// For example, instead of:
+    ///
+    /// ```rust
+    /// pub trait Trait {
+    ///     async fn method(&self) {}
+    /// }
+    /// ```
+    ///
+    /// The author of the trait may want to write:
+    ///
+    ///
+    /// ```rust
+    /// use core::future::Future;
+    /// pub trait Trait {
+    ///     fn method(&self) -> impl Future<Output = ()> + Send { async {} }
+    /// }
+    /// ```
+    ///
+    /// This still allows the use of `async fn` within impls of the trait.
+    /// However, it also means that the trait will never be compatible with
+    /// impls where the returned [`Future`] of the method does not implement
+    /// `Send`.
+    ///
+    /// Conversely, if the trait is used only locally, if it is never used in
+    /// generic functions, or if it is only used in single-threaded contexts
+    /// that do not care whether the returned [`Future`] implements [`Send`],
+    /// then the lint may be suppressed.
+    ///
+    /// [`Future`]: https://doc.rust-lang.org/core/future/trait.Future.html
+    /// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
+    /// [auto traits]: https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits
+    pub ASYNC_FN_IN_TRAIT,
+    Warn,
+    "use of `async fn` in definition of a publicly-reachable trait"
+}
+
+declare_lint_pass!(
+    /// Lint for use of `async fn` in the definition of a publicly-reachable
+    /// trait.
+    AsyncFnInTrait => [ASYNC_FN_IN_TRAIT]
+);
+
+impl<'tcx> LateLintPass<'tcx> for AsyncFnInTrait {
+    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
+        if let hir::TraitItemKind::Fn(sig, body) = item.kind
+            && let hir::IsAsync::Async(async_span) = sig.header.asyncness
+        {
+            // RTN can be used to bound `async fn` in traits in a better way than "always"
+            if cx.tcx.features().return_type_notation {
+                return;
+            }
+
+            // Only need to think about library implications of reachable traits
+            if !cx.tcx.effective_visibilities(()).is_reachable(item.owner_id.def_id) {
+                return;
+            }
+
+            let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
+                sig.decl.output
+            else {
+                // This should never happen, but let's not ICE.
+                return;
+            };
+            let sugg = suggest_desugaring_async_fn_to_impl_future_in_trait(
+                cx.tcx,
+                sig,
+                body,
+                def.owner_id.def_id,
+                " + Send",
+            );
+            cx.tcx.emit_node_span_lint(
+                ASYNC_FN_IN_TRAIT,
+                item.hir_id(),
+                async_span,
+                AsyncFnInTraitDiag { sugg },
+            );
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
new file mode 100644
index 00000000000..485c214ac9d
--- /dev/null
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -0,0 +1,3003 @@
+//! Lints in the Rust compiler.
+//!
+//! This contains lints which can feasibly be implemented as their own
+//! AST visitor. Also see `rustc_session::lint::builtin`, which contains the
+//! definitions of lints that are emitted directly inside the main compiler.
+//!
+//! To add a new lint to rustc, declare it here using `declare_lint!()`.
+//! Then add code to emit the new lint in the appropriate circumstances.
+//! You can do that in an existing `LintPass` if it makes sense, or in a
+//! new `LintPass`, or using `Session::add_lint` elsewhere in the
+//! compiler. Only do the latter if the check can't be written cleanly as a
+//! `LintPass` (also, note that such lints will need to be defined in
+//! `rustc_session::lint::builtin`, not here).
+//!
+//! If you define a new `EarlyLintPass`, you will also need to add it to the
+//! `add_early_builtin!` or `add_early_builtin_with_new!` invocation in
+//! `lib.rs`. Use the former for unit-like structs and the latter for structs
+//! with a `pub fn new()`.
+//!
+//! If you define a new `LateLintPass`, you will also need to add it to the
+//! `late_lint_methods!` invocation in `lib.rs`.
+
+use crate::fluent_generated as fluent;
+use crate::{
+    errors::BuiltinEllipsisInclusiveRangePatterns,
+    lints::{
+        BuiltinAnonymousParams, BuiltinConstNoMangle, BuiltinDeprecatedAttrLink,
+        BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed, BuiltinDerefNullptr,
+        BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives,
+        BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures,
+        BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
+        BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
+        BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns,
+        BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasGenericBounds,
+        BuiltinTypeAliasGenericBoundsSuggestion, BuiltinTypeAliasWhereClause,
+        BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
+        BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe,
+        BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
+        BuiltinWhileTrue, InvalidAsmLabel, SuggestChangingAssocTypes,
+    },
+    EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext,
+};
+use ast::token::TokenKind;
+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, LintDiagnostic};
+use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
+use rustc_hir::intravisit::FnKind as HirFnKind;
+use rustc_hir::{Body, FnDecl, GenericParamKind, PatKind, PredicateOrigin};
+use rustc_middle::bug;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::print::with_no_trimmed_paths;
+use rustc_middle::ty::TypeVisitableExt;
+use rustc_middle::ty::Upcast;
+use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
+use rustc_session::lint::FutureIncompatibilityReason;
+use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
+use rustc_span::edition::Edition;
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{BytePos, InnerSpan, Span};
+use rustc_target::abi::Abi;
+use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt};
+use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
+use rustc_trait_selection::traits::{self, misc::type_allowed_to_implement_copy};
+
+use crate::nonstandard_style::{method_context, MethodLateContext};
+
+use std::fmt::Write;
+
+// hardwired lints from rustc_lint_defs
+pub use rustc_session::lint::builtin::*;
+
+declare_lint! {
+    /// The `while_true` lint detects `while true { }`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,no_run
+    /// while true {
+    ///
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// `while true` should be replaced with `loop`. A `loop` expression is
+    /// the preferred way to write an infinite loop because it more directly
+    /// expresses the intent of the loop.
+    WHILE_TRUE,
+    Warn,
+    "suggest using `loop { }` instead of `while true { }`"
+}
+
+declare_lint_pass!(WhileTrue => [WHILE_TRUE]);
+
+/// Traverse through any amount of parenthesis and return the first non-parens expression.
+fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
+    while let ast::ExprKind::Paren(sub) = &expr.kind {
+        expr = sub;
+    }
+    expr
+}
+
+impl EarlyLintPass for WhileTrue {
+    #[inline]
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        if let ast::ExprKind::While(cond, _, label) = &e.kind
+            && let ast::ExprKind::Lit(token_lit) = pierce_parens(cond).kind
+            && let token::Lit { kind: token::Bool, symbol: kw::True, .. } = token_lit
+            && !cond.span.from_expansion()
+        {
+            let condition_span = e.span.with_hi(cond.span.hi());
+            let replace = format!(
+                "{}loop",
+                label.map_or_else(String::new, |label| format!("{}: ", label.ident,))
+            );
+            cx.emit_span_lint(
+                WHILE_TRUE,
+                condition_span,
+                BuiltinWhileTrue { suggestion: condition_span, replace },
+            );
+        }
+    }
+}
+
+declare_lint! {
+    /// The `non_shorthand_field_patterns` lint detects using `Struct { x: x }`
+    /// instead of `Struct { x }` in a pattern.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// struct Point {
+    ///     x: i32,
+    ///     y: i32,
+    /// }
+    ///
+    ///
+    /// fn main() {
+    ///     let p = Point {
+    ///         x: 5,
+    ///         y: 5,
+    ///     };
+    ///
+    ///     match p {
+    ///         Point { x: x, y: y } => (),
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The preferred style is to avoid the repetition of specifying both the
+    /// field name and the binding name if both identifiers are the same.
+    NON_SHORTHAND_FIELD_PATTERNS,
+    Warn,
+    "using `Struct { x: x }` instead of `Struct { x }` in a pattern"
+}
+
+declare_lint_pass!(NonShorthandFieldPatterns => [NON_SHORTHAND_FIELD_PATTERNS]);
+
+impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns {
+    fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) {
+        if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind {
+            let variant = cx
+                .typeck_results()
+                .pat_ty(pat)
+                .ty_adt_def()
+                .expect("struct pattern type is not an ADT")
+                .variant_of_res(cx.qpath_res(qpath, pat.hir_id));
+            for fieldpat in field_pats {
+                if fieldpat.is_shorthand {
+                    continue;
+                }
+                if fieldpat.span.from_expansion() {
+                    // Don't lint if this is a macro expansion: macro authors
+                    // shouldn't have to worry about this kind of style issue
+                    // (Issue #49588)
+                    continue;
+                }
+                if let PatKind::Binding(binding_annot, _, ident, None) = fieldpat.pat.kind {
+                    if cx.tcx.find_field_index(ident, variant)
+                        == Some(cx.typeck_results().field_index(fieldpat.hir_id))
+                    {
+                        cx.emit_span_lint(
+                            NON_SHORTHAND_FIELD_PATTERNS,
+                            fieldpat.span,
+                            BuiltinNonShorthandFieldPatterns {
+                                ident,
+                                suggestion: fieldpat.span,
+                                prefix: binding_annot.prefix_str(),
+                            },
+                        );
+                    }
+                }
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `unsafe_code` lint catches usage of `unsafe` code and other
+    /// potentially unsound constructs like `no_mangle`, `export_name`,
+    /// and `link_section`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(unsafe_code)]
+    /// fn main() {
+    ///     unsafe {
+    ///
+    ///     }
+    /// }
+    ///
+    /// #[no_mangle]
+    /// fn func_0() { }
+    ///
+    /// #[export_name = "exported_symbol_name"]
+    /// pub fn name_in_rust() { }
+    ///
+    /// #[no_mangle]
+    /// #[link_section = ".example_section"]
+    /// pub static VAR1: u32 = 1;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// This lint is intended to restrict the usage of `unsafe` blocks and other
+    /// constructs (including, but not limited to `no_mangle`, `link_section`
+    /// and `export_name` attributes) wrong usage of which causes undefined
+    /// behavior.
+    UNSAFE_CODE,
+    Allow,
+    "usage of `unsafe` code and other potentially unsound constructs"
+}
+
+declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]);
+
+impl UnsafeCode {
+    fn report_unsafe(
+        &self,
+        cx: &EarlyContext<'_>,
+        span: Span,
+        decorate: impl for<'a> LintDiagnostic<'a, ()>,
+    ) {
+        // This comes from a macro that has `#[allow_internal_unsafe]`.
+        if span.allows_unsafe() {
+            return;
+        }
+
+        cx.emit_span_lint(UNSAFE_CODE, span, decorate);
+    }
+}
+
+impl EarlyLintPass for UnsafeCode {
+    fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+        if attr.has_name(sym::allow_internal_unsafe) {
+            self.report_unsafe(cx, attr.span, BuiltinUnsafe::AllowInternalUnsafe);
+        }
+    }
+
+    #[inline]
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        if let ast::ExprKind::Block(ref blk, _) = e.kind {
+            // Don't warn about generated blocks; that'll just pollute the output.
+            if blk.rules == ast::BlockCheckMode::Unsafe(ast::UserProvided) {
+                self.report_unsafe(cx, blk.span, BuiltinUnsafe::UnsafeBlock);
+            }
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
+        match it.kind {
+            ast::ItemKind::Trait(box ast::Trait { safety: ast::Safety::Unsafe(_), .. }) => {
+                self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeTrait);
+            }
+
+            ast::ItemKind::Impl(box ast::Impl { safety: ast::Safety::Unsafe(_), .. }) => {
+                self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeImpl);
+            }
+
+            ast::ItemKind::Fn(..) => {
+                if let Some(attr) = attr::find_by_name(&it.attrs, sym::no_mangle) {
+                    self.report_unsafe(cx, attr.span, BuiltinUnsafe::NoMangleFn);
+                }
+
+                if let Some(attr) = attr::find_by_name(&it.attrs, sym::export_name) {
+                    self.report_unsafe(cx, attr.span, BuiltinUnsafe::ExportNameFn);
+                }
+
+                if let Some(attr) = attr::find_by_name(&it.attrs, sym::link_section) {
+                    self.report_unsafe(cx, attr.span, BuiltinUnsafe::LinkSectionFn);
+                }
+            }
+
+            ast::ItemKind::Static(..) => {
+                if let Some(attr) = attr::find_by_name(&it.attrs, sym::no_mangle) {
+                    self.report_unsafe(cx, attr.span, BuiltinUnsafe::NoMangleStatic);
+                }
+
+                if let Some(attr) = attr::find_by_name(&it.attrs, sym::export_name) {
+                    self.report_unsafe(cx, attr.span, BuiltinUnsafe::ExportNameStatic);
+                }
+
+                if let Some(attr) = attr::find_by_name(&it.attrs, sym::link_section) {
+                    self.report_unsafe(cx, attr.span, BuiltinUnsafe::LinkSectionStatic);
+                }
+            }
+
+            ast::ItemKind::GlobalAsm(..) => {
+                self.report_unsafe(cx, it.span, BuiltinUnsafe::GlobalAsm);
+            }
+
+            ast::ItemKind::ForeignMod(ForeignMod { safety, .. }) => {
+                if let Safety::Unsafe(_) = safety {
+                    self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeExternBlock);
+                }
+            }
+
+            _ => {}
+        }
+    }
+
+    fn check_impl_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
+        if let ast::AssocItemKind::Fn(..) = it.kind {
+            if let Some(attr) = attr::find_by_name(&it.attrs, sym::no_mangle) {
+                self.report_unsafe(cx, attr.span, BuiltinUnsafe::NoMangleMethod);
+            }
+            if let Some(attr) = attr::find_by_name(&it.attrs, sym::export_name) {
+                self.report_unsafe(cx, attr.span, BuiltinUnsafe::ExportNameMethod);
+            }
+        }
+    }
+
+    fn check_fn(&mut self, cx: &EarlyContext<'_>, fk: FnKind<'_>, span: Span, _: ast::NodeId) {
+        if let FnKind::Fn(
+            ctxt,
+            _,
+            ast::FnSig { header: ast::FnHeader { safety: ast::Safety::Unsafe(_), .. }, .. },
+            _,
+            _,
+            body,
+        ) = fk
+        {
+            let decorator = match ctxt {
+                FnCtxt::Foreign => return,
+                FnCtxt::Free => BuiltinUnsafe::DeclUnsafeFn,
+                FnCtxt::Assoc(_) if body.is_none() => BuiltinUnsafe::DeclUnsafeMethod,
+                FnCtxt::Assoc(_) => BuiltinUnsafe::ImplUnsafeMethod,
+            };
+            self.report_unsafe(cx, span, decorator);
+        }
+    }
+}
+
+declare_lint! {
+    /// The `missing_docs` lint detects missing documentation for public items.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(missing_docs)]
+    /// pub fn foo() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// This lint is intended to ensure that a library is well-documented.
+    /// Items without documentation can be difficult for users to understand
+    /// how to use properly.
+    ///
+    /// This lint is "allow" by default because it can be noisy, and not all
+    /// projects may want to enforce everything to be documented.
+    pub MISSING_DOCS,
+    Allow,
+    "detects missing documentation for public members",
+    report_in_external_macro
+}
+
+pub struct MissingDoc;
+
+impl_lint_pass!(MissingDoc => [MISSING_DOCS]);
+
+fn has_doc(attr: &ast::Attribute) -> bool {
+    if attr.is_doc_comment() {
+        return true;
+    }
+
+    if !attr.has_name(sym::doc) {
+        return false;
+    }
+
+    if attr.value_str().is_some() {
+        return true;
+    }
+
+    if let Some(list) = attr.meta_item_list() {
+        for meta in list {
+            if meta.has_name(sym::hidden) {
+                return true;
+            }
+        }
+    }
+
+    false
+}
+
+impl MissingDoc {
+    fn check_missing_docs_attrs(
+        &self,
+        cx: &LateContext<'_>,
+        def_id: LocalDefId,
+        article: &'static str,
+        desc: &'static str,
+    ) {
+        // If we're building a test harness, then warning about
+        // documentation is probably not really relevant right now.
+        if cx.sess().opts.test {
+            return;
+        }
+
+        // Only check publicly-visible items, using the result from the privacy pass.
+        // It's an option so the crate root can also use this function (it doesn't
+        // have a `NodeId`).
+        if def_id != CRATE_DEF_ID {
+            if !cx.effective_visibilities.is_exported(def_id) {
+                return;
+            }
+        }
+
+        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_span_lint(
+                MISSING_DOCS,
+                cx.tcx.def_span(def_id),
+                BuiltinMissingDoc { article, desc },
+            );
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MissingDoc {
+    fn check_crate(&mut self, cx: &LateContext<'_>) {
+        self.check_missing_docs_attrs(cx, CRATE_DEF_ID, "the", "crate");
+    }
+
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        // Previously the Impl and Use types have been excluded from missing docs,
+        // so we will continue to exclude them for compatibility.
+        //
+        // The documentation on `ExternCrate` is not used at the moment so no need to warn for it.
+        if let hir::ItemKind::Impl(..) | hir::ItemKind::Use(..) | hir::ItemKind::ExternCrate(_) =
+            it.kind
+        {
+            return;
+        }
+
+        let (article, desc) = cx.tcx.article_and_description(it.owner_id.to_def_id());
+        self.check_missing_docs_attrs(cx, it.owner_id.def_id, article, desc);
+    }
+
+    fn check_trait_item(&mut self, cx: &LateContext<'_>, trait_item: &hir::TraitItem<'_>) {
+        let (article, desc) = cx.tcx.article_and_description(trait_item.owner_id.to_def_id());
+
+        self.check_missing_docs_attrs(cx, trait_item.owner_id.def_id, article, desc);
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
+        let context = method_context(cx, impl_item.owner_id.def_id);
+
+        match context {
+            // If the method is an impl for a trait, don't doc.
+            MethodLateContext::TraitImpl => return,
+            MethodLateContext::TraitAutoImpl => {}
+            // If the method is an impl for an item with docs_hidden, don't doc.
+            MethodLateContext::PlainImpl => {
+                let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id());
+                let impl_ty = cx.tcx.type_of(parent).instantiate_identity();
+                let outerdef = match impl_ty.kind() {
+                    ty::Adt(def, _) => Some(def.did()),
+                    ty::Foreign(def_id) => Some(*def_id),
+                    _ => None,
+                };
+                let is_hidden = match outerdef {
+                    Some(id) => cx.tcx.is_doc_hidden(id),
+                    None => false,
+                };
+                if is_hidden {
+                    return;
+                }
+            }
+        }
+
+        let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id());
+        self.check_missing_docs_attrs(cx, impl_item.owner_id.def_id, article, desc);
+    }
+
+    fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'_>) {
+        let (article, desc) = cx.tcx.article_and_description(foreign_item.owner_id.to_def_id());
+        self.check_missing_docs_attrs(cx, foreign_item.owner_id.def_id, article, desc);
+    }
+
+    fn check_field_def(&mut self, cx: &LateContext<'_>, sf: &hir::FieldDef<'_>) {
+        if !sf.is_positional() {
+            self.check_missing_docs_attrs(cx, sf.def_id, "a", "struct field")
+        }
+    }
+
+    fn check_variant(&mut self, cx: &LateContext<'_>, v: &hir::Variant<'_>) {
+        self.check_missing_docs_attrs(cx, v.def_id, "a", "variant");
+    }
+}
+
+declare_lint! {
+    /// The `missing_copy_implementations` lint detects potentially-forgotten
+    /// implementations of [`Copy`] for public types.
+    ///
+    /// [`Copy`]: https://doc.rust-lang.org/std/marker/trait.Copy.html
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(missing_copy_implementations)]
+    /// pub struct Foo {
+    ///     pub field: i32
+    /// }
+    /// # fn main() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Historically (before 1.0), types were automatically marked as `Copy`
+    /// if possible. This was changed so that it required an explicit opt-in
+    /// by implementing the `Copy` trait. As part of this change, a lint was
+    /// added to alert if a copyable type was not marked `Copy`.
+    ///
+    /// This lint is "allow" by default because this code isn't bad; it is
+    /// common to write newtypes like this specifically so that a `Copy` type
+    /// is no longer `Copy`. `Copy` types can result in unintended copies of
+    /// large data which can impact performance.
+    pub MISSING_COPY_IMPLEMENTATIONS,
+    Allow,
+    "detects potentially-forgotten implementations of `Copy`"
+}
+
+declare_lint_pass!(MissingCopyImplementations => [MISSING_COPY_IMPLEMENTATIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        if !cx.effective_visibilities.is_reachable(item.owner_id.def_id) {
+            return;
+        }
+        let (def, ty) = match item.kind {
+            hir::ItemKind::Struct(_, ast_generics) => {
+                if !ast_generics.params.is_empty() {
+                    return;
+                }
+                let def = cx.tcx.adt_def(item.owner_id);
+                (def, Ty::new_adt(cx.tcx, def, ty::List::empty()))
+            }
+            hir::ItemKind::Union(_, ast_generics) => {
+                if !ast_generics.params.is_empty() {
+                    return;
+                }
+                let def = cx.tcx.adt_def(item.owner_id);
+                (def, Ty::new_adt(cx.tcx, def, ty::List::empty()))
+            }
+            hir::ItemKind::Enum(_, ast_generics) => {
+                if !ast_generics.params.is_empty() {
+                    return;
+                }
+                let def = cx.tcx.adt_def(item.owner_id);
+                (def, Ty::new_adt(cx.tcx, def, ty::List::empty()))
+            }
+            _ => return,
+        };
+        if def.has_dtor(cx.tcx) {
+            return;
+        }
+
+        // If the type contains a raw pointer, it may represent something like a handle,
+        // and recommending Copy might be a bad idea.
+        for field in def.all_fields() {
+            let did = field.did;
+            if cx.tcx.type_of(did).instantiate_identity().is_unsafe_ptr() {
+                return;
+            }
+        }
+        if ty.is_copy_modulo_regions(cx.tcx, cx.param_env) {
+            return;
+        }
+        if type_implements_negative_copy_modulo_regions(cx.tcx, ty, cx.param_env) {
+            return;
+        }
+        if def.is_variant_list_non_exhaustive()
+            || def.variants().iter().any(|variant| variant.is_field_list_non_exhaustive())
+        {
+            return;
+        }
+
+        // We shouldn't recommend implementing `Copy` on stateful things,
+        // such as iterators.
+        if let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
+            && cx
+                .tcx
+                .infer_ctxt()
+                .build()
+                .type_implements_trait(iter_trait, [ty], cx.param_env)
+                .must_apply_modulo_regions()
+        {
+            return;
+        }
+
+        // Default value of clippy::trivially_copy_pass_by_ref
+        const MAX_SIZE: u64 = 256;
+
+        if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()) {
+            if size > MAX_SIZE {
+                return;
+            }
+        }
+
+        if type_allowed_to_implement_copy(
+            cx.tcx,
+            cx.param_env,
+            ty,
+            traits::ObligationCause::misc(item.span, item.owner_id.def_id),
+        )
+        .is_ok()
+        {
+            cx.emit_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, BuiltinMissingCopyImpl);
+        }
+    }
+}
+
+/// Check whether a `ty` has a negative `Copy` implementation, ignoring outlives constraints.
+fn type_implements_negative_copy_modulo_regions<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    ty: Ty<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+) -> bool {
+    let trait_ref = ty::TraitRef::new(tcx, tcx.require_lang_item(hir::LangItem::Copy, None), [ty]);
+    let pred = ty::TraitPredicate { trait_ref, polarity: ty::PredicatePolarity::Negative };
+    let obligation = traits::Obligation {
+        cause: traits::ObligationCause::dummy(),
+        param_env,
+        recursion_depth: 0,
+        predicate: pred.upcast(tcx),
+    };
+
+    tcx.infer_ctxt().build().predicate_must_hold_modulo_regions(&obligation)
+}
+
+declare_lint! {
+    /// The `missing_debug_implementations` lint detects missing
+    /// implementations of [`fmt::Debug`] for public types.
+    ///
+    /// [`fmt::Debug`]: https://doc.rust-lang.org/std/fmt/trait.Debug.html
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(missing_debug_implementations)]
+    /// pub struct Foo;
+    /// # fn main() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Having a `Debug` implementation on all types can assist with
+    /// debugging, as it provides a convenient way to format and display a
+    /// value. Using the `#[derive(Debug)]` attribute will automatically
+    /// generate a typical implementation, or a custom implementation can be
+    /// added by manually implementing the `Debug` trait.
+    ///
+    /// This lint is "allow" by default because adding `Debug` to all types can
+    /// have a negative impact on compile time and code size. It also requires
+    /// boilerplate to be added to every type, which can be an impediment.
+    MISSING_DEBUG_IMPLEMENTATIONS,
+    Allow,
+    "detects missing implementations of Debug"
+}
+
+#[derive(Default)]
+pub(crate) struct MissingDebugImplementations;
+
+impl_lint_pass!(MissingDebugImplementations => [MISSING_DEBUG_IMPLEMENTATIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        if !cx.effective_visibilities.is_reachable(item.owner_id.def_id) {
+            return;
+        }
+
+        match item.kind {
+            hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {}
+            _ => return,
+        }
+
+        // Avoid listing trait impls if the trait is allowed.
+        let (level, _) = cx.tcx.lint_level_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id());
+        if level == Level::Allow {
+            return;
+        }
+
+        let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else { return };
+
+        let has_impl = cx
+            .tcx
+            .non_blanket_impls_for_ty(debug, cx.tcx.type_of(item.owner_id).instantiate_identity())
+            .next()
+            .is_some();
+        if !has_impl {
+            cx.emit_span_lint(
+                MISSING_DEBUG_IMPLEMENTATIONS,
+                item.span,
+                BuiltinMissingDebugImpl { tcx: cx.tcx, def_id: debug },
+            );
+        }
+    }
+}
+
+declare_lint! {
+    /// The `anonymous_parameters` lint detects anonymous parameters in trait
+    /// definitions.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2015,compile_fail
+    /// #![deny(anonymous_parameters)]
+    /// // edition 2015
+    /// pub trait Foo {
+    ///     fn foo(usize);
+    /// }
+    /// fn main() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// This syntax is mostly a historical accident, and can be worked around
+    /// quite easily by adding an `_` pattern or a descriptive identifier:
+    ///
+    /// ```rust
+    /// trait Foo {
+    ///     fn foo(_: usize);
+    /// }
+    /// ```
+    ///
+    /// This syntax is now a hard error in the 2018 edition. In the 2015
+    /// edition, this lint is "warn" by default. This lint
+    /// enables the [`cargo fix`] tool with the `--edition` flag to
+    /// automatically transition old code from the 2015 edition to 2018. The
+    /// tool will run this lint and automatically apply the
+    /// suggested fix from the compiler (which is to add `_` to each
+    /// parameter). This provides a completely automated way to update old
+    /// code for a new edition. See [issue #41686] for more details.
+    ///
+    /// [issue #41686]: https://github.com/rust-lang/rust/issues/41686
+    /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
+    pub ANONYMOUS_PARAMETERS,
+    Warn,
+    "detects anonymous parameters",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018),
+        reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>",
+    };
+}
+
+declare_lint_pass!(
+    /// Checks for use of anonymous parameters (RFC 1685).
+    AnonymousParameters => [ANONYMOUS_PARAMETERS]
+);
+
+impl EarlyLintPass for AnonymousParameters {
+    fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
+        if cx.sess().edition() != Edition::Edition2015 {
+            // This is a hard error in future editions; avoid linting and erroring
+            return;
+        }
+        if let ast::AssocItemKind::Fn(box Fn { ref sig, .. }) = it.kind {
+            for arg in sig.decl.inputs.iter() {
+                if let ast::PatKind::Ident(_, ident, None) = arg.pat.kind {
+                    if ident.name == kw::Empty {
+                        let ty_snip = cx.sess().source_map().span_to_snippet(arg.ty.span);
+
+                        let (ty_snip, appl) = if let Ok(ref snip) = ty_snip {
+                            (snip.as_str(), Applicability::MachineApplicable)
+                        } else {
+                            ("<type>", Applicability::HasPlaceholders)
+                        };
+                        cx.emit_span_lint(
+                            ANONYMOUS_PARAMETERS,
+                            arg.pat.span,
+                            BuiltinAnonymousParams { suggestion: (arg.pat.span, appl), ty_snip },
+                        );
+                    }
+                }
+            }
+        }
+    }
+}
+
+/// Check for use of attributes which have been deprecated.
+#[derive(Clone)]
+pub struct DeprecatedAttr {
+    // This is not free to compute, so we want to keep it around, rather than
+    // compute it for every attribute.
+    depr_attrs: Vec<&'static BuiltinAttribute>,
+}
+
+impl_lint_pass!(DeprecatedAttr => []);
+
+impl DeprecatedAttr {
+    pub fn new() -> DeprecatedAttr {
+        DeprecatedAttr { depr_attrs: deprecated_attributes() }
+    }
+}
+
+impl EarlyLintPass for DeprecatedAttr {
+    fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+        for BuiltinAttribute { name, gate, .. } in &self.depr_attrs {
+            if attr.ident().map(|ident| ident.name) == Some(*name) {
+                if let &AttributeGate::Gated(
+                    Stability::Deprecated(link, suggestion),
+                    name,
+                    reason,
+                    _,
+                ) = gate
+                {
+                    let suggestion = match suggestion {
+                        Some(msg) => {
+                            BuiltinDeprecatedAttrLinkSuggestion::Msg { suggestion: attr.span, msg }
+                        }
+                        None => {
+                            BuiltinDeprecatedAttrLinkSuggestion::Default { suggestion: attr.span }
+                        }
+                    };
+                    cx.emit_span_lint(
+                        DEPRECATED,
+                        attr.span,
+                        BuiltinDeprecatedAttrLink { name, reason, link, suggestion },
+                    );
+                }
+                return;
+            }
+        }
+        if attr.has_name(sym::no_start) || attr.has_name(sym::crate_id) {
+            cx.emit_span_lint(
+                DEPRECATED,
+                attr.span,
+                BuiltinDeprecatedAttrUsed {
+                    name: pprust::path_to_string(&attr.get_normal_item().path),
+                    suggestion: attr.span,
+                },
+            );
+        }
+    }
+}
+
+fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &[ast::Attribute]) {
+    use rustc_ast::token::CommentKind;
+
+    let mut attrs = attrs.iter().peekable();
+
+    // Accumulate a single span for sugared doc comments.
+    let mut sugared_span: Option<Span> = None;
+
+    while let Some(attr) = attrs.next() {
+        let is_doc_comment = attr.is_doc_comment();
+        if is_doc_comment {
+            sugared_span =
+                Some(sugared_span.map_or(attr.span, |span| span.with_hi(attr.span.hi())));
+        }
+
+        if attrs.peek().is_some_and(|next_attr| next_attr.is_doc_comment()) {
+            continue;
+        }
+
+        let span = sugared_span.take().unwrap_or(attr.span);
+
+        if is_doc_comment || attr.has_name(sym::doc) {
+            let sub = match attr.kind {
+                AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => {
+                    BuiltinUnusedDocCommentSub::PlainHelp
+                }
+                AttrKind::DocComment(CommentKind::Block, _) => {
+                    BuiltinUnusedDocCommentSub::BlockHelp
+                }
+            };
+            cx.emit_span_lint(
+                UNUSED_DOC_COMMENTS,
+                span,
+                BuiltinUnusedDocComment { kind: node_kind, label: node_span, sub },
+            );
+        }
+    }
+}
+
+impl EarlyLintPass for UnusedDocComment {
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
+        let kind = match stmt.kind {
+            ast::StmtKind::Let(..) => "statements",
+            // Disabled pending discussion in #78306
+            ast::StmtKind::Item(..) => return,
+            // expressions will be reported by `check_expr`.
+            ast::StmtKind::Empty
+            | ast::StmtKind::Semi(_)
+            | ast::StmtKind::Expr(_)
+            | ast::StmtKind::MacCall(_) => return,
+        };
+
+        warn_if_doc(cx, stmt.span, kind, stmt.kind.attrs());
+    }
+
+    fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
+        if let Some(body) = &arm.body {
+            let arm_span = arm.pat.span.with_hi(body.span.hi());
+            warn_if_doc(cx, arm_span, "match arms", &arm.attrs);
+        }
+    }
+
+    fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) {
+        if let ast::PatKind::Struct(_, _, fields, _) = &pat.kind {
+            for field in fields {
+                warn_if_doc(cx, field.span, "pattern fields", &field.attrs);
+            }
+        }
+    }
+
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+        warn_if_doc(cx, expr.span, "expressions", &expr.attrs);
+
+        if let ExprKind::Struct(s) = &expr.kind {
+            for field in &s.fields {
+                warn_if_doc(cx, field.span, "expression fields", &field.attrs);
+            }
+        }
+    }
+
+    fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) {
+        warn_if_doc(cx, param.ident.span, "generic parameters", &param.attrs);
+    }
+
+    fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
+        warn_if_doc(cx, block.span, "blocks", block.attrs());
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        if let ast::ItemKind::ForeignMod(_) = item.kind {
+            warn_if_doc(cx, item.span, "extern blocks", &item.attrs);
+        }
+    }
+}
+
+declare_lint! {
+    /// The `no_mangle_const_items` lint detects any `const` items with the
+    /// [`no_mangle` attribute].
+    ///
+    /// [`no_mangle` attribute]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #[no_mangle]
+    /// const FOO: i32 = 5;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Constants do not have their symbols exported, and therefore, this
+    /// probably means you meant to use a [`static`], not a [`const`].
+    ///
+    /// [`static`]: https://doc.rust-lang.org/reference/items/static-items.html
+    /// [`const`]: https://doc.rust-lang.org/reference/items/constant-items.html
+    NO_MANGLE_CONST_ITEMS,
+    Deny,
+    "const items will not have their symbols exported"
+}
+
+declare_lint! {
+    /// The `no_mangle_generic_items` lint detects generic items that must be
+    /// mangled.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #[no_mangle]
+    /// fn foo<T>(t: T) {
+    ///
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// A function with generics must have its symbol mangled to accommodate
+    /// the generic parameter. The [`no_mangle` attribute] has no effect in
+    /// this situation, and should be removed.
+    ///
+    /// [`no_mangle` attribute]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute
+    NO_MANGLE_GENERIC_ITEMS,
+    Warn,
+    "generic items must be mangled"
+}
+
+declare_lint_pass!(InvalidNoMangleItems => [NO_MANGLE_CONST_ITEMS, NO_MANGLE_GENERIC_ITEMS]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        let attrs = cx.tcx.hir().attrs(it.hir_id());
+        let check_no_mangle_on_generic_fn = |no_mangle_attr: &ast::Attribute,
+                                             impl_generics: Option<&hir::Generics<'_>>,
+                                             generics: &hir::Generics<'_>,
+                                             span| {
+            for param in
+                generics.params.iter().chain(impl_generics.map(|g| g.params).into_iter().flatten())
+            {
+                match param.kind {
+                    GenericParamKind::Lifetime { .. } => {}
+                    GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
+                        cx.emit_span_lint(
+                            NO_MANGLE_GENERIC_ITEMS,
+                            span,
+                            BuiltinNoMangleGeneric { suggestion: no_mangle_attr.span },
+                        );
+                        break;
+                    }
+                }
+            }
+        };
+        match it.kind {
+            hir::ItemKind::Fn(.., generics, _) => {
+                if let Some(no_mangle_attr) = attr::find_by_name(attrs, sym::no_mangle) {
+                    check_no_mangle_on_generic_fn(no_mangle_attr, None, generics, it.span);
+                }
+            }
+            hir::ItemKind::Const(..) => {
+                if attr::contains_name(attrs, sym::no_mangle) {
+                    // account for "pub const" (#45562)
+                    let start = cx
+                        .tcx
+                        .sess
+                        .source_map()
+                        .span_to_snippet(it.span)
+                        .map(|snippet| snippet.find("const").unwrap_or(0))
+                        .unwrap_or(0) as u32;
+                    // `const` is 5 chars
+                    let suggestion = it.span.with_hi(BytePos(it.span.lo().0 + start + 5));
+
+                    // Const items do not refer to a particular location in memory, and therefore
+                    // don't have anything to attach a symbol to
+                    cx.emit_span_lint(
+                        NO_MANGLE_CONST_ITEMS,
+                        it.span,
+                        BuiltinConstNoMangle { suggestion },
+                    );
+                }
+            }
+            hir::ItemKind::Impl(hir::Impl { generics, items, .. }) => {
+                for it in *items {
+                    if let hir::AssocItemKind::Fn { .. } = it.kind {
+                        if let Some(no_mangle_attr) =
+                            attr::find_by_name(cx.tcx.hir().attrs(it.id.hir_id()), sym::no_mangle)
+                        {
+                            check_no_mangle_on_generic_fn(
+                                no_mangle_attr,
+                                Some(generics),
+                                cx.tcx.hir().get_generics(it.id.owner_id.def_id).unwrap(),
+                                it.span,
+                            );
+                        }
+                    }
+                }
+            }
+            _ => {}
+        }
+    }
+}
+
+declare_lint! {
+    /// The `mutable_transmutes` lint catches transmuting from `&T` to `&mut
+    /// T` because it is [undefined behavior].
+    ///
+    /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// unsafe {
+    ///     let y = std::mem::transmute::<&i32, &mut i32>(&5);
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Certain assumptions are made about aliasing of data, and this transmute
+    /// violates those assumptions. Consider using [`UnsafeCell`] instead.
+    ///
+    /// [`UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
+    MUTABLE_TRANSMUTES,
+    Deny,
+    "transmuting &T to &mut T is undefined behavior, even if the reference is unused"
+}
+
+declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]);
+
+impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+        if let Some((&ty::Ref(_, _, from_mutbl), &ty::Ref(_, _, to_mutbl))) =
+            get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind()))
+        {
+            if from_mutbl < to_mutbl {
+                cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes);
+            }
+        }
+
+        fn get_transmute_from_to<'tcx>(
+            cx: &LateContext<'tcx>,
+            expr: &hir::Expr<'_>,
+        ) -> Option<(Ty<'tcx>, Ty<'tcx>)> {
+            let def = if let hir::ExprKind::Path(ref qpath) = expr.kind {
+                cx.qpath_res(qpath, expr.hir_id)
+            } else {
+                return None;
+            };
+            if let Res::Def(DefKind::Fn, did) = def {
+                if !def_id_is_transmute(cx, did) {
+                    return None;
+                }
+                let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx);
+                let from = sig.inputs().skip_binder()[0];
+                let to = sig.output().skip_binder();
+                return Some((from, to));
+            }
+            None
+        }
+
+        fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool {
+            cx.tcx.is_intrinsic(def_id, sym::transmute)
+        }
+    }
+}
+
+declare_lint! {
+    /// 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"
+}
+
+declare_lint_pass!(
+    /// Forbids using the `#[feature(...)]` attribute
+    UnstableFeatures => [UNSTABLE_FEATURES]
+);
+
+impl<'tcx> LateLintPass<'tcx> for UnstableFeatures {
+    fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
+        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);
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `ungated_async_fn_track_caller` lint warns when the
+    /// `#[track_caller]` attribute is used on an async function
+    /// without enabling the corresponding unstable feature flag.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #[track_caller]
+    /// async fn foo() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The attribute must be used in conjunction with the
+    /// [`async_fn_track_caller` feature flag]. Otherwise, the `#[track_caller]`
+    /// annotation will function as a no-op.
+    ///
+    /// [`async_fn_track_caller` feature flag]: https://doc.rust-lang.org/beta/unstable-book/language-features/async-fn-track-caller.html
+    UNGATED_ASYNC_FN_TRACK_CALLER,
+    Warn,
+    "enabling track_caller on an async fn is a no-op unless the async_fn_track_caller feature is enabled"
+}
+
+declare_lint_pass!(
+    /// Explains corresponding feature flag must be enabled for the `#[track_caller]` attribute to
+    /// do anything
+    UngatedAsyncFnTrackCaller => [UNGATED_ASYNC_FN_TRACK_CALLER]
+);
+
+impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller {
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'_>,
+        fn_kind: HirFnKind<'_>,
+        _: &'tcx FnDecl<'_>,
+        _: &'tcx Body<'_>,
+        span: Span,
+        def_id: LocalDefId,
+    ) {
+        if fn_kind.asyncness().is_async()
+            && !cx.tcx.features().async_fn_track_caller
+            // Now, check if the function has the `#[track_caller]` attribute
+            && let Some(attr) = cx.tcx.get_attr(def_id, sym::track_caller)
+        {
+            cx.emit_span_lint(
+                UNGATED_ASYNC_FN_TRACK_CALLER,
+                attr.span,
+                BuiltinUngatedAsyncFnTrackCaller { label: span, session: &cx.tcx.sess },
+            );
+        }
+    }
+}
+
+declare_lint! {
+    /// The `unreachable_pub` lint triggers for `pub` items not reachable from other crates - that
+    /// means neither directly accessible, nor reexported, nor leaked through things like return
+    /// types.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(unreachable_pub)]
+    /// mod foo {
+    ///     pub mod bar {
+    ///
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The `pub` keyword both expresses an intent for an item to be publicly available, and also
+    /// signals to the compiler to make the item publicly accessible. The intent can only be
+    /// satisfied, however, if all items which contain this item are *also* publicly accessible.
+    /// Thus, this lint serves to identify situations where the intent does not match the reality.
+    ///
+    /// If you wish the item to be accessible elsewhere within the crate, but not outside it, the
+    /// `pub(crate)` visibility is recommended to be used instead. This more clearly expresses the
+    /// intent that the item is only visible within its own crate.
+    ///
+    /// This lint is "allow" by default because it will trigger for a large
+    /// amount existing Rust code, and has some false-positives. Eventually it
+    /// is desired for this to become warn-by-default.
+    pub UNREACHABLE_PUB,
+    Allow,
+    "`pub` items not reachable from crate root"
+}
+
+declare_lint_pass!(
+    /// Lint for items marked `pub` that aren't reachable from other crates.
+    UnreachablePub => [UNREACHABLE_PUB]
+);
+
+impl UnreachablePub {
+    fn perform_lint(
+        &self,
+        cx: &LateContext<'_>,
+        what: &str,
+        def_id: LocalDefId,
+        vis_span: Span,
+        exportable: bool,
+    ) {
+        let mut applicability = Applicability::MachineApplicable;
+        if cx.tcx.visibility(def_id).is_public() && !cx.effective_visibilities.is_reachable(def_id)
+        {
+            if vis_span.from_expansion() {
+                applicability = Applicability::MaybeIncorrect;
+            }
+            let def_span = cx.tcx.def_span(def_id);
+            cx.emit_span_lint(
+                UNREACHABLE_PUB,
+                def_span,
+                BuiltinUnreachablePub {
+                    what,
+                    suggestion: (vis_span, applicability),
+                    help: exportable.then_some(()),
+                },
+            );
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnreachablePub {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        // Do not warn for fake `use` statements.
+        if let hir::ItemKind::Use(_, hir::UseKind::ListStem) = &item.kind {
+            return;
+        }
+        self.perform_lint(cx, "item", item.owner_id.def_id, item.vis_span, true);
+    }
+
+    fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'tcx>) {
+        self.perform_lint(cx, "item", foreign_item.owner_id.def_id, foreign_item.vis_span, true);
+    }
+
+    fn check_field_def(&mut self, _cx: &LateContext<'_>, _field: &hir::FieldDef<'_>) {
+        // - If an ADT definition is reported then we don't need to check fields
+        //   (as it would add unnecessary complexity to the source code, the struct
+        //   definition is in the immediate proximity to give the "real" visibility).
+        // - If an ADT is not reported because it's not `pub` - we don't need to
+        //   check fields.
+        // - If an ADT is not reported because it's reachable - we also don't need
+        //   to check fields because then they are reachable by construction if they
+        //   are pub.
+        //
+        // Therefore in no case we check the fields.
+        //
+        // cf. https://github.com/rust-lang/rust/pull/126013#issuecomment-2152839205
+        // cf. https://github.com/rust-lang/rust/pull/126040#issuecomment-2152944506
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
+        // Only lint inherent impl items.
+        if cx.tcx.associated_item(impl_item.owner_id).trait_item_def_id.is_none() {
+            self.perform_lint(cx, "item", impl_item.owner_id.def_id, impl_item.vis_span, false);
+        }
+    }
+}
+
+declare_lint! {
+    /// The `type_alias_bounds` lint detects bounds in type aliases.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// type SendVec<T: Send> = Vec<T>;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The trait bounds in a type alias are currently ignored, and should not
+    /// be included to avoid confusion. This was previously allowed
+    /// unintentionally; this may become a hard error in the future.
+    TYPE_ALIAS_BOUNDS,
+    Warn,
+    "bounds in type aliases are not enforced"
+}
+
+declare_lint_pass!(
+    /// Lint for trait and lifetime bounds in type aliases being mostly ignored.
+    /// They are relevant when using associated types, but otherwise neither checked
+    /// at definition site nor enforced at use site.
+    TypeAliasBounds => [TYPE_ALIAS_BOUNDS]
+);
+
+impl TypeAliasBounds {
+    pub(crate) fn is_type_variable_assoc(qpath: &hir::QPath<'_>) -> bool {
+        match *qpath {
+            hir::QPath::TypeRelative(ty, _) => {
+                // If this is a type variable, we found a `T::Assoc`.
+                match ty.kind {
+                    hir::TyKind::Path(hir::QPath::Resolved(None, path)) => {
+                        matches!(path.res, Res::Def(DefKind::TyParam, _))
+                    }
+                    _ => false,
+                }
+            }
+            hir::QPath::Resolved(..) | hir::QPath::LangItem(..) => false,
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        let hir::ItemKind::TyAlias(hir_ty, type_alias_generics) = &item.kind else { return };
+
+        // Bounds of lazy type aliases and TAITs are respected.
+        if cx.tcx.type_alias_is_lazy(item.owner_id) {
+            return;
+        }
+
+        let ty = cx.tcx.type_of(item.owner_id).skip_binder();
+        if ty.has_inherent_projections() {
+            // Bounds of type aliases that contain opaque types or inherent projections are
+            // respected. E.g: `type X = impl Trait;`, `type X = (impl Trait, Y);`, `type X =
+            // Type::Inherent;`.
+            return;
+        }
+
+        // There must not be a where clause
+        if type_alias_generics.predicates.is_empty() {
+            return;
+        }
+
+        let mut where_spans = Vec::new();
+        let mut inline_spans = Vec::new();
+        let mut inline_sugg = Vec::new();
+        for p in type_alias_generics.predicates {
+            let span = p.span();
+            if p.in_where_clause() {
+                where_spans.push(span);
+            } else {
+                for b in p.bounds() {
+                    inline_spans.push(b.span());
+                }
+                inline_sugg.push((span, String::new()));
+            }
+        }
+
+        let mut suggested_changing_assoc_types = false;
+        if !where_spans.is_empty() {
+            let sub = (!suggested_changing_assoc_types).then(|| {
+                suggested_changing_assoc_types = true;
+                SuggestChangingAssocTypes { ty: hir_ty }
+            });
+            cx.emit_span_lint(
+                TYPE_ALIAS_BOUNDS,
+                where_spans,
+                BuiltinTypeAliasWhereClause {
+                    suggestion: type_alias_generics.where_clause_span,
+                    sub,
+                },
+            );
+        }
+
+        if !inline_spans.is_empty() {
+            let suggestion = BuiltinTypeAliasGenericBoundsSuggestion { suggestions: inline_sugg };
+            let sub = (!suggested_changing_assoc_types).then(|| {
+                suggested_changing_assoc_types = true;
+                SuggestChangingAssocTypes { ty: hir_ty }
+            });
+            cx.emit_span_lint(
+                TYPE_ALIAS_BOUNDS,
+                inline_spans,
+                BuiltinTypeAliasGenericBounds { suggestion, sub },
+            );
+        }
+    }
+}
+
+declare_lint! {
+    /// The `trivial_bounds` lint detects trait bounds that don't depend on
+    /// any type parameters.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![feature(trivial_bounds)]
+    /// pub struct A where i32: Copy;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Usually you would not write a trait bound that you know is always
+    /// true, or never true. However, when using macros, the macro may not
+    /// know whether or not the constraint would hold or not at the time when
+    /// generating the code. Currently, the compiler does not alert you if the
+    /// constraint is always true, and generates an error if it is never true.
+    /// The `trivial_bounds` feature changes this to be a warning in both
+    /// cases, giving macros more freedom and flexibility to generate code,
+    /// while still providing a signal when writing non-macro code that
+    /// something is amiss.
+    ///
+    /// See [RFC 2056] for more details. This feature is currently only
+    /// available on the nightly channel, see [tracking issue #48214].
+    ///
+    /// [RFC 2056]: https://github.com/rust-lang/rfcs/blob/master/text/2056-allow-trivial-where-clause-constraints.md
+    /// [tracking issue #48214]: https://github.com/rust-lang/rust/issues/48214
+    TRIVIAL_BOUNDS,
+    Warn,
+    "these bounds don't depend on an type parameters"
+}
+
+declare_lint_pass!(
+    /// Lint for trait and lifetime bounds that don't depend on type parameters
+    /// which either do nothing, or stop the item from being used.
+    TrivialConstraints => [TRIVIAL_BOUNDS]
+);
+
+impl<'tcx> LateLintPass<'tcx> for TrivialConstraints {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        use rustc_middle::ty::ClauseKind;
+
+        if cx.tcx.features().trivial_bounds {
+            let predicates = cx.tcx.predicates_of(item.owner_id);
+            for &(predicate, span) in predicates.predicates {
+                let predicate_kind_name = match predicate.kind().skip_binder() {
+                    ClauseKind::Trait(..) => "trait",
+                    ClauseKind::TypeOutlives(..) |
+                    ClauseKind::RegionOutlives(..) => "lifetime",
+
+                    // `ConstArgHasType` is never global as `ct` is always a param
+                    ClauseKind::ConstArgHasType(..)
+                    // Ignore projections, as they can only be global
+                    // if the trait bound is global
+                    | ClauseKind::Projection(..)
+                    // Ignore bounds that a user can't type
+                    | ClauseKind::WellFormed(..)
+                    // FIXME(generic_const_exprs): `ConstEvaluatable` can be written
+                    | ClauseKind::ConstEvaluatable(..)  => continue,
+                };
+                if predicate.is_global() {
+                    cx.emit_span_lint(
+                        TRIVIAL_BOUNDS,
+                        span,
+                        BuiltinTrivialBounds { predicate_kind_name, predicate },
+                    );
+                }
+            }
+        }
+    }
+}
+
+declare_lint_pass!(
+    /// Does nothing as a lint pass, but registers some `Lint`s
+    /// which are used by other parts of the compiler.
+    SoftLints => [
+        WHILE_TRUE,
+        NON_SHORTHAND_FIELD_PATTERNS,
+        UNSAFE_CODE,
+        MISSING_DOCS,
+        MISSING_COPY_IMPLEMENTATIONS,
+        MISSING_DEBUG_IMPLEMENTATIONS,
+        ANONYMOUS_PARAMETERS,
+        UNUSED_DOC_COMMENTS,
+        NO_MANGLE_CONST_ITEMS,
+        NO_MANGLE_GENERIC_ITEMS,
+        MUTABLE_TRANSMUTES,
+        UNSTABLE_FEATURES,
+        UNREACHABLE_PUB,
+        TYPE_ALIAS_BOUNDS,
+        TRIVIAL_BOUNDS
+    ]
+);
+
+declare_lint! {
+    /// The `ellipsis_inclusive_range_patterns` lint detects the [`...` range
+    /// pattern], which is deprecated.
+    ///
+    /// [`...` range pattern]: https://doc.rust-lang.org/reference/patterns.html#range-patterns
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2018
+    /// let x = 123;
+    /// match x {
+    ///     0...100 => {}
+    ///     _ => {}
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The `...` range pattern syntax was changed to `..=` to avoid potential
+    /// confusion with the [`..` range expression]. Use the new form instead.
+    ///
+    /// [`..` range expression]: https://doc.rust-lang.org/reference/expressions/range-expr.html
+    pub ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
+    Warn,
+    "`...` range patterns are deprecated",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionError(Edition::Edition2021),
+        reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/warnings-promoted-to-error.html>",
+    };
+}
+
+#[derive(Default)]
+pub struct EllipsisInclusiveRangePatterns {
+    /// If `Some(_)`, suppress all subsequent pattern
+    /// warnings for better diagnostics.
+    node_id: Option<ast::NodeId>,
+}
+
+impl_lint_pass!(EllipsisInclusiveRangePatterns => [ELLIPSIS_INCLUSIVE_RANGE_PATTERNS]);
+
+impl EarlyLintPass for EllipsisInclusiveRangePatterns {
+    fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) {
+        if self.node_id.is_some() {
+            // Don't recursively warn about patterns inside range endpoints.
+            return;
+        }
+
+        use self::ast::{PatKind, RangeSyntax::DotDotDot};
+
+        /// If `pat` is a `...` pattern, return the start and end of the range, as well as the span
+        /// corresponding to the ellipsis.
+        fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(Option<&Expr>, &Expr, Span)> {
+            match &pat.kind {
+                PatKind::Range(
+                    a,
+                    Some(b),
+                    Spanned { span, node: RangeEnd::Included(DotDotDot) },
+                ) => Some((a.as_deref(), b, *span)),
+                _ => None,
+            }
+        }
+
+        let (parentheses, endpoints) = match &pat.kind {
+            PatKind::Ref(subpat, _) => (true, matches_ellipsis_pat(subpat)),
+            _ => (false, matches_ellipsis_pat(pat)),
+        };
+
+        if let Some((start, end, join)) = endpoints {
+            if parentheses {
+                self.node_id = Some(pat.id);
+                let end = expr_to_string(end);
+                let replace = match start {
+                    Some(start) => format!("&({}..={})", expr_to_string(start), end),
+                    None => format!("&(..={end})"),
+                };
+                if join.edition() >= Edition::Edition2021 {
+                    cx.sess().dcx().emit_err(BuiltinEllipsisInclusiveRangePatterns {
+                        span: pat.span,
+                        suggestion: pat.span,
+                        replace,
+                    });
+                } else {
+                    cx.emit_span_lint(
+                        ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
+                        pat.span,
+                        BuiltinEllipsisInclusiveRangePatternsLint::Parenthesise {
+                            suggestion: pat.span,
+                            replace,
+                        },
+                    );
+                }
+            } else {
+                let replace = "..=";
+                if join.edition() >= Edition::Edition2021 {
+                    cx.sess().dcx().emit_err(BuiltinEllipsisInclusiveRangePatterns {
+                        span: pat.span,
+                        suggestion: join,
+                        replace: replace.to_string(),
+                    });
+                } else {
+                    cx.emit_span_lint(
+                        ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
+                        join,
+                        BuiltinEllipsisInclusiveRangePatternsLint::NonParenthesise {
+                            suggestion: join,
+                        },
+                    );
+                }
+            };
+        }
+    }
+
+    fn check_pat_post(&mut self, _cx: &EarlyContext<'_>, pat: &ast::Pat) {
+        if let Some(node_id) = self.node_id {
+            if pat.id == node_id {
+                self.node_id = None
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `keyword_idents_2018` lint detects edition keywords being used as an
+    /// identifier.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2015,compile_fail
+    /// #![deny(keyword_idents_2018)]
+    /// // edition 2015
+    /// fn dyn() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Rust [editions] allow the language to evolve without breaking
+    /// backwards compatibility. This lint catches code that uses new keywords
+    /// that are added to the language that are used as identifiers (such as a
+    /// variable name, function name, etc.). If you switch the compiler to a
+    /// new edition without updating the code, then it will fail to compile if
+    /// you are using a new keyword as an identifier.
+    ///
+    /// You can manually change the identifiers to a non-keyword, or use a
+    /// [raw identifier], for example `r#dyn`, to transition to a new edition.
+    ///
+    /// This lint solves the problem automatically. It is "allow" by default
+    /// because the code is perfectly valid in older editions. The [`cargo
+    /// fix`] tool with the `--edition` flag will switch this lint to "warn"
+    /// and automatically apply the suggested fix from the compiler (which is
+    /// to use a raw identifier). This provides a completely automated way to
+    /// update old code for a new edition.
+    ///
+    /// [editions]: https://doc.rust-lang.org/edition-guide/
+    /// [raw identifier]: https://doc.rust-lang.org/reference/identifiers.html
+    /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
+    pub KEYWORD_IDENTS_2018,
+    Allow,
+    "detects edition keywords being used as an identifier",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018),
+        reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>",
+    };
+}
+
+declare_lint! {
+    /// The `keyword_idents_2024` lint detects edition keywords being used as an
+    /// identifier.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2015,compile_fail
+    /// #![deny(keyword_idents_2024)]
+    /// // edition 2015
+    /// fn gen() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Rust [editions] allow the language to evolve without breaking
+    /// backwards compatibility. This lint catches code that uses new keywords
+    /// that are added to the language that are used as identifiers (such as a
+    /// variable name, function name, etc.). If you switch the compiler to a
+    /// new edition without updating the code, then it will fail to compile if
+    /// you are using a new keyword as an identifier.
+    ///
+    /// You can manually change the identifiers to a non-keyword, or use a
+    /// [raw identifier], for example `r#gen`, to transition to a new edition.
+    ///
+    /// This lint solves the problem automatically. It is "allow" by default
+    /// because the code is perfectly valid in older editions. The [`cargo
+    /// fix`] tool with the `--edition` flag will switch this lint to "warn"
+    /// and automatically apply the suggested fix from the compiler (which is
+    /// to use a raw identifier). This provides a completely automated way to
+    /// update old code for a new edition.
+    ///
+    /// [editions]: https://doc.rust-lang.org/edition-guide/
+    /// [raw identifier]: https://doc.rust-lang.org/reference/identifiers.html
+    /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
+    pub KEYWORD_IDENTS_2024,
+    Allow,
+    "detects edition keywords being used as an identifier",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024),
+        reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>",
+    };
+}
+
+declare_lint_pass!(
+    /// Check for uses of edition keywords used as an identifier.
+    KeywordIdents => [KEYWORD_IDENTS_2018, KEYWORD_IDENTS_2024]
+);
+
+struct UnderMacro(bool);
+
+impl KeywordIdents {
+    fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: &TokenStream) {
+        // Check if the preceding token is `$`, because we want to allow `$async`, etc.
+        let mut prev_dollar = false;
+        for tt in tokens.trees() {
+            match tt {
+                // Only report non-raw idents.
+                TokenTree::Token(token, _) => {
+                    if let Some((ident, token::IdentIsRaw::No)) = token.ident() {
+                        if !prev_dollar {
+                            self.check_ident_token(cx, UnderMacro(true), ident);
+                        }
+                    } else if token.kind == TokenKind::Dollar {
+                        prev_dollar = true;
+                        continue;
+                    }
+                }
+                TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts),
+            }
+            prev_dollar = false;
+        }
+    }
+
+    fn check_ident_token(
+        &mut self,
+        cx: &EarlyContext<'_>,
+        UnderMacro(under_macro): UnderMacro,
+        ident: Ident,
+    ) {
+        let (lint, edition) = match ident.name {
+            kw::Async | kw::Await | kw::Try => (KEYWORD_IDENTS_2018, Edition::Edition2018),
+
+            // rust-lang/rust#56327: Conservatively do not
+            // attempt to report occurrences of `dyn` within
+            // macro definitions or invocations, because `dyn`
+            // can legitimately occur as a contextual keyword
+            // in 2015 code denoting its 2018 meaning, and we
+            // do not want rustfix to inject bugs into working
+            // code by rewriting such occurrences.
+            //
+            // But if we see `dyn` outside of a macro, we know
+            // its precise role in the parsed AST and thus are
+            // assured this is truly an attempt to use it as
+            // an identifier.
+            kw::Dyn if !under_macro => (KEYWORD_IDENTS_2018, Edition::Edition2018),
+
+            kw::Gen => (KEYWORD_IDENTS_2024, Edition::Edition2024),
+
+            _ => return,
+        };
+
+        // Don't lint `r#foo`.
+        if ident.span.edition() >= edition
+            || cx.sess().psess.raw_identifier_spans.contains(ident.span)
+        {
+            return;
+        }
+
+        cx.emit_span_lint(
+            lint,
+            ident.span,
+            BuiltinKeywordIdents { kw: ident, next: edition, suggestion: ident.span },
+        );
+    }
+}
+
+impl EarlyLintPass for KeywordIdents {
+    fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef) {
+        self.check_tokens(cx, &mac_def.body.tokens);
+    }
+    fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) {
+        self.check_tokens(cx, &mac.args.tokens);
+    }
+    fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
+        self.check_ident_token(cx, UnderMacro(false), ident);
+    }
+}
+
+declare_lint_pass!(ExplicitOutlivesRequirements => [EXPLICIT_OUTLIVES_REQUIREMENTS]);
+
+impl ExplicitOutlivesRequirements {
+    fn lifetimes_outliving_lifetime<'tcx>(
+        tcx: TyCtxt<'tcx>,
+        inferred_outlives: &'tcx [(ty::Clause<'tcx>, Span)],
+        item: DefId,
+        lifetime: DefId,
+    ) -> Vec<ty::Region<'tcx>> {
+        let item_generics = tcx.generics_of(item);
+
+        inferred_outlives
+            .iter()
+            .filter_map(|(clause, _)| match clause.kind().skip_binder() {
+                ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => match *a {
+                    ty::ReEarlyParam(ebr)
+                        if item_generics.region_param(ebr, tcx).def_id == lifetime =>
+                    {
+                        Some(b)
+                    }
+                    _ => None,
+                },
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn lifetimes_outliving_type<'tcx>(
+        inferred_outlives: &'tcx [(ty::Clause<'tcx>, Span)],
+        index: u32,
+    ) -> Vec<ty::Region<'tcx>> {
+        inferred_outlives
+            .iter()
+            .filter_map(|(clause, _)| match clause.kind().skip_binder() {
+                ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(a, b)) => {
+                    a.is_param(index).then_some(b)
+                }
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn collect_outlives_bound_spans<'tcx>(
+        &self,
+        tcx: TyCtxt<'tcx>,
+        bounds: &hir::GenericBounds<'_>,
+        inferred_outlives: &[ty::Region<'tcx>],
+        predicate_span: Span,
+        item: DefId,
+    ) -> Vec<(usize, Span)> {
+        use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
+
+        let item_generics = tcx.generics_of(item);
+
+        bounds
+            .iter()
+            .enumerate()
+            .filter_map(|(i, bound)| {
+                let hir::GenericBound::Outlives(lifetime) = bound else {
+                    return None;
+                };
+
+                let is_inferred = match tcx.named_bound_var(lifetime.hir_id) {
+                    Some(ResolvedArg::EarlyBound(def_id)) => inferred_outlives
+                        .iter()
+                        .any(|r| matches!(**r, ty::ReEarlyParam(ebr) if { item_generics.region_param(ebr, tcx).def_id == def_id })),
+                    _ => false,
+                };
+
+                if !is_inferred {
+                    return None;
+                }
+
+                let span = bound.span().find_ancestor_inside(predicate_span)?;
+                if in_external_macro(tcx.sess, span) {
+                    return None;
+                }
+
+                Some((i, span))
+            })
+            .collect()
+    }
+
+    fn consolidate_outlives_bound_spans(
+        &self,
+        lo: Span,
+        bounds: &hir::GenericBounds<'_>,
+        bound_spans: Vec<(usize, Span)>,
+    ) -> Vec<Span> {
+        if bounds.is_empty() {
+            return Vec::new();
+        }
+        if bound_spans.len() == bounds.len() {
+            let (_, last_bound_span) = bound_spans[bound_spans.len() - 1];
+            // If all bounds are inferable, we want to delete the colon, so
+            // start from just after the parameter (span passed as argument)
+            vec![lo.to(last_bound_span)]
+        } else {
+            let mut merged = Vec::new();
+            let mut last_merged_i = None;
+
+            let mut from_start = true;
+            for (i, bound_span) in bound_spans {
+                match last_merged_i {
+                    // If the first bound is inferable, our span should also eat the leading `+`.
+                    None if i == 0 => {
+                        merged.push(bound_span.to(bounds[1].span().shrink_to_lo()));
+                        last_merged_i = Some(0);
+                    }
+                    // If consecutive bounds are inferable, merge their spans
+                    Some(h) if i == h + 1 => {
+                        if let Some(tail) = merged.last_mut() {
+                            // Also eat the trailing `+` if the first
+                            // more-than-one bound is inferable
+                            let to_span = if from_start && i < bounds.len() {
+                                bounds[i + 1].span().shrink_to_lo()
+                            } else {
+                                bound_span
+                            };
+                            *tail = tail.to(to_span);
+                            last_merged_i = Some(i);
+                        } else {
+                            bug!("another bound-span visited earlier");
+                        }
+                    }
+                    _ => {
+                        // When we find a non-inferable bound, subsequent inferable bounds
+                        // won't be consecutive from the start (and we'll eat the leading
+                        // `+` rather than the trailing one)
+                        from_start = false;
+                        merged.push(bounds[i - 1].span().shrink_to_hi().to(bound_span));
+                        last_merged_i = Some(i);
+                    }
+                }
+            }
+            merged
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+        use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
+
+        let def_id = item.owner_id.def_id;
+        if let hir::ItemKind::Struct(_, hir_generics)
+        | hir::ItemKind::Enum(_, hir_generics)
+        | hir::ItemKind::Union(_, hir_generics) = item.kind
+        {
+            let inferred_outlives = cx.tcx.inferred_outlives_of(def_id);
+            if inferred_outlives.is_empty() {
+                return;
+            }
+
+            let ty_generics = cx.tcx.generics_of(def_id);
+            let num_where_predicates = hir_generics
+                .predicates
+                .iter()
+                .filter(|predicate| predicate.in_where_clause())
+                .count();
+
+            let mut bound_count = 0;
+            let mut lint_spans = Vec::new();
+            let mut where_lint_spans = Vec::new();
+            let mut dropped_where_predicate_count = 0;
+            for (i, where_predicate) in hir_generics.predicates.iter().enumerate() {
+                let (relevant_lifetimes, bounds, predicate_span, in_where_clause) =
+                    match where_predicate {
+                        hir::WherePredicate::RegionPredicate(predicate) => {
+                            if let Some(ResolvedArg::EarlyBound(region_def_id)) =
+                                cx.tcx.named_bound_var(predicate.lifetime.hir_id)
+                            {
+                                (
+                                    Self::lifetimes_outliving_lifetime(
+                                        cx.tcx,
+                                        inferred_outlives,
+                                        item.owner_id.to_def_id(),
+                                        region_def_id,
+                                    ),
+                                    &predicate.bounds,
+                                    predicate.span,
+                                    predicate.in_where_clause,
+                                )
+                            } else {
+                                continue;
+                            }
+                        }
+                        hir::WherePredicate::BoundPredicate(predicate) => {
+                            // FIXME we can also infer bounds on associated types,
+                            // and should check for them here.
+                            match predicate.bounded_ty.kind {
+                                hir::TyKind::Path(hir::QPath::Resolved(None, path)) => {
+                                    let Res::Def(DefKind::TyParam, def_id) = path.res else {
+                                        continue;
+                                    };
+                                    let index = ty_generics.param_def_id_to_index[&def_id];
+                                    (
+                                        Self::lifetimes_outliving_type(inferred_outlives, index),
+                                        &predicate.bounds,
+                                        predicate.span,
+                                        predicate.origin == PredicateOrigin::WhereClause,
+                                    )
+                                }
+                                _ => {
+                                    continue;
+                                }
+                            }
+                        }
+                        _ => continue,
+                    };
+                if relevant_lifetimes.is_empty() {
+                    continue;
+                }
+
+                let bound_spans = self.collect_outlives_bound_spans(
+                    cx.tcx,
+                    bounds,
+                    &relevant_lifetimes,
+                    predicate_span,
+                    item.owner_id.to_def_id(),
+                );
+                bound_count += bound_spans.len();
+
+                let drop_predicate = bound_spans.len() == bounds.len();
+                if drop_predicate && in_where_clause {
+                    dropped_where_predicate_count += 1;
+                }
+
+                if drop_predicate {
+                    if !in_where_clause {
+                        lint_spans.push(predicate_span);
+                    } else if predicate_span.from_expansion() {
+                        // Don't try to extend the span if it comes from a macro expansion.
+                        where_lint_spans.push(predicate_span);
+                    } else if i + 1 < num_where_predicates {
+                        // If all the bounds on a predicate were inferable and there are
+                        // further predicates, we want to eat the trailing comma.
+                        let next_predicate_span = hir_generics.predicates[i + 1].span();
+                        if next_predicate_span.from_expansion() {
+                            where_lint_spans.push(predicate_span);
+                        } else {
+                            where_lint_spans
+                                .push(predicate_span.to(next_predicate_span.shrink_to_lo()));
+                        }
+                    } else {
+                        // Eat the optional trailing comma after the last predicate.
+                        let where_span = hir_generics.where_clause_span;
+                        if where_span.from_expansion() {
+                            where_lint_spans.push(predicate_span);
+                        } else {
+                            where_lint_spans.push(predicate_span.to(where_span.shrink_to_hi()));
+                        }
+                    }
+                } else {
+                    where_lint_spans.extend(self.consolidate_outlives_bound_spans(
+                        predicate_span.shrink_to_lo(),
+                        bounds,
+                        bound_spans,
+                    ));
+                }
+            }
+
+            // If all predicates in where clause are inferable, drop the entire clause
+            // (including the `where`)
+            if hir_generics.has_where_clause_predicates
+                && dropped_where_predicate_count == num_where_predicates
+            {
+                let where_span = hir_generics.where_clause_span;
+                // Extend the where clause back to the closing `>` of the
+                // generics, except for tuple struct, which have the `where`
+                // after the fields of the struct.
+                let full_where_span =
+                    if let hir::ItemKind::Struct(hir::VariantData::Tuple(..), _) = item.kind {
+                        where_span
+                    } else {
+                        hir_generics.span.shrink_to_hi().to(where_span)
+                    };
+
+                // Due to macro expansions, the `full_where_span` might not actually contain all
+                // predicates.
+                if where_lint_spans.iter().all(|&sp| full_where_span.contains(sp)) {
+                    lint_spans.push(full_where_span);
+                } else {
+                    lint_spans.extend(where_lint_spans);
+                }
+            } else {
+                lint_spans.extend(where_lint_spans);
+            }
+
+            if !lint_spans.is_empty() {
+                // Do not automatically delete outlives requirements from macros.
+                let applicability = if lint_spans.iter().all(|sp| sp.can_be_used_for_suggestions())
+                {
+                    Applicability::MachineApplicable
+                } else {
+                    Applicability::MaybeIncorrect
+                };
+
+                // Due to macros, there might be several predicates with the same span
+                // and we only want to suggest removing them once.
+                lint_spans.sort_unstable();
+                lint_spans.dedup();
+
+                cx.emit_span_lint(
+                    EXPLICIT_OUTLIVES_REQUIREMENTS,
+                    lint_spans.clone(),
+                    BuiltinExplicitOutlives {
+                        count: bound_count,
+                        suggestion: BuiltinExplicitOutlivesSuggestion {
+                            spans: lint_spans,
+                            applicability,
+                        },
+                    },
+                );
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `incomplete_features` lint detects unstable features enabled with
+    /// the [`feature` attribute] that may function improperly in some or all
+    /// cases.
+    ///
+    /// [`feature` attribute]: https://doc.rust-lang.org/nightly/unstable-book/
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![feature(generic_const_exprs)]
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Although it is encouraged for people to experiment with unstable
+    /// features, some of them are known to be incomplete or faulty. This lint
+    /// is a signal that the feature has not yet been finished, and you may
+    /// experience problems with it.
+    pub INCOMPLETE_FEATURES,
+    Warn,
+    "incomplete features that may function improperly in some or all cases"
+}
+
+declare_lint! {
+    /// The `internal_features` lint detects unstable features enabled with
+    /// the [`feature` attribute] that are internal to the compiler or standard
+    /// library.
+    ///
+    /// [`feature` attribute]: https://doc.rust-lang.org/nightly/unstable-book/
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![feature(rustc_attrs)]
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// These features are an implementation detail of the compiler and standard
+    /// library and are not supposed to be used in user code.
+    pub INTERNAL_FEATURES,
+    Warn,
+    "internal features are not supposed to be used"
+}
+
+declare_lint_pass!(
+    /// Check for used feature gates in `INCOMPLETE_FEATURES` in `rustc_feature/src/unstable.rs`.
+    IncompleteInternalFeatures => [INCOMPLETE_FEATURES, INTERNAL_FEATURES]
+);
+
+impl EarlyLintPass for IncompleteInternalFeatures {
+    fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+        let features = cx.builder.features();
+        features
+            .declared_lang_features
+            .iter()
+            .map(|(name, span, _)| (name, span))
+            .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span)))
+            .filter(|(&name, _)| features.incomplete(name) || features.internal(name))
+            .for_each(|(&name, &span)| {
+                if features.incomplete(name) {
+                    let note = rustc_feature::find_feature_issue(name, GateIssue::Language)
+                        .map(|n| BuiltinFeatureIssueNote { n });
+                    let help =
+                        HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp);
+
+                    cx.emit_span_lint(
+                        INCOMPLETE_FEATURES,
+                        span,
+                        BuiltinIncompleteFeatures { name, note, help },
+                    );
+                } else {
+                    cx.emit_span_lint(INTERNAL_FEATURES, span, BuiltinInternalFeatures { name });
+                }
+            });
+    }
+}
+
+const HAS_MIN_FEATURES: &[Symbol] = &[sym::specialization];
+
+declare_lint! {
+    /// The `invalid_value` lint detects creating a value that is not valid,
+    /// such as a null reference.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,no_run
+    /// # #![allow(unused)]
+    /// unsafe {
+    ///     let x: &'static i32 = std::mem::zeroed();
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// In some situations the compiler can detect that the code is creating
+    /// an invalid value, which should be avoided.
+    ///
+    /// In particular, this lint will check for improper use of
+    /// [`mem::zeroed`], [`mem::uninitialized`], [`mem::transmute`], and
+    /// [`MaybeUninit::assume_init`] that can cause [undefined behavior]. The
+    /// lint should provide extra information to indicate what the problem is
+    /// and a possible solution.
+    ///
+    /// [`mem::zeroed`]: https://doc.rust-lang.org/std/mem/fn.zeroed.html
+    /// [`mem::uninitialized`]: https://doc.rust-lang.org/std/mem/fn.uninitialized.html
+    /// [`mem::transmute`]: https://doc.rust-lang.org/std/mem/fn.transmute.html
+    /// [`MaybeUninit::assume_init`]: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.assume_init
+    /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
+    pub INVALID_VALUE,
+    Warn,
+    "an invalid value is being created (such as a null reference)"
+}
+
+declare_lint_pass!(InvalidValue => [INVALID_VALUE]);
+
+/// Information about why a type cannot be initialized this way.
+pub struct InitError {
+    pub(crate) message: String,
+    /// Spans from struct fields and similar that can be obtained from just the type.
+    pub(crate) span: Option<Span>,
+    /// Used to report a trace through adts.
+    pub(crate) nested: Option<Box<InitError>>,
+}
+impl InitError {
+    fn spanned(self, span: Span) -> InitError {
+        Self { span: Some(span), ..self }
+    }
+
+    fn nested(self, nested: impl Into<Option<InitError>>) -> InitError {
+        assert!(self.nested.is_none());
+        Self { nested: nested.into().map(Box::new), ..self }
+    }
+}
+
+impl<'a> From<&'a str> for InitError {
+    fn from(s: &'a str) -> Self {
+        s.to_owned().into()
+    }
+}
+impl From<String> for InitError {
+    fn from(message: String) -> Self {
+        Self { message, span: None, nested: None }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for InvalidValue {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
+        #[derive(Debug, Copy, Clone, PartialEq)]
+        enum InitKind {
+            Zeroed,
+            Uninit,
+        }
+
+        /// Test if this constant is all-0.
+        fn is_zero(expr: &hir::Expr<'_>) -> bool {
+            use hir::ExprKind::*;
+            use rustc_ast::LitKind::*;
+            match &expr.kind {
+                Lit(lit) => {
+                    if let Int(i, _) = lit.node {
+                        i == 0
+                    } else {
+                        false
+                    }
+                }
+                Tup(tup) => tup.iter().all(is_zero),
+                _ => false,
+            }
+        }
+
+        /// Determine if this expression is a "dangerous initialization".
+        fn is_dangerous_init(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<InitKind> {
+            if let hir::ExprKind::Call(path_expr, args) = expr.kind {
+                // Find calls to `mem::{uninitialized,zeroed}` methods.
+                if let hir::ExprKind::Path(ref qpath) = path_expr.kind {
+                    let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
+                    match cx.tcx.get_diagnostic_name(def_id) {
+                        Some(sym::mem_zeroed) => return Some(InitKind::Zeroed),
+                        Some(sym::mem_uninitialized) => return Some(InitKind::Uninit),
+                        Some(sym::transmute) if is_zero(&args[0]) => return Some(InitKind::Zeroed),
+                        _ => {}
+                    }
+                }
+            } else if let hir::ExprKind::MethodCall(_, receiver, ..) = expr.kind {
+                // Find problematic calls to `MaybeUninit::assume_init`.
+                let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+                if cx.tcx.is_diagnostic_item(sym::assume_init, def_id) {
+                    // This is a call to *some* method named `assume_init`.
+                    // See if the `self` parameter is one of the dangerous constructors.
+                    if let hir::ExprKind::Call(path_expr, _) = receiver.kind {
+                        if let hir::ExprKind::Path(ref qpath) = path_expr.kind {
+                            let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
+                            match cx.tcx.get_diagnostic_name(def_id) {
+                                Some(sym::maybe_uninit_zeroed) => return Some(InitKind::Zeroed),
+                                Some(sym::maybe_uninit_uninit) => return Some(InitKind::Uninit),
+                                _ => {}
+                            }
+                        }
+                    }
+                }
+            }
+
+            None
+        }
+
+        fn variant_find_init_error<'tcx>(
+            cx: &LateContext<'tcx>,
+            ty: Ty<'tcx>,
+            variant: &VariantDef,
+            args: ty::GenericArgsRef<'tcx>,
+            descr: &str,
+            init: InitKind,
+        ) -> Option<InitError> {
+            let mut field_err = variant.fields.iter().find_map(|field| {
+                ty_find_init_error(cx, field.ty(cx.tcx, args), init).map(|mut err| {
+                    if !field.did.is_local() {
+                        err
+                    } else if err.span.is_none() {
+                        err.span = Some(cx.tcx.def_span(field.did));
+                        write!(&mut err.message, " (in this {descr})").unwrap();
+                        err
+                    } else {
+                        InitError::from(format!("in this {descr}"))
+                            .spanned(cx.tcx.def_span(field.did))
+                            .nested(err)
+                    }
+                })
+            });
+
+            // Check if this ADT has a constrained layout (like `NonNull` and friends).
+            if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) {
+                if let Abi::Scalar(scalar) | Abi::ScalarPair(scalar, _) = &layout.abi {
+                    let range = scalar.valid_range(cx);
+                    let msg = if !range.contains(0) {
+                        "must be non-null"
+                    } else if init == InitKind::Uninit && !scalar.is_always_valid(cx) {
+                        // Prefer reporting on the fields over the entire struct for uninit,
+                        // as the information bubbles out and it may be unclear why the type can't
+                        // be null from just its outside signature.
+
+                        "must be initialized inside its custom valid range"
+                    } else {
+                        return field_err;
+                    };
+                    if let Some(field_err) = &mut field_err {
+                        // Most of the time, if the field error is the same as the struct error,
+                        // the struct error only happens because of the field error.
+                        if field_err.message.contains(msg) {
+                            field_err.message = format!("because {}", field_err.message);
+                        }
+                    }
+                    return Some(InitError::from(format!("`{ty}` {msg}")).nested(field_err));
+                }
+            }
+            field_err
+        }
+
+        /// Return `Some` only if we are sure this type does *not*
+        /// allow zero initialization.
+        fn ty_find_init_error<'tcx>(
+            cx: &LateContext<'tcx>,
+            ty: Ty<'tcx>,
+            init: InitKind,
+        ) -> Option<InitError> {
+            let ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
+
+            use rustc_type_ir::TyKind::*;
+            match ty.kind() {
+                // Primitive types that don't like 0 as a value.
+                Ref(..) => Some("references must be non-null".into()),
+                Adt(..) if ty.is_box() => Some("`Box` must be non-null".into()),
+                FnPtr(..) => Some("function pointers must be non-null".into()),
+                Never => Some("the `!` type has no valid value".into()),
+                RawPtr(ty, _) if matches!(ty.kind(), Dynamic(..)) =>
+                // raw ptr to dyn Trait
+                {
+                    Some("the vtable of a wide raw pointer must be non-null".into())
+                }
+                // Primitive types with other constraints.
+                Bool if init == InitKind::Uninit => {
+                    Some("booleans must be either `true` or `false`".into())
+                }
+                Char if init == InitKind::Uninit => {
+                    Some("characters must be a valid Unicode codepoint".into())
+                }
+                Int(_) | Uint(_) if init == InitKind::Uninit => {
+                    Some("integers must be initialized".into())
+                }
+                Float(_) if init == InitKind::Uninit => Some("floats must be initialized".into()),
+                RawPtr(_, _) if init == InitKind::Uninit => {
+                    Some("raw pointers must be initialized".into())
+                }
+                // Recurse and checks for some compound types. (but not unions)
+                Adt(adt_def, args) if !adt_def.is_union() => {
+                    // Handle structs.
+                    if adt_def.is_struct() {
+                        return variant_find_init_error(
+                            cx,
+                            ty,
+                            adt_def.non_enum_variant(),
+                            args,
+                            "struct field",
+                            init,
+                        );
+                    }
+                    // And now, enums.
+                    let span = cx.tcx.def_span(adt_def.did());
+                    let mut potential_variants = adt_def.variants().iter().filter_map(|variant| {
+                        let definitely_inhabited = match variant
+                            .inhabited_predicate(cx.tcx, *adt_def)
+                            .instantiate(cx.tcx, args)
+                            .apply_any_module(cx.tcx, cx.param_env)
+                        {
+                            // Entirely skip uninhabited variants.
+                            Some(false) => return None,
+                            // Forward the others, but remember which ones are definitely inhabited.
+                            Some(true) => true,
+                            None => false,
+                        };
+                        Some((variant, definitely_inhabited))
+                    });
+                    let Some(first_variant) = potential_variants.next() else {
+                        return Some(
+                            InitError::from("enums with no inhabited variants have no valid value")
+                                .spanned(span),
+                        );
+                    };
+                    // So we have at least one potentially inhabited variant. Might we have two?
+                    let Some(second_variant) = potential_variants.next() else {
+                        // There is only one potentially inhabited variant. So we can recursively
+                        // check that variant!
+                        return variant_find_init_error(
+                            cx,
+                            ty,
+                            first_variant.0,
+                            args,
+                            "field of the only potentially inhabited enum variant",
+                            init,
+                        );
+                    };
+                    // So we have at least two potentially inhabited variants. If we can prove that
+                    // we have at least two *definitely* inhabited variants, then we have a tag and
+                    // hence leaving this uninit is definitely disallowed. (Leaving it zeroed could
+                    // be okay, depending on which variant is encoded as zero tag.)
+                    if init == InitKind::Uninit {
+                        let definitely_inhabited = (first_variant.1 as usize)
+                            + (second_variant.1 as usize)
+                            + potential_variants
+                                .filter(|(_variant, definitely_inhabited)| *definitely_inhabited)
+                                .count();
+                        if definitely_inhabited > 1 {
+                            return Some(InitError::from(
+                                "enums with multiple inhabited variants have to be initialized to a variant",
+                            ).spanned(span));
+                        }
+                    }
+                    // We couldn't find anything wrong here.
+                    None
+                }
+                Tuple(..) => {
+                    // Proceed recursively, check all fields.
+                    ty.tuple_fields().iter().find_map(|field| ty_find_init_error(cx, field, init))
+                }
+                Array(ty, len) => {
+                    if matches!(len.try_eval_target_usize(cx.tcx, cx.param_env), Some(v) if v > 0) {
+                        // Array length known at array non-empty -- recurse.
+                        ty_find_init_error(cx, *ty, init)
+                    } else {
+                        // Empty array or size unknown.
+                        None
+                    }
+                }
+                // Conservative fallback.
+                _ => None,
+            }
+        }
+
+        if let Some(init) = is_dangerous_init(cx, expr) {
+            // This conjures an instance of a type out of nothing,
+            // using zeroed or uninitialized memory.
+            // We are extremely conservative with what we warn about.
+            let conjured_ty = cx.typeck_results().expr_ty(expr);
+            if let Some(err) = with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init)) {
+                let msg = match init {
+                    InitKind::Zeroed => fluent::lint_builtin_unpermitted_type_init_zeroed,
+                    InitKind::Uninit => fluent::lint_builtin_unpermitted_type_init_uninit,
+                };
+                let sub = BuiltinUnpermittedTypeInitSub { err };
+                cx.emit_span_lint(
+                    INVALID_VALUE,
+                    expr.span,
+                    BuiltinUnpermittedTypeInit {
+                        msg,
+                        ty: conjured_ty,
+                        label: expr.span,
+                        sub,
+                        tcx: cx.tcx,
+                    },
+                );
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `deref_nullptr` lint detects when an null pointer is dereferenced,
+    /// which causes [undefined behavior].
+    ///
+    /// ### Example
+    ///
+    /// ```rust,no_run
+    /// # #![allow(unused)]
+    /// use std::ptr;
+    /// unsafe {
+    ///     let x = &*ptr::null::<i32>();
+    ///     let x = ptr::addr_of!(*ptr::null::<i32>());
+    ///     let x = *(0 as *const i32);
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Dereferencing a null pointer causes [undefined behavior] even as a place expression,
+    /// like `&*(0 as *const i32)` or `addr_of!(*(0 as *const i32))`.
+    ///
+    /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
+    pub DEREF_NULLPTR,
+    Warn,
+    "detects when an null pointer is dereferenced"
+}
+
+declare_lint_pass!(DerefNullPtr => [DEREF_NULLPTR]);
+
+impl<'tcx> LateLintPass<'tcx> for DerefNullPtr {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
+        /// test if expression is a null ptr
+        fn is_null_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+            match &expr.kind {
+                rustc_hir::ExprKind::Cast(expr, ty) => {
+                    if let rustc_hir::TyKind::Ptr(_) = ty.kind {
+                        return is_zero(expr) || is_null_ptr(cx, expr);
+                    }
+                }
+                // check for call to `core::ptr::null` or `core::ptr::null_mut`
+                rustc_hir::ExprKind::Call(path, _) => {
+                    if let rustc_hir::ExprKind::Path(ref qpath) = path.kind {
+                        if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() {
+                            return matches!(
+                                cx.tcx.get_diagnostic_name(def_id),
+                                Some(sym::ptr_null | sym::ptr_null_mut)
+                            );
+                        }
+                    }
+                }
+                _ => {}
+            }
+            false
+        }
+
+        /// test if expression is the literal `0`
+        fn is_zero(expr: &hir::Expr<'_>) -> bool {
+            match &expr.kind {
+                rustc_hir::ExprKind::Lit(lit) => {
+                    if let LitKind::Int(a, _) = lit.node {
+                        return a == 0;
+                    }
+                }
+                _ => {}
+            }
+            false
+        }
+
+        if let rustc_hir::ExprKind::Unary(rustc_hir::UnOp::Deref, expr_deref) = expr.kind {
+            if is_null_ptr(cx, expr_deref) {
+                cx.emit_span_lint(
+                    DEREF_NULLPTR,
+                    expr.span,
+                    BuiltinDerefNullptr { label: expr.span },
+                );
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `named_asm_labels` lint detects the use of named labels in the
+    /// inline `asm!` macro.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// # #![feature(asm_experimental_arch)]
+    /// use std::arch::asm;
+    ///
+    /// fn main() {
+    ///     unsafe {
+    ///         asm!("foo: bar");
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// LLVM is allowed to duplicate inline assembly blocks for any
+    /// reason, for example when it is in a function that gets inlined. Because
+    /// of this, GNU assembler [local labels] *must* be used instead of labels
+    /// with a name. Using named labels might cause assembler or linker errors.
+    ///
+    /// See the explanation in [Rust By Example] for more details.
+    ///
+    /// [local labels]: https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels
+    /// [Rust By Example]: https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels
+    pub NAMED_ASM_LABELS,
+    Deny,
+    "named labels in inline assembly",
+}
+
+declare_lint! {
+    /// The `binary_asm_labels` lint detects the use of numeric labels containing only binary
+    /// digits in the inline `asm!` macro.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// # #![feature(asm_experimental_arch)]
+    /// use std::arch::asm;
+    ///
+    /// fn main() {
+    ///     unsafe {
+    ///         asm!("0: jmp 0b");
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// A [LLVM bug] causes this code to fail to compile because it interprets the `0b` as a binary
+    /// literal instead of a reference to the previous local label `0`. Note that even though the
+    /// bug is marked as fixed, it only fixes a specific usage of intel syntax within standalone
+    /// files, not inline assembly. To work around this bug, don't use labels that could be
+    /// confused with a binary literal.
+    ///
+    /// See the explanation in [Rust By Example] for more details.
+    ///
+    /// [LLVM bug]: https://bugs.llvm.org/show_bug.cgi?id=36144
+    /// [Rust By Example]: https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels
+    pub BINARY_ASM_LABELS,
+    Deny,
+    "labels in inline assembly containing only 0 or 1 digits",
+}
+
+declare_lint_pass!(AsmLabels => [NAMED_ASM_LABELS, BINARY_ASM_LABELS]);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum AsmLabelKind {
+    Named,
+    FormatArg,
+    Binary,
+}
+
+impl<'tcx> LateLintPass<'tcx> for AsmLabels {
+    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, 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> {
+                    if let Some(template_snippet) = template_snippet {
+                        let snippet = template_snippet.as_str();
+                        if let Some(pos) = snippet.find(needle) {
+                            let end = pos
+                                + snippet[pos..]
+                                    .find(|c| c == ':')
+                                    .unwrap_or(snippet[pos..].len() - 1);
+                            let inner = InnerSpan::new(pos, end);
+                            return Some(template_span.from_inner(inner));
+                        }
+                    }
+
+                    None
+                };
+
+                // diagnostics are emitted per-template, so this is created here as opposed to the outer loop
+                let mut spans = Vec::new();
+
+                // A semicolon might not actually be specified as a separator for all targets, but
+                // it seems like LLVM accepts it always.
+                let statements = template_str.split(|c| matches!(c, '\n' | ';'));
+                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;
+                    'label_loop: for (idx, _) in statement.match_indices(':') {
+                        let possible_label = statement[start_idx..idx].trim();
+                        let mut chars = possible_label.chars();
+
+                        let Some(start) = chars.next() else {
+                            // Empty string means a leading ':' in this section, which is not a
+                            // label.
+                            break 'label_loop;
+                        };
+
+                        // Whether a { bracket has been seen and its } hasn't been found yet.
+                        let mut in_bracket = false;
+                        let mut label_kind = AsmLabelKind::Named;
+
+                        // A label can also start with a format arg, if it's not a raw asm block.
+                        if !raw && start == '{' {
+                            in_bracket = true;
+                            label_kind = AsmLabelKind::FormatArg;
+                        } else if matches!(start, '0' | '1') {
+                            // Binary labels have only the characters `0` or `1`.
+                            label_kind = AsmLabelKind::Binary;
+                        } else if !(start.is_ascii_alphabetic() || matches!(start, '.' | '_')) {
+                            // Named labels start with ASCII letters, `.` or `_`.
+                            // anything else is not a label
+                            break 'label_loop;
+                        }
+
+                        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;
+                                label_kind = AsmLabelKind::FormatArg;
+                            } else {
+                                let can_continue = match label_kind {
+                                    // Format arg labels are considered to be named labels for the purposes
+                                    // of continuing outside of their {} pair.
+                                    AsmLabelKind::Named | AsmLabelKind::FormatArg => {
+                                        c.is_ascii_alphanumeric() || matches!(c, '_' | '$')
+                                    }
+                                    AsmLabelKind::Binary => matches!(c, '0' | '1'),
+                                };
+
+                                if !can_continue {
+                                    // 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 a label.
+                        spans.push((find_label_span(possible_label), label_kind));
+                        start_idx = idx + 1;
+                    }
+                }
+
+                for (span, label_kind) in spans {
+                    let missing_precise_span = span.is_none();
+                    let span = span.unwrap_or(*template_span);
+                    match label_kind {
+                        AsmLabelKind::Named => {
+                            cx.emit_span_lint(
+                                NAMED_ASM_LABELS,
+                                span,
+                                InvalidAsmLabel::Named { missing_precise_span },
+                            );
+                        }
+                        AsmLabelKind::FormatArg => {
+                            cx.emit_span_lint(
+                                NAMED_ASM_LABELS,
+                                span,
+                                InvalidAsmLabel::FormatArg { missing_precise_span },
+                            );
+                        }
+                        AsmLabelKind::Binary => {
+                            // the binary asm issue only occurs when using intel syntax
+                            if !options.contains(InlineAsmOptions::ATT_SYNTAX) {
+                                cx.emit_span_lint(
+                                    BINARY_ASM_LABELS,
+                                    span,
+                                    InvalidAsmLabel::Binary { missing_precise_span, span },
+                                )
+                            }
+                        }
+                    };
+                }
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `special_module_name` lint detects module
+    /// declarations for files that have a special meaning.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// mod lib;
+    ///
+    /// fn main() {
+    ///     lib::run();
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Cargo recognizes `lib.rs` and `main.rs` as the root of a
+    /// library or binary crate, so declaring them as modules
+    /// will lead to miscompilation of the crate unless configured
+    /// explicitly.
+    ///
+    /// To access a library from a binary target within the same crate,
+    /// use `your_crate_name::` as the path instead of `lib::`:
+    ///
+    /// ```rust,compile_fail
+    /// // bar/src/lib.rs
+    /// fn run() {
+    ///     // ...
+    /// }
+    ///
+    /// // bar/src/main.rs
+    /// fn main() {
+    ///     bar::run();
+    /// }
+    /// ```
+    ///
+    /// Binary targets cannot be used as libraries and so declaring
+    /// one as a module is not allowed.
+    pub SPECIAL_MODULE_NAME,
+    Warn,
+    "module declarations for files with a special meaning",
+}
+
+declare_lint_pass!(SpecialModuleName => [SPECIAL_MODULE_NAME]);
+
+impl EarlyLintPass for SpecialModuleName {
+    fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &ast::Crate) {
+        for item in &krate.items {
+            if let ast::ItemKind::Mod(
+                _,
+                ast::ModKind::Unloaded | ast::ModKind::Loaded(_, ast::Inline::No, _),
+            ) = item.kind
+            {
+                if item.attrs.iter().any(|a| a.has_name(sym::path)) {
+                    continue;
+                }
+
+                match item.ident.name.as_str() {
+                    "lib" => cx.emit_span_lint(
+                        SPECIAL_MODULE_NAME,
+                        item.span,
+                        BuiltinSpecialModuleNameUsed::Lib,
+                    ),
+                    "main" => cx.emit_span_lint(
+                        SPECIAL_MODULE_NAME,
+                        item.span,
+                        BuiltinSpecialModuleNameUsed::Main,
+                    ),
+                    _ => continue,
+                }
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
new file mode 100644
index 00000000000..9f0f116cbd0
--- /dev/null
+++ b/compiler/rustc_lint/src/context.rs
@@ -0,0 +1,1023 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use self::TargetLint::*;
+
+use crate::levels::LintLevelsBuilder;
+use crate::passes::{EarlyLintPassObject, LateLintPassObject};
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_data_structures::sync;
+use rustc_data_structures::unord::UnordMap;
+use rustc_errors::{Diag, 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::bug;
+use rustc_middle::middle::privacy::EffectiveVisibilities;
+use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
+use rustc_middle::ty::print::{with_no_trimmed_paths, PrintError, PrintTraitRefExt as _};
+use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, TyCtxt};
+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_names;
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
+use rustc_target::abi;
+use std::cell::Cell;
+use std::iter;
+use std::slice;
+use tracing::debug;
+
+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;
+
+/// Information about the registered lints.
+///
+/// This is basically the subset of `Context` that we can
+/// build early in the compile pipeline.
+pub struct LintStore {
+    /// Registered lints.
+    lints: Vec<&'static Lint>,
+
+    /// Constructor functions for each variety of lint pass.
+    ///
+    /// These should only be called once, but since we want to avoid locks or
+    /// interior mutability, we don't enforce this (and lints should, in theory,
+    /// be compatible with being constructed more than once, though not
+    /// necessarily in a sane manner. This is safe though.)
+    pub pre_expansion_passes: Vec<Box<EarlyLintPassFactory>>,
+    pub early_passes: Vec<Box<EarlyLintPassFactory>>,
+    pub late_passes: Vec<Box<LateLintPassFactory>>,
+    /// This is unique in that we construct them per-module, so not once.
+    pub late_module_passes: Vec<Box<LateLintPassFactory>>,
+
+    /// Lints indexed by name.
+    by_name: UnordMap<String, TargetLint>,
+
+    /// Map of registered lint groups to what lints they expand to.
+    lint_groups: FxIndexMap<&'static str, LintGroup>,
+}
+
+impl LintStoreMarker for LintStore {}
+
+/// The target of the `by_name` map, which accounts for renaming/deprecation.
+#[derive(Debug)]
+enum TargetLint {
+    /// A direct lint target
+    Id(LintId),
+
+    /// Temporary renaming, used for easing migration pain; see #16545
+    Renamed(String, LintId),
+
+    /// Lint with this name existed previously, but has been removed/deprecated.
+    /// The string argument is the reason for removal.
+    Removed(String),
+
+    /// A lint name that should give no warnings and have no effect.
+    ///
+    /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers
+    /// them as tool lints.
+    Ignored,
+}
+
+pub enum FindLintError {
+    NotFound,
+    Removed,
+}
+
+struct LintAlias {
+    name: &'static str,
+    /// Whether deprecation warnings should be suppressed for this alias.
+    silent: bool,
+}
+
+struct LintGroup {
+    lint_ids: Vec<LintId>,
+    is_externally_loaded: bool,
+    depr: Option<LintAlias>,
+}
+
+#[derive(Debug)]
+pub enum CheckLintNameResult<'a> {
+    Ok(&'a [LintId]),
+    /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name.
+    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.
+    Renamed(String),
+    /// The lint has been removed due to the given reason.
+    Removed(String),
+
+    /// The lint is from a tool. The `LintId` will be returned as if it were a
+    /// rustc lint. The `Option<String>` indicates if the lint has been
+    /// renamed.
+    Tool(&'a [LintId], Option<String>),
+
+    /// The lint is from a tool. Either the lint does not exist in the tool or
+    /// the code was not compiled with the tool and therefore the lint was
+    /// never added to the `LintStore`.
+    MissingTool,
+}
+
+impl LintStore {
+    pub fn new() -> LintStore {
+        LintStore {
+            lints: vec![],
+            pre_expansion_passes: vec![],
+            early_passes: vec![],
+            late_passes: vec![],
+            late_module_passes: vec![],
+            by_name: Default::default(),
+            lint_groups: Default::default(),
+        }
+    }
+
+    pub fn get_lints<'t>(&'t self) -> &'t [&'static Lint] {
+        &self.lints
+    }
+
+    pub fn get_lint_groups<'t>(
+        &'t self,
+    ) -> impl Iterator<Item = (&'static str, Vec<LintId>, bool)> + 't {
+        self.lint_groups
+            .iter()
+            .filter(|(_, LintGroup { depr, .. })| {
+                // Don't display deprecated lint groups.
+                depr.is_none()
+            })
+            .map(|(k, LintGroup { lint_ids, is_externally_loaded, .. })| {
+                (*k, lint_ids.clone(), *is_externally_loaded)
+            })
+    }
+
+    pub fn register_early_pass(
+        &mut self,
+        pass: impl Fn() -> EarlyLintPassObject + 'static + sync::DynSend + sync::DynSync,
+    ) {
+        self.early_passes.push(Box::new(pass));
+    }
+
+    /// This lint pass is softly deprecated. It misses expanded code and has caused a few
+    /// errors in the past. Currently, it is only used in Clippy. New implementations
+    /// should avoid using this interface, as it might be removed in the future.
+    ///
+    /// * See [rust#69838](https://github.com/rust-lang/rust/pull/69838)
+    /// * See [rust-clippy#5518](https://github.com/rust-lang/rust-clippy/pull/5518)
+    pub fn register_pre_expansion_pass(
+        &mut self,
+        pass: impl Fn() -> EarlyLintPassObject + 'static + sync::DynSend + sync::DynSync,
+    ) {
+        self.pre_expansion_passes.push(Box::new(pass));
+    }
+
+    pub fn register_late_pass(
+        &mut self,
+        pass: impl for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx>
+        + 'static
+        + sync::DynSend
+        + sync::DynSync,
+    ) {
+        self.late_passes.push(Box::new(pass));
+    }
+
+    pub fn register_late_mod_pass(
+        &mut self,
+        pass: impl for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx>
+        + 'static
+        + sync::DynSend
+        + sync::DynSync,
+    ) {
+        self.late_module_passes.push(Box::new(pass));
+    }
+
+    /// Helper method for register_early/late_pass
+    pub fn register_lints(&mut self, lints: &[&'static Lint]) {
+        for lint in lints {
+            self.lints.push(lint);
+
+            let id = LintId::of(lint);
+            if self.by_name.insert(lint.name_lower(), Id(id)).is_some() {
+                bug!("duplicate specification of lint {}", lint.name_lower())
+            }
+
+            if let Some(FutureIncompatibleInfo { reason, .. }) = lint.future_incompatible {
+                if let Some(edition) = reason.edition() {
+                    self.lint_groups
+                        .entry(edition.lint_name())
+                        .or_insert(LintGroup {
+                            lint_ids: vec![],
+                            is_externally_loaded: lint.is_externally_loaded,
+                            depr: None,
+                        })
+                        .lint_ids
+                        .push(id);
+                } else {
+                    // Lints belonging to the `future_incompatible` lint group are lints where a
+                    // future version of rustc will cause existing code to stop compiling.
+                    // Lints tied to an edition don't count because they are opt-in.
+                    self.lint_groups
+                        .entry("future_incompatible")
+                        .or_insert(LintGroup {
+                            lint_ids: vec![],
+                            is_externally_loaded: lint.is_externally_loaded,
+                            depr: None,
+                        })
+                        .lint_ids
+                        .push(id);
+                }
+            }
+        }
+    }
+
+    pub fn register_group_alias(&mut self, lint_name: &'static str, alias: &'static str) {
+        self.lint_groups.insert(
+            alias,
+            LintGroup {
+                lint_ids: vec![],
+                is_externally_loaded: false,
+                depr: Some(LintAlias { name: lint_name, silent: true }),
+            },
+        );
+    }
+
+    pub fn register_group(
+        &mut self,
+        is_externally_loaded: bool,
+        name: &'static str,
+        deprecated_name: Option<&'static str>,
+        to: Vec<LintId>,
+    ) {
+        let new = self
+            .lint_groups
+            .insert(name, LintGroup { lint_ids: to, is_externally_loaded, depr: None })
+            .is_none();
+        if let Some(deprecated) = deprecated_name {
+            self.lint_groups.insert(
+                deprecated,
+                LintGroup {
+                    lint_ids: vec![],
+                    is_externally_loaded,
+                    depr: Some(LintAlias { name, silent: false }),
+                },
+            );
+        }
+
+        if !new {
+            bug!("duplicate specification of lint group {}", name);
+        }
+    }
+
+    /// This lint should give no warning and have no effect.
+    ///
+    /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers them as tool lints.
+    #[track_caller]
+    pub fn register_ignored(&mut self, name: &str) {
+        if self.by_name.insert(name.to_string(), Ignored).is_some() {
+            bug!("duplicate specification of lint {}", name);
+        }
+    }
+
+    /// This lint has been renamed; warn about using the new name and apply the lint.
+    #[track_caller]
+    pub fn register_renamed(&mut self, old_name: &str, new_name: &str) {
+        let Some(&Id(target)) = self.by_name.get(new_name) else {
+            bug!("invalid lint renaming of {} to {}", old_name, new_name);
+        };
+        self.by_name.insert(old_name.to_string(), Renamed(new_name.to_string(), target));
+    }
+
+    pub fn register_removed(&mut self, name: &str, reason: &str) {
+        self.by_name.insert(name.into(), Removed(reason.into()));
+    }
+
+    pub fn find_lints(&self, mut lint_name: &str) -> Result<Vec<LintId>, FindLintError> {
+        match self.by_name.get(lint_name) {
+            Some(&Id(lint_id)) => Ok(vec![lint_id]),
+            Some(&Renamed(_, lint_id)) => Ok(vec![lint_id]),
+            Some(&Removed(_)) => Err(FindLintError::Removed),
+            Some(&Ignored) => Ok(vec![]),
+            None => loop {
+                return match self.lint_groups.get(lint_name) {
+                    Some(LintGroup { lint_ids, depr, .. }) => {
+                        if let Some(LintAlias { name, .. }) = depr {
+                            lint_name = name;
+                            continue;
+                        }
+                        Ok(lint_ids.clone())
+                    }
+                    None => Err(FindLintError::Removed),
+                };
+            },
+        }
+    }
+
+    /// True if this symbol represents a lint group name.
+    pub fn is_lint_group(&self, lint_name: Symbol) -> bool {
+        debug!(
+            "is_lint_group(lint_name={:?}, lint_groups={:?})",
+            lint_name,
+            self.lint_groups.keys().collect::<Vec<_>>()
+        );
+        let lint_name_str = lint_name.as_str();
+        self.lint_groups.contains_key(lint_name_str) || {
+            let warnings_name_str = crate::WARNINGS.name_lower();
+            lint_name_str == warnings_name_str
+        }
+    }
+
+    /// Checks the name of a lint for its existence, and whether it was
+    /// 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
+    /// inspect attributes, this is only run from the late pass to avoid
+    /// printing duplicate warnings.
+    pub fn check_lint_name(
+        &self,
+        lint_name: &str,
+        tool_name: Option<Symbol>,
+        registered_tools: &RegisteredTools,
+    ) -> CheckLintNameResult<'_> {
+        if let Some(tool_name) = tool_name {
+            // FIXME: rustc and rustdoc are considered tools for lints, but not for attributes.
+            if tool_name != sym::rustc
+                && tool_name != sym::rustdoc
+                && !registered_tools.contains(&Ident::with_dummy_span(tool_name))
+            {
+                return CheckLintNameResult::NoTool;
+            }
+        }
+
+        let complete_name = if let Some(tool_name) = tool_name {
+            format!("{tool_name}::{lint_name}")
+        } else {
+            lint_name.to_string()
+        };
+        // If the lint was scoped with `tool::` check if the tool lint exists
+        if let Some(tool_name) = tool_name {
+            match self.by_name.get(&complete_name) {
+                None => match self.lint_groups.get(&*complete_name) {
+                    // If the lint isn't registered, there are two possibilities:
+                    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);
+                        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, 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.
+                            CheckLintNameResult::MissingTool
+                        };
+                    }
+                    Some(LintGroup { lint_ids, .. }) => {
+                        return CheckLintNameResult::Tool(lint_ids, None);
+                    }
+                },
+                Some(Id(id)) => return CheckLintNameResult::Tool(slice::from_ref(id), None),
+                // If the lint was registered as removed or renamed by the lint tool, we don't need
+                // to treat tool_lints and rustc lints different and can use the code below.
+                _ => {}
+            }
+        }
+        match self.by_name.get(&complete_name) {
+            Some(Renamed(new_name, _)) => CheckLintNameResult::Renamed(new_name.to_string()),
+            Some(Removed(reason)) => CheckLintNameResult::Removed(reason.to_string()),
+            None => match self.lint_groups.get(&*complete_name) {
+                // If neither the lint, nor the lint group exists check if there is a `clippy::`
+                // variant of this lint
+                None => self.check_tool_name_for_backwards_compat(&complete_name, "clippy"),
+                Some(LintGroup { lint_ids, depr, .. }) => {
+                    // Check if the lint group name is deprecated
+                    if let Some(LintAlias { name, silent }) = depr {
+                        let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap();
+                        return if *silent {
+                            CheckLintNameResult::Ok(lint_ids)
+                        } else {
+                            CheckLintNameResult::Tool(lint_ids, Some((*name).to_string()))
+                        };
+                    }
+                    CheckLintNameResult::Ok(lint_ids)
+                }
+            },
+            Some(Id(id)) => CheckLintNameResult::Ok(slice::from_ref(id)),
+            Some(&Ignored) => CheckLintNameResult::Ok(&[]),
+        }
+    }
+
+    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), 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()
+            .filter_map(|(k, LintGroup { depr, .. })| depr.is_none().then_some(k))
+            .collect();
+        groups.sort();
+        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 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)
+    }
+
+    fn check_tool_name_for_backwards_compat(
+        &self,
+        lint_name: &str,
+        tool_name: &str,
+    ) -> CheckLintNameResult<'_> {
+        let complete_name = format!("{tool_name}::{lint_name}");
+        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, 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 {
+                        let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap();
+                        if *silent {
+                            CheckLintNameResult::Tool(lint_ids, Some(complete_name))
+                        } else {
+                            CheckLintNameResult::Tool(lint_ids, Some((*name).to_string()))
+                        }
+                    } else {
+                        CheckLintNameResult::Tool(lint_ids, Some(complete_name))
+                    }
+                }
+            },
+            Some(Id(id)) => CheckLintNameResult::Tool(slice::from_ref(id), Some(complete_name)),
+            Some(other) => {
+                debug!("got renamed lint {:?}", other);
+                CheckLintNameResult::NoLint(None)
+            }
+        }
+    }
+}
+
+/// Context for lint checking outside of type inference.
+pub struct LateContext<'tcx> {
+    /// Type context we're checking in.
+    pub tcx: TyCtxt<'tcx>,
+
+    /// Current body, or `None` if outside a body.
+    pub enclosing_body: Option<hir::BodyId>,
+
+    /// Type-checking results for the current body. Access using the `typeck_results`
+    /// and `maybe_typeck_results` methods, which handle querying the typeck results on demand.
+    // FIXME(eddyb) move all the code accessing internal fields like this,
+    // to this module, to avoid exposing it to lint logic.
+    pub(super) cached_typeck_results: Cell<Option<&'tcx ty::TypeckResults<'tcx>>>,
+
+    /// Parameter environment for the item we are in.
+    pub param_env: ty::ParamEnv<'tcx>,
+
+    /// Items accessible from the crate being checked.
+    pub effective_visibilities: &'tcx EffectiveVisibilities,
+
+    pub last_node_with_lint_attrs: hir::HirId,
+
+    /// Generic type parameters in scope for the item we are in.
+    pub generics: Option<&'tcx hir::Generics<'tcx>>,
+
+    /// We are only looking at one module
+    pub only_module: bool,
+}
+
+/// Context for lint checking of the AST, after expansion, before lowering to HIR.
+pub struct EarlyContext<'a> {
+    pub builder: LintLevelsBuilder<'a, crate::levels::TopDown>,
+    pub buffered: LintBuffer,
+}
+
+impl EarlyContext<'_> {
+    /// Emit a lint at the appropriate level, with an optional associated span and an existing
+    /// diagnostic.
+    ///
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
+    #[rustc_lint_diagnostics]
+    pub fn span_lint_with_diagnostics(
+        &self,
+        lint: &'static Lint,
+        span: MultiSpan,
+        diagnostic: BuiltinLintDiag,
+    ) {
+        self.opt_span_lint(lint, Some(span), |diag| {
+            diagnostics::decorate_lint(self.sess(), diagnostic, diag);
+        });
+    }
+}
+
+pub trait LintContext {
+    fn sess(&self) -> &Session;
+
+    // FIXME: These methods should not take an Into<MultiSpan> -- instead, callers should need to
+    // set the span in their `decorate` function (preferably using set_span).
+    /// Emit a lint at the appropriate level, with an optional associated span.
+    ///
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
+    #[rustc_lint_diagnostics]
+    fn opt_span_lint<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: Option<S>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
+    );
+
+    /// Emit a lint at `span` from a lint struct (some type that implements `LintDiagnostic`,
+    /// typically generated by `#[derive(LintDiagnostic)]`).
+    fn emit_span_lint<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: S,
+        decorator: impl for<'a> LintDiagnostic<'a, ()>,
+    ) {
+        self.opt_span_lint(lint, Some(span), |lint| {
+            decorator.decorate_lint(lint);
+        });
+    }
+
+    /// Emit a lint at the appropriate level, with an associated span.
+    ///
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
+    #[rustc_lint_diagnostics]
+    fn span_lint<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: S,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
+    ) {
+        self.opt_span_lint(lint, Some(span), decorate);
+    }
+
+    /// Emit a lint from a lint struct (some type that implements `LintDiagnostic`, typically
+    /// generated by `#[derive(LintDiagnostic)]`).
+    fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> LintDiagnostic<'a, ()>) {
+        self.opt_span_lint(lint, None as Option<Span>, |lint| {
+            decorator.decorate_lint(lint);
+        });
+    }
+
+    /// Emit a lint at the appropriate level, with no associated span.
+    ///
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
+    #[rustc_lint_diagnostics]
+    fn lint(&self, lint: &'static Lint, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)) {
+        self.opt_span_lint(lint, None as Option<Span>, decorate);
+    }
+
+    /// This returns the lint level for the given lint at the current location.
+    fn get_lint_level(&self, lint: &'static Lint) -> Level;
+
+    /// This function can be used to manually fulfill an expectation. This can
+    /// be used for lints which contain several spans, and should be suppressed,
+    /// if either location was marked with an expectation.
+    ///
+    /// 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.
+    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 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(
+                "this is a dummy diagnostic, to submit and store an expectation",
+                expectation,
+            )
+            .emit();
+    }
+}
+
+impl<'a> EarlyContext<'a> {
+    pub(crate) fn new(
+        sess: &'a Session,
+        features: &'a Features,
+        lint_added_lints: bool,
+        lint_store: &'a LintStore,
+        registered_tools: &'a RegisteredTools,
+        buffered: LintBuffer,
+    ) -> EarlyContext<'a> {
+        EarlyContext {
+            builder: LintLevelsBuilder::new(
+                sess,
+                features,
+                lint_added_lints,
+                lint_store,
+                registered_tools,
+            ),
+            buffered,
+        }
+    }
+}
+
+impl<'tcx> LintContext for LateContext<'tcx> {
+    /// Gets the overall compiler `Session` object.
+    fn sess(&self) -> &Session {
+        self.tcx.sess
+    }
+
+    #[rustc_lint_diagnostics]
+    fn opt_span_lint<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: Option<S>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
+    ) {
+        let hir_id = self.last_node_with_lint_attrs;
+
+        match span {
+            Some(s) => self.tcx.node_span_lint(lint, hir_id, s, decorate),
+            None => self.tcx.node_lint(lint, hir_id, decorate),
+        }
+    }
+
+    fn get_lint_level(&self, lint: &'static Lint) -> Level {
+        self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs).0
+    }
+}
+
+impl LintContext for EarlyContext<'_> {
+    /// Gets the overall compiler `Session` object.
+    fn sess(&self) -> &Session {
+        self.builder.sess()
+    }
+
+    #[rustc_lint_diagnostics]
+    fn opt_span_lint<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: Option<S>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
+    ) {
+        self.builder.opt_span_lint(lint, span.map(|s| s.into()), decorate)
+    }
+
+    fn get_lint_level(&self, lint: &'static Lint) -> Level {
+        self.builder.lint_level(lint).0
+    }
+}
+
+impl<'tcx> LateContext<'tcx> {
+    /// Gets the type-checking results for the current body,
+    /// or `None` if outside a body.
+    pub fn maybe_typeck_results(&self) -> Option<&'tcx ty::TypeckResults<'tcx>> {
+        self.cached_typeck_results.get().or_else(|| {
+            self.enclosing_body.map(|body| {
+                let typeck_results = self.tcx.typeck_body(body);
+                self.cached_typeck_results.set(Some(typeck_results));
+                typeck_results
+            })
+        })
+    }
+
+    /// Gets the type-checking results for the current body.
+    /// As this will ICE if called outside bodies, only call when working with
+    /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
+    #[track_caller]
+    pub fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
+        self.maybe_typeck_results().expect("`LateContext::typeck_results` called outside of body")
+    }
+
+    /// Returns the final resolution of a `QPath`, or `Res::Err` if unavailable.
+    /// Unlike `.typeck_results().qpath_res(qpath, id)`, this can be used even outside
+    /// bodies (e.g. for paths in `hir::Ty`), without any risk of ICE-ing.
+    pub fn qpath_res(&self, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res {
+        match *qpath {
+            hir::QPath::Resolved(_, path) => path.res,
+            hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => self
+                .maybe_typeck_results()
+                .filter(|typeck_results| typeck_results.hir_owner == id.owner)
+                .or_else(|| {
+                    self.tcx
+                        .has_typeck_results(id.owner.def_id)
+                        .then(|| self.tcx.typeck(id.owner.def_id))
+                })
+                .and_then(|typeck_results| typeck_results.type_dependent_def(id))
+                .map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)),
+        }
+    }
+
+    /// Check if a `DefId`'s path matches the given absolute type path usage.
+    ///
+    /// Anonymous scopes such as `extern` imports are matched with `kw::Empty`;
+    /// inherent `impl` blocks are matched with the name of the type.
+    ///
+    /// Instead of using this method, it is often preferable to instead use
+    /// `rustc_diagnostic_item` or a `lang_item`. This is less prone to errors
+    /// as paths get invalidated if the target definition moves.
+    ///
+    /// # Examples
+    ///
+    /// ```rust,ignore (no context or def id available)
+    /// if cx.match_def_path(def_id, &[sym::core, sym::option, sym::Option]) {
+    ///     // The given `def_id` is that of an `Option` type
+    /// }
+    /// ```
+    ///
+    /// Used by clippy, but should be replaced by diagnostic items eventually.
+    pub fn match_def_path(&self, def_id: DefId, path: &[Symbol]) -> bool {
+        let names = self.get_def_path(def_id);
+
+        names.len() == path.len() && iter::zip(names, path).all(|(a, &b)| a == b)
+    }
+
+    /// Gets the absolute path of `def_id` as a vector of `Symbol`.
+    ///
+    /// # Examples
+    ///
+    /// ```rust,ignore (no context or def id available)
+    /// let def_path = cx.get_def_path(def_id);
+    /// if let &[sym::core, sym::option, sym::Option] = &def_path[..] {
+    ///     // The given `def_id` is that of an `Option` type
+    /// }
+    /// ```
+    pub fn get_def_path(&self, def_id: DefId) -> Vec<Symbol> {
+        struct AbsolutePathPrinter<'tcx> {
+            tcx: TyCtxt<'tcx>,
+            path: Vec<Symbol>,
+        }
+
+        impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
+            fn tcx(&self) -> TyCtxt<'tcx> {
+                self.tcx
+            }
+
+            fn print_region(&mut self, _region: ty::Region<'_>) -> Result<(), PrintError> {
+                Ok(())
+            }
+
+            fn print_type(&mut self, _ty: Ty<'tcx>) -> Result<(), PrintError> {
+                Ok(())
+            }
+
+            fn print_dyn_existential(
+                &mut self,
+                _predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
+            ) -> Result<(), PrintError> {
+                Ok(())
+            }
+
+            fn print_const(&mut self, _ct: ty::Const<'tcx>) -> Result<(), PrintError> {
+                Ok(())
+            }
+
+            fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
+                self.path = vec![self.tcx.crate_name(cnum)];
+                Ok(())
+            }
+
+            fn path_qualified(
+                &mut self,
+                self_ty: Ty<'tcx>,
+                trait_ref: Option<ty::TraitRef<'tcx>>,
+            ) -> Result<(), PrintError> {
+                if trait_ref.is_none() {
+                    if let ty::Adt(def, args) = self_ty.kind() {
+                        return self.print_def_path(def.did(), args);
+                    }
+                }
+
+                // This shouldn't ever be needed, but just in case:
+                with_no_trimmed_paths!({
+                    self.path = vec![match trait_ref {
+                        Some(trait_ref) => Symbol::intern(&format!("{trait_ref:?}")),
+                        None => Symbol::intern(&format!("<{self_ty}>")),
+                    }];
+                    Ok(())
+                })
+            }
+
+            fn path_append_impl(
+                &mut self,
+                print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
+                _disambiguated_data: &DisambiguatedDefPathData,
+                self_ty: Ty<'tcx>,
+                trait_ref: Option<ty::TraitRef<'tcx>>,
+            ) -> Result<(), PrintError> {
+                print_prefix(self)?;
+
+                // This shouldn't ever be needed, but just in case:
+                self.path.push(match trait_ref {
+                    Some(trait_ref) => {
+                        with_no_trimmed_paths!(Symbol::intern(&format!(
+                            "<impl {} for {}>",
+                            trait_ref.print_only_trait_path(),
+                            self_ty
+                        )))
+                    }
+                    None => {
+                        with_no_trimmed_paths!(Symbol::intern(&format!("<impl {self_ty}>")))
+                    }
+                });
+
+                Ok(())
+            }
+
+            fn path_append(
+                &mut self,
+                print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
+                disambiguated_data: &DisambiguatedDefPathData,
+            ) -> Result<(), PrintError> {
+                print_prefix(self)?;
+
+                // Skip `::{{extern}}` blocks and `::{{constructor}}` on tuple/unit structs.
+                if let DefPathData::ForeignMod | DefPathData::Ctor = disambiguated_data.data {
+                    return Ok(());
+                }
+
+                self.path.push(Symbol::intern(&disambiguated_data.data.to_string()));
+                Ok(())
+            }
+
+            fn path_generic_args(
+                &mut self,
+                print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
+                _args: &[GenericArg<'tcx>],
+            ) -> Result<(), PrintError> {
+                print_prefix(self)
+            }
+        }
+
+        let mut printer = AbsolutePathPrinter { tcx: self.tcx, path: vec![] };
+        printer.print_def_path(def_id, &[]).unwrap();
+        printer.path
+    }
+
+    /// Returns the associated type `name` for `self_ty` as an implementation of `trait_id`.
+    /// Do not invoke without first verifying that the type implements the trait.
+    pub fn get_associated_type(
+        &self,
+        self_ty: Ty<'tcx>,
+        trait_id: DefId,
+        name: &str,
+    ) -> Option<Ty<'tcx>> {
+        let tcx = self.tcx;
+        tcx.associated_items(trait_id)
+            .find_by_name_and_kind(tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id)
+            .and_then(|assoc| {
+                let proj = Ty::new_projection(tcx, assoc.def_id, [self_ty]);
+                tcx.try_normalize_erasing_regions(self.param_env, proj).ok()
+            })
+    }
+
+    /// If the given expression is a local binding, find the initializer expression.
+    /// If that initializer expression is another local binding, find its initializer again.
+    ///
+    /// This process repeats as long as possible (but usually no more than once).
+    /// Type-check adjustments are not taken in account in this function.
+    ///
+    /// Examples:
+    /// ```
+    /// let abc = 1;
+    /// let def = abc + 2;
+    /// //        ^^^^^^^ output
+    /// let def = def;
+    /// dbg!(def);
+    /// //   ^^^ input
+    /// ```
+    pub fn expr_or_init<'a>(&self, mut expr: &'a hir::Expr<'tcx>) -> &'a hir::Expr<'tcx> {
+        expr = expr.peel_blocks();
+
+        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) => Some(self.tcx.parent_hir_node(hir_id)),
+                _ => None,
+            }
+            && let Some(init) = match parent_node {
+                hir::Node::Expr(expr) => Some(expr),
+                hir::Node::LetStmt(hir::LetStmt { init, .. }) => *init,
+                _ => None,
+            }
+        {
+            expr = init.peel_blocks();
+        }
+        expr
+    }
+
+    /// If the given expression is a local binding, find the initializer expression.
+    /// If that initializer expression is another local or **outside** (`const`/`static`)
+    /// binding, find its initializer again.
+    ///
+    /// This process repeats as long as possible (but usually no more than once).
+    /// Type-check adjustments are not taken in account in this function.
+    ///
+    /// Examples:
+    /// ```
+    /// const ABC: i32 = 1;
+    /// //               ^ output
+    /// let def = ABC;
+    /// dbg!(def);
+    /// //   ^^^ input
+    ///
+    /// // or...
+    /// let abc = 1;
+    /// let def = abc + 2;
+    /// //        ^^^^^^^ output
+    /// dbg!(def);
+    /// //   ^^^ input
+    /// ```
+    pub fn expr_or_init_with_outside_body<'a>(
+        &self,
+        mut expr: &'a hir::Expr<'tcx>,
+    ) -> &'a hir::Expr<'tcx> {
+        expr = expr.peel_blocks();
+
+        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) => Some(self.tcx.parent_hir_node(hir_id)),
+                Res::Def(_, def_id) => self.tcx.hir().get_if_local(def_id),
+                _ => None,
+            }
+            && let Some(init) = match parent_node {
+                hir::Node::Expr(expr) => Some(expr),
+                hir::Node::LetStmt(hir::LetStmt { init, .. }) => *init,
+                hir::Node::Item(item) => match item.kind {
+                    hir::ItemKind::Const(.., body_id) | hir::ItemKind::Static(.., body_id) => {
+                        Some(self.tcx.hir().body(body_id).value)
+                    }
+                    _ => None,
+                },
+                _ => None,
+            }
+        {
+            expr = init.peel_blocks();
+        }
+        expr
+    }
+}
+
+impl<'tcx> abi::HasDataLayout for LateContext<'tcx> {
+    #[inline]
+    fn data_layout(&self) -> &abi::TargetDataLayout {
+        &self.tcx.data_layout
+    }
+}
+
+impl<'tcx> ty::layout::HasTyCtxt<'tcx> for LateContext<'tcx> {
+    #[inline]
+    fn tcx(&self) -> TyCtxt<'tcx> {
+        self.tcx
+    }
+}
+
+impl<'tcx> ty::layout::HasParamEnv<'tcx> for LateContext<'tcx> {
+    #[inline]
+    fn param_env(&self) -> ty::ParamEnv<'tcx> {
+        self.param_env
+    }
+}
+
+impl<'tcx> LayoutOfHelpers<'tcx> for LateContext<'tcx> {
+    type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
+
+    #[inline]
+    fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> {
+        err
+    }
+}
diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs
new file mode 100644
index 00000000000..05e075205c4
--- /dev/null
+++ b/compiler/rustc_lint/src/context/diagnostics.rs
@@ -0,0 +1,441 @@
+#![allow(rustc::diagnostic_outside_of_impl)]
+#![allow(rustc::untranslatable_diagnostic)]
+
+use std::borrow::Cow;
+
+use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
+use rustc_errors::elided_lifetime_in_path_suggestion;
+use rustc_errors::{Applicability, Diag, DiagArgValue, LintDiagnostic};
+use rustc_middle::middle::stability;
+use rustc_session::lint::BuiltinLintDiag;
+use rustc_session::Session;
+use rustc_span::BytePos;
+use tracing::debug;
+
+use crate::lints;
+
+mod check_cfg;
+
+pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Diag<'_, ()>) {
+    match diagnostic {
+        BuiltinLintDiag::UnicodeTextFlow(comment_span, content) => {
+            let spans: Vec<_> = content
+                .char_indices()
+                .filter_map(|(i, c)| {
+                    TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| {
+                        let lo = comment_span.lo() + BytePos(2 + i as u32);
+                        (c, comment_span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32)))
+                    })
+                })
+                .collect();
+            let characters = spans
+                .iter()
+                .map(|&(c, span)| lints::UnicodeCharNoteSub { span, c_debug: format!("{c:?}") })
+                .collect();
+            let suggestions = (!spans.is_empty()).then_some(lints::UnicodeTextFlowSuggestion {
+                spans: spans.iter().map(|(_c, span)| *span).collect(),
+            });
+
+            lints::UnicodeTextFlow {
+                comment_span,
+                characters,
+                suggestions,
+                num_codepoints: spans.len(),
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::AbsPathWithModule(mod_span) => {
+            let (replacement, applicability) = match sess.source_map().span_to_snippet(mod_span) {
+                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),
+            };
+            lints::AbsPathWithModule {
+                sugg: lints::AbsPathWithModuleSugg { span: mod_span, applicability, replacement },
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::ProcMacroDeriveResolutionFallback { span: macro_span, ns, ident } => {
+            lints::ProcMacroDeriveResolutionFallback { span: macro_span, ns, ident }
+                .decorate_lint(diag)
+        }
+        BuiltinLintDiag::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => {
+            lints::MacroExpandedMacroExportsAccessedByAbsolutePaths { definition: span_def }
+                .decorate_lint(diag)
+        }
+
+        BuiltinLintDiag::ElidedLifetimesInPaths(n, path_span, incl_angl_brckt, insertion_span) => {
+            lints::ElidedLifetimesInPaths {
+                subdiag: elided_lifetime_in_path_suggestion(
+                    sess.source_map(),
+                    n,
+                    path_span,
+                    incl_angl_brckt,
+                    insertion_span,
+                ),
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnknownCrateTypes { span, candidate } => {
+            let sugg = candidate.map(|candidate| lints::UnknownCrateTypesSub { span, candidate });
+            lints::UnknownCrateTypes { sugg }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedImports {
+            remove_whole_use,
+            num_to_remove,
+            remove_spans,
+            test_module_span,
+            span_snippets,
+        } => {
+            let sugg = if remove_whole_use {
+                lints::UnusedImportsSugg::RemoveWholeUse { span: remove_spans[0] }
+            } else {
+                lints::UnusedImportsSugg::RemoveImports { remove_spans, num_to_remove }
+            };
+            let test_module_span =
+                test_module_span.map(|span| sess.source_map().guess_head_span(span));
+
+            lints::UnusedImports {
+                sugg,
+                test_module_span,
+                num_snippets: span_snippets.len(),
+                span_snippets: DiagArgValue::StrListSepByAnd(
+                    span_snippets.into_iter().map(Cow::Owned).collect(),
+                ),
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::RedundantImport(spans, ident) => {
+            let subs = spans
+                .into_iter()
+                .map(|(span, is_imported)| {
+                    (match (span.is_dummy(), is_imported) {
+                        (false, true) => lints::RedundantImportSub::ImportedHere,
+                        (false, false) => lints::RedundantImportSub::DefinedHere,
+                        (true, true) => lints::RedundantImportSub::ImportedPrelude,
+                        (true, false) => lints::RedundantImportSub::DefinedPrelude,
+                    })(span)
+                })
+                .collect();
+            lints::RedundantImport { subs, ident }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::DeprecatedMacro {
+            suggestion,
+            suggestion_span,
+            note,
+            path,
+            since_kind,
+        } => {
+            let sub = suggestion.map(|suggestion| stability::DeprecationSuggestion {
+                span: suggestion_span,
+                kind: "macro".to_owned(),
+                suggestion,
+            });
+
+            stability::Deprecated { sub, kind: "macro".to_owned(), path, note, since_kind }
+                .decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedDocComment(attr_span) => {
+            lints::UnusedDocComment { span: attr_span }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::PatternsInFnsWithoutBody { span: remove_span, ident, is_foreign } => {
+            let sub = lints::PatternsInFnsWithoutBodySub { ident, span: remove_span };
+            if is_foreign {
+                lints::PatternsInFnsWithoutBody::Foreign { sub }
+            } else {
+                lints::PatternsInFnsWithoutBody::Bodiless { sub }
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::MissingAbi(label_span, default_abi) => {
+            lints::MissingAbi { span: label_span, default_abi: default_abi.name() }
+                .decorate_lint(diag);
+        }
+        BuiltinLintDiag::LegacyDeriveHelpers(label_span) => {
+            lints::LegacyDeriveHelpers { span: label_span }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::OrPatternsBackCompat(suggestion_span, suggestion) => {
+            lints::OrPatternsBackCompat { span: suggestion_span, suggestion }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::ReservedPrefix(label_span, prefix) => {
+            lints::ReservedPrefix {
+                label: label_span,
+                suggestion: label_span.shrink_to_hi(),
+                prefix,
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => {
+            lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::TrailingMacro(is_trailing, name) => {
+            lints::TrailingMacro { is_trailing, name }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::BreakWithLabelAndLoop(sugg_span) => {
+            lints::BreakWithLabelAndLoop {
+                sub: lints::BreakWithLabelAndLoopSub {
+                    left: sugg_span.shrink_to_lo(),
+                    right: sugg_span.shrink_to_hi(),
+                },
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnexpectedCfgName(name, value) => {
+            check_cfg::unexpected_cfg_name(sess, name, value).decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnexpectedCfgValue(name, value) => {
+            check_cfg::unexpected_cfg_value(sess, name, value).decorate_lint(diag);
+        }
+        BuiltinLintDiag::DeprecatedWhereclauseLocation(left_sp, sugg) => {
+            let suggestion = match sugg {
+                Some((right_sp, sugg)) => lints::DeprecatedWhereClauseLocationSugg::MoveToEnd {
+                    left: left_sp,
+                    right: right_sp,
+                    sugg,
+                },
+                None => lints::DeprecatedWhereClauseLocationSugg::RemoveWhere { span: left_sp },
+            };
+            lints::DeprecatedWhereClauseLocation { suggestion }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::MissingUnsafeOnExtern { suggestion } => {
+            lints::MissingUnsafeOnExtern { suggestion }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::SingleUseLifetime {
+            param_span,
+            use_span: Some((use_span, elide)),
+            deletion_span,
+            ident,
+        } => {
+            debug!(?param_span, ?use_span, ?deletion_span);
+            let suggestion = if let Some(deletion_span) = deletion_span {
+                let (use_span, replace_lt) = if elide {
+                    let use_span = sess.source_map().span_extend_while_whitespace(use_span);
+                    (use_span, String::new())
+                } 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 deletion_span =
+                    if deletion_span.is_empty() { None } else { Some(deletion_span) };
+                Some(lints::SingleUseLifetimeSugg { deletion_span, use_span, replace_lt })
+            } else {
+                None
+            };
+
+            lints::SingleUseLifetime { suggestion, param_span, use_span, ident }
+                .decorate_lint(diag);
+        }
+        BuiltinLintDiag::SingleUseLifetime { use_span: None, deletion_span, ident, .. } => {
+            debug!(?deletion_span);
+            lints::UnusedLifetime { deletion_span, ident }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::NamedArgumentUsedPositionally {
+            position_sp_to_replace,
+            position_sp_for_msg,
+            named_arg_sp,
+            named_arg_name,
+            is_formatting_arg,
+        } => {
+            let (suggestion, name) = if let Some(positional_arg_to_replace) = position_sp_to_replace
+            {
+                let mut name = named_arg_name.clone();
+                if is_formatting_arg {
+                    name.push('$')
+                };
+                let span_to_replace = if let Ok(positional_arg_content) =
+                    sess.source_map().span_to_snippet(positional_arg_to_replace)
+                    && positional_arg_content.starts_with(':')
+                {
+                    positional_arg_to_replace.shrink_to_lo()
+                } else {
+                    positional_arg_to_replace
+                };
+                (Some(span_to_replace), name)
+            } else {
+                (None, String::new())
+            };
+
+            lints::NamedArgumentUsedPositionally {
+                named_arg_sp,
+                position_label_sp: position_sp_for_msg,
+                suggestion,
+                name,
+                named_arg_name,
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::ByteSliceInPackedStructWithDerive { ty } => {
+            lints::ByteSliceInPackedStructWithDerive { ty }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedExternCrate { removal_span } => {
+            lints::UnusedExternCrate { removal_span }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::ExternCrateNotIdiomatic { vis_span, ident_span } => {
+            let suggestion_span = vis_span.between(ident_span);
+            let code = if vis_span.is_empty() { "use " } else { " use " };
+
+            lints::ExternCrateNotIdiomatic { span: suggestion_span, code }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::AmbiguousGlobImports { diag: ambiguity } => {
+            lints::AmbiguousGlobImports { ambiguity }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::AmbiguousGlobReexports {
+            name,
+            namespace,
+            first_reexport_span,
+            duplicate_reexport_span,
+        } => {
+            lints::AmbiguousGlobReexports {
+                first_reexport: first_reexport_span,
+                duplicate_reexport: duplicate_reexport_span,
+                name,
+                namespace,
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::HiddenGlobReexports {
+            name,
+            namespace,
+            glob_reexport_span,
+            private_item_span,
+        } => {
+            lints::HiddenGlobReexports {
+                glob_reexport: glob_reexport_span,
+                private_item: private_item_span,
+
+                name,
+                namespace,
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedQualifications { removal_span } => {
+            lints::UnusedQualifications { removal_span }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnsafeAttrOutsideUnsafe {
+            attribute_name_span,
+            sugg_spans: (left, right),
+        } => {
+            lints::UnsafeAttrOutsideUnsafe {
+                span: attribute_name_span,
+                suggestion: lints::UnsafeAttrOutsideUnsafeSuggestion { left, right },
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::AssociatedConstElidedLifetime {
+            elided,
+            span: lt_span,
+            lifetimes_in_scope,
+        } => {
+            let lt_span = if elided { lt_span.shrink_to_hi() } else { lt_span };
+            let code = if elided { "'static " } else { "'static" };
+            lints::AssociatedConstElidedLifetime {
+                span: lt_span,
+                code,
+                elided,
+                lifetimes_in_scope,
+            }
+            .decorate_lint(diag);
+        }
+        BuiltinLintDiag::RedundantImportVisibility { max_vis, span: vis_span, import_vis } => {
+            lints::RedundantImportVisibility { span: vis_span, help: (), max_vis, import_vis }
+                .decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnknownDiagnosticAttribute { span: typo_span, typo_name } => {
+            let typo = typo_name.map(|typo_name| lints::UnknownDiagnosticAttributeTypoSugg {
+                span: typo_span,
+                typo_name,
+            });
+            lints::UnknownDiagnosticAttribute { typo }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::MacroUseDeprecated => {
+            lints::MacroUseDeprecated.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedMacroUse => lints::UnusedMacroUse.decorate_lint(diag),
+        BuiltinLintDiag::PrivateExternCrateReexport { source: ident, extern_crate_span } => {
+            lints::PrivateExternCrateReexport { ident, sugg: extern_crate_span.shrink_to_lo() }
+                .decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedLabel => lints::UnusedLabel.decorate_lint(diag),
+        BuiltinLintDiag::MacroIsPrivate(ident) => {
+            lints::MacroIsPrivate { ident }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedMacroDefinition(name) => {
+            lints::UnusedMacroDefinition { name }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::MacroRuleNeverUsed(n, name) => {
+            lints::MacroRuleNeverUsed { n: n + 1, name }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnstableFeature(msg) => {
+            lints::UnstableFeature { msg }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::AvoidUsingIntelSyntax => {
+            lints::AvoidIntelSyntax.decorate_lint(diag);
+        }
+        BuiltinLintDiag::AvoidUsingAttSyntax => {
+            lints::AvoidAttSyntax.decorate_lint(diag);
+        }
+        BuiltinLintDiag::IncompleteInclude => {
+            lints::IncompleteInclude.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnnameableTestItems => {
+            lints::UnnameableTestItems.decorate_lint(diag);
+        }
+        BuiltinLintDiag::DuplicateMacroAttribute => {
+            lints::DuplicateMacroAttribute.decorate_lint(diag);
+        }
+        BuiltinLintDiag::CfgAttrNoAttributes => {
+            lints::CfgAttrNoAttributes.decorate_lint(diag);
+        }
+        BuiltinLintDiag::CrateTypeInCfgAttr => {
+            lints::CrateTypeInCfgAttr.decorate_lint(diag);
+        }
+        BuiltinLintDiag::CrateNameInCfgAttr => {
+            lints::CrateNameInCfgAttr.decorate_lint(diag);
+        }
+        BuiltinLintDiag::MissingFragmentSpecifier => {
+            lints::MissingFragmentSpecifier.decorate_lint(diag);
+        }
+        BuiltinLintDiag::MetaVariableStillRepeating(name) => {
+            lints::MetaVariableStillRepeating { name }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::MetaVariableWrongOperator => {
+            lints::MetaVariableWrongOperator.decorate_lint(diag);
+        }
+        BuiltinLintDiag::DuplicateMatcherBinding => {
+            lints::DuplicateMatcherBinding.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnknownMacroVariable(name) => {
+            lints::UnknownMacroVariable { name }.decorate_lint(diag);
+        }
+        BuiltinLintDiag::UnusedCrateDependency { extern_crate, local_crate } => {
+            lints::UnusedCrateDependency { extern_crate, local_crate }.decorate_lint(diag)
+        }
+        BuiltinLintDiag::WasmCAbi => lints::WasmCAbi.decorate_lint(diag),
+        BuiltinLintDiag::IllFormedAttributeInput { suggestions } => {
+            lints::IllFormedAttributeInput {
+                num_suggestions: suggestions.len(),
+                suggestions: DiagArgValue::StrListSepByAnd(
+                    suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(),
+                ),
+            }
+            .decorate_lint(diag)
+        }
+        BuiltinLintDiag::InnerAttributeUnstable { is_macro } => if is_macro {
+            lints::InnerAttributeUnstable::InnerMacroAttribute
+        } else {
+            lints::InnerAttributeUnstable::CustomInnerAttribute
+        }
+        .decorate_lint(diag),
+        BuiltinLintDiag::OutOfScopeMacroCalls { path } => {
+            lints::OutOfScopeMacroCalls { path }.decorate_lint(diag)
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs b/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs
new file mode 100644
index 00000000000..da36f68fca9
--- /dev/null
+++ b/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs
@@ -0,0 +1,305 @@
+use rustc_middle::bug;
+use rustc_session::{config::ExpectedValues, Session};
+use rustc_span::edit_distance::find_best_match_for_name;
+use rustc_span::{sym, Span, Symbol};
+
+use crate::lints;
+
+const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35;
+
+fn sort_and_truncate_possibilities(
+    sess: &Session,
+    mut possibilities: Vec<Symbol>,
+) -> (Vec<Symbol>, usize) {
+    let n_possibilities = if sess.opts.unstable_opts.check_cfg_all_expected {
+        possibilities.len()
+    } else {
+        std::cmp::min(possibilities.len(), MAX_CHECK_CFG_NAMES_OR_VALUES)
+    };
+
+    possibilities.sort_by(|s1, s2| s1.as_str().cmp(s2.as_str()));
+
+    let and_more = possibilities.len().saturating_sub(n_possibilities);
+    possibilities.truncate(n_possibilities);
+    (possibilities, and_more)
+}
+
+enum EscapeQuotes {
+    Yes,
+    No,
+}
+
+fn to_check_cfg_arg(name: Symbol, value: Option<Symbol>, quotes: EscapeQuotes) -> String {
+    if let Some(value) = value {
+        let value = str::escape_debug(value.as_str()).to_string();
+        let values = match quotes {
+            EscapeQuotes::Yes => format!("\\\"{}\\\"", value.replace("\"", "\\\\\\\\\"")),
+            EscapeQuotes::No => format!("\"{value}\""),
+        };
+        format!("cfg({name}, values({values}))")
+    } else {
+        format!("cfg({name})")
+    }
+}
+
+fn cargo_help_sub(
+    sess: &Session,
+    inst: &impl Fn(EscapeQuotes) -> String,
+) -> lints::UnexpectedCfgCargoHelp {
+    // We don't want to suggest the `build.rs` way to expected cfgs if we are already in a
+    // `build.rs`. We therefor do a best effort check (looking if the `--crate-name` is
+    // `build_script_build`) to try to figure out if we are building a Cargo build script
+
+    let unescaped = &inst(EscapeQuotes::No);
+    if matches!(&sess.opts.crate_name, Some(crate_name) if crate_name == "build_script_build") {
+        lints::UnexpectedCfgCargoHelp::lint_cfg(unescaped)
+    } else {
+        lints::UnexpectedCfgCargoHelp::lint_cfg_and_build_rs(unescaped, &inst(EscapeQuotes::Yes))
+    }
+}
+
+pub(super) fn unexpected_cfg_name(
+    sess: &Session,
+    (name, name_span): (Symbol, Span),
+    value: Option<(Symbol, Span)>,
+) -> lints::UnexpectedCfgName {
+    #[allow(rustc::potential_query_instability)]
+    let possibilities: Vec<Symbol> = sess.psess.check_config.expecteds.keys().copied().collect();
+
+    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;
+
+    let code_sugg = if is_feature_cfg && is_from_cargo {
+        lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures
+    // Suggest the most probable if we found one
+    } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
+        is_feature_cfg |= best_match == sym::feature;
+
+        if let Some(ExpectedValues::Some(best_match_values)) =
+            sess.psess.check_config.expecteds.get(&best_match)
+        {
+            // We will soon sort, so the initial order does not matter.
+            #[allow(rustc::potential_query_instability)]
+            let mut possibilities = best_match_values.iter().flatten().collect::<Vec<_>>();
+            possibilities.sort_by_key(|s| s.as_str());
+
+            let get_possibilities_sub = || {
+                if !possibilities.is_empty() {
+                    let possibilities =
+                        possibilities.iter().copied().cloned().collect::<Vec<_>>().into();
+                    Some(lints::unexpected_cfg_name::ExpectedValues { best_match, possibilities })
+                } else {
+                    None
+                }
+            };
+
+            if let Some((value, value_span)) = value {
+                if best_match_values.contains(&Some(value)) {
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue {
+                        span: name_span,
+                        code: best_match.to_string(),
+                    }
+                } else if best_match_values.contains(&None) {
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameNoValue {
+                        span: name_span.to(value_span),
+                        code: best_match.to_string(),
+                    }
+                } else if let Some(first_value) = possibilities.first() {
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
+                        span: name_span.to(value_span),
+                        code: format!("{best_match} = \"{first_value}\""),
+                        expected: get_possibilities_sub(),
+                    }
+                } else {
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
+                        span: name_span.to(value_span),
+                        code: best_match.to_string(),
+                        expected: get_possibilities_sub(),
+                    }
+                }
+            } else {
+                lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
+                    span: name_span,
+                    code: best_match.to_string(),
+                    expected: get_possibilities_sub(),
+                }
+            }
+        } else {
+            lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
+                span: name_span,
+                code: best_match.to_string(),
+                expected: None,
+            }
+        }
+    } else {
+        let similar_values = if !names_possibilities.is_empty() && names_possibilities.len() <= 3 {
+            names_possibilities.sort();
+            names_possibilities
+                .iter()
+                .map(|cfg_name| lints::unexpected_cfg_name::FoundWithSimilarValue {
+                    span: name_span,
+                    code: format!("{cfg_name} = \"{name}\""),
+                })
+                .collect()
+        } else {
+            vec![]
+        };
+        let expected_names = if !possibilities.is_empty() {
+            let (possibilities, and_more) = sort_and_truncate_possibilities(sess, possibilities);
+            Some(lints::unexpected_cfg_name::ExpectedNames {
+                possibilities: possibilities.into(),
+                and_more,
+            })
+        } else {
+            None
+        };
+        lints::unexpected_cfg_name::CodeSuggestion::SimilarValues {
+            with_similar_values: similar_values,
+            expected_names,
+        }
+    };
+
+    let inst = |escape_quotes| to_check_cfg_arg(name, value.map(|(v, _s)| v), escape_quotes);
+
+    let invocation_help = if is_from_cargo {
+        let sub = if !is_feature_cfg { Some(cargo_help_sub(sess, &inst)) } else { None };
+        lints::unexpected_cfg_name::InvocationHelp::Cargo { sub }
+    } else {
+        lints::unexpected_cfg_name::InvocationHelp::Rustc(lints::UnexpectedCfgRustcHelp::new(
+            &inst(EscapeQuotes::No),
+        ))
+    };
+
+    lints::UnexpectedCfgName { code_sugg, invocation_help, name }
+}
+
+pub(super) fn unexpected_cfg_value(
+    sess: &Session,
+    (name, name_span): (Symbol, Span),
+    value: Option<(Symbol, Span)>,
+) -> lints::UnexpectedCfgValue {
+    let Some(ExpectedValues::Some(values)) = &sess.psess.check_config.expecteds.get(&name) else {
+        bug!(
+            "it shouldn't be possible to have a diagnostic on a value whose name is not in values"
+        );
+    };
+    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
+    let code_sugg = if !possibilities.is_empty() {
+        let expected_values = {
+            let (possibilities, and_more) =
+                sort_and_truncate_possibilities(sess, possibilities.clone());
+            lints::unexpected_cfg_value::ExpectedValues {
+                name,
+                have_none_possibility,
+                possibilities: possibilities.into(),
+                and_more,
+            }
+        };
+
+        let suggestion = if let Some((value, value_span)) = value {
+            // Suggest the most probable if we found one
+            if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
+                Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
+                    span: value_span,
+                    best_match,
+                })
+            } else {
+                None
+            }
+        } else if let &[first_possibility] = &possibilities[..] {
+            Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
+                span: name_span.shrink_to_hi(),
+                first_possibility,
+            })
+        } else {
+            None
+        };
+
+        lints::unexpected_cfg_value::CodeSuggestion::ChangeValue { expected_values, suggestion }
+    } else if have_none_possibility {
+        let suggestion =
+            value.map(|(_value, value_span)| lints::unexpected_cfg_value::RemoveValueSuggestion {
+                span: name_span.shrink_to_hi().to(value_span),
+            });
+        lints::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name }
+    } else {
+        let span = if let Some((_value, value_span)) = value {
+            name_span.to(value_span)
+        } else {
+            name_span
+        };
+        let suggestion = lints::unexpected_cfg_value::RemoveConditionSuggestion { span };
+        lints::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
+    };
+
+    // We don't want to encourage people to add values to a well-known names, as these are
+    // defined by rustc/Rust itself. Users can still do this if they wish, but should not be
+    // encouraged to do so.
+    let can_suggest_adding_value = !sess.psess.check_config.well_known_names.contains(&name)
+        // Except when working on rustc or the standard library itself, in which case we want to
+        // suggest adding these cfgs to the "normal" place because of bootstraping reasons. As a
+        // basic heuristic, we use the "cheat" unstable feature enable method and the
+        // non-ui-testing enabled option.
+        || (matches!(sess.psess.unstable_features, rustc_feature::UnstableFeatures::Cheat)
+            && !sess.opts.unstable_opts.ui_testing);
+
+    let inst = |escape_quotes| to_check_cfg_arg(name, value.map(|(v, _s)| v), escape_quotes);
+
+    let invocation_help = if is_from_cargo {
+        let help = if name == sym::feature {
+            if let Some((value, _value_span)) = value {
+                Some(lints::unexpected_cfg_value::CargoHelp::AddFeature { value })
+            } else {
+                Some(lints::unexpected_cfg_value::CargoHelp::DefineFeatures)
+            }
+        } else if can_suggest_adding_value {
+            Some(lints::unexpected_cfg_value::CargoHelp::Other(cargo_help_sub(sess, &inst)))
+        } else {
+            None
+        };
+        lints::unexpected_cfg_value::InvocationHelp::Cargo(help)
+    } else {
+        let help = if can_suggest_adding_value {
+            Some(lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
+        } else {
+            None
+        };
+        lints::unexpected_cfg_value::InvocationHelp::Rustc(help)
+    };
+
+    lints::UnexpectedCfgValue {
+        code_sugg,
+        invocation_help,
+        has_value: value.is_some(),
+        value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
+    }
+}
diff --git a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs
new file mode 100644
index 00000000000..911975f6179
--- /dev/null
+++ b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs
@@ -0,0 +1,106 @@
+use crate::{
+    lints::{SupertraitAsDerefTarget, SupertraitAsDerefTargetLabel},
+    LateContext, LateLintPass, LintContext,
+};
+
+use rustc_hir::{self as hir, LangItem};
+use rustc_middle::ty;
+use rustc_session::lint::FutureIncompatibilityReason;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::sym;
+use rustc_trait_selection::traits::supertraits;
+
+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
+    /// #![deny(deref_into_dyn_supertrait)]
+    /// #![allow(dead_code)]
+    ///
+    /// use core::ops::Deref;
+    ///
+    /// trait A {}
+    /// trait B: A {}
+    /// impl<'a> Deref for dyn 'a + B {
+    ///     type Target = dyn A;
+    ///     fn deref(&self) -> &Self::Target {
+    ///         todo!()
+    ///     }
+    /// }
+    ///
+    /// fn take_a(_: &dyn A) { }
+    ///
+    /// fn take_b(b: &dyn B) {
+    ///     take_a(b);
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// 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 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]);
+
+impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        let tcx = cx.tcx;
+        // `Deref` is being implemented for `t`
+        if let hir::ItemKind::Impl(impl_) = item.kind
+            // the trait is a `Deref` implementation
+            && let Some(trait_) = &impl_.of_trait
+            && let Some(did) = trait_.trait_def_id()
+            && tcx.is_lang_item(did, LangItem::Deref)
+            // the self type is `dyn t_principal`
+            && let self_ty = tcx.type_of(item.owner_id).instantiate_identity()
+            && let ty::Dynamic(data, _, ty::Dyn) = self_ty.kind()
+            && let Some(self_principal) = data.principal()
+            // `<T as Deref>::Target` is `dyn target_principal`
+            && let Some(target) = cx.get_associated_type(self_ty, did, "Target")
+            && let ty::Dynamic(data, _, ty::Dyn) = target.kind()
+            && let Some(target_principal) = data.principal()
+            // `target_principal` is a supertrait of `t_principal`
+            && let Some(supertrait_principal) = supertraits(tcx, self_principal.with_self_ty(tcx, self_ty))
+                .find(|supertrait| supertrait.def_id() == target_principal.def_id())
+        {
+            // erase regions in self type for better diagnostic presentation
+            let (self_ty, target_principal, supertrait_principal) =
+                tcx.erase_regions((self_ty, target_principal, supertrait_principal));
+            let label2 = impl_
+                .items
+                .iter()
+                .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_span_lint(
+                DEREF_INTO_DYN_SUPERTRAIT,
+                span,
+                SupertraitAsDerefTarget {
+                    self_ty,
+                    supertrait_principal: supertrait_principal.map_bound(|trait_ref| {
+                        ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
+                    }),
+                    target_principal,
+                    label: span,
+                    label2,
+                },
+            );
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/drop_forget_useless.rs b/compiler/rustc_lint/src/drop_forget_useless.rs
new file mode 100644
index 00000000000..eea0898d83f
--- /dev/null
+++ b/compiler/rustc_lint/src/drop_forget_useless.rs
@@ -0,0 +1,249 @@
+use rustc_hir::{Arm, Expr, ExprKind, Node, StmtKind};
+use rustc_middle::ty;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::sym;
+
+use crate::{
+    lints::{
+        DropCopyDiag, DropRefDiag, ForgetCopyDiag, ForgetRefDiag, UndroppedManuallyDropsDiag,
+        UndroppedManuallyDropsSuggestion, UseLetUnderscoreIgnoreSuggestion,
+    },
+    LateContext, LateLintPass, LintContext,
+};
+
+declare_lint! {
+    /// The `dropping_references` lint checks for calls to `std::mem::drop` with a reference
+    /// instead of an owned value.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # fn operation_that_requires_mutex_to_be_unlocked() {} // just to make it compile
+    /// # let mutex = std::sync::Mutex::new(1); // just to make it compile
+    /// let mut lock_guard = mutex.lock();
+    /// std::mem::drop(&lock_guard); // Should have been drop(lock_guard), mutex
+    /// // still locked
+    /// operation_that_requires_mutex_to_be_unlocked();
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Calling `drop` on a reference will only drop the
+    /// reference itself, which is a no-op. It will not call the `drop` method (from
+    /// the `Drop` trait implementation) on the underlying referenced value, which
+    /// is likely what was intended.
+    pub DROPPING_REFERENCES,
+    Warn,
+    "calls to `std::mem::drop` with a reference instead of an owned value"
+}
+
+declare_lint! {
+    /// The `forgetting_references` lint checks for calls to `std::mem::forget` with a reference
+    /// instead of an owned value.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// let x = Box::new(1);
+    /// std::mem::forget(&x); // Should have been forget(x), x will still be dropped
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Calling `forget` on a reference will only forget the
+    /// reference itself, which is a no-op. It will not forget the underlying
+    /// referenced value, which is likely what was intended.
+    pub FORGETTING_REFERENCES,
+    Warn,
+    "calls to `std::mem::forget` with a reference instead of an owned value"
+}
+
+declare_lint! {
+    /// The `dropping_copy_types` lint checks for calls to `std::mem::drop` with a value
+    /// that derives the Copy trait.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// let x: i32 = 42; // i32 implements Copy
+    /// std::mem::drop(x); // A copy of x is passed to the function, leaving the
+    ///                    // original unaffected
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Calling `std::mem::drop` [does nothing for types that
+    /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html), since the
+    /// value will be copied and moved into the function on invocation.
+    pub DROPPING_COPY_TYPES,
+    Warn,
+    "calls to `std::mem::drop` with a value that implements Copy"
+}
+
+declare_lint! {
+    /// The `forgetting_copy_types` lint checks for calls to `std::mem::forget` with a value
+    /// that derives the Copy trait.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// let x: i32 = 42; // i32 implements Copy
+    /// std::mem::forget(x); // A copy of x is passed to the function, leaving the
+    ///                      // original unaffected
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Calling `std::mem::forget` [does nothing for types that
+    /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html) since the
+    /// value will be copied and moved into the function on invocation.
+    ///
+    /// An alternative, but also valid, explanation is that Copy types do not
+    /// implement the Drop trait, which means they have no destructors. Without a
+    /// destructor, there is nothing for `std::mem::forget` to ignore.
+    pub FORGETTING_COPY_TYPES,
+    Warn,
+    "calls to `std::mem::forget` with a value that implements Copy"
+}
+
+declare_lint! {
+    /// The `undropped_manually_drops` lint check for calls to `std::mem::drop` with
+    /// a value of `std::mem::ManuallyDrop` which doesn't drop.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// struct S;
+    /// drop(std::mem::ManuallyDrop::new(S));
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// `ManuallyDrop` does not drop it's inner value so calling `std::mem::drop` will
+    /// not drop the inner value of the `ManuallyDrop` either.
+    pub UNDROPPED_MANUALLY_DROPS,
+    Deny,
+    "calls to `std::mem::drop` with `std::mem::ManuallyDrop` instead of it's inner value"
+}
+
+declare_lint_pass!(DropForgetUseless => [DROPPING_REFERENCES, FORGETTING_REFERENCES, DROPPING_COPY_TYPES, FORGETTING_COPY_TYPES, UNDROPPED_MANUALLY_DROPS]);
+
+impl<'tcx> LateLintPass<'tcx> for DropForgetUseless {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+        if let ExprKind::Call(path, [arg]) = expr.kind
+            && let ExprKind::Path(ref qpath) = path.kind
+            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+            && let Some(fn_name) = cx.tcx.get_diagnostic_name(def_id)
+        {
+            let arg_ty = cx.typeck_results().expr_ty(arg);
+            let is_copy = arg_ty.is_copy_modulo_regions(cx.tcx, cx.param_env);
+            let drop_is_single_call_in_arm = is_single_call_in_arm(cx, arg, expr);
+            let let_underscore_ignore_sugg = || {
+                if let Some((_, node)) = cx.tcx.hir().parent_iter(expr.hir_id).nth(0)
+                    && let Node::Stmt(stmt) = node
+                    && let StmtKind::Semi(e) = stmt.kind
+                    && e.hir_id == expr.hir_id
+                {
+                    UseLetUnderscoreIgnoreSuggestion::Suggestion {
+                        start_span: expr.span.shrink_to_lo().until(arg.span),
+                        end_span: arg.span.shrink_to_hi().until(expr.span.shrink_to_hi()),
+                    }
+                } else {
+                    UseLetUnderscoreIgnoreSuggestion::Note
+                }
+            };
+            match fn_name {
+                sym::mem_drop if arg_ty.is_ref() && !drop_is_single_call_in_arm => {
+                    cx.emit_span_lint(
+                        DROPPING_REFERENCES,
+                        expr.span,
+                        DropRefDiag { arg_ty, label: arg.span, sugg: let_underscore_ignore_sugg() },
+                    );
+                }
+                sym::mem_forget if arg_ty.is_ref() => {
+                    cx.emit_span_lint(
+                        FORGETTING_REFERENCES,
+                        expr.span,
+                        ForgetRefDiag {
+                            arg_ty,
+                            label: arg.span,
+                            sugg: let_underscore_ignore_sugg(),
+                        },
+                    );
+                }
+                sym::mem_drop if is_copy && !drop_is_single_call_in_arm => {
+                    cx.emit_span_lint(
+                        DROPPING_COPY_TYPES,
+                        expr.span,
+                        DropCopyDiag {
+                            arg_ty,
+                            label: arg.span,
+                            sugg: let_underscore_ignore_sugg(),
+                        },
+                    );
+                }
+                sym::mem_forget if is_copy => {
+                    cx.emit_span_lint(
+                        FORGETTING_COPY_TYPES,
+                        expr.span,
+                        ForgetCopyDiag {
+                            arg_ty,
+                            label: arg.span,
+                            sugg: let_underscore_ignore_sugg(),
+                        },
+                    );
+                }
+                sym::mem_drop
+                    if let ty::Adt(adt, _) = arg_ty.kind()
+                        && adt.is_manually_drop() =>
+                {
+                    cx.emit_span_lint(
+                        UNDROPPED_MANUALLY_DROPS,
+                        expr.span,
+                        UndroppedManuallyDropsDiag {
+                            arg_ty,
+                            label: arg.span,
+                            suggestion: UndroppedManuallyDropsSuggestion {
+                                start_span: arg.span.shrink_to_lo(),
+                                end_span: arg.span.shrink_to_hi(),
+                            },
+                        },
+                    );
+                }
+                _ => return,
+            };
+        }
+    }
+}
+
+// Dropping returned value of a function, as in the following snippet is considered idiomatic, see
+// rust-lang/rust-clippy#9482 for examples.
+//
+// ```
+// match <var> {
+//     <pat> => drop(fn_with_side_effect_and_returning_some_value()),
+//     ..
+// }
+// ```
+fn is_single_call_in_arm<'tcx>(
+    cx: &LateContext<'tcx>,
+    arg: &'tcx Expr<'_>,
+    drop_expr: &'tcx Expr<'_>,
+) -> bool {
+    if arg.can_have_side_effects() {
+        if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) {
+            return body.hir_id == drop_expr.hir_id;
+        }
+    }
+    false
+}
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
new file mode 100644
index 00000000000..329221612b5
--- /dev/null
+++ b/compiler/rustc_lint/src/early.rs
@@ -0,0 +1,438 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use crate::context::{EarlyContext, LintStore};
+use crate::passes::{EarlyLintPass, EarlyLintPassObject};
+use rustc_ast::ptr::P;
+use rustc_ast::visit::{self as ast_visit, walk_list, Visitor};
+use rustc_ast::{self as ast, HasAttrs};
+use rustc_data_structures::stack::ensure_sufficient_stack;
+use rustc_feature::Features;
+use rustc_middle::ty::RegisteredTools;
+use rustc_session::lint::{BufferedEarlyLint, LintBuffer, LintPass};
+use rustc_session::Session;
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+use tracing::debug;
+
+macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({
+    $cx.pass.$f(&$cx.context, $($args),*);
+}) }
+
+/// Implements the AST traversal for early lint passes. `T` provides the
+/// `check_*` methods.
+pub struct EarlyContextAndPass<'a, T: EarlyLintPass> {
+    context: EarlyContext<'a>,
+    pass: T,
+}
+
+impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
+    // This always-inlined function is for the hot call site.
+    #[inline(always)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    fn inlined_check_id(&mut self, id: ast::NodeId) {
+        for early_lint in self.context.buffered.take(id) {
+            let BufferedEarlyLint { span, node_id: _, lint_id, diagnostic } = early_lint;
+            self.context.span_lint_with_diagnostics(lint_id.lint, span, diagnostic);
+        }
+    }
+
+    // This non-inlined function is for the cold call sites.
+    fn check_id(&mut self, id: ast::NodeId) {
+        self.inlined_check_id(id)
+    }
+
+    /// Merge the lints specified by any lint attributes into the
+    /// current lint context, call the provided function, then reset the
+    /// lints in effect to their previous state.
+    fn with_lint_attrs<F>(&mut self, id: ast::NodeId, attrs: &'a [ast::Attribute], f: F)
+    where
+        F: FnOnce(&mut Self),
+    {
+        let is_crate_node = id == ast::CRATE_NODE_ID;
+        debug!(?id);
+        let push = self.context.builder.push(attrs, is_crate_node, None);
+
+        self.inlined_check_id(id);
+        debug!("early context: enter_attrs({:?})", attrs);
+        lint_callback!(self, check_attributes, attrs);
+        ensure_sufficient_stack(|| f(self));
+        debug!("early context: exit_attrs({:?})", attrs);
+        lint_callback!(self, check_attributes_post, attrs);
+        self.context.builder.pop(push);
+    }
+}
+
+impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T> {
+    fn visit_param(&mut self, param: &'a ast::Param) {
+        self.with_lint_attrs(param.id, &param.attrs, |cx| {
+            lint_callback!(cx, check_param, param);
+            ast_visit::walk_param(cx, param);
+        });
+    }
+
+    fn visit_item(&mut self, it: &'a ast::Item) {
+        self.with_lint_attrs(it.id, &it.attrs, |cx| {
+            lint_callback!(cx, check_item, it);
+            ast_visit::walk_item(cx, it);
+            lint_callback!(cx, check_item_post, it);
+        })
+    }
+
+    fn visit_foreign_item(&mut self, it: &'a ast::ForeignItem) {
+        self.with_lint_attrs(it.id, &it.attrs, |cx| {
+            ast_visit::walk_item(cx, it);
+        })
+    }
+
+    fn visit_pat(&mut self, p: &'a ast::Pat) {
+        lint_callback!(self, check_pat, p);
+        self.check_id(p.id);
+        ast_visit::walk_pat(self, p);
+        lint_callback!(self, check_pat_post, p);
+    }
+
+    fn visit_pat_field(&mut self, field: &'a ast::PatField) {
+        self.with_lint_attrs(field.id, &field.attrs, |cx| {
+            ast_visit::walk_pat_field(cx, field);
+        });
+    }
+
+    fn visit_anon_const(&mut self, c: &'a ast::AnonConst) {
+        self.check_id(c.id);
+        ast_visit::walk_anon_const(self, c);
+    }
+
+    fn visit_expr(&mut self, e: &'a ast::Expr) {
+        self.with_lint_attrs(e.id, &e.attrs, |cx| {
+            lint_callback!(cx, check_expr, e);
+            ast_visit::walk_expr(cx, e);
+        })
+    }
+
+    fn visit_expr_field(&mut self, f: &'a ast::ExprField) {
+        self.with_lint_attrs(f.id, &f.attrs, |cx| {
+            ast_visit::walk_expr_field(cx, f);
+        })
+    }
+
+    fn visit_stmt(&mut self, s: &'a ast::Stmt) {
+        // Add the statement's lint attributes to our
+        // current state when checking the statement itself.
+        // This allows us to handle attributes like
+        // `#[allow(unused_doc_comments)]`, which apply to
+        // sibling attributes on the same target
+        //
+        // Note that statements get their attributes from
+        // the AST struct that they wrap (e.g. an item)
+        self.with_lint_attrs(s.id, s.attrs(), |cx| {
+            lint_callback!(cx, check_stmt, s);
+            cx.check_id(s.id);
+        });
+        // The visitor for the AST struct wrapped
+        // by the statement (e.g. `Item`) will call
+        // `with_lint_attrs`, so do this walk
+        // outside of the above `with_lint_attrs` call
+        ast_visit::walk_stmt(self, s);
+    }
+
+    fn visit_fn(&mut self, fk: ast_visit::FnKind<'a>, span: Span, id: ast::NodeId) {
+        lint_callback!(self, check_fn, fk, span, id);
+        self.check_id(id);
+        ast_visit::walk_fn(self, fk);
+
+        // Explicitly check for lints associated with 'closure_id', since
+        // it does not have a corresponding AST node
+        if let ast_visit::FnKind::Fn(_, _, sig, _, _, _) = fk {
+            if let Some(coroutine_kind) = sig.header.coroutine_kind {
+                self.check_id(coroutine_kind.closure_id());
+            }
+        }
+    }
+
+    fn visit_variant_data(&mut self, s: &'a ast::VariantData) {
+        if let Some(ctor_node_id) = s.ctor_node_id() {
+            self.check_id(ctor_node_id);
+        }
+        ast_visit::walk_struct_def(self, s);
+    }
+
+    fn visit_field_def(&mut self, s: &'a ast::FieldDef) {
+        self.with_lint_attrs(s.id, &s.attrs, |cx| {
+            ast_visit::walk_field_def(cx, s);
+        })
+    }
+
+    fn visit_variant(&mut self, v: &'a ast::Variant) {
+        self.with_lint_attrs(v.id, &v.attrs, |cx| {
+            lint_callback!(cx, check_variant, v);
+            ast_visit::walk_variant(cx, v);
+        })
+    }
+
+    fn visit_ty(&mut self, t: &'a ast::Ty) {
+        lint_callback!(self, check_ty, t);
+        self.check_id(t.id);
+        ast_visit::walk_ty(self, t);
+    }
+
+    fn visit_ident(&mut self, ident: Ident) {
+        lint_callback!(self, check_ident, ident);
+    }
+
+    fn visit_local(&mut self, l: &'a ast::Local) {
+        self.with_lint_attrs(l.id, &l.attrs, |cx| {
+            lint_callback!(cx, check_local, l);
+            ast_visit::walk_local(cx, l);
+        })
+    }
+
+    fn visit_block(&mut self, b: &'a ast::Block) {
+        lint_callback!(self, check_block, b);
+        self.check_id(b.id);
+        ast_visit::walk_block(self, b);
+    }
+
+    fn visit_arm(&mut self, a: &'a ast::Arm) {
+        self.with_lint_attrs(a.id, &a.attrs, |cx| {
+            lint_callback!(cx, check_arm, a);
+            ast_visit::walk_arm(cx, a);
+        })
+    }
+
+    fn visit_expr_post(&mut self, e: &'a ast::Expr) {
+        // Explicitly check for lints associated with 'closure_id', since
+        // it does not have a corresponding AST node
+        match e.kind {
+            ast::ExprKind::Closure(box ast::Closure {
+                coroutine_kind: Some(coroutine_kind),
+                ..
+            }) => {
+                self.check_id(coroutine_kind.closure_id());
+            }
+            _ => {}
+        }
+        lint_callback!(self, check_expr_post, e);
+    }
+
+    fn visit_generic_arg(&mut self, arg: &'a ast::GenericArg) {
+        lint_callback!(self, check_generic_arg, arg);
+        ast_visit::walk_generic_arg(self, arg);
+    }
+
+    fn visit_generic_param(&mut self, param: &'a ast::GenericParam) {
+        self.with_lint_attrs(param.id, &param.attrs, |cx| {
+            lint_callback!(cx, check_generic_param, param);
+            ast_visit::walk_generic_param(cx, param);
+        });
+    }
+
+    fn visit_generics(&mut self, g: &'a ast::Generics) {
+        lint_callback!(self, check_generics, g);
+        ast_visit::walk_generics(self, g);
+    }
+
+    fn visit_where_predicate(&mut self, p: &'a ast::WherePredicate) {
+        lint_callback!(self, enter_where_predicate, p);
+        ast_visit::walk_where_predicate(self, p);
+        lint_callback!(self, exit_where_predicate, p);
+    }
+
+    fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef) {
+        lint_callback!(self, check_poly_trait_ref, t);
+        ast_visit::walk_poly_trait_ref(self, t);
+    }
+
+    fn visit_assoc_item(&mut self, item: &'a ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
+        self.with_lint_attrs(item.id, &item.attrs, |cx| match ctxt {
+            ast_visit::AssocCtxt::Trait => {
+                lint_callback!(cx, check_trait_item, item);
+                ast_visit::walk_assoc_item(cx, item, ctxt);
+            }
+            ast_visit::AssocCtxt::Impl => {
+                lint_callback!(cx, check_impl_item, item);
+                ast_visit::walk_assoc_item(cx, item, ctxt);
+            }
+        });
+    }
+
+    fn visit_lifetime(&mut self, lt: &'a ast::Lifetime, _: ast_visit::LifetimeCtxt) {
+        self.check_id(lt.id);
+    }
+
+    fn visit_path(&mut self, p: &'a ast::Path, id: ast::NodeId) {
+        self.check_id(id);
+        ast_visit::walk_path(self, p);
+    }
+
+    fn visit_path_segment(&mut self, s: &'a ast::PathSegment) {
+        self.check_id(s.id);
+        ast_visit::walk_path_segment(self, s);
+    }
+
+    fn visit_attribute(&mut self, attr: &'a ast::Attribute) {
+        lint_callback!(self, check_attribute, attr);
+    }
+
+    fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) {
+        lint_callback!(self, check_mac_def, mac);
+        self.check_id(id);
+    }
+
+    fn visit_mac_call(&mut self, mac: &'a ast::MacCall) {
+        lint_callback!(self, check_mac, mac);
+        ast_visit::walk_mac(self, mac);
+    }
+}
+
+// Combines multiple lint passes into a single pass, at runtime. Each
+// `check_foo` method in `$methods` within this pass simply calls `check_foo`
+// once per `$pass`. Compare with `declare_combined_early_lint_pass`, which is
+// similar, but combines lint passes at compile time.
+struct RuntimeCombinedEarlyLintPass<'a> {
+    passes: &'a mut [EarlyLintPassObject],
+}
+
+#[allow(rustc::lint_pass_impl_without_macro)]
+impl LintPass for RuntimeCombinedEarlyLintPass<'_> {
+    fn name(&self) -> &'static str {
+        panic!()
+    }
+}
+
+macro_rules! impl_early_lint_pass {
+    ([], [$($(#[$attr:meta])* fn $f:ident($($param:ident: $arg:ty),*);)*]) => (
+        impl EarlyLintPass for RuntimeCombinedEarlyLintPass<'_> {
+            $(fn $f(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) {
+                for pass in self.passes.iter_mut() {
+                    pass.$f(context, $($param),*);
+                }
+            })*
+        }
+    )
+}
+
+crate::early_lint_methods!(impl_early_lint_pass, []);
+
+/// Early lints work on different nodes - either on the crate root, or on freshly loaded modules.
+/// This trait generalizes over those nodes.
+pub trait EarlyCheckNode<'a>: Copy {
+    fn id(self) -> ast::NodeId;
+    fn attrs<'b>(self) -> &'b [ast::Attribute]
+    where
+        'a: 'b;
+    fn check<'b, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>)
+    where
+        'a: 'b;
+}
+
+impl<'a> EarlyCheckNode<'a> for (&'a ast::Crate, &'a [ast::Attribute]) {
+    fn id(self) -> ast::NodeId {
+        ast::CRATE_NODE_ID
+    }
+    fn attrs<'b>(self) -> &'b [ast::Attribute]
+    where
+        'a: 'b,
+    {
+        self.1
+    }
+    fn check<'b, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>)
+    where
+        'a: 'b,
+    {
+        lint_callback!(cx, check_crate, self.0);
+        ast_visit::walk_crate(cx, self.0);
+        lint_callback!(cx, check_crate_post, self.0);
+    }
+}
+
+impl<'a> EarlyCheckNode<'a> for (ast::NodeId, &'a [ast::Attribute], &'a [P<ast::Item>]) {
+    fn id(self) -> ast::NodeId {
+        self.0
+    }
+    fn attrs<'b>(self) -> &'b [ast::Attribute]
+    where
+        'a: 'b,
+    {
+        self.1
+    }
+    fn check<'b, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>)
+    where
+        'a: 'b,
+    {
+        walk_list!(cx, visit_attribute, self.1);
+        walk_list!(cx, visit_item, self.2);
+    }
+}
+
+pub fn check_ast_node<'a>(
+    sess: &Session,
+    features: &Features,
+    pre_expansion: bool,
+    lint_store: &LintStore,
+    registered_tools: &RegisteredTools,
+    lint_buffer: Option<LintBuffer>,
+    builtin_lints: impl EarlyLintPass + 'static,
+    check_node: impl EarlyCheckNode<'a>,
+) {
+    let context = EarlyContext::new(
+        sess,
+        features,
+        !pre_expansion,
+        lint_store,
+        registered_tools,
+        lint_buffer.unwrap_or_default(),
+    );
+
+    // Note: `passes` is often empty. In that case, it's faster to run
+    // `builtin_lints` directly rather than bundling it up into the
+    // `RuntimeCombinedEarlyLintPass`.
+    let passes =
+        if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes };
+    if passes.is_empty() {
+        check_ast_node_inner(sess, check_node, context, builtin_lints);
+    } else {
+        let mut passes: Vec<_> = passes.iter().map(|mk_pass| (mk_pass)()).collect();
+        passes.push(Box::new(builtin_lints));
+        let pass = RuntimeCombinedEarlyLintPass { passes: &mut passes[..] };
+        check_ast_node_inner(sess, check_node, context, pass);
+    }
+}
+
+pub fn check_ast_node_inner<'a, T: EarlyLintPass>(
+    sess: &Session,
+    check_node: impl EarlyCheckNode<'a>,
+    context: EarlyContext<'_>,
+    pass: T,
+) {
+    let mut cx = EarlyContextAndPass { context, pass };
+
+    cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx));
+
+    // All of the buffered lints should have been emitted at this point.
+    // 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 {
+        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
new file mode 100644
index 00000000000..958da177eda
--- /dev/null
+++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
@@ -0,0 +1,84 @@
+use crate::{
+    context::LintContext,
+    lints::{EnumIntrinsicsMemDiscriminate, EnumIntrinsicsMemVariant},
+    LateContext, LateLintPass,
+};
+use rustc_hir as hir;
+use rustc_middle::ty::{visit::TypeVisitableExt, Ty};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::{symbol::sym, Span};
+
+declare_lint! {
+    /// The `enum_intrinsics_non_enums` lint detects calls to
+    /// intrinsic functions that require an enum ([`core::mem::discriminant`],
+    /// [`core::mem::variant_count`]), but are called with a non-enum type.
+    ///
+    /// [`core::mem::discriminant`]: https://doc.rust-lang.org/core/mem/fn.discriminant.html
+    /// [`core::mem::variant_count`]: https://doc.rust-lang.org/core/mem/fn.variant_count.html
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(enum_intrinsics_non_enums)]
+    /// core::mem::discriminant::<i32>(&123);
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// In order to accept any enum, the `mem::discriminant` and
+    /// `mem::variant_count` functions are generic over a type `T`.
+    /// This makes it technically possible for `T` to be a non-enum,
+    /// in which case the return value is unspecified.
+    ///
+    /// This lint prevents such incorrect usage of these functions.
+    ENUM_INTRINSICS_NON_ENUMS,
+    Deny,
+    "detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types"
+}
+
+declare_lint_pass!(EnumIntrinsicsNonEnums => [ENUM_INTRINSICS_NON_ENUMS]);
+
+/// Returns `true` if we know for sure that the given type is not an enum. Note that for cases where
+/// the type is generic, we can't be certain if it will be an enum so we have to assume that it is.
+fn is_non_enum(t: Ty<'_>) -> bool {
+    !t.is_enum() && !t.has_param()
+}
+
+fn enforce_mem_discriminant(
+    cx: &LateContext<'_>,
+    func_expr: &hir::Expr<'_>,
+    expr_span: Span,
+    args_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_span_lint(
+            ENUM_INTRINSICS_NON_ENUMS,
+            expr_span,
+            EnumIntrinsicsMemDiscriminate { ty_param, note: args_span },
+        );
+    }
+}
+
+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_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, EnumIntrinsicsMemVariant { ty_param });
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+        let hir::ExprKind::Call(func, args) = &expr.kind else { return };
+        let hir::ExprKind::Path(qpath) = &func.kind else { return };
+        let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() else { return };
+        let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return };
+        match name {
+            sym::mem_discriminant => enforce_mem_discriminant(cx, func, expr.span, args[0].span),
+            sym::mem_variant_count => enforce_mem_variant_count(cx, func, expr.span),
+            _ => {}
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs
new file mode 100644
index 00000000000..46dfaf0b83f
--- /dev/null
+++ b/compiler/rustc_lint/src/errors.rs
@@ -0,0 +1,110 @@
+use crate::fluent_generated as fluent;
+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)]
+pub struct OverruledAttribute<'a> {
+    #[primary_span]
+    pub span: Span,
+    #[label]
+    pub overruled: Span,
+    pub lint_level: &'a str,
+    pub lint_source: Symbol,
+    #[subdiagnostic]
+    pub sub: OverruledAttributeSub,
+}
+
+pub enum OverruledAttributeSub {
+    DefaultSource { id: String },
+    NodeSource { span: Span, reason: Option<Symbol> },
+    CommandLineSource,
+}
+
+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.arg("id", id);
+            }
+            OverruledAttributeSub::NodeSource { span, reason } => {
+                diag.span_label(span, fluent::lint_node_source);
+                if let Some(rationale) = reason {
+                    #[allow(rustc::untranslatable_diagnostic)]
+                    diag.note(rationale.to_string());
+                }
+            }
+            OverruledAttributeSub::CommandLineSource => {
+                diag.note(fluent::lint_command_line_source);
+            }
+        }
+    }
+}
+
+#[derive(Diagnostic)]
+#[diag(lint_malformed_attribute, code = E0452)]
+pub struct MalformedAttribute {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub sub: MalformedAttributeSub,
+}
+
+#[derive(Subdiagnostic)]
+pub enum MalformedAttributeSub {
+    #[label(lint_bad_attribute_argument)]
+    BadAttributeArgument(#[primary_span] Span),
+    #[label(lint_reason_must_be_string_literal)]
+    ReasonMustBeStringLiteral(#[primary_span] Span),
+    #[label(lint_reason_must_come_last)]
+    ReasonMustComeLast(#[primary_span] Span),
+}
+
+#[derive(Diagnostic)]
+#[diag(lint_unknown_tool_in_scoped_lint, code = E0710)]
+pub struct UnknownToolInScopedLint {
+    #[primary_span]
+    pub span: Option<Span>,
+    pub tool_name: Symbol,
+    pub lint_name: String,
+    #[help]
+    pub is_nightly_build: Option<()>,
+}
+
+#[derive(Diagnostic)]
+#[diag(lint_builtin_ellipsis_inclusive_range_patterns, code = E0783)]
+pub struct BuiltinEllipsisInclusiveRangePatterns {
+    #[primary_span]
+    pub span: Span,
+    #[suggestion(style = "short", code = "{replace}", applicability = "machine-applicable")]
+    pub suggestion: Span,
+    pub replace: String,
+}
+
+#[derive(Subdiagnostic)]
+#[note(lint_requested_level)]
+pub struct RequestedLevel<'a> {
+    pub level: Level,
+    pub lint_name: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(lint_unsupported_group, code = E0602)]
+pub struct UnsupportedGroup {
+    pub lint_group: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(lint_check_name_unknown_tool, code = E0602)]
+pub struct CheckNameUnknownTool<'a> {
+    pub tool_name: Symbol,
+    #[subdiagnostic]
+    pub sub: RequestedLevel<'a>,
+}
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
new file mode 100644
index 00000000000..04c2ebf189f
--- /dev/null
+++ b/compiler/rustc_lint/src/expect.rs
@@ -0,0 +1,38 @@
+use crate::lints::{Expectation, ExpectationNote};
+use rustc_middle::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::lint::builtin::UNFULFILLED_LINT_EXPECTATIONS;
+use rustc_session::lint::LintExpectationId;
+use rustc_span::Symbol;
+
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers { check_expectations, ..*providers };
+}
+
+fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) {
+    let lint_expectations = tcx.lint_expectations(());
+    let fulfilled_expectations = tcx.dcx().steal_fulfilled_expectation_ids();
+
+    tracing::debug!(?lint_expectations, ?fulfilled_expectations);
+
+    for (id, expectation) in lint_expectations {
+        // This check will always be true, since `lint_expectations` only
+        // holds stable ids
+        if let LintExpectationId::Stable { hir_id, .. } = id {
+            if !fulfilled_expectations.contains(id)
+                && tool_filter.map_or(true, |filter| expectation.lint_tool == Some(filter))
+            {
+                let rationale = expectation.reason.map(|rationale| ExpectationNote { rationale });
+                let note = expectation.is_unfulfilled_lint_expectations.then_some(());
+                tcx.emit_node_span_lint(
+                    UNFULFILLED_LINT_EXPECTATIONS,
+                    *hir_id,
+                    expectation.emission_span,
+                    Expectation { rationale, note },
+                );
+            }
+        } else {
+            unreachable!("at this stage all `LintExpectationId`s are stable");
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/for_loops_over_fallibles.rs b/compiler/rustc_lint/src/for_loops_over_fallibles.rs
new file mode 100644
index 00000000000..aa00fb4573d
--- /dev/null
+++ b/compiler/rustc_lint/src/for_loops_over_fallibles.rs
@@ -0,0 +1,180 @@
+use crate::{
+    lints::{
+        ForLoopsOverFalliblesDiag, ForLoopsOverFalliblesLoopSub, ForLoopsOverFalliblesQuestionMark,
+        ForLoopsOverFalliblesSuggestion,
+    },
+    LateContext, LateLintPass, LintContext,
+};
+
+use hir::{Expr, Pat};
+use rustc_hir as hir;
+use rustc_infer::{infer::TyCtxtInferExt, traits::ObligationCause};
+use rustc_middle::ty;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::{sym, Span};
+use rustc_trait_selection::traits::ObligationCtxt;
+
+declare_lint! {
+    /// The `for_loops_over_fallibles` lint checks for `for` loops over `Option` or `Result` values.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// let opt = Some(1);
+    /// for x in opt { /* ... */}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Both `Option` and `Result` implement `IntoIterator` trait, which allows using them in a `for` loop.
+    /// `for` loop over `Option` or `Result` will iterate either 0 (if the value is `None`/`Err(_)`)
+    /// or 1 time (if the value is `Some(_)`/`Ok(_)`). This is not very useful and is more clearly expressed
+    /// via `if let`.
+    ///
+    /// `for` loop can also be accidentally written with the intention to call a function multiple times,
+    /// while the function returns `Some(_)`, in these cases `while let` loop should be used instead.
+    ///
+    /// The "intended" use of `IntoIterator` implementations for `Option` and `Result` is passing them to
+    /// generic code that expects something implementing `IntoIterator`. For example using `.chain(option)`
+    /// to optionally add a value to an iterator.
+    pub FOR_LOOPS_OVER_FALLIBLES,
+    Warn,
+    "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`"
+}
+
+declare_lint_pass!(ForLoopsOverFallibles => [FOR_LOOPS_OVER_FALLIBLES]);
+
+impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        let Some((pat, arg)) = extract_for_loop(expr) else { return };
+
+        let ty = cx.typeck_results().expr_ty(arg);
+
+        let (adt, args, ref_mutability) = match ty.kind() {
+            &ty::Adt(adt, args) => (adt, args, None),
+            &ty::Ref(_, ty, mutability) => match ty.kind() {
+                &ty::Adt(adt, args) => (adt, args, Some(mutability)),
+                _ => return,
+            },
+            _ => return,
+        };
+
+        let (article, ty, var) = match adt.did() {
+            did if cx.tcx.is_diagnostic_item(sym::Option, did) && ref_mutability.is_some() => {
+                ("a", "Option", "Some")
+            }
+            did if cx.tcx.is_diagnostic_item(sym::Option, did) => ("an", "Option", "Some"),
+            did if cx.tcx.is_diagnostic_item(sym::Result, did) => ("a", "Result", "Ok"),
+            _ => return,
+        };
+
+        let ref_prefix = match ref_mutability {
+            None => "",
+            Some(ref_mutability) => ref_mutability.ref_prefix_str(),
+        };
+
+        let sub = if let Some(recv) = extract_iterator_next_call(cx, arg)
+            && let Ok(recv_snip) = cx.sess().source_map().span_to_snippet(recv.span)
+        {
+            ForLoopsOverFalliblesLoopSub::RemoveNext {
+                suggestion: recv.span.between(arg.span.shrink_to_hi()),
+                recv_snip,
+            }
+        } else {
+            ForLoopsOverFalliblesLoopSub::UseWhileLet {
+                start_span: expr.span.with_hi(pat.span.lo()),
+                end_span: pat.span.between(arg.span),
+                var,
+            }
+        };
+        let question_mark = suggest_question_mark(cx, adt, args, expr.span)
+            .then(|| ForLoopsOverFalliblesQuestionMark { suggestion: arg.span.shrink_to_hi() });
+        let suggestion = ForLoopsOverFalliblesSuggestion {
+            var,
+            start_span: expr.span.with_hi(pat.span.lo()),
+            end_span: pat.span.between(arg.span),
+        };
+
+        cx.emit_span_lint(
+            FOR_LOOPS_OVER_FALLIBLES,
+            arg.span,
+            ForLoopsOverFalliblesDiag { article, ref_prefix, ty, sub, question_mark, suggestion },
+        );
+    }
+}
+
+fn extract_for_loop<'tcx>(expr: &Expr<'tcx>) -> Option<(&'tcx Pat<'tcx>, &'tcx Expr<'tcx>)> {
+    if let hir::ExprKind::DropTemps(e) = expr.kind
+        && let hir::ExprKind::Match(iterexpr, [arm], hir::MatchSource::ForLoopDesugar) = e.kind
+        && let hir::ExprKind::Call(_, [arg]) = iterexpr.kind
+        && let hir::ExprKind::Loop(block, ..) = arm.body.kind
+        && let [stmt] = block.stmts
+        && let hir::StmtKind::Expr(e) = stmt.kind
+        && let hir::ExprKind::Match(_, [_, some_arm], _) = e.kind
+        && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
+    {
+        Some((field.pat, arg))
+    } else {
+        None
+    }
+}
+
+fn extract_iterator_next_call<'tcx>(
+    cx: &LateContext<'_>,
+    expr: &Expr<'tcx>,
+) -> Option<&'tcx Expr<'tcx>> {
+    // This won't work for `Iterator::next(iter)`, is this an issue?
+    if let hir::ExprKind::MethodCall(_, recv, _, _) = expr.kind
+        && cx.typeck_results().type_dependent_def_id(expr.hir_id) == cx.tcx.lang_items().next_fn()
+    {
+        Some(recv)
+    } else {
+        return None;
+    }
+}
+
+fn suggest_question_mark<'tcx>(
+    cx: &LateContext<'tcx>,
+    adt: ty::AdtDef<'tcx>,
+    args: ty::GenericArgsRef<'tcx>,
+    span: Span,
+) -> bool {
+    let Some(body_id) = cx.enclosing_body else { return false };
+    let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else {
+        return false;
+    };
+
+    if !cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
+        return false;
+    }
+
+    // Check that the function/closure/constant we are in has a `Result` type.
+    // Otherwise suggesting using `?` may not be a good idea.
+    {
+        let ty = cx.typeck_results().expr_ty(cx.tcx.hir().body(body_id).value);
+        let ty::Adt(ret_adt, ..) = ty.kind() else { return false };
+        if !cx.tcx.is_diagnostic_item(sym::Result, ret_adt.did()) {
+            return false;
+        }
+    }
+
+    let ty = args.type_at(0);
+    let infcx = cx.tcx.infer_ctxt().build();
+    let ocx = ObligationCtxt::new(&infcx);
+
+    let body_def_id = cx.tcx.hir().body_owner_def_id(body_id);
+    let cause =
+        ObligationCause::new(span, body_def_id, rustc_infer::traits::ObligationCauseCode::Misc);
+
+    ocx.register_bound(
+        cause,
+        cx.param_env,
+        // Erase any region vids from the type, which may not be resolved
+        infcx.tcx.erase_regions(ty),
+        into_iterator_did,
+    );
+
+    ocx.select_all_or_error().is_empty()
+}
diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs
new file mode 100644
index 00000000000..5da1cbc2283
--- /dev/null
+++ b/compiler/rustc_lint/src/foreign_modules.rs
@@ -0,0 +1,403 @@
+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;
+use rustc_middle::ty::layout::LayoutError;
+use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
+use rustc_session::declare_lint;
+use rustc_span::{sym, Span, Symbol};
+use rustc_target::abi::FIRST_VARIANT;
+use tracing::{debug, instrument};
+
+use crate::lints::{BuiltinClashingExtern, BuiltinClashingExternSub};
+use crate::{types, LintVec};
+
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers { clashing_extern_declarations, ..*providers };
+}
+
+pub(crate) fn get_lints() -> LintVec {
+    vec![CLASHING_EXTERN_DECLARATIONS]
+}
+
+fn clashing_extern_declarations(tcx: TyCtxt<'_>, (): ()) {
+    let mut lint = ClashingExternDeclarations::new();
+    for id in tcx.hir_crate_items(()).foreign_items() {
+        lint.check_foreign_item(tcx, id);
+    }
+}
+
+declare_lint! {
+    /// The `clashing_extern_declarations` lint detects when an `extern fn`
+    /// has been declared with the same name but different types.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// mod m {
+    ///     extern "C" {
+    ///         fn foo();
+    ///     }
+    /// }
+    ///
+    /// extern "C" {
+    ///     fn foo(_: u32);
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Because two symbols of the same name cannot be resolved to two
+    /// different functions at link time, and one function cannot possibly
+    /// have two types, a clashing extern declaration is almost certainly a
+    /// mistake. Check to make sure that the `extern` definitions are correct
+    /// and equivalent, and possibly consider unifying them in one location.
+    ///
+    /// This lint does not run between crates because a project may have
+    /// dependencies which both rely on the same extern function, but declare
+    /// it in a different (but valid) way. For example, they may both declare
+    /// an opaque type for one or more of the arguments (which would end up
+    /// distinct types), or use types that are valid conversions in the
+    /// language the `extern fn` is defined in. In these cases, the compiler
+    /// can't say that the clashing declaration is incorrect.
+    pub CLASHING_EXTERN_DECLARATIONS,
+    Warn,
+    "detects when an extern fn has been declared with the same name but different types"
+}
+
+struct ClashingExternDeclarations {
+    /// Map of function symbol name to the first-seen hir id for that symbol name.. If seen_decls
+    /// contains an entry for key K, it means a symbol with name K has been seen by this lint and
+    /// 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: UnordMap<Symbol, hir::OwnerId>,
+}
+
+/// Differentiate between whether the name for an extern decl came from the link_name attribute or
+/// just from declaration itself. This is important because we don't want to report clashes on
+/// symbol name if they don't actually clash because one or the other links against a symbol with a
+/// different name.
+enum SymbolName {
+    /// The name of the symbol + the span of the annotation which introduced the link name.
+    Link(Symbol, Span),
+    /// No link name, so just the name of the symbol.
+    Normal(Symbol),
+}
+
+impl SymbolName {
+    fn get_name(&self) -> Symbol {
+        match self {
+            SymbolName::Link(s, _) | SymbolName::Normal(s) => *s,
+        }
+    }
+}
+
+impl ClashingExternDeclarations {
+    pub(crate) fn new() -> Self {
+        ClashingExternDeclarations { seen_decls: Default::default() }
+    }
+
+    /// Insert a new foreign item into the seen set. If a symbol with the same name already exists
+    /// for the item, return its HirId without updating the set.
+    fn insert(&mut self, tcx: TyCtxt<'_>, fi: hir::ForeignItemId) -> Option<hir::OwnerId> {
+        let did = fi.owner_id.to_def_id();
+        let instance = Instance::new(did, ty::List::identity_for_item(tcx, did));
+        let name = Symbol::intern(tcx.symbol_name(instance).name);
+        if let Some(&existing_id) = self.seen_decls.get(&name) {
+            // Avoid updating the map with the new entry when we do find a collision. We want to
+            // make sure we're always pointing to the first definition as the previous declaration.
+            // This lets us avoid emitting "knock-on" diagnostics.
+            Some(existing_id)
+        } else {
+            self.seen_decls.insert(name, fi.owner_id)
+        }
+    }
+
+    #[instrument(level = "trace", skip(self, tcx))]
+    fn check_foreign_item<'tcx>(&mut self, tcx: TyCtxt<'tcx>, this_fi: hir::ForeignItemId) {
+        let DefKind::Fn = tcx.def_kind(this_fi.owner_id) else { return };
+        let Some(existing_did) = self.insert(tcx, this_fi) else { return };
+
+        let existing_decl_ty = tcx.type_of(existing_did).skip_binder();
+        let this_decl_ty = tcx.type_of(this_fi.owner_id).instantiate_identity();
+        debug!(
+            "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}",
+            existing_did, existing_decl_ty, this_fi.owner_id, this_decl_ty
+        );
+
+        // Check that the declarations match.
+        if !structurally_same_type(
+            tcx,
+            tcx.param_env(this_fi.owner_id),
+            existing_decl_ty,
+            this_decl_ty,
+            types::CItemKind::Declaration,
+        ) {
+            let orig = name_of_extern_decl(tcx, existing_did);
+
+            // Finally, emit the diagnostic.
+            let this = tcx.item_name(this_fi.owner_id.to_def_id());
+            let orig = orig.get_name();
+            let previous_decl_label = get_relevant_span(tcx, existing_did);
+            let mismatch_label = get_relevant_span(tcx, this_fi.owner_id);
+            let sub =
+                BuiltinClashingExternSub { tcx, expected: existing_decl_ty, found: this_decl_ty };
+            let decorator = if orig == this {
+                BuiltinClashingExtern::SameName {
+                    this,
+                    orig,
+                    previous_decl_label,
+                    mismatch_label,
+                    sub,
+                }
+            } else {
+                BuiltinClashingExtern::DiffName {
+                    this,
+                    orig,
+                    previous_decl_label,
+                    mismatch_label,
+                    sub,
+                }
+            };
+            tcx.emit_node_span_lint(
+                CLASHING_EXTERN_DECLARATIONS,
+                this_fi.hir_id(),
+                mismatch_label,
+                decorator,
+            );
+        }
+    }
+}
+
+/// Get the name of the symbol that's linked against for a given extern declaration. That is,
+/// the name specified in a #[link_name = ...] attribute if one was specified, else, just the
+/// symbol's name.
+fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> SymbolName {
+    if let Some((overridden_link_name, overridden_link_name_span)) =
+        tcx.codegen_fn_attrs(fi).link_name.map(|overridden_link_name| {
+            // FIXME: Instead of searching through the attributes again to get span
+            // information, we could have codegen_fn_attrs also give span information back for
+            // where the attribute was defined. However, until this is found to be a
+            // bottleneck, this does just fine.
+            (overridden_link_name, tcx.get_attr(fi, sym::link_name).unwrap().span)
+        })
+    {
+        SymbolName::Link(overridden_link_name, overridden_link_name_span)
+    } else {
+        SymbolName::Normal(tcx.item_name(fi.to_def_id()))
+    }
+}
+
+/// We want to ensure that we use spans for both decls that include where the
+/// name was defined, whether that was from the link_name attribute or not.
+fn get_relevant_span(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> Span {
+    match name_of_extern_decl(tcx, fi) {
+        SymbolName::Normal(_) => tcx.def_span(fi),
+        SymbolName::Link(_, annot_span) => annot_span,
+    }
+}
+
+/// Checks whether two types are structurally the same enough that the declarations shouldn't
+/// clash. We need this so we don't emit a lint when two modules both declare an extern struct,
+/// with the same members (as the declarations shouldn't clash).
+fn structurally_same_type<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    a: Ty<'tcx>,
+    b: Ty<'tcx>,
+    ckind: types::CItemKind,
+) -> bool {
+    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 UnordSet<(Ty<'tcx>, Ty<'tcx>)>,
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    a: Ty<'tcx>,
+    b: Ty<'tcx>,
+    ckind: types::CItemKind,
+) -> bool {
+    debug!("structurally_same_type_impl(tcx, a = {:?}, b = {:?})", a, b);
+
+    // Given a transparent newtype, reach through and grab the inner
+    // type unless the newtype makes the type non-null.
+    let non_transparent_ty = |mut ty: Ty<'tcx>| -> Ty<'tcx> {
+        loop {
+            if let ty::Adt(def, args) = *ty.kind() {
+                let is_transparent = def.repr().transparent();
+                let is_non_null = types::nonnull_optimization_guaranteed(tcx, def);
+                debug!(
+                    "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}",
+                    ty, is_transparent, is_non_null
+                );
+                if is_transparent && !is_non_null {
+                    debug_assert_eq!(def.variants().len(), 1);
+                    let v = &def.variant(FIRST_VARIANT);
+                    // continue with `ty`'s non-ZST field,
+                    // otherwise `ty` is a ZST and we can return
+                    if let Some(field) = types::transparent_newtype_field(tcx, v) {
+                        ty = field.ty(tcx, args);
+                        continue;
+                    }
+                }
+            }
+            debug!("non_transparent_ty -> {:?}", ty);
+            return ty;
+        }
+    };
+
+    let a = non_transparent_ty(a);
+    let b = non_transparent_ty(b);
+
+    if !seen_types.insert((a, b)) {
+        // We've encountered a cycle. There's no point going any further -- the types are
+        // structurally the same.
+        true
+    } else if a == b {
+        // All nominally-same types are structurally same, too.
+        true
+    } else {
+        // Do a full, depth-first comparison between the two.
+        use rustc_type_ir::TyKind::*;
+        let a_kind = a.kind();
+        let b_kind = b.kind();
+
+        let compare_layouts = |a, b| -> Result<bool, &'tcx LayoutError<'tcx>> {
+            debug!("compare_layouts({:?}, {:?})", a, b);
+            let a_layout = &tcx.layout_of(param_env.and(a))?.layout.abi();
+            let b_layout = &tcx.layout_of(param_env.and(b))?.layout.abi();
+            debug!(
+                "comparing layouts: {:?} == {:?} = {}",
+                a_layout,
+                b_layout,
+                a_layout == b_layout
+            );
+            Ok(a_layout == b_layout)
+        };
+
+        #[allow(rustc::usage_of_ty_tykind)]
+        let is_primitive_or_pointer =
+            |kind: &ty::TyKind<'_>| kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..));
+
+        ensure_sufficient_stack(|| {
+            match (a_kind, b_kind) {
+                (Adt(a_def, _), Adt(b_def, _)) => {
+                    // We can immediately rule out these types as structurally same if
+                    // their layouts differ.
+                    match compare_layouts(a, b) {
+                        Ok(false) => return false,
+                        _ => (), // otherwise, continue onto the full, fields comparison
+                    }
+
+                    // Grab a flattened representation of all fields.
+                    let a_fields = a_def.variants().iter().flat_map(|v| v.fields.iter());
+                    let b_fields = b_def.variants().iter().flat_map(|v| v.fields.iter());
+
+                    // Perform a structural comparison for each field.
+                    a_fields.eq_by(
+                        b_fields,
+                        |&ty::FieldDef { did: a_did, .. }, &ty::FieldDef { did: b_did, .. }| {
+                            structurally_same_type_impl(
+                                seen_types,
+                                tcx,
+                                param_env,
+                                tcx.type_of(a_did).instantiate_identity(),
+                                tcx.type_of(b_did).instantiate_identity(),
+                                ckind,
+                            )
+                        },
+                    )
+                }
+                (Array(a_ty, a_const), Array(b_ty, b_const)) => {
+                    // For arrays, we also check the constness of the type.
+                    a_const.kind() == b_const.kind()
+                        && structurally_same_type_impl(
+                            seen_types, tcx, param_env, *a_ty, *b_ty, ckind,
+                        )
+                }
+                (Slice(a_ty), Slice(b_ty)) => {
+                    structurally_same_type_impl(seen_types, tcx, param_env, *a_ty, *b_ty, ckind)
+                }
+                (RawPtr(a_ty, a_mutbl), RawPtr(b_ty, b_mutbl)) => {
+                    a_mutbl == b_mutbl
+                        && structurally_same_type_impl(
+                            seen_types, tcx, param_env, *a_ty, *b_ty, ckind,
+                        )
+                }
+                (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => {
+                    // For structural sameness, we don't need the region to be same.
+                    a_mut == b_mut
+                        && structurally_same_type_impl(
+                            seen_types, tcx, param_env, *a_ty, *b_ty, ckind,
+                        )
+                }
+                (FnDef(..), FnDef(..)) => {
+                    let a_poly_sig = a.fn_sig(tcx);
+                    let b_poly_sig = b.fn_sig(tcx);
+
+                    // We don't compare regions, but leaving bound regions around ICEs, so
+                    // we erase them.
+                    let a_sig = tcx.instantiate_bound_regions_with_erased(a_poly_sig);
+                    let b_sig = tcx.instantiate_bound_regions_with_erased(b_poly_sig);
+
+                    (a_sig.abi, a_sig.safety, a_sig.c_variadic)
+                        == (b_sig.abi, b_sig.safety, b_sig.c_variadic)
+                        && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
+                            structurally_same_type_impl(seen_types, tcx, param_env, *a, *b, ckind)
+                        })
+                        && structurally_same_type_impl(
+                            seen_types,
+                            tcx,
+                            param_env,
+                            a_sig.output(),
+                            b_sig.output(),
+                            ckind,
+                        )
+                }
+                (Tuple(a_args), Tuple(b_args)) => {
+                    a_args.iter().eq_by(b_args.iter(), |a_ty, b_ty| {
+                        structurally_same_type_impl(seen_types, tcx, param_env, a_ty, b_ty, ckind)
+                    })
+                }
+                // For these, it's not quite as easy to define structural-sameness quite so easily.
+                // For the purposes of this lint, take the conservative approach and mark them as
+                // not structurally same.
+                (Dynamic(..), Dynamic(..))
+                | (Error(..), Error(..))
+                | (Closure(..), Closure(..))
+                | (Coroutine(..), Coroutine(..))
+                | (CoroutineWitness(..), CoroutineWitness(..))
+                | (Alias(ty::Projection, ..), Alias(ty::Projection, ..))
+                | (Alias(ty::Inherent, ..), Alias(ty::Inherent, ..))
+                | (Alias(ty::Opaque, ..), Alias(ty::Opaque, ..)) => false,
+
+                // These definitely should have been caught above.
+                (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
+
+                // An Adt and a primitive or pointer type. This can be FFI-safe if non-null
+                // enum layout optimisation is being applied.
+                (Adt(..), other_kind) | (other_kind, Adt(..))
+                    if is_primitive_or_pointer(other_kind) =>
+                {
+                    let (primitive, adt) =
+                        if is_primitive_or_pointer(a.kind()) { (a, b) } else { (b, a) };
+                    if let Some(ty) = types::repr_nullable_ptr(tcx, param_env, adt, ckind) {
+                        ty == primitive
+                    } else {
+                        compare_layouts(a, b).unwrap_or(false)
+                    }
+                }
+                // Otherwise, just compare the layouts. This may fail to lint for some
+                // incompatible types, but at the very least, will stop reads into
+                // uninitialised memory.
+                _ => compare_layouts(a, b).unwrap_or(false),
+            }
+        })
+    }
+}
diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
new file mode 100644
index 00000000000..aa8ca1776dc
--- /dev/null
+++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
@@ -0,0 +1,115 @@
+use crate::{
+    lints::{
+        HiddenUnicodeCodepointsDiag, HiddenUnicodeCodepointsDiagLabels,
+        HiddenUnicodeCodepointsDiagSub,
+    },
+    EarlyContext, EarlyLintPass, LintContext,
+};
+use ast::util::unicode::{contains_text_flow_control_chars, TEXT_FLOW_CONTROL_CHARS};
+use rustc_ast as ast;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::{BytePos, Span, Symbol};
+
+declare_lint! {
+    /// The `text_direction_codepoint_in_literal` lint detects Unicode codepoints that change the
+    /// visual representation of text on screen in a way that does not correspond to their on
+    /// memory representation.
+    ///
+    /// ### Explanation
+    ///
+    /// The unicode characters `\u{202A}`, `\u{202B}`, `\u{202D}`, `\u{202E}`, `\u{2066}`,
+    /// `\u{2067}`, `\u{2068}`, `\u{202C}` and `\u{2069}` make the flow of text on screen change
+    /// its direction on software that supports these codepoints. This makes the text "abc" display
+    /// as "cba" on screen. By leveraging software that supports these, people can write specially
+    /// crafted literals that make the surrounding code seem like it's performing one action, when
+    /// in reality it is performing another. Because of this, we proactively lint against their
+    /// presence to avoid surprises.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(text_direction_codepoint_in_literal)]
+    /// fn main() {
+    ///     println!("{:?}", 'โ€ฎ');
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    pub TEXT_DIRECTION_CODEPOINT_IN_LITERAL,
+    Deny,
+    "detect special Unicode codepoints that affect the visual representation of text on screen, \
+     changing the direction in which text flows",
+}
+
+declare_lint_pass!(HiddenUnicodeCodepoints => [TEXT_DIRECTION_CODEPOINT_IN_LITERAL]);
+
+impl HiddenUnicodeCodepoints {
+    fn lint_text_direction_codepoint(
+        &self,
+        cx: &EarlyContext<'_>,
+        text: Symbol,
+        span: Span,
+        padding: u32,
+        point_at_inner_spans: bool,
+        label: &str,
+    ) {
+        // Obtain the `Span`s for each of the forbidden chars.
+        let spans: Vec<_> = text
+            .as_str()
+            .char_indices()
+            .filter_map(|(i, c)| {
+                TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| {
+                    let lo = span.lo() + BytePos(i as u32 + padding);
+                    (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32)))
+                })
+            })
+            .collect();
+
+        let count = spans.len();
+        let labels = point_at_inner_spans
+            .then_some(HiddenUnicodeCodepointsDiagLabels { spans: spans.clone() });
+        let sub = if point_at_inner_spans && !spans.is_empty() {
+            HiddenUnicodeCodepointsDiagSub::Escape { spans }
+        } else {
+            HiddenUnicodeCodepointsDiagSub::NoEscape { spans }
+        };
+
+        cx.emit_span_lint(
+            TEXT_DIRECTION_CODEPOINT_IN_LITERAL,
+            span,
+            HiddenUnicodeCodepointsDiag { label, count, span_label: span, labels, sub },
+        );
+    }
+}
+impl EarlyLintPass for HiddenUnicodeCodepoints {
+    fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+        if let ast::AttrKind::DocComment(_, comment) = attr.kind {
+            if contains_text_flow_control_chars(comment.as_str()) {
+                self.lint_text_direction_codepoint(cx, comment, attr.span, 0, false, "doc comment");
+            }
+        }
+    }
+
+    #[inline]
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+        // byte strings are already handled well enough by `EscapeError::NonAsciiCharInByteString`
+        match &expr.kind {
+            ast::ExprKind::Lit(token_lit) => {
+                let text = token_lit.symbol;
+                if !contains_text_flow_control_chars(text.as_str()) {
+                    return;
+                }
+                let padding = match token_lit.kind {
+                    // account for `"` or `'`
+                    ast::token::LitKind::Str | ast::token::LitKind::Char => 1,
+                    // account for `r###"`
+                    ast::token::LitKind::StrRaw(n) => n as u32 + 2,
+                    _ => return,
+                };
+                self.lint_text_direction_codepoint(cx, text, expr.span, padding, true, "literal");
+            }
+            _ => {}
+        };
+    }
+}
diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs
new file mode 100644
index 00000000000..0860413190c
--- /dev/null
+++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs
@@ -0,0 +1,418 @@
+use rustc_data_structures::fx::FxIndexSet;
+use rustc_data_structures::unord::UnordSet;
+use rustc_errors::{Applicability, LintDiagnostic};
+use rustc_hir as hir;
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_macros::LintDiagnostic;
+use rustc_middle::bug;
+use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
+use rustc_middle::ty::{
+    self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
+};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::Span;
+
+use crate::fluent_generated as fluent;
+use crate::{LateContext, LateLintPass};
+
+declare_lint! {
+    /// The `impl_trait_overcaptures` lint warns against cases where lifetime
+    /// capture behavior will differ in edition 2024.
+    ///
+    /// In the 2024 edition, `impl Trait`s will capture all lifetimes in scope,
+    /// rather than just the lifetimes that are mentioned in the bounds of the type.
+    /// Often these sets are equal, but if not, it means that the `impl Trait` may
+    /// cause erroneous borrow-checker errors.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// # #![feature(precise_capturing)]
+    /// # #![allow(incomplete_features)]
+    /// # #![deny(impl_trait_overcaptures)]
+    /// # use std::fmt::Display;
+    /// let mut x = vec![];
+    /// x.push(1);
+    ///
+    /// fn test(x: &Vec<i32>) -> impl Display {
+    ///     x[0]
+    /// }
+    ///
+    /// let element = test(&x);
+    /// x.push(2);
+    /// println!("{element}");
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// In edition < 2024, the returned `impl Display` doesn't capture the
+    /// lifetime from the `&Vec<i32>`, so the vector can be mutably borrowed
+    /// while the `impl Display` is live.
+    ///
+    /// To fix this, we can explicitly state that the `impl Display` doesn't
+    /// capture any lifetimes, using `impl Display + use<>`.
+    pub IMPL_TRAIT_OVERCAPTURES,
+    Allow,
+    "`impl Trait` will capture more lifetimes than possibly intended in edition 2024",
+    @feature_gate = precise_capturing;
+    //@future_incompatible = FutureIncompatibleInfo {
+    //    reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
+    //    reference: "<FIXME>",
+    //};
+}
+
+declare_lint! {
+    /// The `impl_trait_redundant_captures` lint warns against cases where use of the
+    /// precise capturing `use<...>` syntax is not needed.
+    ///
+    /// In the 2024 edition, `impl Trait`s will capture all lifetimes in scope.
+    /// If precise-capturing `use<...>` syntax is used, and the set of parameters
+    /// that are captures are *equal* to the set of parameters in scope, then
+    /// the syntax is redundant, and can be removed.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// # #![feature(precise_capturing, lifetime_capture_rules_2024)]
+    /// # #![allow(incomplete_features)]
+    /// # #![deny(impl_trait_redundant_captures)]
+    /// fn test<'a>(x: &'a i32) -> impl Sized + use<'a> { x }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// To fix this, remove the `use<'a>`, since the lifetime is already captured
+    /// since it is in scope.
+    pub IMPL_TRAIT_REDUNDANT_CAPTURES,
+    Warn,
+    "redundant precise-capturing `use<...>` syntax on an `impl Trait`",
+    @feature_gate = precise_capturing;
+}
+
+declare_lint_pass!(
+    /// Lint for opaque types that will begin capturing in-scope but unmentioned lifetimes
+    /// in edition 2024.
+    ImplTraitOvercaptures => [IMPL_TRAIT_OVERCAPTURES, IMPL_TRAIT_REDUNDANT_CAPTURES]
+);
+
+impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'tcx>) {
+        match &it.kind {
+            hir::ItemKind::Fn(..) => check_fn(cx.tcx, it.owner_id.def_id),
+            _ => {}
+        }
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::ImplItem<'tcx>) {
+        match &it.kind {
+            hir::ImplItemKind::Fn(_, _) => check_fn(cx.tcx, it.owner_id.def_id),
+            _ => {}
+        }
+    }
+
+    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::TraitItem<'tcx>) {
+        match &it.kind {
+            hir::TraitItemKind::Fn(_, _) => check_fn(cx.tcx, it.owner_id.def_id),
+            _ => {}
+        }
+    }
+}
+
+fn check_fn(tcx: TyCtxt<'_>, parent_def_id: LocalDefId) {
+    let sig = tcx.fn_sig(parent_def_id).instantiate_identity();
+
+    let mut in_scope_parameters = FxIndexSet::default();
+    // Populate the in_scope_parameters list first with all of the generics in scope
+    let mut current_def_id = Some(parent_def_id.to_def_id());
+    while let Some(def_id) = current_def_id {
+        let generics = tcx.generics_of(def_id);
+        for param in &generics.own_params {
+            in_scope_parameters.insert(param.def_id);
+        }
+        current_def_id = generics.parent;
+    }
+
+    // Then visit the signature to walk through all the binders (incl. the late-bound
+    // vars on the function itself, which we need to count too).
+    sig.visit_with(&mut VisitOpaqueTypes {
+        tcx,
+        parent_def_id,
+        in_scope_parameters,
+        seen: Default::default(),
+    });
+}
+
+struct VisitOpaqueTypes<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    parent_def_id: LocalDefId,
+    in_scope_parameters: FxIndexSet<DefId>,
+    seen: FxIndexSet<LocalDefId>,
+}
+
+impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
+    fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
+        &mut self,
+        t: &ty::Binder<'tcx, T>,
+    ) -> Self::Result {
+        // When we get into a binder, we need to add its own bound vars to the scope.
+        let mut added = vec![];
+        for arg in t.bound_vars() {
+            let arg: ty::BoundVariableKind = arg;
+            match arg {
+                ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, ..))
+                | ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(def_id, _)) => {
+                    added.push(def_id);
+                    let unique = self.in_scope_parameters.insert(def_id);
+                    assert!(unique);
+                }
+                _ => {
+                    self.tcx.dcx().span_delayed_bug(
+                        self.tcx.def_span(self.parent_def_id),
+                        format!("unsupported bound variable kind: {arg:?}"),
+                    );
+                }
+            }
+        }
+
+        t.super_visit_with(self);
+
+        // And remove them. The `shift_remove` should be `O(1)` since we're popping
+        // them off from the end.
+        for arg in added.into_iter().rev() {
+            self.in_scope_parameters.shift_remove(&arg);
+        }
+    }
+
+    fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
+        if !t.has_aliases() {
+            return;
+        }
+
+        if let ty::Alias(ty::Projection, opaque_ty) = *t.kind()
+            && self.tcx.is_impl_trait_in_trait(opaque_ty.def_id)
+        {
+            // visit the opaque of the RPITIT
+            self.tcx
+                .type_of(opaque_ty.def_id)
+                .instantiate(self.tcx, opaque_ty.args)
+                .visit_with(self)
+        } else if let ty::Alias(ty::Opaque, opaque_ty) = *t.kind()
+            && let Some(opaque_def_id) = opaque_ty.def_id.as_local()
+            // Don't recurse infinitely on an opaque
+            && self.seen.insert(opaque_def_id)
+            // If it's owned by this function
+            && let opaque =
+                self.tcx.hir_node_by_def_id(opaque_def_id).expect_item().expect_opaque_ty()
+            && let hir::OpaqueTyOrigin::FnReturn(parent_def_id) = opaque.origin
+            && parent_def_id == self.parent_def_id
+        {
+            // Compute the set of args that are captured by the opaque...
+            let mut captured = FxIndexSet::default();
+            let variances = self.tcx.variances_of(opaque_def_id);
+            let mut current_def_id = Some(opaque_def_id.to_def_id());
+            while let Some(def_id) = current_def_id {
+                let generics = self.tcx.generics_of(def_id);
+                for param in &generics.own_params {
+                    // A param is captured if it's invariant.
+                    if variances[param.index as usize] != ty::Invariant {
+                        continue;
+                    }
+                    // We need to turn all `ty::Param`/`ConstKind::Param` and
+                    // `ReEarlyParam`/`ReBound` into def ids.
+                    captured.insert(extract_def_id_from_arg(
+                        self.tcx,
+                        generics,
+                        opaque_ty.args[param.index as usize],
+                    ));
+                }
+                current_def_id = generics.parent;
+            }
+
+            // Compute the set of in scope params that are not captured. Get their spans,
+            // since that's all we really care about them for emitting the diagnostic.
+            let uncaptured_spans: Vec<_> = self
+                .in_scope_parameters
+                .iter()
+                .filter(|def_id| !captured.contains(*def_id))
+                .map(|def_id| self.tcx.def_span(def_id))
+                .collect();
+
+            let opaque_span = self.tcx.def_span(opaque_def_id);
+            let new_capture_rules =
+                opaque_span.at_least_rust_2024() || self.tcx.features().lifetime_capture_rules_2024;
+
+            // If we have uncaptured args, and if the opaque doesn't already have
+            // `use<>` syntax on it, and we're < edition 2024, then warn the user.
+            if !new_capture_rules
+                && !opaque.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Use(..)))
+                && !uncaptured_spans.is_empty()
+            {
+                let suggestion = if let Ok(snippet) =
+                    self.tcx.sess.source_map().span_to_snippet(opaque_span)
+                    && snippet.starts_with("impl ")
+                {
+                    let (lifetimes, others): (Vec<_>, Vec<_>) = captured
+                        .into_iter()
+                        .partition(|def_id| self.tcx.def_kind(*def_id) == DefKind::LifetimeParam);
+                    // Take all lifetime params first, then all others (ty/ct).
+                    let generics: Vec<_> = lifetimes
+                        .into_iter()
+                        .chain(others)
+                        .map(|def_id| self.tcx.item_name(def_id).to_string())
+                        .collect();
+                    // Make sure that we're not trying to name any APITs
+                    if generics.iter().all(|name| !name.starts_with("impl ")) {
+                        Some((
+                            format!(" + use<{}>", generics.join(", ")),
+                            opaque_span.shrink_to_hi(),
+                        ))
+                    } else {
+                        None
+                    }
+                } else {
+                    None
+                };
+
+                self.tcx.emit_node_span_lint(
+                    IMPL_TRAIT_OVERCAPTURES,
+                    self.tcx.local_def_id_to_hir_id(opaque_def_id),
+                    opaque_span,
+                    ImplTraitOvercapturesLint {
+                        self_ty: t,
+                        num_captured: uncaptured_spans.len(),
+                        uncaptured_spans,
+                        suggestion,
+                    },
+                );
+            }
+            // Otherwise, if we are edition 2024, have `use<>` syntax, and
+            // have no uncaptured args, then we should warn to the user that
+            // it's redundant to capture all args explicitly.
+            else if new_capture_rules
+                && let Some((captured_args, capturing_span)) =
+                    opaque.bounds.iter().find_map(|bound| match *bound {
+                        hir::GenericBound::Use(a, s) => Some((a, s)),
+                        _ => None,
+                    })
+            {
+                let mut explicitly_captured = UnordSet::default();
+                for arg in captured_args {
+                    match self.tcx.named_bound_var(arg.hir_id()) {
+                        Some(
+                            ResolvedArg::EarlyBound(def_id) | ResolvedArg::LateBound(_, _, def_id),
+                        ) => {
+                            if self.tcx.def_kind(self.tcx.parent(def_id)) == DefKind::OpaqueTy {
+                                let def_id = self
+                                    .tcx
+                                    .map_opaque_lifetime_to_parent_lifetime(def_id.expect_local())
+                                    .opt_param_def_id(self.tcx, self.parent_def_id.to_def_id())
+                                    .expect("variable should have been duplicated from parent");
+
+                                explicitly_captured.insert(def_id);
+                            } else {
+                                explicitly_captured.insert(def_id);
+                            }
+                        }
+                        _ => {
+                            self.tcx.dcx().span_delayed_bug(
+                                self.tcx.hir().span(arg.hir_id()),
+                                "no valid for captured arg",
+                            );
+                        }
+                    }
+                }
+
+                if self
+                    .in_scope_parameters
+                    .iter()
+                    .all(|def_id| explicitly_captured.contains(def_id))
+                {
+                    self.tcx.emit_node_span_lint(
+                        IMPL_TRAIT_REDUNDANT_CAPTURES,
+                        self.tcx.local_def_id_to_hir_id(opaque_def_id),
+                        opaque_span,
+                        ImplTraitRedundantCapturesLint { capturing_span },
+                    );
+                }
+            }
+
+            // Walk into the bounds of the opaque, too, since we want to get nested opaques
+            // in this lint as well. Interestingly, one place that I expect this lint to fire
+            // is for `impl for<'a> Bound<Out = impl Other>`, since `impl Other` will begin
+            // to capture `'a` in e2024 (even though late-bound vars in opaques are not allowed).
+            for clause in
+                self.tcx.item_bounds(opaque_ty.def_id).iter_instantiated(self.tcx, opaque_ty.args)
+            {
+                clause.visit_with(self)
+            }
+        }
+
+        t.super_visit_with(self);
+    }
+}
+
+struct ImplTraitOvercapturesLint<'tcx> {
+    uncaptured_spans: Vec<Span>,
+    self_ty: Ty<'tcx>,
+    num_captured: usize,
+    suggestion: Option<(String, Span)>,
+}
+
+impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_impl_trait_overcaptures);
+        diag.arg("self_ty", self.self_ty.to_string())
+            .arg("num_captured", self.num_captured)
+            .span_note(self.uncaptured_spans, fluent::lint_note)
+            .note(fluent::lint_note2);
+        if let Some((suggestion, span)) = self.suggestion {
+            diag.span_suggestion(
+                span,
+                fluent::lint_suggestion,
+                suggestion,
+                Applicability::MachineApplicable,
+            );
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_impl_trait_redundant_captures)]
+struct ImplTraitRedundantCapturesLint {
+    #[suggestion(lint_suggestion, code = "", applicability = "machine-applicable")]
+    capturing_span: Span,
+}
+
+fn extract_def_id_from_arg<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    generics: &'tcx ty::Generics,
+    arg: ty::GenericArg<'tcx>,
+) -> DefId {
+    match arg.unpack() {
+        ty::GenericArgKind::Lifetime(re) => match *re {
+            ty::ReEarlyParam(ebr) => generics.region_param(ebr, tcx).def_id,
+            ty::ReBound(
+                _,
+                ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. },
+            ) => def_id,
+            _ => unreachable!(),
+        },
+        ty::GenericArgKind::Type(ty) => {
+            let ty::Param(param_ty) = *ty.kind() else {
+                bug!();
+            };
+            generics.type_param(param_ty, tcx).def_id
+        }
+        ty::GenericArgKind::Const(ct) => {
+            let ty::ConstKind::Param(param_ct) = ct.kind() else {
+                bug!();
+            };
+            generics.const_param(param_ct, tcx).def_id
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
new file mode 100644
index 00000000000..772cc2ff8b9
--- /dev/null
+++ b/compiler/rustc_lint/src/internal.rs
@@ -0,0 +1,560 @@
+//! Some lints that are only useful in the compiler or crates that use compiler internals, such as
+//! Clippy.
+
+use crate::lints::{
+    BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword,
+    QueryInstability, SpanUseEqCtxtDiag, TyQualified, TykindDiag, TykindKind, UntranslatableDiag,
+};
+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::{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};
+use rustc_span::Span;
+use tracing::debug;
+
+declare_tool_lint! {
+    /// The `default_hash_type` lint detects use of [`std::collections::HashMap`] and
+    /// [`std::collections::HashSet`], suggesting the use of `FxHashMap`/`FxHashSet`.
+    ///
+    /// This can help as `FxHasher` can perform better than the default hasher. DOS protection is
+    /// not required as input is assumed to be trusted.
+    pub rustc::DEFAULT_HASH_TYPES,
+    Allow,
+    "forbid HashMap and HashSet and suggest the FxHash* variants",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
+
+impl LateLintPass<'_> for DefaultHashTypes {
+    fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
+        let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return };
+        if matches!(cx.tcx.hir_node(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) {
+            // Don't lint imports, only actual usages.
+            return;
+        }
+        let preferred = match cx.tcx.get_diagnostic_name(def_id) {
+            Some(sym::HashMap) => "FxHashMap",
+            Some(sym::HashSet) => "FxHashSet",
+            _ => return,
+        };
+        cx.emit_span_lint(
+            DEFAULT_HASH_TYPES,
+            path.span,
+            DefaultHashTypesDiag { preferred, used: cx.tcx.item_name(def_id) },
+        );
+    }
+}
+
+/// Helper function for lints that check for expressions with calls and use typeck results to
+/// get the `DefId` and `GenericArgsRef` of the function.
+fn typeck_results_of_method_fn<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &Expr<'_>,
+) -> Option<(Span, DefId, ty::GenericArgsRef<'tcx>)> {
+    match expr.kind {
+        ExprKind::MethodCall(segment, ..)
+            if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
+        {
+            Some((segment.ident.span, def_id, cx.typeck_results().node_args(expr.hir_id)))
+        }
+        _ => match cx.typeck_results().node_type(expr.hir_id).kind() {
+            &ty::FnDef(def_id, args) => Some((expr.span, def_id, args)),
+            _ => None,
+        },
+    }
+}
+
+declare_tool_lint! {
+    /// The `potential_query_instability` lint detects use of methods which can lead to
+    /// potential query instability, such as iterating over a `HashMap`.
+    ///
+    /// Due to the [incremental compilation](https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html) model,
+    /// queries must return deterministic, stable results. `HashMap` iteration order can change
+    /// between compilations, and will introduce instability if query results expose the order.
+    pub rustc::POTENTIAL_QUERY_INSTABILITY,
+    Allow,
+    "require explicit opt-in when using potentially unstable methods or functions",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]);
+
+impl LateLintPass<'_> for QueryStability {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+        let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return };
+        if let Ok(Some(instance)) = ty::Instance::try_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_span_lint(
+                    POTENTIAL_QUERY_INSTABILITY,
+                    span,
+                    QueryInstability { query: cx.tcx.item_name(def_id) },
+                );
+            }
+        }
+    }
+}
+
+declare_tool_lint! {
+    /// The `usage_of_ty_tykind` lint detects usages of `ty::TyKind::<kind>`,
+    /// where `ty::<kind>` would suffice.
+    pub rustc::USAGE_OF_TY_TYKIND,
+    Allow,
+    "usage of `ty::TyKind` outside of the `ty::sty` module",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    /// The `usage_of_qualified_ty` lint detects usages of `ty::TyKind`,
+    /// where `Ty` should be used instead.
+    pub rustc::USAGE_OF_QUALIFIED_TY,
+    Allow,
+    "using `ty::{Ty,TyCtxt}` instead of importing it",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(TyTyKind => [
+    USAGE_OF_TY_TYKIND,
+    USAGE_OF_QUALIFIED_TY,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for TyTyKind {
+    fn check_path(
+        &mut self,
+        cx: &LateContext<'tcx>,
+        path: &rustc_hir::Path<'tcx>,
+        _: rustc_hir::HirId,
+    ) {
+        if let Some(segment) = path.segments.iter().nth_back(1)
+            && lint_ty_kind_usage(cx, &segment.res)
+        {
+            let span =
+                path.span.with_hi(segment.args.map_or(segment.ident.span, |a| a.span_ext).hi());
+            cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span });
+        }
+    }
+
+    fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
+        match &ty.kind {
+            TyKind::Path(QPath::Resolved(_, path)) => {
+                if lint_ty_kind_usage(cx, &path.res) {
+                    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
+                            {
+                                Some(path.span)
+                            } else {
+                                None
+                            }
+                        }
+                        Node::Expr(Expr { kind: ExprKind::Path(qpath), .. }) => {
+                            if let QPath::TypeRelative(qpath_ty, ..) = qpath
+                                && qpath_ty.hir_id == ty.hir_id
+                            {
+                                Some(path.span)
+                            } else {
+                                None
+                            }
+                        }
+                        // 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.
+                        Node::Expr(Expr { kind: ExprKind::Struct(qpath, ..), .. }) => {
+                            if let QPath::TypeRelative(qpath_ty, ..) = qpath
+                                && qpath_ty.hir_id == ty.hir_id
+                            {
+                                Some(path.span)
+                            } else {
+                                None
+                            }
+                        }
+                        _ => None,
+                    };
+
+                    match span {
+                        Some(span) => {
+                            cx.emit_span_lint(
+                                USAGE_OF_TY_TYKIND,
+                                path.span,
+                                TykindKind { suggestion: span },
+                            );
+                        }
+                        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_span_lint(
+                        USAGE_OF_QUALIFIED_TY,
+                        path.span,
+                        TyQualified { ty, suggestion: path.span },
+                    );
+                }
+            }
+            _ => {}
+        }
+    }
+}
+
+fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
+    if let Some(did) = res.opt_def_id() {
+        cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
+    } else {
+        false
+    }
+}
+
+fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> {
+    match &path.res {
+        Res::Def(_, def_id) => {
+            if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
+                return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
+            }
+        }
+        // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
+        Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
+            if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() {
+                if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
+                {
+                    // NOTE: This path is currently unreachable as `Ty<'tcx>` is
+                    // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
+                    // is not actually allowed.
+                    //
+                    // I(@lcnr) still kept this branch in so we don't miss this
+                    // if we ever change it in the future.
+                    return Some(format!("{}<{}>", name, args[0]));
+                }
+            }
+        }
+        _ => (),
+    }
+
+    None
+}
+
+fn gen_args(segment: &PathSegment<'_>) -> String {
+    if let Some(args) = &segment.args {
+        let lifetimes = args
+            .args
+            .iter()
+            .filter_map(|arg| {
+                if let GenericArg::Lifetime(lt) = arg { Some(lt.ident.to_string()) } else { None }
+            })
+            .collect::<Vec<_>>();
+
+        if !lifetimes.is_empty() {
+            return format!("<{}>", lifetimes.join(", "));
+        }
+    }
+
+    String::new()
+}
+
+declare_tool_lint! {
+    /// The `lint_pass_impl_without_macro` detects manual implementations of a lint
+    /// pass, without using [`declare_lint_pass`] or [`impl_lint_pass`].
+    pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
+    Allow,
+    "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
+}
+
+declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
+
+impl EarlyLintPass for LintPassImpl {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
+            if let Some(last) = lint_pass.path.segments.last() {
+                if last.ident.name == sym::LintPass {
+                    let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
+                    let call_site = expn_data.call_site;
+                    if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
+                        && call_site.ctxt().outer_expn_data().kind
+                            != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
+                    {
+                        cx.emit_span_lint(
+                            LINT_PASS_IMPL_WITHOUT_MACRO,
+                            lint_pass.path.span,
+                            LintPassByHand,
+                        );
+                    }
+                }
+            }
+        }
+    }
+}
+
+declare_tool_lint! {
+    /// The `existing_doc_keyword` lint detects use `#[doc()]` keywords
+    /// that don't exist, e.g. `#[doc(keyword = "..")]`.
+    pub rustc::EXISTING_DOC_KEYWORD,
+    Allow,
+    "Check that documented keywords in std and core actually exist",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
+
+fn is_doc_keyword(s: Symbol) -> bool {
+    s <= kw::Union
+}
+
+impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
+        for attr in cx.tcx.hir().attrs(item.hir_id()) {
+            if !attr.has_name(sym::doc) {
+                continue;
+            }
+            if let Some(list) = attr.meta_item_list() {
+                for nested in list {
+                    if nested.has_name(sym::keyword) {
+                        let keyword = nested
+                            .value_str()
+                            .expect("#[doc(keyword = \"...\")] expected a value!");
+                        if is_doc_keyword(keyword) {
+                            return;
+                        }
+                        cx.emit_span_lint(
+                            EXISTING_DOC_KEYWORD,
+                            attr.span,
+                            NonExistentDocKeyword { keyword },
+                        );
+                    }
+                }
+            }
+        }
+    }
+}
+
+declare_tool_lint! {
+    /// 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).
+    pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
+    Deny,
+    "prevent creation of diagnostics which cannot be translated",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    /// 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).
+    pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
+    Deny,
+    "prevent diagnostic creation outside of `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL]);
+
+impl LateLintPass<'_> for Diagnostics {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+        // 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::try_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));
+
+        // 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
+            }
+        };
+
+        // 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));
+                    }
+                }
+            }
+        }
+
+        // Is the callee interesting?
+        if !has_attr && impl_into_diagnostic_message_params.is_empty() {
+            return;
+        }
+
+        // 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)
+            {
+                parent_has_attr = true;
+                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);
+            }
+        }
+
+        // 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);
+            }
+        }
+    }
+}
+
+declare_tool_lint! {
+    /// The `bad_opt_access` lint detects accessing options by field instead of
+    /// the wrapper function.
+    pub rustc::BAD_OPT_ACCESS,
+    Deny,
+    "prevent using options by field access when there is a wrapper function",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]);
+
+impl LateLintPass<'_> for BadOptAccess {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+        let ExprKind::Field(base, target) = expr.kind else { return };
+        let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
+        // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be
+        // avoided.
+        if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) {
+            return;
+        }
+
+        for field in adt_def.all_fields() {
+            if field.name == target.name
+                && let Some(attr) =
+                    cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access)
+                && let Some(items) = attr.meta_item_list()
+                && let Some(item) = items.first()
+                && let Some(lit) = item.lit()
+                && let ast::LitKind::Str(val, _) = lit.kind
+            {
+                cx.emit_span_lint(
+                    BAD_OPT_ACCESS,
+                    expr.span,
+                    BadOptAccessDiag { msg: val.as_str() },
+                );
+            }
+        }
+    }
+}
+
+declare_tool_lint! {
+    pub rustc::SPAN_USE_EQ_CTXT,
+    Allow,
+    "forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead",
+    report_in_external_macro: true
+}
+
+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 | BinOpKind::Ne, .. }, lhs, rhs) =
+            expr.kind
+        {
+            if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) {
+                cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag);
+            }
+        }
+    }
+}
+
+fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    match &expr.kind {
+        ExprKind::MethodCall(..) => cx
+            .typeck_results()
+            .type_dependent_def_id(expr.hir_id)
+            .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)),
+
+        _ => false,
+    }
+}
diff --git a/compiler/rustc_lint/src/invalid_from_utf8.rs b/compiler/rustc_lint/src/invalid_from_utf8.rs
new file mode 100644
index 00000000000..0081374922e
--- /dev/null
+++ b/compiler/rustc_lint/src/invalid_from_utf8.rs
@@ -0,0 +1,132 @@
+use std::str::Utf8Error;
+
+use rustc_ast::LitKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::source_map::Spanned;
+use rustc_span::sym;
+
+use crate::lints::InvalidFromUtf8Diag;
+use crate::{LateContext, LateLintPass, LintContext};
+
+declare_lint! {
+    /// The `invalid_from_utf8_unchecked` lint checks for calls to
+    /// `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`
+    /// with a known invalid UTF-8 value.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// # #[allow(unused)]
+    /// unsafe {
+    ///     std::str::from_utf8_unchecked(b"Ru\x82st");
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Creating such a `str` would result in undefined behavior as per documentation
+    /// for `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`.
+    pub INVALID_FROM_UTF8_UNCHECKED,
+    Deny,
+    "using a non UTF-8 literal in `std::str::from_utf8_unchecked`"
+}
+
+declare_lint! {
+    /// The `invalid_from_utf8` lint checks for calls to
+    /// `std::str::from_utf8` and `std::str::from_utf8_mut`
+    /// with a known invalid UTF-8 value.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # #[allow(unused)]
+    /// std::str::from_utf8(b"Ru\x82st");
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Trying to create such a `str` would always return an error as per documentation
+    /// for `std::str::from_utf8` and `std::str::from_utf8_mut`.
+    pub INVALID_FROM_UTF8,
+    Warn,
+    "using a non UTF-8 literal in `std::str::from_utf8`"
+}
+
+declare_lint_pass!(InvalidFromUtf8 => [INVALID_FROM_UTF8_UNCHECKED, INVALID_FROM_UTF8]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+        if let ExprKind::Call(path, [arg]) = expr.kind
+            && let ExprKind::Path(ref qpath) = path.kind
+            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+            && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
+            && [
+                sym::str_from_utf8,
+                sym::str_from_utf8_mut,
+                sym::str_from_utf8_unchecked,
+                sym::str_from_utf8_unchecked_mut,
+            ]
+            .contains(&diag_item)
+        {
+            let lint = |label, utf8_error: Utf8Error| {
+                let method = diag_item.as_str().strip_prefix("str_").unwrap();
+                let method = format!("std::str::{method}");
+                let valid_up_to = utf8_error.valid_up_to();
+                let is_unchecked_variant = diag_item.as_str().contains("unchecked");
+
+                cx.emit_span_lint(
+                    if is_unchecked_variant {
+                        INVALID_FROM_UTF8_UNCHECKED
+                    } else {
+                        INVALID_FROM_UTF8
+                    },
+                    expr.span,
+                    if is_unchecked_variant {
+                        InvalidFromUtf8Diag::Unchecked { method, valid_up_to, label }
+                    } else {
+                        InvalidFromUtf8Diag::Checked { method, valid_up_to, label }
+                    },
+                )
+            };
+
+            let mut init = cx.expr_or_init_with_outside_body(arg);
+            while let ExprKind::AddrOf(.., inner) = init.kind {
+                init = cx.expr_or_init_with_outside_body(inner);
+            }
+            match init.kind {
+                ExprKind::Lit(Spanned { node: lit, .. }) => {
+                    if let LitKind::ByteStr(bytes, _) = &lit
+                        && let Err(utf8_error) = std::str::from_utf8(bytes)
+                    {
+                        lint(init.span, utf8_error);
+                    }
+                }
+                ExprKind::Array(args) => {
+                    let elements = args
+                        .iter()
+                        .map(|e| match &e.kind {
+                            ExprKind::Lit(Spanned { node: lit, .. }) => match lit {
+                                LitKind::Byte(b) => Some(*b),
+                                LitKind::Int(b, _) => Some(b.get() as u8),
+                                _ => None,
+                            },
+                            _ => None,
+                        })
+                        .collect::<Option<Vec<_>>>();
+
+                    if let Some(elements) = elements
+                        && let Err(utf8_error) = std::str::from_utf8(&elements)
+                    {
+                        lint(init.span, utf8_error);
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs
new file mode 100644
index 00000000000..aa328fb87b2
--- /dev/null
+++ b/compiler/rustc_lint/src/late.rs
@@ -0,0 +1,455 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore};
+use rustc_data_structures::stack::ensure_sufficient_stack;
+use rustc_data_structures::sync::{join, Lrc};
+use rustc_hir as hir;
+use rustc_hir::def_id::{LocalDefId, LocalModDefId};
+use rustc_hir::intravisit as hir_visit;
+use rustc_hir::HirId;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_session::lint::LintPass;
+use rustc_session::Session;
+use rustc_span::Span;
+use std::any::Any;
+use std::cell::Cell;
+use tracing::debug;
+
+/// Extract the [`LintStore`] from [`Session`].
+///
+/// This function exists because [`Session::lint_store`] is type-erased.
+pub fn unerased_lint_store(sess: &Session) -> &LintStore {
+    let store: &Lrc<_> = sess.lint_store.as_ref().unwrap();
+    let store: &dyn Any = &**store;
+    store.downcast_ref().unwrap()
+}
+
+macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({
+    $cx.pass.$f(&$cx.context, $($args),*);
+}) }
+
+/// Implements the AST traversal for late lint passes. `T` provides the
+/// `check_*` methods.
+pub struct LateContextAndPass<'tcx, T: LateLintPass<'tcx>> {
+    context: LateContext<'tcx>,
+    pass: T,
+}
+
+impl<'tcx, T: LateLintPass<'tcx>> LateContextAndPass<'tcx, T> {
+    /// Merge the lints specified by any lint attributes into the
+    /// current lint context, call the provided function, then reset the
+    /// lints in effect to their previous state.
+    fn with_lint_attrs<F>(&mut self, id: HirId, f: F)
+    where
+        F: FnOnce(&mut Self),
+    {
+        let attrs = self.context.tcx.hir().attrs(id);
+        let prev = self.context.last_node_with_lint_attrs;
+        self.context.last_node_with_lint_attrs = id;
+        debug!("late context: enter_attrs({:?})", attrs);
+        lint_callback!(self, check_attributes, attrs);
+        for attr in attrs {
+            lint_callback!(self, check_attribute, attr);
+        }
+        f(self);
+        debug!("late context: exit_attrs({:?})", attrs);
+        lint_callback!(self, check_attributes_post, attrs);
+        self.context.last_node_with_lint_attrs = prev;
+    }
+
+    fn with_param_env<F>(&mut self, id: hir::OwnerId, f: F)
+    where
+        F: FnOnce(&mut Self),
+    {
+        let old_param_env = self.context.param_env;
+        self.context.param_env = self.context.tcx.param_env(id);
+        f(self);
+        self.context.param_env = old_param_env;
+    }
+
+    fn process_mod(&mut self, m: &'tcx hir::Mod<'tcx>, n: HirId) {
+        lint_callback!(self, check_mod, m, n);
+        hir_visit::walk_mod(self, m, n);
+    }
+}
+
+impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPass<'tcx, T> {
+    type NestedFilter = nested_filter::All;
+
+    /// Because lints are scoped lexically, we want to walk nested
+    /// items in the context of the outer item, so enable
+    /// deep-walking.
+    fn nested_visit_map(&mut self) -> Self::Map {
+        self.context.tcx.hir()
+    }
+
+    fn visit_nested_body(&mut self, body_id: hir::BodyId) {
+        let old_enclosing_body = self.context.enclosing_body.replace(body_id);
+        let old_cached_typeck_results = self.context.cached_typeck_results.get();
+
+        // HACK(eddyb) avoid trashing `cached_typeck_results` when we're
+        // nested in `visit_fn`, which may have already resulted in them
+        // being queried.
+        if old_enclosing_body != Some(body_id) {
+            self.context.cached_typeck_results.set(None);
+        }
+
+        let body = self.context.tcx.hir().body(body_id);
+        self.visit_body(body);
+        self.context.enclosing_body = old_enclosing_body;
+
+        // See HACK comment above.
+        if old_enclosing_body != Some(body_id) {
+            self.context.cached_typeck_results.set(old_cached_typeck_results);
+        }
+    }
+
+    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+        self.with_lint_attrs(param.hir_id, |cx| {
+            hir_visit::walk_param(cx, param);
+        });
+    }
+
+    fn visit_body(&mut self, body: &hir::Body<'tcx>) {
+        lint_callback!(self, check_body, body);
+        hir_visit::walk_body(self, body);
+        lint_callback!(self, check_body_post, body);
+    }
+
+    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+        let generics = self.context.generics.take();
+        self.context.generics = it.kind.generics();
+        let old_cached_typeck_results = self.context.cached_typeck_results.take();
+        let old_enclosing_body = self.context.enclosing_body.take();
+        self.with_lint_attrs(it.hir_id(), |cx| {
+            cx.with_param_env(it.owner_id, |cx| {
+                lint_callback!(cx, check_item, it);
+                hir_visit::walk_item(cx, it);
+                lint_callback!(cx, check_item_post, it);
+            });
+        });
+        self.context.enclosing_body = old_enclosing_body;
+        self.context.cached_typeck_results.set(old_cached_typeck_results);
+        self.context.generics = generics;
+    }
+
+    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+        self.with_lint_attrs(it.hir_id(), |cx| {
+            cx.with_param_env(it.owner_id, |cx| {
+                lint_callback!(cx, check_foreign_item, it);
+                hir_visit::walk_foreign_item(cx, it);
+            });
+        })
+    }
+
+    fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
+        lint_callback!(self, check_pat, p);
+        hir_visit::walk_pat(self, p);
+    }
+
+    fn visit_expr_field(&mut self, field: &'tcx hir::ExprField<'tcx>) {
+        self.with_lint_attrs(field.hir_id, |cx| hir_visit::walk_expr_field(cx, field))
+    }
+
+    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+        ensure_sufficient_stack(|| {
+            self.with_lint_attrs(e.hir_id, |cx| {
+                lint_callback!(cx, check_expr, e);
+                hir_visit::walk_expr(cx, e);
+                lint_callback!(cx, check_expr_post, e);
+            })
+        })
+    }
+
+    fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) {
+        // See `EarlyContextAndPass::visit_stmt` for an explanation
+        // of why we call `walk_stmt` outside of `with_lint_attrs`
+        self.with_lint_attrs(s.hir_id, |cx| {
+            lint_callback!(cx, check_stmt, s);
+        });
+        hir_visit::walk_stmt(self, s);
+    }
+
+    fn visit_fn(
+        &mut self,
+        fk: hir_visit::FnKind<'tcx>,
+        decl: &'tcx hir::FnDecl<'tcx>,
+        body_id: hir::BodyId,
+        span: Span,
+        id: LocalDefId,
+    ) {
+        // Wrap in typeck results here, not just in visit_nested_body,
+        // in order for `check_fn` to be able to use them.
+        let old_enclosing_body = self.context.enclosing_body.replace(body_id);
+        let old_cached_typeck_results = self.context.cached_typeck_results.take();
+        let body = self.context.tcx.hir().body(body_id);
+        lint_callback!(self, check_fn, fk, decl, body, span, id);
+        hir_visit::walk_fn(self, fk, decl, body_id, id);
+        self.context.enclosing_body = old_enclosing_body;
+        self.context.cached_typeck_results.set(old_cached_typeck_results);
+    }
+
+    fn visit_variant_data(&mut self, s: &'tcx hir::VariantData<'tcx>) {
+        lint_callback!(self, check_struct_def, s);
+        hir_visit::walk_struct_def(self, s);
+    }
+
+    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
+        self.with_lint_attrs(s.hir_id, |cx| {
+            lint_callback!(cx, check_field_def, s);
+            hir_visit::walk_field_def(cx, s);
+        })
+    }
+
+    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
+        self.with_lint_attrs(v.hir_id, |cx| {
+            lint_callback!(cx, check_variant, v);
+            hir_visit::walk_variant(cx, v);
+        })
+    }
+
+    fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) {
+        lint_callback!(self, check_ty, t);
+        hir_visit::walk_ty(self, t);
+    }
+
+    fn visit_infer(&mut self, inf: &'tcx hir::InferArg) {
+        hir_visit::walk_inf(self, inf);
+    }
+
+    fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, _: Span, n: HirId) {
+        if !self.context.only_module {
+            self.process_mod(m, n);
+        }
+    }
+
+    fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
+        self.with_lint_attrs(l.hir_id, |cx| {
+            lint_callback!(cx, check_local, l);
+            hir_visit::walk_local(cx, l);
+        })
+    }
+
+    fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) {
+        lint_callback!(self, check_block, b);
+        hir_visit::walk_block(self, b);
+        lint_callback!(self, check_block_post, b);
+    }
+
+    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+        self.with_lint_attrs(a.hir_id, |cx| {
+            lint_callback!(cx, check_arm, a);
+            hir_visit::walk_arm(cx, a);
+        })
+    }
+
+    fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
+        lint_callback!(self, check_generic_param, p);
+        hir_visit::walk_generic_param(self, p);
+    }
+
+    fn visit_generics(&mut self, g: &'tcx hir::Generics<'tcx>) {
+        lint_callback!(self, check_generics, g);
+        hir_visit::walk_generics(self, g);
+    }
+
+    fn visit_where_predicate(&mut self, p: &'tcx hir::WherePredicate<'tcx>) {
+        hir_visit::walk_where_predicate(self, p);
+    }
+
+    fn visit_poly_trait_ref(&mut self, t: &'tcx hir::PolyTraitRef<'tcx>) {
+        lint_callback!(self, check_poly_trait_ref, t);
+        hir_visit::walk_poly_trait_ref(self, t);
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+        let generics = self.context.generics.take();
+        self.context.generics = Some(trait_item.generics);
+        self.with_lint_attrs(trait_item.hir_id(), |cx| {
+            cx.with_param_env(trait_item.owner_id, |cx| {
+                lint_callback!(cx, check_trait_item, trait_item);
+                hir_visit::walk_trait_item(cx, trait_item);
+            });
+        });
+        self.context.generics = generics;
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+        let generics = self.context.generics.take();
+        self.context.generics = Some(impl_item.generics);
+        self.with_lint_attrs(impl_item.hir_id(), |cx| {
+            cx.with_param_env(impl_item.owner_id, |cx| {
+                lint_callback!(cx, check_impl_item, impl_item);
+                hir_visit::walk_impl_item(cx, impl_item);
+                lint_callback!(cx, check_impl_item_post, impl_item);
+            });
+        });
+        self.context.generics = generics;
+    }
+
+    fn visit_lifetime(&mut self, lt: &'tcx hir::Lifetime) {
+        hir_visit::walk_lifetime(self, lt);
+    }
+
+    fn visit_path(&mut self, p: &hir::Path<'tcx>, id: HirId) {
+        lint_callback!(self, check_path, p, id);
+        hir_visit::walk_path(self, p);
+    }
+}
+
+// Combines multiple lint passes into a single pass, at runtime. Each
+// `check_foo` method in `$methods` within this pass simply calls `check_foo`
+// once per `$pass`. Compare with `declare_combined_late_lint_pass`, which is
+// similar, but combines lint passes at compile time.
+struct RuntimeCombinedLateLintPass<'a, 'tcx> {
+    passes: &'a mut [LateLintPassObject<'tcx>],
+}
+
+#[allow(rustc::lint_pass_impl_without_macro)]
+impl LintPass for RuntimeCombinedLateLintPass<'_, '_> {
+    fn name(&self) -> &'static str {
+        panic!()
+    }
+}
+
+macro_rules! impl_late_lint_pass {
+    ([], [$($(#[$attr:meta])* fn $f:ident($($param:ident: $arg:ty),*);)*]) => {
+        impl<'tcx> LateLintPass<'tcx> for RuntimeCombinedLateLintPass<'_, 'tcx> {
+            $(fn $f(&mut self, context: &LateContext<'tcx>, $($param: $arg),*) {
+                for pass in self.passes.iter_mut() {
+                    pass.$f(context, $($param),*);
+                }
+            })*
+        }
+    };
+}
+
+crate::late_lint_methods!(impl_late_lint_pass, []);
+
+pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>(
+    tcx: TyCtxt<'tcx>,
+    module_def_id: LocalModDefId,
+    builtin_lints: T,
+) {
+    let context = LateContext {
+        tcx,
+        enclosing_body: None,
+        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),
+        generics: None,
+        only_module: true,
+    };
+
+    // 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 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);
+    }
+}
+
+fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>(
+    tcx: TyCtxt<'tcx>,
+    module_def_id: LocalModDefId,
+    context: LateContext<'tcx>,
+    pass: T,
+) {
+    let mut cx = LateContextAndPass { context, pass };
+
+    let (module, _span, hir_id) = tcx.hir().get_module(module_def_id);
+
+    cx.with_lint_attrs(hir_id, |cx| {
+        // There is no module lint that will have the crate itself as an item, so check it here.
+        if hir_id == hir::CRATE_HIR_ID {
+            lint_callback!(cx, check_crate,);
+        }
+
+        cx.process_mod(module, hir_id);
+
+        if hir_id == hir::CRATE_HIR_ID {
+            lint_callback!(cx, check_crate_post,);
+        }
+    });
+}
+
+fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) {
+    // Note: `passes` is often empty.
+    let mut passes: Vec<_> =
+        unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect();
+
+    if passes.is_empty() {
+        return;
+    }
+
+    let context = LateContext {
+        tcx,
+        enclosing_body: None,
+        cached_typeck_results: Cell::new(None),
+        param_env: ty::ParamEnv::empty(),
+        effective_visibilities: tcx.effective_visibilities(()),
+        last_node_with_lint_attrs: hir::CRATE_HIR_ID,
+        generics: None,
+        only_module: false,
+    };
+
+    let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] };
+    late_lint_crate_inner(tcx, context, pass);
+}
+
+fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>(
+    tcx: TyCtxt<'tcx>,
+    context: LateContext<'tcx>,
+    pass: T,
+) {
+    let mut cx = LateContextAndPass { context, pass };
+
+    // Visit the whole crate.
+    cx.with_lint_attrs(hir::CRATE_HIR_ID, |cx| {
+        // Since the root module isn't visited as an item (because it isn't an
+        // item), warn for it here.
+        lint_callback!(cx, check_crate,);
+        tcx.hir().walk_toplevel_module(cx);
+        lint_callback!(cx, check_crate_post,);
+    })
+}
+
+/// Performs lint checking on a crate.
+pub fn check_crate<'tcx>(tcx: TyCtxt<'tcx>) {
+    join(
+        || {
+            tcx.sess.time("crate_lints", || {
+                // Run whole crate non-incremental lints
+                late_lint_crate(tcx);
+            });
+        },
+        || {
+            tcx.sess.time("module_lints", || {
+                // Run per-module lints
+                tcx.hir().par_for_each_module(|module| tcx.ensure().lint_mod(module));
+            });
+        },
+    );
+}
diff --git a/compiler/rustc_lint/src/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs
new file mode 100644
index 00000000000..e6c274ec09a
--- /dev/null
+++ b/compiler/rustc_lint/src/let_underscore.rs
@@ -0,0 +1,172 @@
+use crate::{
+    lints::{NonBindingLet, NonBindingLetSub},
+    LateContext, LateLintPass, LintContext,
+};
+use rustc_errors::MultiSpan;
+use rustc_hir as hir;
+use rustc_middle::ty;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::{sym, Symbol};
+
+declare_lint! {
+    /// The `let_underscore_drop` lint checks for statements which don't bind
+    /// an expression which has a non-trivial Drop implementation to anything,
+    /// causing the expression to be dropped immediately instead of at end of
+    /// scope.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// struct SomeStruct;
+    /// impl Drop for SomeStruct {
+    ///     fn drop(&mut self) {
+    ///         println!("Dropping SomeStruct");
+    ///     }
+    /// }
+    ///
+    /// fn main() {
+    ///    #[warn(let_underscore_drop)]
+    ///     // SomeStruct is dropped immediately instead of at end of scope,
+    ///     // so "Dropping SomeStruct" is printed before "end of main".
+    ///     // The order of prints would be reversed if SomeStruct was bound to
+    ///     // a name (such as "_foo").
+    ///     let _ = SomeStruct;
+    ///     println!("end of main");
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Statements which assign an expression to an underscore causes the
+    /// expression to immediately drop instead of extending the expression's
+    /// lifetime to the end of the scope. This is usually unintended,
+    /// especially for types like `MutexGuard`, which are typically used to
+    /// lock a mutex for the duration of an entire scope.
+    ///
+    /// If you want to extend the expression's lifetime to the end of the scope,
+    /// assign an underscore-prefixed name (such as `_foo`) to the expression.
+    /// If you do actually want to drop the expression immediately, then
+    /// calling `std::mem::drop` on the expression is clearer and helps convey
+    /// intent.
+    pub LET_UNDERSCORE_DROP,
+    Allow,
+    "non-binding let on a type that implements `Drop`"
+}
+
+declare_lint! {
+    /// The `let_underscore_lock` lint checks for statements which don't bind
+    /// a mutex to anything, causing the lock to be released immediately instead
+    /// of at end of scope, which is typically incorrect.
+    ///
+    /// ### Example
+    /// ```rust,compile_fail
+    /// use std::sync::{Arc, Mutex};
+    /// use std::thread;
+    /// let data = Arc::new(Mutex::new(0));
+    ///
+    /// thread::spawn(move || {
+    ///     // The lock is immediately released instead of at the end of the
+    ///     // scope, which is probably not intended.
+    ///     let _ = data.lock().unwrap();
+    ///     println!("doing some work");
+    ///     let mut lock = data.lock().unwrap();
+    ///     *lock += 1;
+    /// });
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Statements which assign an expression to an underscore causes the
+    /// expression to immediately drop instead of extending the expression's
+    /// lifetime to the end of the scope. This is usually unintended,
+    /// especially for types like `MutexGuard`, which are typically used to
+    /// lock a mutex for the duration of an entire scope.
+    ///
+    /// If you want to extend the expression's lifetime to the end of the scope,
+    /// assign an underscore-prefixed name (such as `_foo`) to the expression.
+    /// If you do actually want to drop the expression immediately, then
+    /// calling `std::mem::drop` on the expression is clearer and helps convey
+    /// intent.
+    pub LET_UNDERSCORE_LOCK,
+    Deny,
+    "non-binding let on a synchronization lock"
+}
+
+declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]);
+
+const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
+    rustc_span::sym::MutexGuard,
+    rustc_span::sym::RwLockReadGuard,
+    rustc_span::sym::RwLockWriteGuard,
+];
+
+impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
+    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
+    fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::LetStmt<'_>) {
+        if matches!(local.source, rustc_hir::LocalSource::AsyncFn) {
+            return;
+        }
+
+        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 !ty.needs_drop(cx.tcx, cx.param_env) {
+                return;
+            }
+            // 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: 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_span(pat.span);
+                span.push_span_label(
+                    pat.span,
+                    "this lock is not assigned to a binding and is immediately dropped".to_string(),
+                );
+                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
new file mode 100644
index 00000000000..0df34c32e38
--- /dev/null
+++ b/compiler/rustc_lint/src/levels.rs
@@ -0,0 +1,1125 @@
+use crate::errors::{CheckNameUnknownTool, RequestedLevel, UnsupportedGroup};
+use crate::lints::{
+    DeprecatedLintNameFromCommandLine, RemovedLintFromCommandLine, RenamedLintFromCommandLine,
+    UnknownLintFromCommandLine,
+};
+use crate::{
+    builtin::MISSING_DOCS,
+    context::{CheckLintNameResult, LintStore},
+    fluent_generated as fluent,
+    late::unerased_lint_store,
+    lints::{
+        DeprecatedLintName, IgnoredUnlessCrateSpecified, OverruledAttributeLint, RemovedLint,
+        RenamedLint, RenamedLintSuggestion, UnknownLint, UnknownLintSuggestion,
+    },
+};
+use rustc_ast as ast;
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_errors::{Diag, LintDiagnostic, MultiSpan};
+use rustc_feature::{Features, GateIssue};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::HirId;
+use rustc_index::IndexVec;
+use rustc_middle::bug;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::lint::{
+    lint_level, reveal_actual_level, LevelAndSource, LintExpectation, LintLevelSource,
+    ShallowLintLevelMap,
+};
+use rustc_middle::query::Providers;
+use rustc_middle::ty::{RegisteredTools, TyCtxt};
+use rustc_session::lint::{
+    builtin::{
+        self, FORBIDDEN_LINT_GROUPS, RENAMED_AND_REMOVED_LINTS, SINGLE_USE_LIFETIMES,
+        UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES,
+    },
+    Level, Lint, LintExpectationId, LintId,
+};
+use rustc_session::Session;
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use tracing::{debug, instrument};
+
+use crate::errors::{
+    MalformedAttribute, MalformedAttributeSub, OverruledAttribute, OverruledAttributeSub,
+    UnknownToolInScopedLint,
+};
+
+/// Collection of lint levels for the whole crate.
+/// This is used by AST-based lints, which do not
+/// wait until we have built HIR to be emitted.
+#[derive(Debug)]
+struct LintLevelSets {
+    /// Linked list of specifications.
+    list: IndexVec<LintStackIndex, LintSet>,
+}
+
+rustc_index::newtype_index! {
+    struct LintStackIndex {
+        const COMMAND_LINE = 0;
+    }
+}
+
+/// Specifications found at this position in the stack. This map only represents the lints
+/// found for one set of attributes (like `shallow_lint_levels_on` does).
+///
+/// We store the level specifications as a linked list.
+/// Each `LintSet` represents a set of attributes on the same AST node.
+/// The `parent` forms a linked list that matches the AST tree.
+/// This way, walking the linked list is equivalent to walking the AST bottom-up
+/// to find the specifications for a given lint.
+#[derive(Debug)]
+struct LintSet {
+    // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
+    // flag.
+    specs: FxIndexMap<LintId, LevelAndSource>,
+    parent: LintStackIndex,
+}
+
+impl LintLevelSets {
+    fn new() -> Self {
+        LintLevelSets { list: IndexVec::new() }
+    }
+
+    fn get_lint_level(
+        &self,
+        lint: &'static Lint,
+        idx: LintStackIndex,
+        aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
+        sess: &Session,
+    ) -> LevelAndSource {
+        let lint = LintId::of(lint);
+        let (level, mut src) = self.raw_lint_id_level(lint, idx, aux);
+        let level = reveal_actual_level(level, &mut src, sess, lint, |id| {
+            self.raw_lint_id_level(id, idx, aux)
+        });
+        (level, src)
+    }
+
+    fn raw_lint_id_level(
+        &self,
+        id: LintId,
+        mut idx: LintStackIndex,
+        aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
+    ) -> (Option<Level>, LintLevelSource) {
+        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) {
+                return (Some(level), src);
+            }
+            if idx == COMMAND_LINE {
+                return (None, LintLevelSource::Default);
+            }
+            idx = parent;
+        }
+    }
+}
+
+fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExpectation)> {
+    let store = unerased_lint_store(tcx.sess);
+
+    let mut builder = LintLevelsBuilder {
+        sess: tcx.sess,
+        features: tcx.features(),
+        provider: QueryMapExpectationsWrapper {
+            tcx,
+            cur: hir::CRATE_HIR_ID,
+            specs: ShallowLintLevelMap::default(),
+            expectations: Vec::new(),
+            unstable_to_stable_ids: FxIndexMap::default(),
+            empty: FxIndexMap::default(),
+        },
+        lint_added_lints: false,
+        store,
+        registered_tools: tcx.registered_tools(()),
+    };
+
+    builder.add_command_line();
+    builder.add_id(hir::CRATE_HIR_ID);
+    tcx.hir().walk_toplevel_module(&mut builder);
+
+    tcx.dcx().update_unstable_expectation_id(&builder.provider.unstable_to_stable_ids);
+
+    builder.provider.expectations
+}
+
+#[instrument(level = "trace", skip(tcx), ret)]
+fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap {
+    let store = unerased_lint_store(tcx.sess);
+    let attrs = tcx.hir_attrs(owner);
+
+    let mut levels = LintLevelsBuilder {
+        sess: tcx.sess,
+        features: tcx.features(),
+        provider: LintLevelQueryMap {
+            tcx,
+            cur: owner.into(),
+            specs: ShallowLintLevelMap::default(),
+            empty: FxIndexMap::default(),
+            attrs,
+        },
+        lint_added_lints: false,
+        store,
+        registered_tools: tcx.registered_tools(()),
+    };
+
+    if owner == hir::CRATE_OWNER_ID {
+        levels.add_command_line();
+    }
+
+    match attrs.map.range(..) {
+        // 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 }),
+        // 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_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),
+            hir::OwnerNode::ImplItem(item) => levels.visit_impl_item(item),
+            hir::OwnerNode::Crate(mod_) => {
+                levels.add_id(hir::CRATE_HIR_ID);
+                levels.visit_mod(mod_, mod_.spans.inner_span, hir::CRATE_HIR_ID)
+            }
+            hir::OwnerNode::Synthetic => unreachable!(),
+        },
+    }
+
+    let specs = levels.provider.specs;
+
+    #[cfg(debug_assertions)]
+    for (_, v) in specs.specs.iter() {
+        debug_assert!(!v.is_empty());
+    }
+
+    specs
+}
+
+pub struct TopDown {
+    sets: LintLevelSets,
+    cur: LintStackIndex,
+}
+
+pub trait LintLevelsProvider {
+    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) -> &FxIndexMap<LintId, LevelAndSource> {
+        &self.sets.list[self.cur].specs
+    }
+
+    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
+        self.sets.list[self.cur].specs.insert(id, lvl);
+    }
+
+    fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource {
+        self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), sess)
+    }
+}
+
+struct LintLevelQueryMap<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    cur: HirId,
+    specs: ShallowLintLevelMap,
+    /// Empty hash map to simplify code.
+    empty: FxIndexMap<LintId, LevelAndSource>,
+    attrs: &'tcx hir::AttributeMap<'tcx>,
+}
+
+impl LintLevelsProvider for LintLevelQueryMap<'_> {
+    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) {
+        self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl);
+    }
+    fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource {
+        self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur)
+    }
+}
+
+struct QueryMapExpectationsWrapper<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    /// HirId of the currently investigated element.
+    cur: HirId,
+    /// Level map for `cur`.
+    specs: ShallowLintLevelMap,
+    expectations: Vec<(LintExpectationId, LintExpectation)>,
+    unstable_to_stable_ids: FxIndexMap<LintExpectationId, LintExpectationId>,
+    /// Empty hash map to simplify code.
+    empty: FxIndexMap<LintId, LevelAndSource>,
+}
+
+impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> {
+    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) {
+        self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl);
+    }
+    fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource {
+        // We cannot use `tcx.lint_level_at_node` because we want to know in which order the
+        // attributes have been inserted, in particular whether an `expect` follows a `forbid`.
+        self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur)
+    }
+    fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) {
+        let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id
+        else {
+            bug!("unstable expectation id should already be mapped")
+        };
+        let key = LintExpectationId::Unstable { attr_id, lint_index: None };
+
+        self.unstable_to_stable_ids.entry(key).or_insert(LintExpectationId::Stable {
+            hir_id,
+            attr_index,
+            lint_index: None,
+            attr_id: None,
+        });
+
+        self.expectations.push((id.normalize(), expectation));
+    }
+}
+
+impl<'tcx> LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
+    fn add_id(&mut self, hir_id: HirId) {
+        self.provider.cur = hir_id;
+        self.add(
+            self.provider.attrs.get(hir_id.local_id),
+            hir_id == hir::CRATE_HIR_ID,
+            Some(hir_id),
+        );
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
+    type NestedFilter = nested_filter::OnlyBodies;
+
+    fn nested_visit_map(&mut self) -> Self::Map {
+        self.provider.tcx.hir()
+    }
+
+    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+        self.add_id(param.hir_id);
+        intravisit::walk_param(self, param);
+    }
+
+    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_item(self, it);
+    }
+
+    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_foreign_item(self, it);
+    }
+
+    fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
+        // We will call `add_id` when we walk
+        // the `StmtKind`. The outer statement itself doesn't
+        // define the lint levels.
+        intravisit::walk_stmt(self, e);
+    }
+
+    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+        self.add_id(e.hir_id);
+        intravisit::walk_expr(self, e);
+    }
+
+    fn visit_expr_field(&mut self, f: &'tcx hir::ExprField<'tcx>) {
+        self.add_id(f.hir_id);
+        intravisit::walk_expr_field(self, f);
+    }
+
+    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
+        self.add_id(s.hir_id);
+        intravisit::walk_field_def(self, s);
+    }
+
+    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
+        self.add_id(v.hir_id);
+        intravisit::walk_variant(self, v);
+    }
+
+    fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
+        self.add_id(l.hir_id);
+        intravisit::walk_local(self, l);
+    }
+
+    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+        self.add_id(a.hir_id);
+        intravisit::walk_arm(self, a);
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+        self.add_id(trait_item.hir_id());
+        intravisit::walk_trait_item(self, trait_item);
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+        self.add_id(impl_item.hir_id());
+        intravisit::walk_impl_item(self, impl_item);
+    }
+}
+
+impl<'tcx> LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> {
+    fn add_id(&mut self, hir_id: HirId) {
+        // Change both the `HirId` and the associated specs.
+        self.provider.cur = hir_id;
+        self.provider.specs.specs.clear();
+        self.add(self.provider.tcx.hir().attrs(hir_id), hir_id == hir::CRATE_HIR_ID, Some(hir_id));
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> {
+    type NestedFilter = nested_filter::All;
+
+    fn nested_visit_map(&mut self) -> Self::Map {
+        self.provider.tcx.hir()
+    }
+
+    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+        self.add_id(param.hir_id);
+        intravisit::walk_param(self, param);
+    }
+
+    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_item(self, it);
+    }
+
+    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+        self.add_id(it.hir_id());
+        intravisit::walk_foreign_item(self, it);
+    }
+
+    fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
+        // We will call `add_id` when we walk
+        // the `StmtKind`. The outer statement itself doesn't
+        // define the lint levels.
+        intravisit::walk_stmt(self, e);
+    }
+
+    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+        self.add_id(e.hir_id);
+        intravisit::walk_expr(self, e);
+    }
+
+    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
+        self.add_id(s.hir_id);
+        intravisit::walk_field_def(self, s);
+    }
+
+    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
+        self.add_id(v.hir_id);
+        intravisit::walk_variant(self, v);
+    }
+
+    fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
+        self.add_id(l.hir_id);
+        intravisit::walk_local(self, l);
+    }
+
+    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+        self.add_id(a.hir_id);
+        intravisit::walk_arm(self, a);
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+        self.add_id(trait_item.hir_id());
+        intravisit::walk_trait_item(self, trait_item);
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+        self.add_id(impl_item.hir_id());
+        intravisit::walk_impl_item(self, impl_item);
+    }
+}
+
+pub struct LintLevelsBuilder<'s, P> {
+    sess: &'s Session,
+    features: &'s Features,
+    provider: P,
+    lint_added_lints: bool,
+    store: &'s LintStore,
+    registered_tools: &'s RegisteredTools,
+}
+
+pub(crate) struct BuilderPush {
+    prev: LintStackIndex,
+}
+
+impl<'s> LintLevelsBuilder<'s, TopDown> {
+    pub(crate) fn new(
+        sess: &'s Session,
+        features: &'s Features,
+        lint_added_lints: bool,
+        store: &'s LintStore,
+        registered_tools: &'s RegisteredTools,
+    ) -> Self {
+        let mut builder = LintLevelsBuilder {
+            sess,
+            features,
+            provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE },
+            lint_added_lints,
+            store,
+            registered_tools,
+        };
+        builder.process_command_line();
+        assert_eq!(builder.provider.sets.list.len(), 1);
+        builder
+    }
+
+    fn process_command_line(&mut self) {
+        self.provider.cur = self
+            .provider
+            .sets
+            .list
+            .push(LintSet { specs: FxIndexMap::default(), parent: COMMAND_LINE });
+        self.add_command_line();
+    }
+
+    /// Pushes a list of AST lint attributes onto this context.
+    ///
+    /// This function will return a `BuilderPush` object which should be passed
+    /// to `pop` when this scope for the attributes provided is exited.
+    ///
+    /// This function will perform a number of tasks:
+    ///
+    /// * It'll validate all lint-related attributes in `attrs`
+    /// * It'll mark all lint-related attributes as used
+    /// * Lint levels will be updated based on the attributes provided
+    /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
+    ///   `#[allow]`
+    ///
+    /// Don't forget to call `pop`!
+    pub(crate) fn push(
+        &mut self,
+        attrs: &[ast::Attribute],
+        is_crate_node: bool,
+        source_hir_id: Option<HirId>,
+    ) -> BuilderPush {
+        let prev = self.provider.cur;
+        self.provider.cur =
+            self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev });
+
+        self.add(attrs, is_crate_node, source_hir_id);
+
+        if self.provider.current_specs().is_empty() {
+            self.provider.sets.list.pop();
+            self.provider.cur = prev;
+        }
+
+        BuilderPush { prev }
+    }
+
+    /// Called after `push` when the scope of a set of attributes are exited.
+    pub(crate) fn pop(&mut self, push: BuilderPush) {
+        self.provider.cur = push.prev;
+        std::mem::forget(push);
+    }
+}
+
+#[cfg(debug_assertions)]
+impl Drop for BuilderPush {
+    fn drop(&mut self) {
+        panic!("Found a `push` without a `pop`.");
+    }
+}
+
+impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
+    pub(crate) fn sess(&self) -> &Session {
+        self.sess
+    }
+
+    pub(crate) fn features(&self) -> &Features {
+        self.features
+    }
+
+    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> {
+        self.provider.current_specs()
+    }
+
+    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
+        self.provider.insert(id, lvl)
+    }
+
+    fn add_command_line(&mut self) {
+        for &(ref lint_name, level) in &self.sess.opts.lint_opts {
+            // Checks the validity of lint names derived from the command line.
+            let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name);
+            if lint_name_only == crate::WARNINGS.name_lower()
+                && matches!(level, Level::ForceWarn(_))
+            {
+                self.sess
+                    .dcx()
+                    .emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() });
+            }
+            match self.store.check_lint_name(lint_name_only, tool_name, self.registered_tools) {
+                CheckLintNameResult::Renamed(ref replace) => {
+                    let name = lint_name.as_str();
+                    let suggestion = RenamedLintSuggestion::WithoutSpan { replace };
+                    let requested_level = RequestedLevel { level, lint_name };
+                    let lint = RenamedLintFromCommandLine { name, suggestion, requested_level };
+                    self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint);
+                }
+                CheckLintNameResult::Removed(ref reason) => {
+                    let name = lint_name.as_str();
+                    let requested_level = RequestedLevel { level, lint_name };
+                    let lint = RemovedLintFromCommandLine { name, reason, requested_level };
+                    self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint);
+                }
+                CheckLintNameResult::NoLint(suggestion) => {
+                    let name = lint_name.clone();
+                    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);
+                }
+                CheckLintNameResult::Tool(_, Some(ref replace)) => {
+                    let name = lint_name.clone();
+                    let requested_level = RequestedLevel { level, lint_name };
+                    let lint = DeprecatedLintNameFromCommandLine { name, replace, requested_level };
+                    self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint);
+                }
+                CheckLintNameResult::NoTool => {
+                    self.sess.dcx().emit_err(CheckNameUnknownTool {
+                        tool_name: tool_name.unwrap(),
+                        sub: RequestedLevel { level, lint_name },
+                    });
+                }
+                _ => {}
+            };
+
+            let orig_level = level;
+            let lint_flag_val = Symbol::intern(lint_name);
+
+            let Ok(ids) = self.store.find_lints(lint_name) else {
+                // errors already handled above
+                continue;
+            };
+            for id in ids {
+                // ForceWarn and Forbid cannot be overridden
+                if let Some((Level::ForceWarn(_) | Level::Forbid, _)) =
+                    self.current_specs().get(&id)
+                {
+                    continue;
+                }
+
+                if self.check_gated_lint(id, DUMMY_SP, true) {
+                    let src = LintLevelSource::CommandLine(lint_flag_val, orig_level);
+                    self.insert(id, (level, src));
+                }
+            }
+        }
+    }
+
+    /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful
+    /// (e.g. if a forbid was already inserted on the same scope), then emits a
+    /// diagnostic with no change to `specs`.
+    fn insert_spec(&mut self, id: LintId, (mut level, src): LevelAndSource) {
+        let (old_level, old_src) = self.provider.get_lint_level(id.lint, self.sess);
+        if let Level::Expect(id) = &mut level
+            && let LintExpectationId::Stable { .. } = id
+        {
+            *id = id.normalize();
+        }
+        // Setting to a non-forbid level is an error if the lint previously had
+        // a forbid level. Note that this is not necessarily true even with a
+        // `#[forbid(..)]` attribute present, as that is overridden by `--cap-lints`.
+        //
+        // This means that this only errors if we're truly lowering the lint
+        // level from forbid.
+        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,
+                    },
+                );
+            }
+
+            // 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;
+            }
+        }
+
+        // The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself.
+        // Handling expectations of this lint would add additional complexity with little to no
+        // benefit. The expect level for this lint will therefore be ignored.
+        if let Level::Expect(_) = level
+            && id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS)
+        {
+            return;
+        }
+
+        match (old_level, level) {
+            // If the new level is an expectation store it in `ForceWarn`
+            (Level::ForceWarn(_), Level::Expect(expectation_id)) => {
+                self.insert(id, (Level::ForceWarn(Some(expectation_id)), old_src))
+            }
+            // Keep `ForceWarn` level but drop the expectation
+            (Level::ForceWarn(_), _) => self.insert(id, (Level::ForceWarn(None), old_src)),
+            // Set the lint level as normal
+            _ => self.insert(id, (level, src)),
+        };
+    }
+
+    #[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() {
+            if attr.has_name(sym::automatically_derived) {
+                self.insert(
+                    LintId::of(SINGLE_USE_LIFETIMES),
+                    (Level::Allow, LintLevelSource::Default),
+                );
+                continue;
+            }
+
+            // `#[doc(hidden)]` disables missing_docs check.
+            if attr.has_name(sym::doc)
+                && attr
+                    .meta_item_list()
+                    .is_some_and(|l| ast::attr::list_contains_name(&l, sym::hidden))
+            {
+                self.insert(LintId::of(MISSING_DOCS), (Level::Allow, LintLevelSource::Default));
+                continue;
+            }
+
+            let level = match Level::from_attr(attr) {
+                None => continue,
+                // This is the only lint level with a `LintExpectationId` that can be created from
+                // an attribute.
+                Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
+                    let LintExpectationId::Unstable { attr_id, lint_index } = unstable_id else {
+                        bug!("stable id Level::from_attr")
+                    };
+
+                    let stable_id = LintExpectationId::Stable {
+                        hir_id,
+                        attr_index: attr_index.try_into().unwrap(),
+                        lint_index,
+                        // We pass the previous unstable attr_id such that we can trace the ast id
+                        // when building a map to go from unstable to stable id.
+                        attr_id: Some(attr_id),
+                    };
+
+                    Level::Expect(stable_id)
+                }
+                Some(lvl) => lvl,
+            };
+
+            let Some(mut metas) = attr.meta_item_list() else { continue };
+
+            // 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;
+            if let Some(item) = tail_li.meta_item() {
+                match item.kind {
+                    ast::MetaItemKind::Word => {} // actual lint names handled later
+                    ast::MetaItemKind::NameValue(ref name_value) => {
+                        if item.path == sym::reason {
+                            if let ast::LitKind::Str(rationale, _) = name_value.kind {
+                                reason = Some(rationale);
+                            } else {
+                                sess.dcx().emit_err(MalformedAttribute {
+                                    span: name_value.span,
+                                    sub: MalformedAttributeSub::ReasonMustBeStringLiteral(
+                                        name_value.span,
+                                    ),
+                                });
+                            }
+                            // found reason, reslice meta list to exclude it
+                            metas.pop().unwrap();
+                        } else {
+                            sess.dcx().emit_err(MalformedAttribute {
+                                span: item.span,
+                                sub: MalformedAttributeSub::BadAttributeArgument(item.span),
+                            });
+                        }
+                    }
+                    ast::MetaItemKind::List(_) => {
+                        sess.dcx().emit_err(MalformedAttribute {
+                            span: item.span,
+                            sub: MalformedAttributeSub::BadAttributeArgument(item.span),
+                        });
+                    }
+                }
+            }
+
+            for (lint_index, li) in metas.iter_mut().enumerate() {
+                let level = match level {
+                    Level::Expect(mut id) => {
+                        id.set_lint_index(Some(lint_index as u16));
+                        Level::Expect(id)
+                    }
+                    level => level,
+                };
+
+                let sp = li.span();
+                let meta_item = match li {
+                    ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item,
+                    _ => {
+                        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;
+                    }
+                };
+                let tool_ident = if meta_item.path.segments.len() > 1 {
+                    Some(meta_item.path.segments.remove(0).ident)
+                } else {
+                    None
+                };
+                let tool_name = tool_ident.map(|ident| ident.name);
+                let name = pprust::path_to_string(&meta_item.path);
+                let lint_result =
+                    self.store.check_lint_name(&name, tool_name, self.registered_tools);
+                match &lint_result {
+                    CheckLintNameResult::Ok(ids) => {
+                        // This checks for instances where the user writes
+                        // `#[expect(unfulfilled_lint_expectations)]` in that case we want to avoid
+                        // overriding the lint level but instead add an expectation that can't be
+                        // fulfilled. The lint message will include an explanation, that the
+                        // `unfulfilled_lint_expectations` lint can't be expected.
+                        if let Level::Expect(expect_id) = level {
+                            // The `unfulfilled_lint_expectations` lint is not part of any lint
+                            // groups. Therefore. we only need to check the slice if it contains a
+                            // single lint.
+                            let is_unfulfilled_lint_expectations = match ids {
+                                [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS),
+                                _ => false,
+                            };
+                            self.provider.push_expectation(
+                                expect_id,
+                                LintExpectation::new(
+                                    reason,
+                                    sp,
+                                    is_unfulfilled_lint_expectations,
+                                    tool_name,
+                                ),
+                            );
+                        }
+                        let src = LintLevelSource::Node {
+                            name: meta_item
+                                .path
+                                .segments
+                                .last()
+                                .expect("empty lint name")
+                                .ident
+                                .name,
+                            span: sp,
+                            reason,
+                        };
+                        for &id in *ids {
+                            if self.check_gated_lint(id, attr.span, false) {
+                                self.insert_spec(id, (level, src));
+                            }
+                        }
+                    }
+
+                    CheckLintNameResult::Tool(ids, new_lint_name) => {
+                        let src = match new_lint_name {
+                            None => {
+                                let complete_name =
+                                    &format!("{}::{}", tool_ident.unwrap().name, name);
+                                LintLevelSource::Node {
+                                    name: Symbol::intern(complete_name),
+                                    span: sp,
+                                    reason,
+                                }
+                            }
+                            Some(new_lint_name) => {
+                                self.emit_span_lint(
+                                    builtin::RENAMED_AND_REMOVED_LINTS,
+                                    sp.into(),
+                                    DeprecatedLintName {
+                                        name,
+                                        suggestion: sp,
+                                        replace: new_lint_name,
+                                    },
+                                );
+                                LintLevelSource::Node {
+                                    name: Symbol::intern(new_lint_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),
+                            );
+                        }
+                    }
+
+                    CheckLintNameResult::MissingTool => {
+                        // If `MissingTool` is returned, then either the lint does not
+                        // exist in the tool or the code was not compiled with the tool and
+                        // therefore the lint was never added to the `LintStore`. To detect
+                        // this is the responsibility of the lint tool.
+                    }
+
+                    &CheckLintNameResult::NoTool => {
+                        sess.dcx().emit_err(UnknownToolInScopedLint {
+                            span: tool_ident.map(|ident| ident.span),
+                            tool_name: tool_name.unwrap(),
+                            lint_name: pprust::path_to_string(&meta_item.path),
+                            is_nightly_build: sess.is_nightly_build().then_some(()),
+                        });
+                        continue;
+                    }
+
+                    _ 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_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_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint);
+                    }
+
+                    CheckLintNameResult::NoLint(suggestion) => {
+                        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_span_lint(UNKNOWN_LINTS, sp.into(), lint);
+                    }
+                }
+                // If this lint was renamed, apply the new lint instead of ignoring the attribute.
+                // This happens outside of the match because the new lint should be applied even if
+                // we don't warn about the name change.
+                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.
+                    let CheckLintNameResult::Ok(ids) =
+                        self.store.check_lint_name(&new_name, None, self.registered_tools)
+                    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 self.lint_added_lints && !is_crate_node {
+            for (id, &(level, ref src)) in self.current_specs().iter() {
+                if !id.lint.crate_level_only {
+                    continue;
+                }
+
+                let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src
+                else {
+                    continue;
+                };
+
+                self.emit_span_lint(
+                    UNUSED_ATTRIBUTES,
+                    lint_attr_span.into(),
+                    IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name },
+                );
+                // don't set a separate error for every lint in the group
+                break;
+            }
+        }
+    }
+
+    /// Checks if the lint is gated on a feature that is not enabled.
+    ///
+    /// Returns `true` if the lint's feature is enabled.
+    #[track_caller]
+    fn check_gated_lint(&self, lint_id: LintId, span: Span, lint_from_cli: bool) -> bool {
+        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()), |lint| {
+                lint.primary_message(fluent::lint_unknown_gated_lint);
+                lint.arg("name", lint_id.lint.name_lower());
+                lint.note(fluent::lint_note);
+                rustc_session::parse::add_feature_diagnostics_for_issue(
+                    lint,
+                    &self.sess,
+                    feature,
+                    GateIssue::Language,
+                    lint_from_cli,
+                    None,
+                );
+            });
+        }
+
+        false
+    }
+
+    /// Find the lint level for a lint.
+    pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource {
+        self.provider.get_lint_level(lint, self.sess)
+    }
+
+    /// Used to emit a lint-related diagnostic based on the current state of
+    /// this lint context.
+    ///
+    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
+    #[rustc_lint_diagnostics]
+    #[track_caller]
+    pub(crate) fn opt_span_lint(
+        &self,
+        lint: &'static Lint,
+        span: Option<MultiSpan>,
+        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
+    ) {
+        let (level, src) = self.lint_level(lint);
+        lint_level(self.sess, lint, level, src, span, decorate)
+    }
+
+    #[track_caller]
+    pub fn emit_span_lint(
+        &self,
+        lint: &'static Lint,
+        span: MultiSpan,
+        decorate: impl for<'a> LintDiagnostic<'a, ()>,
+    ) {
+        let (level, src) = self.lint_level(lint);
+        lint_level(self.sess, lint, level, src, Some(span), |lint| {
+            decorate.decorate_lint(lint);
+        });
+    }
+
+    #[track_caller]
+    pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) {
+        let (level, src) = self.lint_level(lint);
+        lint_level(self.sess, lint, level, src, None, |lint| {
+            decorate.decorate_lint(lint);
+        });
+    }
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers { shallow_lint_levels_on, lint_expectations, ..*providers };
+}
+
+pub fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) {
+    match lint_name.split_once("::") {
+        Some((tool_name, lint_name)) => {
+            let tool_name = Symbol::intern(tool_name);
+
+            (Some(tool_name), lint_name)
+        }
+        None => (None, lint_name),
+    }
+}
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
new file mode 100644
index 00000000000..8be8996e4c8
--- /dev/null
+++ b/compiler/rustc_lint/src/lib.rs
@@ -0,0 +1,607 @@
+//! Lints, aka compiler warnings.
+//!
+//! A 'lint' check is a kind of miscellaneous constraint that a user _might_
+//! want to enforce, but might reasonably want to permit as well, on a
+//! module-by-module basis. They contrast with static constraints enforced by
+//! other phases of the compiler, which are generally required to hold in order
+//! to compile the program at all.
+//!
+//! Most lints can be written as [LintPass] instances. These run after
+//! all other analyses. The `LintPass`es built into rustc are defined
+//! within [rustc_session::lint::builtin],
+//! which has further comments on how to add such a lint.
+//! rustc can also load external lint plugins, as is done for Clippy.
+//!
+//! Some of rustc's lints are defined elsewhere in the compiler and work by
+//! calling `add_lint()` on the overall `Session` object. This works when
+//! it happens before the main lint pass, which emits the lints stored by
+//! `add_lint()`. To emit lints after the main lint pass (from codegen, for
+//! example) requires more effort. See `emit_lint` and `GatherNodeLevels`
+//! in `context.rs`.
+//!
+//! Some code also exists in [rustc_session::lint], [rustc_middle::lint].
+//!
+//! ## Note
+//!
+//! This API is completely unstable and subject to change.
+
+// tidy-alphabetical-start
+#![allow(internal_features)]
+#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![doc(rust_logo)]
+#![feature(array_windows)]
+#![feature(box_patterns)]
+#![feature(control_flow_enum)]
+#![feature(extract_if)]
+#![feature(if_let_guard)]
+#![feature(iter_order_by)]
+#![feature(let_chains)]
+#![feature(rustc_attrs)]
+#![feature(rustdoc_internals)]
+#![feature(trait_upcasting)]
+// tidy-alphabetical-end
+
+mod async_closures;
+mod async_fn_in_trait;
+pub mod builtin;
+mod context;
+mod deref_into_dyn_supertrait;
+mod drop_forget_useless;
+mod early;
+mod enum_intrinsics_non_enums;
+mod errors;
+mod expect;
+mod for_loops_over_fallibles;
+mod foreign_modules;
+pub mod hidden_unicode_codepoints;
+mod impl_trait_overcaptures;
+mod internal;
+mod invalid_from_utf8;
+mod late;
+mod let_underscore;
+mod levels;
+mod lints;
+mod macro_expr_fragment_specifier_2024_migration;
+mod map_unit_fn;
+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;
+mod pass_by_value;
+mod passes;
+mod ptr_nulls;
+mod redundant_semicolon;
+mod reference_casting;
+mod shadowed_into_iter;
+mod traits;
+mod types;
+mod unit_bindings;
+mod unused;
+
+pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER};
+
+use rustc_hir::def_id::LocalModDefId;
+use rustc_middle::query::Providers;
+use rustc_middle::ty::TyCtxt;
+
+use async_closures::AsyncClosureUsage;
+use async_fn_in_trait::AsyncFnInTrait;
+use builtin::*;
+use deref_into_dyn_supertrait::*;
+use drop_forget_useless::*;
+use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
+use for_loops_over_fallibles::*;
+use hidden_unicode_codepoints::*;
+use impl_trait_overcaptures::ImplTraitOvercaptures;
+use internal::*;
+use invalid_from_utf8::*;
+use let_underscore::*;
+use macro_expr_fragment_specifier_2024_migration::*;
+use map_unit_fn::*;
+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::*;
+use pass_by_value::*;
+use ptr_nulls::*;
+use redundant_semicolon::*;
+use reference_casting::*;
+use shadowed_into_iter::ShadowedIntoIter;
+use traits::*;
+use types::*;
+use unit_bindings::*;
+use unused::*;
+
+/// Useful for other parts of the compiler / Clippy.
+pub use builtin::{MissingDoc, SoftLints};
+pub use context::{CheckLintNameResult, FindLintError, LintStore};
+pub use context::{EarlyContext, LateContext, LintContext};
+pub use early::{check_ast_node, EarlyCheckNode};
+pub use late::{check_crate, late_lint_mod, unerased_lint_store};
+pub use passes::{EarlyLintPass, LateLintPass};
+pub use rustc_session::lint::Level::{self, *};
+pub use rustc_session::lint::{BufferedEarlyLint, FutureIncompatibleInfo, Lint, LintId};
+pub use rustc_session::lint::{LintPass, LintVec};
+
+rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
+
+pub fn provide(providers: &mut Providers) {
+    levels::provide(providers);
+    expect::provide(providers);
+    foreign_modules::provide(providers);
+    *providers = Providers { lint_mod, ..*providers };
+}
+
+fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
+    late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new());
+}
+
+early_lint_methods!(
+    declare_combined_early_lint_pass,
+    [
+        pub BuiltinCombinedPreExpansionLintPass,
+        [
+            KeywordIdents: KeywordIdents,
+        ]
+    ]
+);
+
+early_lint_methods!(
+    declare_combined_early_lint_pass,
+    [
+        pub BuiltinCombinedEarlyLintPass,
+        [
+            UnusedParens: UnusedParens::new(),
+            UnusedBraces: UnusedBraces,
+            UnusedImportBraces: UnusedImportBraces,
+            UnsafeCode: UnsafeCode,
+            SpecialModuleName: SpecialModuleName,
+            AnonymousParameters: AnonymousParameters,
+            EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
+            NonCamelCaseTypes: NonCamelCaseTypes,
+            DeprecatedAttr: DeprecatedAttr::new(),
+            WhileTrue: WhileTrue,
+            NonAsciiIdents: NonAsciiIdents,
+            HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
+            IncompleteInternalFeatures: IncompleteInternalFeatures,
+            RedundantSemicolons: RedundantSemicolons,
+            UnusedDocComment: UnusedDocComment,
+            Expr2024: Expr2024,
+        ]
+    ]
+);
+
+late_lint_methods!(
+    declare_combined_late_lint_pass,
+    [
+        BuiltinCombinedModuleLateLintPass,
+        [
+            ForLoopsOverFallibles: ForLoopsOverFallibles,
+            DerefIntoDynSupertrait: DerefIntoDynSupertrait,
+            DropForgetUseless: DropForgetUseless,
+            HardwiredLints: HardwiredLints,
+            ImproperCTypesDeclarations: ImproperCTypesDeclarations,
+            ImproperCTypesDefinitions: ImproperCTypesDefinitions,
+            InvalidFromUtf8: InvalidFromUtf8,
+            VariantSizeDifferences: VariantSizeDifferences,
+            PathStatements: PathStatements,
+            LetUnderscore: LetUnderscore,
+            InvalidReferenceCasting: InvalidReferenceCasting,
+            // Depends on referenced function signatures in expressions
+            UnusedResults: UnusedResults,
+            UnitBindings: UnitBindings,
+            NonUpperCaseGlobals: NonUpperCaseGlobals,
+            NonShorthandFieldPatterns: NonShorthandFieldPatterns,
+            UnusedAllocation: UnusedAllocation,
+            // Depends on types used in type definitions
+            MissingCopyImplementations: MissingCopyImplementations,
+            // Depends on referenced function signatures in expressions
+            PtrNullChecks: PtrNullChecks,
+            MutableTransmutes: MutableTransmutes,
+            TypeAliasBounds: TypeAliasBounds,
+            TrivialConstraints: TrivialConstraints,
+            TypeLimits: TypeLimits::new(),
+            NonSnakeCase: NonSnakeCase,
+            InvalidNoMangleItems: InvalidNoMangleItems,
+            // Depends on effective visibilities
+            UnreachablePub: UnreachablePub,
+            ExplicitOutlivesRequirements: ExplicitOutlivesRequirements,
+            InvalidValue: InvalidValue,
+            DerefNullPtr: DerefNullPtr,
+            UnstableFeatures: UnstableFeatures,
+            UngatedAsyncFnTrackCaller: UngatedAsyncFnTrackCaller,
+            ShadowedIntoIter: ShadowedIntoIter,
+            DropTraitConstraints: DropTraitConstraints,
+            TemporaryCStringAsPtr: TemporaryCStringAsPtr,
+            NonPanicFmt: NonPanicFmt,
+            NoopMethodCall: NoopMethodCall,
+            EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
+            InvalidAtomicOrdering: InvalidAtomicOrdering,
+            AsmLabels: AsmLabels,
+            OpaqueHiddenInferredBound: OpaqueHiddenInferredBound,
+            MultipleSupertraitUpcastable: MultipleSupertraitUpcastable,
+            MapUnitFn: MapUnitFn,
+            MissingDebugImplementations: MissingDebugImplementations,
+            MissingDoc: MissingDoc,
+            AsyncClosureUsage: AsyncClosureUsage,
+            AsyncFnInTrait: AsyncFnInTrait,
+            NonLocalDefinitions: NonLocalDefinitions::default(),
+            ImplTraitOvercaptures: ImplTraitOvercaptures,
+        ]
+    ]
+);
+
+pub fn new_lint_store(internal_lints: bool) -> LintStore {
+    let mut lint_store = LintStore::new();
+
+    register_builtins(&mut lint_store);
+    if internal_lints {
+        register_internals(&mut lint_store);
+    }
+
+    lint_store
+}
+
+/// Tell the `LintStore` about all the built-in lints (the ones
+/// defined in this crate and the ones defined in
+/// `rustc_session::lint::builtin`).
+fn register_builtins(store: &mut LintStore) {
+    macro_rules! add_lint_group {
+        ($name:expr, $($lint:ident),*) => (
+            store.register_group(false, $name, None, vec![$(LintId::of($lint)),*]);
+        )
+    }
+
+    store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints());
+    store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints());
+    store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints());
+    store.register_lints(&foreign_modules::get_lints());
+
+    add_lint_group!(
+        "nonstandard_style",
+        NON_CAMEL_CASE_TYPES,
+        NON_SNAKE_CASE,
+        NON_UPPER_CASE_GLOBALS
+    );
+
+    add_lint_group!(
+        "unused",
+        UNUSED_IMPORTS,
+        UNUSED_VARIABLES,
+        UNUSED_ASSIGNMENTS,
+        DEAD_CODE,
+        UNUSED_MUT,
+        UNREACHABLE_CODE,
+        UNREACHABLE_PATTERNS,
+        UNUSED_MUST_USE,
+        UNUSED_UNSAFE,
+        PATH_STATEMENTS,
+        UNUSED_ATTRIBUTES,
+        UNUSED_MACROS,
+        UNUSED_MACRO_RULES,
+        UNUSED_ALLOCATION,
+        UNUSED_DOC_COMMENTS,
+        UNUSED_EXTERN_CRATES,
+        UNUSED_FEATURES,
+        UNUSED_LABELS,
+        UNUSED_PARENS,
+        UNUSED_BRACES,
+        REDUNDANT_SEMICOLONS,
+        MAP_UNIT_FN
+    );
+
+    add_lint_group!("let_underscore", LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK);
+
+    add_lint_group!(
+        "rust_2018_idioms",
+        BARE_TRAIT_OBJECTS,
+        UNUSED_EXTERN_CRATES,
+        ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
+        ELIDED_LIFETIMES_IN_PATHS,
+        EXPLICIT_OUTLIVES_REQUIREMENTS // FIXME(#52665, #47816) not always applicable and not all
+                                       // macros are ready for this yet.
+                                       // UNREACHABLE_PUB,
+
+                                       // FIXME macro crates are not up for this yet, too much
+                                       // breakage is seen if we try to encourage this lint.
+                                       // MACRO_USE_EXTERN_CRATE
+    );
+
+    add_lint_group!("keyword_idents", KEYWORD_IDENTS_2018, KEYWORD_IDENTS_2024);
+
+    add_lint_group!(
+        "refining_impl_trait",
+        REFINING_IMPL_TRAIT_REACHABLE,
+        REFINING_IMPL_TRAIT_INTERNAL
+    );
+
+    // Register renamed and removed lints.
+    store.register_renamed("single_use_lifetime", "single_use_lifetimes");
+    store.register_renamed("elided_lifetime_in_path", "elided_lifetimes_in_paths");
+    store.register_renamed("bare_trait_object", "bare_trait_objects");
+    store.register_renamed("unstable_name_collision", "unstable_name_collisions");
+    store.register_renamed("unused_doc_comment", "unused_doc_comments");
+    store.register_renamed("async_idents", "keyword_idents_2018");
+    store.register_renamed("exceeding_bitshifts", "arithmetic_overflow");
+    store.register_renamed("redundant_semicolon", "redundant_semicolons");
+    store.register_renamed("overlapping_patterns", "overlapping_range_endpoints");
+    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
+    // `register_removed` explicitly.
+    const RUSTDOC_LINTS: &[&str] = &[
+        "broken_intra_doc_links",
+        "private_intra_doc_links",
+        "missing_crate_level_docs",
+        "missing_doc_code_examples",
+        "private_doc_tests",
+        "invalid_codeblock_attributes",
+        "invalid_html_tags",
+        "non_autolinks",
+    ];
+    for rustdoc_lint in RUSTDOC_LINTS {
+        store.register_ignored(rustdoc_lint);
+    }
+    store.register_removed(
+        "intra_doc_link_resolution_failure",
+        "use `rustdoc::broken_intra_doc_links` instead",
+    );
+    store.register_removed("rustdoc", "use `rustdoc::all` instead");
+
+    store.register_removed("unknown_features", "replaced by an error");
+    store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate");
+    store.register_removed("negate_unsigned", "cast a signed value instead");
+    store.register_removed("raw_pointer_derive", "using derive with raw pointers is ok");
+    // Register lint group aliases.
+    store.register_group_alias("nonstandard_style", "bad_style");
+    // This was renamed to `raw_pointer_derive`, which was then removed,
+    // so it is also considered removed.
+    store.register_removed("raw_pointer_deriving", "using derive with raw pointers is ok");
+    store.register_removed("drop_with_repr_extern", "drop flags have been removed");
+    store.register_removed("fat_ptr_transmutes", "was accidentally removed back in 2014");
+    store.register_removed("deprecated_attr", "use `deprecated` instead");
+    store.register_removed(
+        "transmute_from_fn_item_types",
+        "always cast functions before transmuting them",
+    );
+    store.register_removed(
+        "hr_lifetime_in_assoc_type",
+        "converted into hard error, see issue #33685 \
+         <https://github.com/rust-lang/rust/issues/33685> for more information",
+    );
+    store.register_removed(
+        "inaccessible_extern_crate",
+        "converted into hard error, see issue #36886 \
+         <https://github.com/rust-lang/rust/issues/36886> for more information",
+    );
+    store.register_removed(
+        "super_or_self_in_global_path",
+        "converted into hard error, see issue #36888 \
+         <https://github.com/rust-lang/rust/issues/36888> for more information",
+    );
+    store.register_removed(
+        "overlapping_inherent_impls",
+        "converted into hard error, see issue #36889 \
+         <https://github.com/rust-lang/rust/issues/36889> for more information",
+    );
+    store.register_removed(
+        "illegal_floating_point_constant_pattern",
+        "converted into hard error, see issue #36890 \
+         <https://github.com/rust-lang/rust/issues/36890> for more information",
+    );
+    store.register_removed(
+        "illegal_struct_or_enum_constant_pattern",
+        "converted into hard error, see issue #36891 \
+         <https://github.com/rust-lang/rust/issues/36891> for more information",
+    );
+    store.register_removed(
+        "lifetime_underscore",
+        "converted into hard error, see issue #36892 \
+         <https://github.com/rust-lang/rust/issues/36892> for more information",
+    );
+    store.register_removed(
+        "extra_requirement_in_impl",
+        "converted into hard error, see issue #37166 \
+         <https://github.com/rust-lang/rust/issues/37166> for more information",
+    );
+    store.register_removed(
+        "legacy_imports",
+        "converted into hard error, see issue #38260 \
+         <https://github.com/rust-lang/rust/issues/38260> for more information",
+    );
+    store.register_removed(
+        "coerce_never",
+        "converted into hard error, see issue #48950 \
+         <https://github.com/rust-lang/rust/issues/48950> for more information",
+    );
+    store.register_removed(
+        "resolve_trait_on_defaulted_unit",
+        "converted into hard error, see issue #48950 \
+         <https://github.com/rust-lang/rust/issues/48950> for more information",
+    );
+    store.register_removed(
+        "private_no_mangle_fns",
+        "no longer a warning, `#[no_mangle]` functions always exported",
+    );
+    store.register_removed(
+        "private_no_mangle_statics",
+        "no longer a warning, `#[no_mangle]` statics always exported",
+    );
+    store.register_removed("bad_repr", "replaced with a generic attribute input check");
+    store.register_removed(
+        "duplicate_matcher_binding_name",
+        "converted into hard error, see issue #57742 \
+         <https://github.com/rust-lang/rust/issues/57742> for more information",
+    );
+    store.register_removed(
+        "incoherent_fundamental_impls",
+        "converted into hard error, see issue #46205 \
+         <https://github.com/rust-lang/rust/issues/46205> for more information",
+    );
+    store.register_removed(
+        "legacy_constructor_visibility",
+        "converted into hard error, see issue #39207 \
+         <https://github.com/rust-lang/rust/issues/39207> for more information",
+    );
+    store.register_removed(
+        "legacy_directory_ownership",
+        "converted into hard error, see issue #37872 \
+         <https://github.com/rust-lang/rust/issues/37872> for more information",
+    );
+    store.register_removed(
+        "safe_extern_statics",
+        "converted into hard error, see issue #36247 \
+         <https://github.com/rust-lang/rust/issues/36247> for more information",
+    );
+    store.register_removed(
+        "parenthesized_params_in_types_and_modules",
+        "converted into hard error, see issue #42238 \
+         <https://github.com/rust-lang/rust/issues/42238> for more information",
+    );
+    store.register_removed(
+        "duplicate_macro_exports",
+        "converted into hard error, see issue #35896 \
+         <https://github.com/rust-lang/rust/issues/35896> for more information",
+    );
+    store.register_removed(
+        "nested_impl_trait",
+        "converted into hard error, see issue #59014 \
+         <https://github.com/rust-lang/rust/issues/59014> for more information",
+    );
+    store.register_removed("plugin_as_library", "plugins have been deprecated and retired");
+    store.register_removed(
+        "unsupported_naked_functions",
+        "converted into hard error, see RFC 2972 \
+         <https://github.com/rust-lang/rfcs/blob/master/text/2972-constrained-naked.md> for more information",
+    );
+    store.register_removed(
+        "mutable_borrow_reservation_conflict",
+        "now allowed, see issue #59159 \
+         <https://github.com/rust-lang/rust/issues/59159> for more information",
+    );
+    store.register_removed(
+        "const_err",
+        "converted into hard error, see issue #71800 \
+         <https://github.com/rust-lang/rust/issues/71800> for more information",
+    );
+    store.register_removed(
+        "safe_packed_borrows",
+        "converted into hard error, see issue #82523 \
+         <https://github.com/rust-lang/rust/issues/82523> for more information",
+    );
+    store.register_removed(
+        "unaligned_references",
+        "converted into hard error, see issue #82523 \
+         <https://github.com/rust-lang/rust/issues/82523> for more information",
+    );
+    store.register_removed(
+        "private_in_public",
+        "replaced with another group of lints, see RFC \
+         <https://rust-lang.github.io/rfcs/2145-type-privacy.html> for more information",
+    );
+    store.register_removed(
+        "invalid_alignment",
+        "converted into hard error, see PR #104616 \
+         <https://github.com/rust-lang/rust/pull/104616> for more information",
+    );
+    store.register_removed(
+        "implied_bounds_entailment",
+        "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",
+    );
+    store.register_removed(
+        "indirect_structural_match",
+        "converted into hard error, see RFC #3535 \
+         <https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for more information",
+    );
+    store.register_removed(
+        "pointer_structural_match",
+        "converted into hard error, see RFC #3535 \
+         <https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for more information",
+    );
+    store.register_removed(
+        "box_pointers",
+        "it does not detect other kinds of allocations, and existed only for historical reasons",
+    );
+}
+
+fn register_internals(store: &mut LintStore) {
+    store.register_lints(&LintPassImpl::get_lints());
+    store.register_early_pass(|| Box::new(LintPassImpl));
+    store.register_lints(&DefaultHashTypes::get_lints());
+    store.register_late_mod_pass(|_| Box::new(DefaultHashTypes));
+    store.register_lints(&QueryStability::get_lints());
+    store.register_late_mod_pass(|_| Box::new(QueryStability));
+    store.register_lints(&ExistingDocKeyword::get_lints());
+    store.register_late_mod_pass(|_| Box::new(ExistingDocKeyword));
+    store.register_lints(&TyTyKind::get_lints());
+    store.register_late_mod_pass(|_| Box::new(TyTyKind));
+    store.register_lints(&Diagnostics::get_lints());
+    store.register_late_mod_pass(|_| Box::new(Diagnostics));
+    store.register_lints(&BadOptAccess::get_lints());
+    store.register_late_mod_pass(|_| Box::new(BadOptAccess));
+    store.register_lints(&PassByValue::get_lints());
+    store.register_late_mod_pass(|_| Box::new(PassByValue));
+    store.register_lints(&SpanUseEqCtxt::get_lints());
+    store.register_late_mod_pass(|_| Box::new(SpanUseEqCtxt));
+    // FIXME(davidtwco): deliberately do not include `UNTRANSLATABLE_DIAGNOSTIC` and
+    // `DIAGNOSTIC_OUTSIDE_OF_IMPL` here because `-Wrustc::internal` is provided to every crate and
+    // these lints will trigger all of the time - change this once migration to diagnostic structs
+    // and translation is completed
+    store.register_group(
+        false,
+        "rustc::internal",
+        None,
+        vec![
+            LintId::of(DEFAULT_HASH_TYPES),
+            LintId::of(POTENTIAL_QUERY_INSTABILITY),
+            LintId::of(USAGE_OF_TY_TYKIND),
+            LintId::of(PASS_BY_VALUE),
+            LintId::of(LINT_PASS_IMPL_WITHOUT_MACRO),
+            LintId::of(USAGE_OF_QUALIFIED_TY),
+            LintId::of(EXISTING_DOC_KEYWORD),
+            LintId::of(BAD_OPT_ACCESS),
+            LintId::of(SPAN_USE_EQ_CTXT),
+        ],
+    );
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
new file mode 100644
index 00000000000..308bb73f4ce
--- /dev/null
+++ b/compiler/rustc_lint/src/lints.rs
@@ -0,0 +1,2945 @@
+#![allow(rustc::diagnostic_outside_of_impl)]
+#![allow(rustc::untranslatable_diagnostic)]
+use std::num::NonZero;
+
+use crate::errors::RequestedLevel;
+use crate::fluent_generated as fluent;
+use rustc_errors::{
+    codes::*, Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString,
+    ElidedLifetimeInPathSubdiag, EmissionGuarantee, LintDiagnostic, MultiSpan, SubdiagMessageOp,
+    Subdiagnostic, SuggestionStyle,
+};
+use rustc_hir::{def::Namespace, def_id::DefId};
+use rustc_macros::{LintDiagnostic, Subdiagnostic};
+use rustc_middle::ty::{
+    inhabitedness::InhabitedPredicate, Clause, PolyExistentialTraitRef, Ty, TyCtxt,
+};
+use rustc_session::{lint::AmbiguityErrorDiag, Session};
+use rustc_span::{
+    edition::Edition,
+    sym,
+    symbol::{Ident, MacroRulesNormalizedIdent},
+    Span, Symbol,
+};
+
+use crate::{
+    builtin::InitError, builtin::TypeAliasBounds, errors::OverruledAttributeSub, LateContext,
+};
+
+// array_into_iter.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_shadowed_into_iter)]
+pub struct ShadowedIntoIterDiag {
+    pub target: &'static str,
+    pub edition: &'static str,
+    #[suggestion(lint_use_iter_suggestion, code = "iter", applicability = "machine-applicable")]
+    pub suggestion: Span,
+    #[subdiagnostic]
+    pub sub: Option<ShadowedIntoIterDiagSub>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum ShadowedIntoIterDiagSub {
+    #[suggestion(lint_remove_into_iter_suggestion, code = "", applicability = "maybe-incorrect")]
+    RemoveIntoIter {
+        #[primary_span]
+        span: Span,
+    },
+    #[multipart_suggestion(
+        lint_use_explicit_into_iter_suggestion,
+        applicability = "maybe-incorrect"
+    )]
+    UseExplicitIntoIter {
+        #[suggestion_part(code = "IntoIterator::into_iter(")]
+        start_span: Span,
+        #[suggestion_part(code = ")")]
+        end_span: Span,
+    },
+}
+
+// builtin.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_while_true)]
+pub struct BuiltinWhileTrue {
+    #[suggestion(style = "short", code = "{replace}", applicability = "machine-applicable")]
+    pub suggestion: Span,
+    pub replace: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_non_shorthand_field_patterns)]
+pub struct BuiltinNonShorthandFieldPatterns {
+    pub ident: Ident,
+    #[suggestion(code = "{prefix}{ident}", applicability = "machine-applicable")]
+    pub suggestion: Span,
+    pub prefix: &'static str,
+}
+
+#[derive(LintDiagnostic)]
+pub enum BuiltinUnsafe {
+    #[diag(lint_builtin_allow_internal_unsafe)]
+    AllowInternalUnsafe,
+    #[diag(lint_builtin_unsafe_block)]
+    UnsafeBlock,
+    #[diag(lint_builtin_unsafe_extern_block)]
+    UnsafeExternBlock,
+    #[diag(lint_builtin_unsafe_trait)]
+    UnsafeTrait,
+    #[diag(lint_builtin_unsafe_impl)]
+    UnsafeImpl,
+    #[diag(lint_builtin_no_mangle_fn)]
+    #[note(lint_builtin_overridden_symbol_name)]
+    NoMangleFn,
+    #[diag(lint_builtin_export_name_fn)]
+    #[note(lint_builtin_overridden_symbol_name)]
+    ExportNameFn,
+    #[diag(lint_builtin_link_section_fn)]
+    #[note(lint_builtin_overridden_symbol_section)]
+    LinkSectionFn,
+    #[diag(lint_builtin_no_mangle_static)]
+    #[note(lint_builtin_overridden_symbol_name)]
+    NoMangleStatic,
+    #[diag(lint_builtin_export_name_static)]
+    #[note(lint_builtin_overridden_symbol_name)]
+    ExportNameStatic,
+    #[diag(lint_builtin_link_section_static)]
+    #[note(lint_builtin_overridden_symbol_section)]
+    LinkSectionStatic,
+    #[diag(lint_builtin_no_mangle_method)]
+    #[note(lint_builtin_overridden_symbol_name)]
+    NoMangleMethod,
+    #[diag(lint_builtin_export_name_method)]
+    #[note(lint_builtin_overridden_symbol_name)]
+    ExportNameMethod,
+    #[diag(lint_builtin_decl_unsafe_fn)]
+    DeclUnsafeFn,
+    #[diag(lint_builtin_decl_unsafe_method)]
+    DeclUnsafeMethod,
+    #[diag(lint_builtin_impl_unsafe_method)]
+    ImplUnsafeMethod,
+    #[diag(lint_builtin_global_asm)]
+    #[note(lint_builtin_global_macro_unsafety)]
+    GlobalAsm,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_missing_doc)]
+pub struct BuiltinMissingDoc<'a> {
+    pub article: &'a str,
+    pub desc: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_missing_copy_impl)]
+pub struct BuiltinMissingCopyImpl;
+
+pub struct BuiltinMissingDebugImpl<'a> {
+    pub tcx: TyCtxt<'a>,
+    pub def_id: DefId,
+}
+
+// Needed for def_path_str
+impl<'a> LintDiagnostic<'a, ()> for BuiltinMissingDebugImpl<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_builtin_missing_debug_impl);
+        diag.arg("debug", self.tcx.def_path_str(self.def_id));
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_anonymous_params)]
+pub struct BuiltinAnonymousParams<'a> {
+    #[suggestion(code = "_: {ty_snip}")]
+    pub suggestion: (Span, Applicability),
+    pub ty_snip: &'a str,
+}
+
+// FIXME(davidtwco) translatable deprecated attr
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_deprecated_attr_link)]
+pub struct BuiltinDeprecatedAttrLink<'a> {
+    pub name: Symbol,
+    pub reason: &'a str,
+    pub link: &'a str,
+    #[subdiagnostic]
+    pub suggestion: BuiltinDeprecatedAttrLinkSuggestion<'a>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum BuiltinDeprecatedAttrLinkSuggestion<'a> {
+    #[suggestion(lint_msg_suggestion, code = "", applicability = "machine-applicable")]
+    Msg {
+        #[primary_span]
+        suggestion: Span,
+        msg: &'a str,
+    },
+    #[suggestion(lint_default_suggestion, code = "", applicability = "machine-applicable")]
+    Default {
+        #[primary_span]
+        suggestion: Span,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_deprecated_attr_used)]
+pub struct BuiltinDeprecatedAttrUsed {
+    pub name: String,
+    #[suggestion(
+        lint_builtin_deprecated_attr_default_suggestion,
+        style = "short",
+        code = "",
+        applicability = "machine-applicable"
+    )]
+    pub suggestion: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_unused_doc_comment)]
+pub struct BuiltinUnusedDocComment<'a> {
+    pub kind: &'a str,
+    #[label]
+    pub label: Span,
+    #[subdiagnostic]
+    pub sub: BuiltinUnusedDocCommentSub,
+}
+
+#[derive(Subdiagnostic)]
+pub enum BuiltinUnusedDocCommentSub {
+    #[help(lint_plain_help)]
+    PlainHelp,
+    #[help(lint_block_help)]
+    BlockHelp,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_no_mangle_generic)]
+pub struct BuiltinNoMangleGeneric {
+    // Use of `#[no_mangle]` suggests FFI intent; correct
+    // fix may be to monomorphize source by hand
+    #[suggestion(style = "short", code = "", applicability = "maybe-incorrect")]
+    pub suggestion: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_const_no_mangle)]
+pub struct BuiltinConstNoMangle {
+    #[suggestion(code = "pub static", applicability = "machine-applicable")]
+    pub suggestion: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_mutable_transmutes)]
+pub struct BuiltinMutablesTransmutes;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_unstable_features)]
+pub struct BuiltinUnstableFeatures;
+
+// lint_ungated_async_fn_track_caller
+pub struct BuiltinUngatedAsyncFnTrackCaller<'a> {
+    pub label: Span,
+    pub session: &'a Session,
+}
+
+impl<'a> LintDiagnostic<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_ungated_async_fn_track_caller);
+        diag.span_label(self.label, fluent::lint_label);
+        rustc_session::parse::add_feature_diagnostics(
+            diag,
+            self.session,
+            sym::async_fn_track_caller,
+        );
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_unreachable_pub)]
+pub struct BuiltinUnreachablePub<'a> {
+    pub what: &'a str,
+    #[suggestion(code = "pub(crate)")]
+    pub suggestion: (Span, Applicability),
+    #[help]
+    pub help: Option<()>,
+}
+
+pub struct SuggestChangingAssocTypes<'a, 'b> {
+    pub ty: &'a rustc_hir::Ty<'b>,
+}
+
+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, 'b, G: EmissionGuarantee> {
+            err: &'a mut Diag<'b, G>,
+        }
+        impl<'a, 'b, G: EmissionGuarantee> Visitor<'_> for WalkAssocTypes<'a, 'b, G> {
+            fn visit_qpath(
+                &mut self,
+                qpath: &rustc_hir::QPath<'_>,
+                id: rustc_hir::HirId,
+                span: Span,
+            ) {
+                if TypeAliasBounds::is_type_variable_assoc(qpath) {
+                    self.err.span_help(span, fluent::lint_builtin_type_alias_bounds_help);
+                }
+                intravisit::walk_qpath(self, qpath, id)
+            }
+        }
+
+        // Let's go for a walk!
+        let mut visitor = WalkAssocTypes { err: diag };
+        visitor.visit_ty(self.ty);
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_type_alias_where_clause)]
+pub struct BuiltinTypeAliasWhereClause<'a, 'b> {
+    #[suggestion(code = "", applicability = "machine-applicable")]
+    pub suggestion: Span,
+    #[subdiagnostic]
+    pub sub: Option<SuggestChangingAssocTypes<'a, 'b>>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_type_alias_generic_bounds)]
+pub struct BuiltinTypeAliasGenericBounds<'a, 'b> {
+    #[subdiagnostic]
+    pub suggestion: BuiltinTypeAliasGenericBoundsSuggestion,
+    #[subdiagnostic]
+    pub sub: Option<SuggestChangingAssocTypes<'a, 'b>>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_expr_fragment_specifier_2024_migration)]
+pub struct MacroExprFragment2024 {
+    #[suggestion(code = "expr_2021", applicability = "machine-applicable")]
+    pub suggestion: Span,
+}
+
+pub struct BuiltinTypeAliasGenericBoundsSuggestion {
+    pub suggestions: Vec<(Span, String)>,
+}
+
+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,
+            Applicability::MachineApplicable,
+        );
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_trivial_bounds)]
+pub struct BuiltinTrivialBounds<'a> {
+    pub predicate_kind_name: &'a str,
+    pub predicate: Clause<'a>,
+}
+
+#[derive(LintDiagnostic)]
+pub enum BuiltinEllipsisInclusiveRangePatternsLint {
+    #[diag(lint_builtin_ellipsis_inclusive_range_patterns)]
+    Parenthesise {
+        #[suggestion(code = "{replace}", applicability = "machine-applicable")]
+        suggestion: Span,
+        replace: String,
+    },
+    #[diag(lint_builtin_ellipsis_inclusive_range_patterns)]
+    NonParenthesise {
+        #[suggestion(style = "short", code = "..=", applicability = "machine-applicable")]
+        suggestion: Span,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_keyword_idents)]
+pub struct BuiltinKeywordIdents {
+    pub kw: Ident,
+    pub next: Edition,
+    #[suggestion(code = "r#{kw}", applicability = "machine-applicable")]
+    pub suggestion: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_explicit_outlives)]
+pub struct BuiltinExplicitOutlives {
+    pub count: usize,
+    #[subdiagnostic]
+    pub suggestion: BuiltinExplicitOutlivesSuggestion,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion)]
+pub struct BuiltinExplicitOutlivesSuggestion {
+    #[suggestion_part(code = "")]
+    pub spans: Vec<Span>,
+    #[applicability]
+    pub applicability: Applicability,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_incomplete_features)]
+pub struct BuiltinIncompleteFeatures {
+    pub name: Symbol,
+    #[subdiagnostic]
+    pub note: Option<BuiltinFeatureIssueNote>,
+    #[subdiagnostic]
+    pub help: Option<BuiltinIncompleteFeaturesHelp>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_internal_features)]
+#[note]
+pub struct BuiltinInternalFeatures {
+    pub name: Symbol,
+}
+
+#[derive(Subdiagnostic)]
+#[help(lint_help)]
+pub struct BuiltinIncompleteFeaturesHelp;
+
+#[derive(Subdiagnostic)]
+#[note(lint_note)]
+pub struct BuiltinFeatureIssueNote {
+    pub n: NonZero<u32>,
+}
+
+pub struct BuiltinUnpermittedTypeInit<'a> {
+    pub msg: DiagMessage,
+    pub ty: Ty<'a>,
+    pub label: Span,
+    pub sub: BuiltinUnpermittedTypeInitSub,
+    pub tcx: TyCtxt<'a>,
+}
+
+impl<'a> LintDiagnostic<'a, ()> for BuiltinUnpermittedTypeInit<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(self.msg);
+        diag.arg("ty", self.ty);
+        diag.span_label(self.label, fluent::lint_builtin_unpermitted_type_init_label);
+        if let InhabitedPredicate::True = self.ty.inhabited_predicate(self.tcx) {
+            // Only suggest late `MaybeUninit::assume_init` initialization if the type is inhabited.
+            diag.span_label(
+                self.label,
+                fluent::lint_builtin_unpermitted_type_init_label_suggestion,
+            );
+        }
+        self.sub.add_to_diag(diag);
+    }
+}
+
+// FIXME(davidtwco): make translatable
+pub struct BuiltinUnpermittedTypeInitSub {
+    pub err: InitError,
+}
+
+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 {
+                diag.span_note(span, err.message);
+            } else {
+                diag.note(err.message);
+            }
+            if let Some(e) = err.nested {
+                err = *e;
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+pub enum BuiltinClashingExtern<'a> {
+    #[diag(lint_builtin_clashing_extern_same_name)]
+    SameName {
+        this: Symbol,
+        orig: Symbol,
+        #[label(lint_previous_decl_label)]
+        previous_decl_label: Span,
+        #[label(lint_mismatch_label)]
+        mismatch_label: Span,
+        #[subdiagnostic]
+        sub: BuiltinClashingExternSub<'a>,
+    },
+    #[diag(lint_builtin_clashing_extern_diff_name)]
+    DiffName {
+        this: Symbol,
+        orig: Symbol,
+        #[label(lint_previous_decl_label)]
+        previous_decl_label: Span,
+        #[label(lint_mismatch_label)]
+        mismatch_label: Span,
+        #[subdiagnostic]
+        sub: BuiltinClashingExternSub<'a>,
+    },
+}
+
+// FIXME(davidtwco): translatable expected/found
+pub struct BuiltinClashingExternSub<'a> {
+    pub tcx: TyCtxt<'a>,
+    pub expected: Ty<'a>,
+    pub found: Ty<'a>,
+}
+
+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 = DiagStyledString::new();
+        found_str.push(self.found.fn_sig(self.tcx).to_string(), true);
+        diag.note_expected_found(&"", expected_str, &"", found_str);
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_builtin_deref_nullptr)]
+pub struct BuiltinDerefNullptr {
+    #[label]
+    pub label: Span,
+}
+
+// FIXME: migrate fluent::lint::builtin_asm_labels
+
+#[derive(LintDiagnostic)]
+pub enum BuiltinSpecialModuleNameUsed {
+    #[diag(lint_builtin_special_module_name_used_lib)]
+    #[note]
+    #[help]
+    Lib,
+    #[diag(lint_builtin_special_module_name_used_main)]
+    #[note]
+    Main,
+}
+
+// deref_into_dyn_supertrait.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_supertrait_as_deref_target)]
+pub struct SupertraitAsDerefTarget<'a> {
+    pub self_ty: Ty<'a>,
+    pub supertrait_principal: PolyExistentialTraitRef<'a>,
+    pub target_principal: PolyExistentialTraitRef<'a>,
+    #[label]
+    pub label: Span,
+    #[subdiagnostic]
+    pub label2: Option<SupertraitAsDerefTargetLabel>,
+}
+
+#[derive(Subdiagnostic)]
+#[label(lint_label2)]
+pub struct SupertraitAsDerefTargetLabel {
+    #[primary_span]
+    pub label: Span,
+}
+
+// enum_intrinsics_non_enums.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_enum_intrinsics_mem_discriminant)]
+pub struct EnumIntrinsicsMemDiscriminate<'a> {
+    pub ty_param: Ty<'a>,
+    #[note]
+    pub note: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_enum_intrinsics_mem_variant)]
+#[note]
+pub struct EnumIntrinsicsMemVariant<'a> {
+    pub ty_param: Ty<'a>,
+}
+
+// expect.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_expectation)]
+pub struct Expectation {
+    #[subdiagnostic]
+    pub rationale: Option<ExpectationNote>,
+    #[note]
+    pub note: Option<()>,
+}
+
+#[derive(Subdiagnostic)]
+#[note(lint_rationale)]
+pub struct ExpectationNote {
+    pub rationale: Symbol,
+}
+
+// ptr_nulls.rs
+#[derive(LintDiagnostic)]
+pub enum PtrNullChecksDiag<'a> {
+    #[diag(lint_ptr_null_checks_fn_ptr)]
+    #[help(lint_help)]
+    FnPtr {
+        orig_ty: Ty<'a>,
+        #[label]
+        label: Span,
+    },
+    #[diag(lint_ptr_null_checks_ref)]
+    Ref {
+        orig_ty: Ty<'a>,
+        #[label]
+        label: Span,
+    },
+    #[diag(lint_ptr_null_checks_fn_ret)]
+    FnRet { fn_name: Ident },
+}
+
+// for_loops_over_fallibles.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_for_loops_over_fallibles)]
+pub struct ForLoopsOverFalliblesDiag<'a> {
+    pub article: &'static str,
+    pub ref_prefix: &'static str,
+    pub ty: &'static str,
+    #[subdiagnostic]
+    pub sub: ForLoopsOverFalliblesLoopSub<'a>,
+    #[subdiagnostic]
+    pub question_mark: Option<ForLoopsOverFalliblesQuestionMark>,
+    #[subdiagnostic]
+    pub suggestion: ForLoopsOverFalliblesSuggestion<'a>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum ForLoopsOverFalliblesLoopSub<'a> {
+    #[suggestion(lint_remove_next, code = ".by_ref()", applicability = "maybe-incorrect")]
+    RemoveNext {
+        #[primary_span]
+        suggestion: Span,
+        recv_snip: String,
+    },
+    #[multipart_suggestion(lint_use_while_let, applicability = "maybe-incorrect")]
+    UseWhileLet {
+        #[suggestion_part(code = "while let {var}(")]
+        start_span: Span,
+        #[suggestion_part(code = ") = ")]
+        end_span: Span,
+        var: &'a str,
+    },
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(lint_use_question_mark, code = "?", applicability = "maybe-incorrect")]
+pub struct ForLoopsOverFalliblesQuestionMark {
+    #[primary_span]
+    pub suggestion: Span,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")]
+pub struct ForLoopsOverFalliblesSuggestion<'a> {
+    pub var: &'a str,
+    #[suggestion_part(code = "if let {var}(")]
+    pub start_span: Span,
+    #[suggestion_part(code = ") = ")]
+    pub end_span: Span,
+}
+
+#[derive(Subdiagnostic)]
+pub enum UseLetUnderscoreIgnoreSuggestion {
+    #[note(lint_use_let_underscore_ignore_suggestion)]
+    Note,
+    #[multipart_suggestion(
+        lint_use_let_underscore_ignore_suggestion,
+        style = "verbose",
+        applicability = "maybe-incorrect"
+    )]
+    Suggestion {
+        #[suggestion_part(code = "let _ = ")]
+        start_span: Span,
+        #[suggestion_part(code = "")]
+        end_span: Span,
+    },
+}
+
+// drop_forget_useless.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_dropping_references)]
+pub struct DropRefDiag<'a> {
+    pub arg_ty: Ty<'a>,
+    #[label]
+    pub label: Span,
+    #[subdiagnostic]
+    pub sugg: UseLetUnderscoreIgnoreSuggestion,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_dropping_copy_types)]
+pub struct DropCopyDiag<'a> {
+    pub arg_ty: Ty<'a>,
+    #[label]
+    pub label: Span,
+    #[subdiagnostic]
+    pub sugg: UseLetUnderscoreIgnoreSuggestion,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_forgetting_references)]
+pub struct ForgetRefDiag<'a> {
+    pub arg_ty: Ty<'a>,
+    #[label]
+    pub label: Span,
+    #[subdiagnostic]
+    pub sugg: UseLetUnderscoreIgnoreSuggestion,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_forgetting_copy_types)]
+pub struct ForgetCopyDiag<'a> {
+    pub arg_ty: Ty<'a>,
+    #[label]
+    pub label: Span,
+    #[subdiagnostic]
+    pub sugg: UseLetUnderscoreIgnoreSuggestion,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_undropped_manually_drops)]
+pub struct UndroppedManuallyDropsDiag<'a> {
+    pub arg_ty: Ty<'a>,
+    #[label]
+    pub label: Span,
+    #[subdiagnostic]
+    pub suggestion: UndroppedManuallyDropsSuggestion,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
+pub struct UndroppedManuallyDropsSuggestion {
+    #[suggestion_part(code = "std::mem::ManuallyDrop::into_inner(")]
+    pub start_span: Span,
+    #[suggestion_part(code = ")")]
+    pub end_span: Span,
+}
+
+// invalid_from_utf8.rs
+#[derive(LintDiagnostic)]
+pub enum InvalidFromUtf8Diag {
+    #[diag(lint_invalid_from_utf8_unchecked)]
+    Unchecked {
+        method: String,
+        valid_up_to: usize,
+        #[label]
+        label: Span,
+    },
+    #[diag(lint_invalid_from_utf8_checked)]
+    Checked {
+        method: String,
+        valid_up_to: usize,
+        #[label]
+        label: Span,
+    },
+}
+
+// reference_casting.rs
+#[derive(LintDiagnostic)]
+pub enum InvalidReferenceCastingDiag<'tcx> {
+    #[diag(lint_invalid_reference_casting_borrow_as_mut)]
+    #[note(lint_invalid_reference_casting_note_book)]
+    BorrowAsMut {
+        #[label]
+        orig_cast: Option<Span>,
+        #[note(lint_invalid_reference_casting_note_ty_has_interior_mutability)]
+        ty_has_interior_mutability: Option<()>,
+    },
+    #[diag(lint_invalid_reference_casting_assign_to_ref)]
+    #[note(lint_invalid_reference_casting_note_book)]
+    AssignToRef {
+        #[label]
+        orig_cast: Option<Span>,
+        #[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
+#[derive(LintDiagnostic)]
+#[diag(lint_hidden_unicode_codepoints)]
+#[note]
+pub struct HiddenUnicodeCodepointsDiag<'a> {
+    pub label: &'a str,
+    pub count: usize,
+    #[label]
+    pub span_label: Span,
+    #[subdiagnostic]
+    pub labels: Option<HiddenUnicodeCodepointsDiagLabels>,
+    #[subdiagnostic]
+    pub sub: HiddenUnicodeCodepointsDiagSub,
+}
+
+pub struct HiddenUnicodeCodepointsDiagLabels {
+    pub spans: Vec<(char, Span)>,
+}
+
+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:?}"));
+        }
+    }
+}
+
+pub enum HiddenUnicodeCodepointsDiagSub {
+    Escape { spans: Vec<(char, Span)> },
+    NoEscape { spans: Vec<(char, Span)> },
+}
+
+// Used because of multiple multipart_suggestion and note
+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(
+                    fluent::lint_suggestion_remove,
+                    spans.iter().map(|(_, span)| (*span, "".to_string())).collect(),
+                    Applicability::MachineApplicable,
+                    SuggestionStyle::HideCodeAlways,
+                );
+                diag.multipart_suggestion(
+                    fluent::lint_suggestion_escape,
+                    spans
+                        .into_iter()
+                        .map(|(c, span)| {
+                            let c = format!("{c:?}");
+                            (span, c[1..c.len() - 1].to_string())
+                        })
+                        .collect(),
+                    Applicability::MachineApplicable,
+                );
+            }
+            HiddenUnicodeCodepointsDiagSub::NoEscape { spans } => {
+                // 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.arg(
+                    "escaped",
+                    spans
+                        .into_iter()
+                        .map(|(c, _)| format!("{c:?}"))
+                        .collect::<Vec<String>>()
+                        .join(", "),
+                );
+                diag.note(fluent::lint_suggestion_remove);
+                diag.note(fluent::lint_no_suggestion_note_escape);
+            }
+        }
+    }
+}
+
+// map_unit_fn.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_map_unit_fn)]
+#[note]
+pub struct MappingToUnit {
+    #[label(lint_function_label)]
+    pub function_label: Span,
+    #[label(lint_argument_label)]
+    pub argument_label: Span,
+    #[label(lint_map_label)]
+    pub map_label: Span,
+    #[suggestion(style = "verbose", code = "{replace}", applicability = "maybe-incorrect")]
+    pub suggestion: Span,
+    pub replace: String,
+}
+
+// internal.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_default_hash_types)]
+#[note]
+pub struct DefaultHashTypesDiag<'a> {
+    pub preferred: &'a str,
+    pub used: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_query_instability)]
+#[note]
+pub struct QueryInstability {
+    pub query: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_span_use_eq_ctxt)]
+pub struct SpanUseEqCtxtDiag;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_tykind_kind)]
+pub struct TykindKind {
+    #[suggestion(code = "ty", applicability = "maybe-incorrect")]
+    pub suggestion: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_tykind)]
+#[help]
+pub struct TykindDiag;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_ty_qualified)]
+pub struct TyQualified {
+    pub ty: String,
+    #[suggestion(code = "{ty}", applicability = "maybe-incorrect")]
+    pub suggestion: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_lintpass_by_hand)]
+#[help]
+pub struct LintPassByHand;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_non_existent_doc_keyword)]
+#[help]
+pub struct NonExistentDocKeyword {
+    pub keyword: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_diag_out_of_impl)]
+pub struct DiagOutOfImpl;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_untranslatable_diag)]
+pub struct UntranslatableDiag;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_bad_opt_access)]
+pub struct BadOptAccessDiag<'a> {
+    pub msg: &'a str,
+}
+
+// let_underscore.rs
+#[derive(LintDiagnostic)]
+pub enum NonBindingLet {
+    #[diag(lint_non_binding_let_on_sync_lock)]
+    SyncLock {
+        #[subdiagnostic]
+        sub: NonBindingLetSub,
+    },
+    #[diag(lint_non_binding_let_on_drop_type)]
+    DropType {
+        #[subdiagnostic]
+        sub: NonBindingLetSub,
+    },
+}
+
+pub struct NonBindingLetSub {
+    pub suggestion: Span,
+    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);
+        }
+    }
+}
+
+// levels.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_overruled_attribute)]
+pub struct OverruledAttributeLint<'a> {
+    #[label]
+    pub overruled: Span,
+    pub lint_level: &'a str,
+    pub lint_source: Symbol,
+    #[subdiagnostic]
+    pub sub: OverruledAttributeSub,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_deprecated_lint_name)]
+pub struct DeprecatedLintName<'a> {
+    pub name: String,
+    #[suggestion(code = "{replace}", applicability = "machine-applicable")]
+    pub suggestion: Span,
+    pub replace: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_deprecated_lint_name)]
+#[help]
+pub struct DeprecatedLintNameFromCommandLine<'a> {
+    pub name: String,
+    pub replace: &'a str,
+    #[subdiagnostic]
+    pub requested_level: RequestedLevel<'a>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_renamed_lint)]
+pub struct RenamedLint<'a> {
+    pub name: &'a str,
+    #[subdiagnostic]
+    pub suggestion: RenamedLintSuggestion<'a>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum RenamedLintSuggestion<'a> {
+    #[suggestion(lint_suggestion, code = "{replace}", applicability = "machine-applicable")]
+    WithSpan {
+        #[primary_span]
+        suggestion: Span,
+        replace: &'a str,
+    },
+    #[help(lint_help)]
+    WithoutSpan { replace: &'a str },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_renamed_lint)]
+pub struct RenamedLintFromCommandLine<'a> {
+    pub name: &'a str,
+    #[subdiagnostic]
+    pub suggestion: RenamedLintSuggestion<'a>,
+    #[subdiagnostic]
+    pub requested_level: RequestedLevel<'a>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_removed_lint)]
+pub struct RemovedLint<'a> {
+    pub name: &'a str,
+    pub reason: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_removed_lint)]
+pub struct RemovedLintFromCommandLine<'a> {
+    pub name: &'a str,
+    pub reason: &'a str,
+    #[subdiagnostic]
+    pub requested_level: RequestedLevel<'a>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unknown_lint)]
+pub struct UnknownLint {
+    pub name: String,
+    #[subdiagnostic]
+    pub suggestion: Option<UnknownLintSuggestion>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum UnknownLintSuggestion {
+    #[suggestion(lint_suggestion, code = "{replace}", applicability = "maybe-incorrect")]
+    WithSpan {
+        #[primary_span]
+        suggestion: Span,
+        replace: Symbol,
+        from_rustc: bool,
+    },
+    #[help(lint_help)]
+    WithoutSpan { replace: Symbol, from_rustc: bool },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unknown_lint, code = E0602)]
+pub struct UnknownLintFromCommandLine<'a> {
+    pub name: String,
+    #[subdiagnostic]
+    pub suggestion: Option<UnknownLintSuggestion>,
+    #[subdiagnostic]
+    pub requested_level: RequestedLevel<'a>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_ignored_unless_crate_specified)]
+pub struct IgnoredUnlessCrateSpecified<'a> {
+    pub level: &'a str,
+    pub name: Symbol,
+}
+
+// methods.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_cstring_ptr)]
+#[note]
+#[help]
+pub struct CStringPtr {
+    #[label(lint_as_ptr_label)]
+    pub as_ptr: Span,
+    #[label(lint_unwrap_label)]
+    pub unwrap: Span,
+}
+
+// multiple_supertrait_upcastable.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_multiple_supertrait_upcastable)]
+pub struct MultipleSupertraitUpcastable {
+    pub ident: Ident,
+}
+
+// non_ascii_idents.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_identifier_non_ascii_char)]
+pub struct IdentifierNonAsciiChar;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_identifier_uncommon_codepoints)]
+#[note]
+pub struct IdentifierUncommonCodepoints {
+    pub codepoints: Vec<char>,
+    pub codepoints_len: usize,
+    pub identifier_type: &'static str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_confusable_identifier_pair)]
+pub struct ConfusableIdentifierPair {
+    pub existing_sym: Symbol,
+    pub sym: Symbol,
+    #[label(lint_other_use)]
+    pub label: Span,
+    #[label(lint_current_use)]
+    pub main_label: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_mixed_script_confusables)]
+#[note(lint_includes_note)]
+#[note]
+pub struct MixedScriptConfusables {
+    pub set: String,
+    pub includes: String,
+}
+
+// non_fmt_panic.rs
+pub struct NonFmtPanicUnused {
+    pub count: usize,
+    pub suggestion: Option<Span>,
+}
+
+// Used because of two suggestions based on one Option<Span>
+impl<'a> LintDiagnostic<'a, ()> for NonFmtPanicUnused {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_non_fmt_panic_unused);
+        diag.arg("count", self.count);
+        diag.note(fluent::lint_note);
+        if let Some(span) = self.suggestion {
+            diag.span_suggestion(
+                span.shrink_to_hi(),
+                fluent::lint_add_args_suggestion,
+                ", ...",
+                Applicability::HasPlaceholders,
+            );
+            diag.span_suggestion(
+                span.shrink_to_lo(),
+                fluent::lint_add_fmt_suggestion,
+                "\"{}\", ",
+                Applicability::MachineApplicable,
+            );
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_non_fmt_panic_braces)]
+#[note]
+pub struct NonFmtPanicBraces {
+    pub count: usize,
+    #[suggestion(code = "\"{{}}\", ", applicability = "machine-applicable")]
+    pub suggestion: Option<Span>,
+}
+
+// nonstandard_style.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_non_camel_case_type)]
+pub struct NonCamelCaseType<'a> {
+    pub sort: &'a str,
+    pub name: &'a str,
+    #[subdiagnostic]
+    pub sub: NonCamelCaseTypeSub,
+}
+
+#[derive(Subdiagnostic)]
+pub enum NonCamelCaseTypeSub {
+    #[label(lint_label)]
+    Label {
+        #[primary_span]
+        span: Span,
+    },
+    #[suggestion(lint_suggestion, code = "{replace}", applicability = "maybe-incorrect")]
+    Suggestion {
+        #[primary_span]
+        span: Span,
+        replace: String,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_non_snake_case)]
+pub struct NonSnakeCaseDiag<'a> {
+    pub sort: &'a str,
+    pub name: &'a str,
+    pub sc: String,
+    #[subdiagnostic]
+    pub sub: NonSnakeCaseDiagSub,
+}
+
+pub enum NonSnakeCaseDiagSub {
+    Label { span: Span },
+    Help,
+    RenameOrConvertSuggestion { span: Span, suggestion: Ident },
+    ConvertSuggestion { span: Span, suggestion: String },
+    SuggestionAndNote { span: Span },
+}
+
+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);
+            }
+            NonSnakeCaseDiagSub::Help => {
+                diag.help(fluent::lint_help);
+            }
+            NonSnakeCaseDiagSub::ConvertSuggestion { span, suggestion } => {
+                diag.span_suggestion(
+                    span,
+                    fluent::lint_convert_suggestion,
+                    suggestion,
+                    Applicability::MaybeIncorrect,
+                );
+            }
+            NonSnakeCaseDiagSub::RenameOrConvertSuggestion { span, suggestion } => {
+                diag.span_suggestion(
+                    span,
+                    fluent::lint_rename_or_convert_suggestion,
+                    suggestion,
+                    Applicability::MaybeIncorrect,
+                );
+            }
+            NonSnakeCaseDiagSub::SuggestionAndNote { span } => {
+                diag.note(fluent::lint_cannot_convert_note);
+                diag.span_suggestion(
+                    span,
+                    fluent::lint_rename_suggestion,
+                    "",
+                    Applicability::MaybeIncorrect,
+                );
+            }
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_non_upper_case_global)]
+pub struct NonUpperCaseGlobal<'a> {
+    pub sort: &'a str,
+    pub name: &'a str,
+    #[subdiagnostic]
+    pub sub: NonUpperCaseGlobalSub,
+}
+
+#[derive(Subdiagnostic)]
+pub enum NonUpperCaseGlobalSub {
+    #[label(lint_label)]
+    Label {
+        #[primary_span]
+        span: Span,
+    },
+    #[suggestion(lint_suggestion, code = "{replace}", applicability = "maybe-incorrect")]
+    Suggestion {
+        #[primary_span]
+        span: Span,
+        replace: String,
+    },
+}
+
+// noop_method_call.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_noop_method_call)]
+#[note]
+pub struct NoopMethodCallDiag<'a> {
+    pub method: Symbol,
+    pub orig_ty: Ty<'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)]
+#[diag(lint_suspicious_double_ref_deref)]
+pub struct SuspiciousDoubleRefDerefDiag<'a> {
+    pub ty: Ty<'a>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_suspicious_double_ref_clone)]
+pub struct SuspiciousDoubleRefCloneDiag<'a> {
+    pub ty: Ty<'a>,
+}
+
+// non_local_defs.rs
+pub enum NonLocalDefinitionsDiag {
+    Impl {
+        depth: u32,
+        body_kind_descr: &'static str,
+        body_name: String,
+        cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>,
+        const_anon: Option<Option<Span>>,
+        move_to: Option<(Span, Vec<Span>)>,
+        doctest: bool,
+        may_remove: Option<(Span, String)>,
+        has_trait: bool,
+        self_ty_str: String,
+        of_trait_str: Option<String>,
+        macro_to_change: Option<(String, &'static str)>,
+    },
+    MacroRules {
+        depth: u32,
+        body_kind_descr: &'static str,
+        body_name: String,
+        doctest: bool,
+        cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>,
+    },
+}
+
+impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        match self {
+            NonLocalDefinitionsDiag::Impl {
+                depth,
+                body_kind_descr,
+                body_name,
+                cargo_update,
+                const_anon,
+                move_to,
+                doctest,
+                may_remove,
+                has_trait,
+                self_ty_str,
+                of_trait_str,
+                macro_to_change,
+            } => {
+                diag.primary_message(fluent::lint_non_local_definitions_impl);
+                diag.arg("depth", depth);
+                diag.arg("body_kind_descr", body_kind_descr);
+                diag.arg("body_name", body_name);
+                diag.arg("self_ty_str", self_ty_str);
+                if let Some(of_trait_str) = of_trait_str {
+                    diag.arg("of_trait_str", of_trait_str);
+                }
+
+                if let Some((macro_to_change, macro_kind)) = macro_to_change {
+                    diag.arg("macro_to_change", macro_to_change);
+                    diag.arg("macro_kind", macro_kind);
+                    diag.note(fluent::lint_macro_to_change);
+                }
+                if let Some(cargo_update) = cargo_update {
+                    diag.subdiagnostic(cargo_update);
+                }
+
+                if has_trait {
+                    diag.note(fluent::lint_bounds);
+                    diag.note(fluent::lint_with_trait);
+                } else {
+                    diag.note(fluent::lint_without_trait);
+                }
+
+                if let Some((move_help, may_move)) = move_to {
+                    let mut ms = MultiSpan::from_span(move_help);
+                    for sp in may_move {
+                        ms.push_span_label(sp, fluent::lint_non_local_definitions_may_move);
+                    }
+                    diag.span_help(ms, fluent::lint_non_local_definitions_impl_move_help);
+                }
+                if doctest {
+                    diag.help(fluent::lint_doctest);
+                }
+
+                if let Some((span, part)) = may_remove {
+                    diag.arg("may_remove_part", part);
+                    diag.span_suggestion(
+                        span,
+                        fluent::lint_remove_help,
+                        "",
+                        Applicability::MaybeIncorrect,
+                    );
+                }
+
+                if let Some(const_anon) = const_anon {
+                    diag.note(fluent::lint_exception);
+                    if let Some(const_anon) = const_anon {
+                        diag.span_suggestion(
+                            const_anon,
+                            fluent::lint_const_anon,
+                            "_",
+                            Applicability::MachineApplicable,
+                        );
+                    }
+                }
+
+                diag.note(fluent::lint_non_local_definitions_deprecation);
+            }
+            NonLocalDefinitionsDiag::MacroRules {
+                depth,
+                body_kind_descr,
+                body_name,
+                doctest,
+                cargo_update,
+            } => {
+                diag.primary_message(fluent::lint_non_local_definitions_macro_rules);
+                diag.arg("depth", depth);
+                diag.arg("body_kind_descr", body_kind_descr);
+                diag.arg("body_name", body_name);
+
+                if doctest {
+                    diag.help(fluent::lint_help_doctest);
+                } else {
+                    diag.help(fluent::lint_help);
+                }
+
+                diag.note(fluent::lint_non_local);
+                diag.note(fluent::lint_non_local_definitions_deprecation);
+
+                if let Some(cargo_update) = cargo_update {
+                    diag.subdiagnostic(cargo_update);
+                }
+            }
+        }
+    }
+}
+
+#[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)]
+pub struct PassByValueDiag {
+    pub ty: String,
+    #[suggestion(code = "{ty}", applicability = "maybe-incorrect")]
+    pub suggestion: Span,
+}
+
+// redundant_semicolon.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_redundant_semicolons)]
+pub struct RedundantSemicolonsDiag {
+    pub multiple: bool,
+    #[suggestion(code = "", applicability = "maybe-incorrect")]
+    pub suggestion: Span,
+}
+
+// traits.rs
+pub struct DropTraitConstraintsDiag<'a> {
+    pub predicate: Clause<'a>,
+    pub tcx: TyCtxt<'a>,
+    pub def_id: DefId,
+}
+
+// Needed for def_path_str
+impl<'a> LintDiagnostic<'a, ()> for DropTraitConstraintsDiag<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_drop_trait_constraints);
+        diag.arg("predicate", self.predicate);
+        diag.arg("needs_drop", self.tcx.def_path_str(self.def_id));
+    }
+}
+
+pub struct DropGlue<'a> {
+    pub tcx: TyCtxt<'a>,
+    pub def_id: DefId,
+}
+
+// Needed for def_path_str
+impl<'a> LintDiagnostic<'a, ()> for DropGlue<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_drop_glue);
+        diag.arg("needs_drop", self.tcx.def_path_str(self.def_id));
+    }
+}
+
+// types.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_range_endpoint_out_of_range)]
+pub struct RangeEndpointOutOfRange<'a> {
+    pub ty: &'a str,
+    #[subdiagnostic]
+    pub sub: UseInclusiveRange<'a>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum UseInclusiveRange<'a> {
+    #[suggestion(
+        lint_range_use_inclusive_range,
+        code = "{start}..={literal}{suffix}",
+        applicability = "machine-applicable"
+    )]
+    WithoutParen {
+        #[primary_span]
+        sugg: Span,
+        start: String,
+        literal: u128,
+        suffix: &'a str,
+    },
+    #[multipart_suggestion(lint_range_use_inclusive_range, applicability = "machine-applicable")]
+    WithParen {
+        #[suggestion_part(code = "=")]
+        eq_sugg: Span,
+        #[suggestion_part(code = "{literal}{suffix}")]
+        lit_sugg: Span,
+        literal: u128,
+        suffix: &'a str,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_overflowing_bin_hex)]
+pub struct OverflowingBinHex<'a> {
+    pub ty: &'a str,
+    pub lit: String,
+    pub dec: u128,
+    pub actually: String,
+    #[subdiagnostic]
+    pub sign: OverflowingBinHexSign,
+    #[subdiagnostic]
+    pub sub: Option<OverflowingBinHexSub<'a>>,
+    #[subdiagnostic]
+    pub sign_bit_sub: Option<OverflowingBinHexSignBitSub<'a>>,
+}
+
+pub enum OverflowingBinHexSign {
+    Positive,
+    Negative,
+}
+
+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);
+            }
+            OverflowingBinHexSign::Negative => {
+                diag.note(fluent::lint_negative_note);
+                diag.note(fluent::lint_negative_becomes_note);
+            }
+        }
+    }
+}
+
+#[derive(Subdiagnostic)]
+pub enum OverflowingBinHexSub<'a> {
+    #[suggestion(
+        lint_suggestion,
+        code = "{sans_suffix}{suggestion_ty}",
+        applicability = "machine-applicable"
+    )]
+    Suggestion {
+        #[primary_span]
+        span: Span,
+        suggestion_ty: &'a str,
+        sans_suffix: &'a str,
+    },
+    #[help(lint_help)]
+    Help { suggestion_ty: &'a str },
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    lint_sign_bit_suggestion,
+    code = "{lit_no_suffix}{uint_ty} as {int_ty}",
+    applicability = "maybe-incorrect"
+)]
+pub struct OverflowingBinHexSignBitSub<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub lit_no_suffix: &'a str,
+    pub negative_val: String,
+    pub uint_ty: &'a str,
+    pub int_ty: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_overflowing_int)]
+#[note]
+pub struct OverflowingInt<'a> {
+    pub ty: &'a str,
+    pub lit: String,
+    pub min: i128,
+    pub max: u128,
+    #[subdiagnostic]
+    pub help: Option<OverflowingIntHelp<'a>>,
+}
+
+#[derive(Subdiagnostic)]
+#[help(lint_help)]
+pub struct OverflowingIntHelp<'a> {
+    pub suggestion_ty: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_only_cast_u8_to_char)]
+pub struct OnlyCastu8ToChar {
+    #[suggestion(code = "'\\u{{{literal:X}}}'", applicability = "machine-applicable")]
+    pub span: Span,
+    pub literal: u128,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_overflowing_uint)]
+#[note]
+pub struct OverflowingUInt<'a> {
+    pub ty: &'a str,
+    pub lit: String,
+    pub min: u128,
+    pub max: u128,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_overflowing_literal)]
+#[note]
+pub struct OverflowingLiteral<'a> {
+    pub ty: &'a str,
+    pub lit: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_comparisons)]
+pub struct UnusedComparisons;
+
+#[derive(LintDiagnostic)]
+pub enum InvalidNanComparisons {
+    #[diag(lint_invalid_nan_comparisons_eq_ne)]
+    EqNe {
+        #[subdiagnostic]
+        suggestion: Option<InvalidNanComparisonsSuggestion>,
+    },
+    #[diag(lint_invalid_nan_comparisons_lt_le_gt_ge)]
+    LtLeGtGe,
+}
+
+#[derive(Subdiagnostic)]
+pub enum InvalidNanComparisonsSuggestion {
+    #[multipart_suggestion(
+        lint_suggestion,
+        style = "verbose",
+        applicability = "machine-applicable"
+    )]
+    Spanful {
+        #[suggestion_part(code = "!")]
+        neg: Option<Span>,
+        #[suggestion_part(code = ".is_nan()")]
+        float: Span,
+        #[suggestion_part(code = "")]
+        nan_plus_binop: Span,
+    },
+    #[help(lint_suggestion)]
+    Spanless,
+}
+
+#[derive(LintDiagnostic)]
+pub enum AmbiguousWidePointerComparisons<'a> {
+    #[diag(lint_ambiguous_wide_pointer_comparisons)]
+    Spanful {
+        #[subdiagnostic]
+        addr_suggestion: AmbiguousWidePointerComparisonsAddrSuggestion<'a>,
+        #[subdiagnostic]
+        addr_metadata_suggestion: Option<AmbiguousWidePointerComparisonsAddrMetadataSuggestion<'a>>,
+    },
+    #[diag(lint_ambiguous_wide_pointer_comparisons)]
+    #[help(lint_addr_metadata_suggestion)]
+    #[help(lint_addr_suggestion)]
+    Spanless,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    lint_addr_metadata_suggestion,
+    style = "verbose",
+    // FIXME(#53934): make machine-applicable again
+    applicability = "maybe-incorrect"
+)]
+pub struct AmbiguousWidePointerComparisonsAddrMetadataSuggestion<'a> {
+    pub ne: &'a str,
+    pub deref_left: &'a str,
+    pub deref_right: &'a str,
+    pub l_modifiers: &'a str,
+    pub r_modifiers: &'a str,
+    #[suggestion_part(code = "{ne}std::ptr::eq({deref_left}")]
+    pub left: Span,
+    #[suggestion_part(code = "{l_modifiers}, {deref_right}")]
+    pub middle: Span,
+    #[suggestion_part(code = "{r_modifiers})")]
+    pub right: Span,
+}
+
+#[derive(Subdiagnostic)]
+pub enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> {
+    #[multipart_suggestion(
+        lint_addr_suggestion,
+        style = "verbose",
+        // FIXME(#53934): make machine-applicable again
+        applicability = "maybe-incorrect"
+    )]
+    AddrEq {
+        ne: &'a str,
+        deref_left: &'a str,
+        deref_right: &'a str,
+        l_modifiers: &'a str,
+        r_modifiers: &'a str,
+        #[suggestion_part(code = "{ne}std::ptr::addr_eq({deref_left}")]
+        left: Span,
+        #[suggestion_part(code = "{l_modifiers}, {deref_right}")]
+        middle: Span,
+        #[suggestion_part(code = "{r_modifiers})")]
+        right: Span,
+    },
+    #[multipart_suggestion(
+        lint_addr_suggestion,
+        style = "verbose",
+        // FIXME(#53934): make machine-applicable again
+        applicability = "maybe-incorrect"
+    )]
+    Cast {
+        deref_left: &'a str,
+        deref_right: &'a str,
+        paren_left: &'a str,
+        paren_right: &'a str,
+        l_modifiers: &'a str,
+        r_modifiers: &'a str,
+        #[suggestion_part(code = "({deref_left}")]
+        left_before: Option<Span>,
+        #[suggestion_part(code = "{l_modifiers}{paren_left}.cast::<()>()")]
+        left_after: Span,
+        #[suggestion_part(code = "({deref_right}")]
+        right_before: Option<Span>,
+        #[suggestion_part(code = "{r_modifiers}{paren_right}.cast::<()>()")]
+        right_after: Span,
+    },
+}
+
+pub struct ImproperCTypes<'a> {
+    pub ty: Ty<'a>,
+    pub desc: &'a str,
+    pub label: Span,
+    pub help: Option<DiagMessage>,
+    pub note: DiagMessage,
+    pub span_note: Option<Span>,
+}
+
+// Used because of the complexity of Option<DiagMessage>, DiagMessage, and Option<Span>
+impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_improper_ctypes);
+        diag.arg("ty", self.ty);
+        diag.arg("desc", self.desc);
+        diag.span_label(self.label, fluent::lint_label);
+        if let Some(help) = self.help {
+            diag.help(help);
+        }
+        diag.note(self.note);
+        if let Some(note) = self.span_note {
+            diag.span_note(note, fluent::lint_note);
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_variant_size_differences)]
+pub struct VariantSizeDifferencesDiag {
+    pub largest: u64,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_atomic_ordering_load)]
+#[help]
+pub struct AtomicOrderingLoad;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_atomic_ordering_store)]
+#[help]
+pub struct AtomicOrderingStore;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_atomic_ordering_fence)]
+#[help]
+pub struct AtomicOrderingFence;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_atomic_ordering_invalid)]
+#[help]
+pub struct InvalidAtomicOrderingDiag {
+    pub method: Symbol,
+    #[label]
+    pub fail_order_arg_span: Span,
+}
+
+// unused.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_op)]
+pub struct UnusedOp<'a> {
+    pub op: &'a str,
+    #[label]
+    pub label: Span,
+    #[subdiagnostic]
+    pub suggestion: UnusedOpSuggestion,
+}
+
+#[derive(Subdiagnostic)]
+pub enum UnusedOpSuggestion {
+    #[suggestion(
+        lint_suggestion,
+        style = "verbose",
+        code = "let _ = ",
+        applicability = "maybe-incorrect"
+    )]
+    NormalExpr {
+        #[primary_span]
+        span: Span,
+    },
+    #[multipart_suggestion(lint_suggestion, style = "verbose", applicability = "maybe-incorrect")]
+    BlockTailExpr {
+        #[suggestion_part(code = "let _ = ")]
+        before_span: Span,
+        #[suggestion_part(code = ";")]
+        after_span: Span,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_result)]
+pub struct UnusedResult<'a> {
+    pub ty: Ty<'a>,
+}
+
+// FIXME(davidtwco): this isn't properly translatable because of the
+// pre/post strings
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_closure)]
+#[note]
+pub struct UnusedClosure<'a> {
+    pub count: usize,
+    pub pre: &'a str,
+    pub post: &'a str,
+}
+
+// FIXME(davidtwco): this isn't properly translatable because of the
+// pre/post strings
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_coroutine)]
+#[note]
+pub struct UnusedCoroutine<'a> {
+    pub count: usize,
+    pub pre: &'a str,
+    pub post: &'a str,
+}
+
+// FIXME(davidtwco): this isn't properly translatable because of the pre/post
+// strings
+pub struct UnusedDef<'a, 'b> {
+    pub pre: &'a str,
+    pub post: &'a str,
+    pub cx: &'a LateContext<'b>,
+    pub def_id: DefId,
+    pub note: Option<Symbol>,
+    pub suggestion: Option<UnusedDefSuggestion>,
+}
+
+#[derive(Subdiagnostic)]
+
+pub enum UnusedDefSuggestion {
+    #[suggestion(
+        lint_suggestion,
+        style = "verbose",
+        code = "let _ = ",
+        applicability = "maybe-incorrect"
+    )]
+    NormalExpr {
+        #[primary_span]
+        span: Span,
+    },
+    #[multipart_suggestion(lint_suggestion, style = "verbose", applicability = "maybe-incorrect")]
+    BlockTailExpr {
+        #[suggestion_part(code = "let _ = ")]
+        before_span: Span,
+        #[suggestion_part(code = ";")]
+        after_span: Span,
+    },
+}
+
+// Needed because of def_path_str
+impl<'a> LintDiagnostic<'a, ()> for UnusedDef<'_, '_> {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_unused_def);
+        diag.arg("pre", self.pre);
+        diag.arg("post", self.post);
+        diag.arg("def", self.cx.tcx.def_path_str(self.def_id));
+        // check for #[must_use = "..."]
+        if let Some(note) = self.note {
+            diag.note(note.to_string());
+        }
+        if let Some(sugg) = self.suggestion {
+            diag.subdiagnostic(sugg);
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_path_statement_drop)]
+pub struct PathStatementDrop {
+    #[subdiagnostic]
+    pub sub: PathStatementDropSub,
+}
+
+#[derive(Subdiagnostic)]
+pub enum PathStatementDropSub {
+    #[suggestion(lint_suggestion, code = "drop({snippet});", applicability = "machine-applicable")]
+    Suggestion {
+        #[primary_span]
+        span: Span,
+        snippet: String,
+    },
+    #[help(lint_help)]
+    Help {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_path_statement_no_effect)]
+pub struct PathStatementNoEffect;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_delim)]
+pub struct UnusedDelim<'a> {
+    pub delim: &'static str,
+    pub item: &'a str,
+    #[subdiagnostic]
+    pub suggestion: Option<UnusedDelimSuggestion>,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
+pub struct UnusedDelimSuggestion {
+    #[suggestion_part(code = "{start_replace}")]
+    pub start_span: Span,
+    pub start_replace: &'static str,
+    #[suggestion_part(code = "{end_replace}")]
+    pub end_span: Span,
+    pub end_replace: &'static str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_import_braces)]
+pub struct UnusedImportBracesDiag {
+    pub node: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_allocation)]
+pub struct UnusedAllocationDiag;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_allocation_mut)]
+pub struct UnusedAllocationMutDiag;
+
+pub struct AsyncFnInTraitDiag {
+    pub sugg: Option<Vec<(Span, String)>>,
+}
+
+impl<'a> LintDiagnostic<'a, ()> for AsyncFnInTraitDiag {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(fluent::lint_async_fn_in_trait);
+        diag.note(fluent::lint_note);
+        if let Some(sugg) = self.sugg {
+            diag.multipart_suggestion(fluent::lint_suggestion, sugg, Applicability::MaybeIncorrect);
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unit_bindings)]
+pub struct UnitBindingsDiag {
+    #[label]
+    pub label: Span,
+}
+
+#[derive(LintDiagnostic)]
+pub enum InvalidAsmLabel {
+    #[diag(lint_invalid_asm_label_named)]
+    #[help]
+    #[note]
+    Named {
+        #[note(lint_invalid_asm_label_no_span)]
+        missing_precise_span: bool,
+    },
+    #[diag(lint_invalid_asm_label_format_arg)]
+    #[help]
+    #[note(lint_note1)]
+    #[note(lint_note2)]
+    FormatArg {
+        #[note(lint_invalid_asm_label_no_span)]
+        missing_precise_span: bool,
+    },
+    #[diag(lint_invalid_asm_label_binary)]
+    #[note]
+    Binary {
+        #[note(lint_invalid_asm_label_no_span)]
+        missing_precise_span: bool,
+        // hack to get a label on the whole span, must match the emitted span
+        #[label]
+        span: Span,
+    },
+}
+
+#[derive(Subdiagnostic)]
+pub enum UnexpectedCfgCargoHelp {
+    #[help(lint_unexpected_cfg_add_cargo_feature)]
+    #[help(lint_unexpected_cfg_add_cargo_toml_lint_cfg)]
+    LintCfg { cargo_toml_lint_cfg: String },
+    #[help(lint_unexpected_cfg_add_cargo_feature)]
+    #[help(lint_unexpected_cfg_add_cargo_toml_lint_cfg)]
+    #[help(lint_unexpected_cfg_add_build_rs_println)]
+    LintCfgAndBuildRs { cargo_toml_lint_cfg: String, build_rs_println: String },
+}
+
+impl UnexpectedCfgCargoHelp {
+    fn cargo_toml_lint_cfg(unescaped: &str) -> String {
+        format!(
+            "\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{unescaped}'] }}"
+        )
+    }
+
+    pub fn lint_cfg(unescaped: &str) -> Self {
+        UnexpectedCfgCargoHelp::LintCfg {
+            cargo_toml_lint_cfg: Self::cargo_toml_lint_cfg(unescaped),
+        }
+    }
+
+    pub fn lint_cfg_and_build_rs(unescaped: &str, escaped: &str) -> Self {
+        UnexpectedCfgCargoHelp::LintCfgAndBuildRs {
+            cargo_toml_lint_cfg: Self::cargo_toml_lint_cfg(unescaped),
+            build_rs_println: format!("println!(\"cargo::rustc-check-cfg={escaped}\");"),
+        }
+    }
+}
+
+#[derive(Subdiagnostic)]
+#[help(lint_unexpected_cfg_add_cmdline_arg)]
+pub struct UnexpectedCfgRustcHelp {
+    pub cmdline_arg: String,
+}
+
+impl UnexpectedCfgRustcHelp {
+    pub fn new(unescaped: &str) -> Self {
+        Self { cmdline_arg: format!("--check-cfg={unescaped}") }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unexpected_cfg_name)]
+pub struct UnexpectedCfgName {
+    #[subdiagnostic]
+    pub code_sugg: unexpected_cfg_name::CodeSuggestion,
+    #[subdiagnostic]
+    pub invocation_help: unexpected_cfg_name::InvocationHelp,
+
+    pub name: Symbol,
+}
+
+pub mod unexpected_cfg_name {
+    use rustc_errors::DiagSymbolList;
+    use rustc_macros::Subdiagnostic;
+    use rustc_span::{Span, Symbol};
+
+    #[derive(Subdiagnostic)]
+    pub enum CodeSuggestion {
+        #[help(lint_unexpected_cfg_define_features)]
+        DefineFeatures,
+        #[suggestion(
+            lint_unexpected_cfg_name_similar_name_value,
+            applicability = "maybe-incorrect",
+            code = "{code}"
+        )]
+        SimilarNameAndValue {
+            #[primary_span]
+            span: Span,
+            code: String,
+        },
+        #[suggestion(
+            lint_unexpected_cfg_name_similar_name_no_value,
+            applicability = "maybe-incorrect",
+            code = "{code}"
+        )]
+        SimilarNameNoValue {
+            #[primary_span]
+            span: Span,
+            code: String,
+        },
+        #[suggestion(
+            lint_unexpected_cfg_name_similar_name_different_values,
+            applicability = "maybe-incorrect",
+            code = "{code}"
+        )]
+        SimilarNameDifferentValues {
+            #[primary_span]
+            span: Span,
+            code: String,
+            #[subdiagnostic]
+            expected: Option<ExpectedValues>,
+        },
+        #[suggestion(
+            lint_unexpected_cfg_name_similar_name,
+            applicability = "maybe-incorrect",
+            code = "{code}"
+        )]
+        SimilarName {
+            #[primary_span]
+            span: Span,
+            code: String,
+            #[subdiagnostic]
+            expected: Option<ExpectedValues>,
+        },
+        SimilarValues {
+            #[subdiagnostic]
+            with_similar_values: Vec<FoundWithSimilarValue>,
+            #[subdiagnostic]
+            expected_names: Option<ExpectedNames>,
+        },
+    }
+
+    #[derive(Subdiagnostic)]
+    #[help(lint_unexpected_cfg_name_expected_values)]
+    pub struct ExpectedValues {
+        pub best_match: Symbol,
+        pub possibilities: DiagSymbolList,
+    }
+
+    #[derive(Subdiagnostic)]
+    #[suggestion(
+        lint_unexpected_cfg_name_with_similar_value,
+        applicability = "maybe-incorrect",
+        code = "{code}"
+    )]
+    pub struct FoundWithSimilarValue {
+        #[primary_span]
+        pub span: Span,
+        pub code: String,
+    }
+
+    #[derive(Subdiagnostic)]
+    #[help_once(lint_unexpected_cfg_name_expected_names)]
+    pub struct ExpectedNames {
+        pub possibilities: DiagSymbolList,
+        pub and_more: usize,
+    }
+
+    #[derive(Subdiagnostic)]
+    pub enum InvocationHelp {
+        #[note(lint_unexpected_cfg_doc_cargo)]
+        Cargo {
+            #[subdiagnostic]
+            sub: Option<super::UnexpectedCfgCargoHelp>,
+        },
+        #[note(lint_unexpected_cfg_doc_rustc)]
+        Rustc(#[subdiagnostic] super::UnexpectedCfgRustcHelp),
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unexpected_cfg_value)]
+pub struct UnexpectedCfgValue {
+    #[subdiagnostic]
+    pub code_sugg: unexpected_cfg_value::CodeSuggestion,
+    #[subdiagnostic]
+    pub invocation_help: unexpected_cfg_value::InvocationHelp,
+
+    pub has_value: bool,
+    pub value: String,
+}
+
+pub mod unexpected_cfg_value {
+    use rustc_errors::DiagSymbolList;
+    use rustc_macros::Subdiagnostic;
+    use rustc_span::{Span, Symbol};
+
+    #[derive(Subdiagnostic)]
+    pub enum CodeSuggestion {
+        ChangeValue {
+            #[subdiagnostic]
+            expected_values: ExpectedValues,
+            #[subdiagnostic]
+            suggestion: Option<ChangeValueSuggestion>,
+        },
+        #[note(lint_unexpected_cfg_value_no_expected_value)]
+        RemoveValue {
+            #[subdiagnostic]
+            suggestion: Option<RemoveValueSuggestion>,
+
+            name: Symbol,
+        },
+        #[note(lint_unexpected_cfg_value_no_expected_values)]
+        RemoveCondition {
+            #[subdiagnostic]
+            suggestion: RemoveConditionSuggestion,
+
+            name: Symbol,
+        },
+    }
+
+    #[derive(Subdiagnostic)]
+    pub enum ChangeValueSuggestion {
+        #[suggestion(
+            lint_unexpected_cfg_value_similar_name,
+            code = r#""{best_match}""#,
+            applicability = "maybe-incorrect"
+        )]
+        SimilarName {
+            #[primary_span]
+            span: Span,
+            best_match: Symbol,
+        },
+        #[suggestion(
+            lint_unexpected_cfg_value_specify_value,
+            code = r#" = "{first_possibility}""#,
+            applicability = "maybe-incorrect"
+        )]
+        SpecifyValue {
+            #[primary_span]
+            span: Span,
+            first_possibility: Symbol,
+        },
+    }
+
+    #[derive(Subdiagnostic)]
+    #[suggestion(
+        lint_unexpected_cfg_value_remove_value,
+        code = "",
+        applicability = "maybe-incorrect"
+    )]
+    pub struct RemoveValueSuggestion {
+        #[primary_span]
+        pub span: Span,
+    }
+
+    #[derive(Subdiagnostic)]
+    #[suggestion(
+        lint_unexpected_cfg_value_remove_condition,
+        code = "",
+        applicability = "maybe-incorrect"
+    )]
+    pub struct RemoveConditionSuggestion {
+        #[primary_span]
+        pub span: Span,
+    }
+
+    #[derive(Subdiagnostic)]
+    #[note(lint_unexpected_cfg_value_expected_values)]
+    pub struct ExpectedValues {
+        pub name: Symbol,
+        pub have_none_possibility: bool,
+        pub possibilities: DiagSymbolList,
+        pub and_more: usize,
+    }
+
+    #[derive(Subdiagnostic)]
+    pub enum InvocationHelp {
+        #[note(lint_unexpected_cfg_doc_cargo)]
+        Cargo(#[subdiagnostic] Option<CargoHelp>),
+        #[note(lint_unexpected_cfg_doc_rustc)]
+        Rustc(#[subdiagnostic] Option<super::UnexpectedCfgRustcHelp>),
+    }
+
+    #[derive(Subdiagnostic)]
+    pub enum CargoHelp {
+        #[help(lint_unexpected_cfg_value_add_feature)]
+        AddFeature {
+            value: Symbol,
+        },
+        #[help(lint_unexpected_cfg_define_features)]
+        DefineFeatures,
+        Other(#[subdiagnostic] super::UnexpectedCfgCargoHelp),
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_use_deprecated)]
+#[help]
+pub struct MacroUseDeprecated;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_macro_use)]
+pub struct UnusedMacroUse;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_private_extern_crate_reexport, code = E0365)]
+pub struct PrivateExternCrateReexport {
+    pub ident: Ident,
+    #[suggestion(code = "pub ", style = "verbose", applicability = "maybe-incorrect")]
+    pub sugg: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_label)]
+pub struct UnusedLabel;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_is_private)]
+pub struct MacroIsPrivate {
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_macro_definition)]
+pub struct UnusedMacroDefinition {
+    pub name: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_rule_never_used)]
+pub struct MacroRuleNeverUsed {
+    pub n: usize,
+    pub name: Symbol,
+}
+
+pub struct UnstableFeature {
+    pub msg: DiagMessage,
+}
+
+impl<'a> LintDiagnostic<'a, ()> for UnstableFeature {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
+        diag.primary_message(self.msg);
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_avoid_intel_syntax)]
+pub struct AvoidIntelSyntax;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_avoid_att_syntax)]
+pub struct AvoidAttSyntax;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_incomplete_include)]
+pub struct IncompleteInclude;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unnameable_test_items)]
+pub struct UnnameableTestItems;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_duplicate_macro_attribute)]
+pub struct DuplicateMacroAttribute;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_cfg_attr_no_attributes)]
+pub struct CfgAttrNoAttributes;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_crate_type_in_cfg_attr_deprecated)]
+pub struct CrateTypeInCfgAttr;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_crate_name_in_cfg_attr_deprecated)]
+pub struct CrateNameInCfgAttr;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_missing_fragment_specifier)]
+pub struct MissingFragmentSpecifier;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_metavariable_still_repeating)]
+pub struct MetaVariableStillRepeating {
+    pub name: MacroRulesNormalizedIdent,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_metavariable_wrong_operator)]
+pub struct MetaVariableWrongOperator;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_duplicate_matcher_binding)]
+pub struct DuplicateMatcherBinding;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unknown_macro_variable)]
+pub struct UnknownMacroVariable {
+    pub name: MacroRulesNormalizedIdent,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_crate_dependency)]
+#[help]
+pub struct UnusedCrateDependency {
+    pub extern_crate: Symbol,
+    pub local_crate: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_wasm_c_abi)]
+pub struct WasmCAbi;
+
+#[derive(LintDiagnostic)]
+#[diag(lint_ill_formed_attribute_input)]
+pub struct IllFormedAttributeInput {
+    pub num_suggestions: usize,
+    pub suggestions: DiagArgValue,
+}
+
+#[derive(LintDiagnostic)]
+pub enum InnerAttributeUnstable {
+    #[diag(lint_inner_macro_attribute_unstable)]
+    InnerMacroAttribute,
+    #[diag(lint_custom_inner_attribute_unstable)]
+    CustomInnerAttribute,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unknown_diagnostic_attribute)]
+pub struct UnknownDiagnosticAttribute {
+    #[subdiagnostic]
+    pub typo: Option<UnknownDiagnosticAttributeTypoSugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    lint_unknown_diagnostic_attribute_typo_sugg,
+    style = "verbose",
+    code = "{typo_name}",
+    applicability = "machine-applicable"
+)]
+pub struct UnknownDiagnosticAttributeTypoSugg {
+    #[primary_span]
+    pub span: Span,
+    pub typo_name: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unicode_text_flow)]
+#[note]
+pub struct UnicodeTextFlow {
+    #[label]
+    pub comment_span: Span,
+    #[subdiagnostic]
+    pub characters: Vec<UnicodeCharNoteSub>,
+    #[subdiagnostic]
+    pub suggestions: Option<UnicodeTextFlowSuggestion>,
+
+    pub num_codepoints: usize,
+}
+
+#[derive(Subdiagnostic)]
+#[label(lint_label_comment_char)]
+pub struct UnicodeCharNoteSub {
+    #[primary_span]
+    pub span: Span,
+    pub c_debug: String,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable", style = "hidden")]
+pub struct UnicodeTextFlowSuggestion {
+    #[suggestion_part(code = "")]
+    pub spans: Vec<Span>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_abs_path_with_module)]
+pub struct AbsPathWithModule {
+    #[subdiagnostic]
+    pub sugg: AbsPathWithModuleSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(lint_suggestion, code = "{replacement}")]
+pub struct AbsPathWithModuleSugg {
+    #[primary_span]
+    pub span: Span,
+    #[applicability]
+    pub applicability: Applicability,
+    pub replacement: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_proc_macro_derive_resolution_fallback)]
+pub struct ProcMacroDeriveResolutionFallback {
+    #[label]
+    pub span: Span,
+    pub ns: Namespace,
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_macro_expanded_macro_exports_accessed_by_absolute_paths)]
+pub struct MacroExpandedMacroExportsAccessedByAbsolutePaths {
+    #[note]
+    pub definition: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_hidden_lifetime_parameters)]
+pub struct ElidedLifetimesInPaths {
+    #[subdiagnostic]
+    pub subdiag: ElidedLifetimeInPathSubdiag,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_invalid_crate_type_value)]
+pub struct UnknownCrateTypes {
+    #[subdiagnostic]
+    pub sugg: Option<UnknownCrateTypesSub>,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(lint_suggestion, code = r#""{candidate}""#, applicability = "maybe-incorrect")]
+pub struct UnknownCrateTypesSub {
+    #[primary_span]
+    pub span: Span,
+    pub candidate: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_imports)]
+pub struct UnusedImports {
+    #[subdiagnostic]
+    pub sugg: UnusedImportsSugg,
+    #[help]
+    pub test_module_span: Option<Span>,
+
+    pub span_snippets: DiagArgValue,
+    pub num_snippets: usize,
+}
+
+#[derive(Subdiagnostic)]
+pub enum UnusedImportsSugg {
+    #[suggestion(
+        lint_suggestion_remove_whole_use,
+        applicability = "machine-applicable",
+        code = "",
+        style = "tool-only"
+    )]
+    RemoveWholeUse {
+        #[primary_span]
+        span: Span,
+    },
+    #[multipart_suggestion(
+        lint_suggestion_remove_imports,
+        applicability = "machine-applicable",
+        style = "tool-only"
+    )]
+    RemoveImports {
+        #[suggestion_part(code = "")]
+        remove_spans: Vec<Span>,
+        num_to_remove: usize,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_redundant_import)]
+pub struct RedundantImport {
+    #[subdiagnostic]
+    pub subs: Vec<RedundantImportSub>,
+
+    pub ident: Ident,
+}
+
+#[derive(Subdiagnostic)]
+pub enum RedundantImportSub {
+    #[label(lint_label_imported_here)]
+    ImportedHere(#[primary_span] Span),
+    #[label(lint_label_defined_here)]
+    DefinedHere(#[primary_span] Span),
+    #[label(lint_label_imported_prelude)]
+    ImportedPrelude(#[primary_span] Span),
+    #[label(lint_label_defined_prelude)]
+    DefinedPrelude(#[primary_span] Span),
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_doc_comment)]
+#[help]
+pub struct UnusedDocComment {
+    #[label]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+pub enum PatternsInFnsWithoutBody {
+    #[diag(lint_pattern_in_foreign)]
+    Foreign {
+        #[subdiagnostic]
+        sub: PatternsInFnsWithoutBodySub,
+    },
+    #[diag(lint_pattern_in_bodiless)]
+    Bodiless {
+        #[subdiagnostic]
+        sub: PatternsInFnsWithoutBodySub,
+    },
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(lint_remove_mut_from_pattern, code = "{ident}", applicability = "machine-applicable")]
+pub struct PatternsInFnsWithoutBodySub {
+    #[primary_span]
+    pub span: Span,
+
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_extern_without_abi)]
+#[help]
+pub struct MissingAbi {
+    #[label]
+    pub span: Span,
+
+    pub default_abi: &'static str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_legacy_derive_helpers)]
+pub struct LegacyDeriveHelpers {
+    #[label]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_or_patterns_back_compat)]
+pub struct OrPatternsBackCompat {
+    #[suggestion(code = "{suggestion}", applicability = "machine-applicable")]
+    pub span: Span,
+    pub suggestion: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_reserved_prefix)]
+pub struct ReservedPrefix {
+    #[label]
+    pub label: Span,
+    #[suggestion(code = " ", applicability = "machine-applicable")]
+    pub suggestion: Span,
+
+    pub prefix: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_builtin_attribute)]
+pub struct UnusedBuiltinAttribute {
+    #[note]
+    pub invoc_span: Span,
+
+    pub attr_name: Symbol,
+    pub macro_name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_trailing_semi_macro)]
+pub struct TrailingMacro {
+    #[note(lint_note1)]
+    #[note(lint_note2)]
+    pub is_trailing: bool,
+
+    pub name: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_break_with_label_and_loop)]
+pub struct BreakWithLabelAndLoop {
+    #[subdiagnostic]
+    pub sub: BreakWithLabelAndLoopSub,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
+pub struct BreakWithLabelAndLoopSub {
+    #[suggestion_part(code = "(")]
+    pub left: Span,
+    #[suggestion_part(code = ")")]
+    pub right: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_deprecated_where_clause_location)]
+#[note]
+pub struct DeprecatedWhereClauseLocation {
+    #[subdiagnostic]
+    pub suggestion: DeprecatedWhereClauseLocationSugg,
+}
+
+#[derive(Subdiagnostic)]
+pub enum DeprecatedWhereClauseLocationSugg {
+    #[multipart_suggestion(lint_suggestion_move_to_end, applicability = "machine-applicable")]
+    MoveToEnd {
+        #[suggestion_part(code = "")]
+        left: Span,
+        #[suggestion_part(code = "{sugg}")]
+        right: Span,
+
+        sugg: String,
+    },
+    #[suggestion(lint_suggestion_remove_where, code = "", applicability = "machine-applicable")]
+    RemoveWhere {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_missing_unsafe_on_extern)]
+pub struct MissingUnsafeOnExtern {
+    #[suggestion(code = "unsafe ", applicability = "machine-applicable")]
+    pub suggestion: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_single_use_lifetime)]
+pub struct SingleUseLifetime {
+    #[label(lint_label_param)]
+    pub param_span: Span,
+    #[label(lint_label_use)]
+    pub use_span: Span,
+    #[subdiagnostic]
+    pub suggestion: Option<SingleUseLifetimeSugg>,
+
+    pub ident: Ident,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")]
+pub struct SingleUseLifetimeSugg {
+    #[suggestion_part(code = "")]
+    pub deletion_span: Option<Span>,
+    #[suggestion_part(code = "{replace_lt}")]
+    pub use_span: Span,
+
+    pub replace_lt: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_lifetime)]
+pub struct UnusedLifetime {
+    #[suggestion(code = "", applicability = "machine-applicable")]
+    pub deletion_span: Option<Span>,
+
+    pub ident: Ident,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_named_argument_used_positionally)]
+pub struct NamedArgumentUsedPositionally {
+    #[label(lint_label_named_arg)]
+    pub named_arg_sp: Span,
+    #[label(lint_label_position_arg)]
+    pub position_label_sp: Option<Span>,
+    #[suggestion(style = "verbose", code = "{name}", applicability = "maybe-incorrect")]
+    pub suggestion: Option<Span>,
+
+    pub name: String,
+    pub named_arg_name: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_byte_slice_in_packed_struct_with_derive)]
+#[help]
+pub struct ByteSliceInPackedStructWithDerive {
+    // FIXME: make this translatable
+    pub ty: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unused_extern_crate)]
+pub struct UnusedExternCrate {
+    #[suggestion(code = "", applicability = "machine-applicable")]
+    pub removal_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_extern_crate_not_idiomatic)]
+pub struct ExternCrateNotIdiomatic {
+    #[suggestion(style = "verbose", code = "{code}", applicability = "machine-applicable")]
+    pub span: Span,
+
+    pub code: &'static str,
+}
+
+// FIXME: make this translatable
+pub struct AmbiguousGlobImports {
+    pub ambiguity: AmbiguityErrorDiag,
+}
+
+impl<'a, G: EmissionGuarantee> LintDiagnostic<'a, G> for AmbiguousGlobImports {
+    fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) {
+        diag.primary_message(self.ambiguity.msg.clone());
+        rustc_errors::report_ambiguity_error(diag, self.ambiguity);
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_ambiguous_glob_reexport)]
+pub struct AmbiguousGlobReexports {
+    #[label(lint_label_first_reexport)]
+    pub first_reexport: Span,
+    #[label(lint_label_duplicate_reexport)]
+    pub duplicate_reexport: Span,
+
+    pub name: String,
+    // FIXME: make this translatable
+    pub namespace: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_hidden_glob_reexport)]
+pub struct HiddenGlobReexports {
+    #[note(lint_note_glob_reexport)]
+    pub glob_reexport: Span,
+    #[note(lint_note_private_item)]
+    pub private_item: Span,
+
+    pub name: String,
+    // FIXME: make this translatable
+    pub namespace: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unnecessary_qualification)]
+pub struct UnusedQualifications {
+    #[suggestion(style = "verbose", code = "", applicability = "machine-applicable")]
+    pub removal_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_associated_const_elided_lifetime)]
+pub struct AssociatedConstElidedLifetime {
+    #[suggestion(style = "verbose", code = "{code}", applicability = "machine-applicable")]
+    pub span: Span,
+
+    pub code: &'static str,
+    pub elided: bool,
+    #[note]
+    pub lifetimes_in_scope: MultiSpan,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_redundant_import_visibility)]
+pub struct RedundantImportVisibility {
+    #[note]
+    pub span: Span,
+    #[help]
+    pub help: (),
+
+    pub import_vis: String,
+    pub max_vis: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unsafe_attr_outside_unsafe)]
+pub struct UnsafeAttrOutsideUnsafe {
+    #[label]
+    pub span: Span,
+    #[subdiagnostic]
+    pub suggestion: UnsafeAttrOutsideUnsafeSuggestion,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    lint_unsafe_attr_outside_unsafe_suggestion,
+    applicability = "machine-applicable"
+)]
+pub struct UnsafeAttrOutsideUnsafeSuggestion {
+    #[suggestion_part(code = "unsafe(")]
+    pub left: Span,
+    #[suggestion_part(code = ")")]
+    pub right: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_out_of_scope_macro_calls)]
+#[help]
+pub struct OutOfScopeMacroCalls {
+    pub path: String,
+}
diff --git a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs
new file mode 100644
index 00000000000..867e132b106
--- /dev/null
+++ b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs
@@ -0,0 +1,155 @@
+//! Migration code for the `expr_fragment_specifier_2024`
+//! rule.
+use tracing::debug;
+
+use rustc_ast::token::Token;
+use rustc_ast::token::TokenKind;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::tokenstream::TokenTree;
+use rustc_session::declare_lint;
+use rustc_session::declare_lint_pass;
+use rustc_session::lint::FutureIncompatibilityReason;
+use rustc_span::edition::Edition;
+use rustc_span::sym;
+
+use crate::lints::MacroExprFragment2024;
+use crate::EarlyLintPass;
+
+declare_lint! {
+    /// The `edition_2024_expr_fragment_specifier` lint detects the use of
+    /// `expr` fragments in macros during migration to the 2024 edition.
+    ///
+    /// The `expr` fragment specifier will accept more expressions in the 2024
+    /// edition. To maintain the behavior from the 2021 edition and earlier, use
+    /// the `expr_2021` fragment specifier.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2021,compile_fail
+    /// #![deny(edition_2024_expr_fragment_specifier)]
+    /// macro_rules! m {
+    ///   ($e:expr) => {
+    ///       $e
+    ///   }
+    /// }
+    ///
+    /// fn main() {
+    ///    m!(1);
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Rust [editions] allow the language to evolve without breaking backwards
+    /// compatibility. This lint catches code that uses [macro matcher fragment
+    /// specifiers] that have changed meaning in the 2024 edition. If you switch
+    /// to the new edition without updating the code, your macros may behave
+    /// differently.
+    ///
+    /// In the 2024 edition, the `expr` fragment specifier `expr` will also
+    /// match `const { ... }` blocks. This means if a macro had a pattern that
+    /// matched `$e:expr` and another that matches `const { $e: expr }`, for
+    /// example, that under the 2024 edition the first pattern would match while
+    /// in the 2021 and earlier editions the second pattern would match. To keep
+    /// the old behavior, use the `expr_2021` fragment specifier.
+    ///
+    /// This lint detects macros whose behavior might change due to the changing
+    /// meaning of the `expr` fragment specifier. It is "allow" by default
+    /// because the code is perfectly valid in older editions. The [`cargo fix`]
+    /// tool with the `--edition` flag will switch this lint to "warn" and
+    /// automatically apply the suggested fix from the compiler. This provides a
+    /// completely automated way to update old code for a new edition.
+    ///
+    /// Using `cargo fix --edition` with this lint will ensure that your code
+    /// retains the same behavior. This may not be the desired, as macro authors
+    /// often will want their macros to use the latest grammar for matching
+    /// expressions. Be sure to carefully review changes introduced by this lint
+    /// to ensure the macros implement the desired behavior.
+    ///
+    /// [editions]: https://doc.rust-lang.org/edition-guide/
+    /// [macro matcher fragment specifiers]: https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html
+    /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
+    pub EDITION_2024_EXPR_FRAGMENT_SPECIFIER,
+    Allow,
+    "The `expr` fragment specifier will accept more expressions in the 2024 edition. \
+    To keep the existing behavior, use the `expr_2021` fragment specifier.",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
+        reference: "Migration Guide <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html>",
+    };
+}
+
+declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]);
+
+impl Expr2024 {
+    fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: &TokenStream) {
+        let mut prev_colon = false;
+        let mut prev_identifier = false;
+        let mut prev_dollar = false;
+        for tt in tokens.trees() {
+            debug!(
+                "check_tokens: {:?} - colon {prev_dollar} - ident {prev_identifier} - colon {prev_colon}",
+                tt
+            );
+            match tt {
+                TokenTree::Token(token, _) => match token.kind {
+                    TokenKind::Dollar => {
+                        prev_dollar = true;
+                        continue;
+                    }
+                    TokenKind::Ident(..) | TokenKind::NtIdent(..) => {
+                        if prev_colon && prev_identifier && prev_dollar {
+                            self.check_ident_token(cx, token);
+                        } else if prev_dollar {
+                            prev_identifier = true;
+                            continue;
+                        }
+                    }
+                    TokenKind::Colon => {
+                        if prev_dollar && prev_identifier {
+                            prev_colon = true;
+                            continue;
+                        }
+                    }
+                    _ => {}
+                },
+                TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts),
+            }
+            prev_colon = false;
+            prev_identifier = false;
+            prev_dollar = false;
+        }
+    }
+
+    fn check_ident_token(&mut self, cx: &crate::EarlyContext<'_>, token: &Token) {
+        debug!("check_ident_token: {:?}", token);
+        let (sym, edition) = match token.kind {
+            TokenKind::Ident(sym, _) => (sym, Edition::Edition2024),
+            _ => return,
+        };
+
+        debug!("token.span.edition(): {:?}", token.span.edition());
+        if token.span.edition() >= edition {
+            return;
+        }
+
+        if sym != sym::expr {
+            return;
+        }
+
+        debug!("emitting lint");
+        cx.builder.emit_span_lint(
+            &EDITION_2024_EXPR_FRAGMENT_SPECIFIER,
+            token.span.into(),
+            MacroExprFragment2024 { suggestion: token.span },
+        );
+    }
+}
+
+impl EarlyLintPass for Expr2024 {
+    fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) {
+        self.check_tokens(cx, &mc.body.tokens);
+    }
+}
diff --git a/compiler/rustc_lint/src/map_unit_fn.rs b/compiler/rustc_lint/src/map_unit_fn.rs
new file mode 100644
index 00000000000..e355604e206
--- /dev/null
+++ b/compiler/rustc_lint/src/map_unit_fn.rs
@@ -0,0 +1,128 @@
+use crate::lints::MappingToUnit;
+use crate::{LateContext, LateLintPass, LintContext};
+
+use rustc_hir::{Expr, ExprKind, HirId, Stmt, StmtKind};
+use rustc_middle::{
+    query::Key,
+    ty::{self, Ty},
+};
+use rustc_session::{declare_lint, declare_lint_pass};
+
+declare_lint! {
+    /// The `map_unit_fn` lint checks for `Iterator::map` receive
+    /// a callable that returns `()`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// fn foo(items: &mut Vec<u8>) {
+    ///     items.sort();
+    /// }
+    ///
+    /// fn main() {
+    ///     let mut x: Vec<Vec<u8>> = vec![
+    ///         vec![0, 2, 1],
+    ///         vec![5, 4, 3],
+    ///     ];
+    ///     x.iter_mut().map(foo);
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Mapping to `()` is almost always a mistake.
+    pub MAP_UNIT_FN,
+    Warn,
+    "`Iterator::map` call that discard the iterator's values"
+}
+
+declare_lint_pass!(MapUnitFn => [MAP_UNIT_FN]);
+
+impl<'tcx> LateLintPass<'tcx> for MapUnitFn {
+    fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) {
+        if stmt.span.from_expansion() {
+            return;
+        }
+
+        if let StmtKind::Semi(expr) = stmt.kind {
+            if let ExprKind::MethodCall(path, receiver, args, span) = expr.kind {
+                if path.ident.name.as_str() == "map" {
+                    if receiver.span.from_expansion()
+                        || args.iter().any(|e| e.span.from_expansion())
+                        || !is_impl_slice(cx, receiver)
+                        || !is_diagnostic_name(cx, expr.hir_id, "IteratorMap")
+                    {
+                        return;
+                    }
+                    let arg_ty = cx.typeck_results().expr_ty(&args[0]);
+                    let default_span = args[0].span;
+                    if let ty::FnDef(id, _) = arg_ty.kind() {
+                        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_span_lint(
+                                MAP_UNIT_FN,
+                                span,
+                                MappingToUnit {
+                                    function_label: cx
+                                        .tcx
+                                        .span_of_impl(*id)
+                                        .unwrap_or(default_span),
+                                    argument_label: args[0].span,
+                                    map_label: arg_ty.default_span(cx.tcx),
+                                    suggestion: path.ident.span,
+                                    replace: "for_each".to_string(),
+                                },
+                            )
+                        }
+                    } else if let ty::Closure(id, subs) = arg_ty.kind() {
+                        let cl_ty = subs.as_closure().sig();
+                        let ret_ty = cl_ty.output().skip_binder();
+                        if is_unit_type(ret_ty) {
+                            cx.emit_span_lint(
+                                MAP_UNIT_FN,
+                                span,
+                                MappingToUnit {
+                                    function_label: cx
+                                        .tcx
+                                        .span_of_impl(*id)
+                                        .unwrap_or(default_span),
+                                    argument_label: args[0].span,
+                                    map_label: arg_ty.default_span(cx.tcx),
+                                    suggestion: path.ident.span,
+                                    replace: "for_each".to_string(),
+                                },
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn is_impl_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
+        if let Some(impl_id) = cx.tcx.impl_of_method(method_id) {
+            return cx.tcx.type_of(impl_id).skip_binder().is_slice();
+        }
+    }
+    false
+}
+
+fn is_unit_type(ty: Ty<'_>) -> bool {
+    ty.is_unit() || ty.is_never()
+}
+
+fn is_diagnostic_name(cx: &LateContext<'_>, id: HirId, name: &str) -> bool {
+    if let Some(def_id) = cx.typeck_results().type_dependent_def_id(id) {
+        if let Some(item) = cx.tcx.get_diagnostic_name(def_id) {
+            if item.as_str() == name {
+                return true;
+            }
+        }
+    }
+    false
+}
diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs
new file mode 100644
index 00000000000..7a71fec769f
--- /dev/null
+++ b/compiler/rustc_lint/src/methods.rs
@@ -0,0 +1,70 @@
+use crate::lints::CStringPtr;
+use crate::LateContext;
+use crate::LateLintPass;
+use crate::LintContext;
+use rustc_hir::{Expr, ExprKind};
+use rustc_middle::ty;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::{symbol::sym, Span};
+
+declare_lint! {
+    /// The `temporary_cstring_as_ptr` lint detects getting the inner pointer of
+    /// a temporary `CString`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # #![allow(unused)]
+    /// # use std::ffi::CString;
+    /// let c_str = CString::new("foo").unwrap().as_ptr();
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The inner pointer of a `CString` lives only as long as the `CString` it
+    /// points to. Getting the inner pointer of a *temporary* `CString` allows the `CString`
+    /// to be dropped at the end of the statement, as it is not being referenced as far as the
+    /// typesystem is concerned. This means outside of the statement the pointer will point to
+    /// freed memory, which causes undefined behavior if the pointer is later dereferenced.
+    pub TEMPORARY_CSTRING_AS_PTR,
+    Warn,
+    "detects getting the inner pointer of a temporary `CString`"
+}
+
+declare_lint_pass!(TemporaryCStringAsPtr => [TEMPORARY_CSTRING_AS_PTR]);
+
+impl<'tcx> LateLintPass<'tcx> for TemporaryCStringAsPtr {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        if let ExprKind::MethodCall(as_ptr_path, as_ptr_receiver, ..) = expr.kind
+            && as_ptr_path.ident.name == sym::as_ptr
+            && let ExprKind::MethodCall(unwrap_path, unwrap_receiver, ..) = as_ptr_receiver.kind
+            && (unwrap_path.ident.name == sym::unwrap || unwrap_path.ident.name == sym::expect)
+        {
+            lint_cstring_as_ptr(cx, as_ptr_path.ident.span, unwrap_receiver, as_ptr_receiver);
+        }
+    }
+}
+
+fn lint_cstring_as_ptr(
+    cx: &LateContext<'_>,
+    as_ptr_span: Span,
+    source: &rustc_hir::Expr<'_>,
+    unwrap: &rustc_hir::Expr<'_>,
+) {
+    let source_type = cx.typeck_results().expr_ty(source);
+    if let ty::Adt(def, args) = source_type.kind() {
+        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_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
new file mode 100644
index 00000000000..93dd5e764c6
--- /dev/null
+++ b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
@@ -0,0 +1,60 @@
+use crate::{LateContext, LateLintPass, LintContext};
+
+use rustc_hir as hir;
+use rustc_session::{declare_lint, declare_lint_pass};
+
+declare_lint! {
+    /// The `multiple_supertrait_upcastable` lint detects when an object-safe trait has multiple
+    /// supertraits.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![feature(multiple_supertrait_upcastable)]
+    /// trait A {}
+    /// trait B {}
+    ///
+    /// #[warn(multiple_supertrait_upcastable)]
+    /// trait C: A + B {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// To support upcasting with multiple supertraits, we need to store multiple vtables and this
+    /// can result in extra space overhead, even if no code actually uses upcasting.
+    /// This lint allows users to identify when such scenarios occur and to decide whether the
+    /// additional overhead is justified.
+    pub MULTIPLE_SUPERTRAIT_UPCASTABLE,
+    Allow,
+    "detect when an object-safe trait has multiple supertraits",
+    @feature_gate = multiple_supertrait_upcastable;
+}
+
+declare_lint_pass!(MultipleSupertraitUpcastable => [MULTIPLE_SUPERTRAIT_UPCASTABLE]);
+
+impl<'tcx> LateLintPass<'tcx> for MultipleSupertraitUpcastable {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        let def_id = item.owner_id.to_def_id();
+        // NOTE(nbdd0121): use `object_safety_violations` instead of `is_object_safe` because
+        // the latter will report `where_clause_object_safety` lint.
+        if let hir::ItemKind::Trait(_, _, _, _, _) = item.kind
+            && cx.tcx.is_object_safe(def_id)
+        {
+            let direct_super_traits_iter = cx
+                .tcx
+                .explicit_super_predicates_of(def_id)
+                .predicates
+                .into_iter()
+                .filter_map(|(pred, _)| pred.as_trait_clause());
+            if direct_super_traits_iter.count() > 1 {
+                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
new file mode 100644
index 00000000000..9f298a6071c
--- /dev/null
+++ b/compiler/rustc_lint/src/non_ascii_idents.rs
@@ -0,0 +1,392 @@
+use crate::lints::{
+    ConfusableIdentifierPair, IdentifierNonAsciiChar, IdentifierUncommonCodepoints,
+    MixedScriptConfusables,
+};
+use crate::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_data_structures::unord::UnordMap;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::symbol::Symbol;
+use unicode_security::general_security_profile::IdentifierType;
+
+declare_lint! {
+    /// The `non_ascii_idents` lint detects non-ASCII identifiers.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// # #![allow(unused)]
+    /// #![deny(non_ascii_idents)]
+    /// fn main() {
+    ///     let fรถรถ = 1;
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// This lint allows projects that wish to retain the limit of only using
+    /// ASCII characters to switch this lint to "forbid" (for example to ease
+    /// collaboration or for security reasons).
+    /// See [RFC 2457] for more details.
+    ///
+    /// [RFC 2457]: https://github.com/rust-lang/rfcs/blob/master/text/2457-non-ascii-idents.md
+    pub NON_ASCII_IDENTS,
+    Allow,
+    "detects non-ASCII identifiers",
+    crate_level_only
+}
+
+declare_lint! {
+    /// The `uncommon_codepoints` lint detects uncommon Unicode codepoints in
+    /// identifiers.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # #![allow(unused)]
+    /// const ยต: f64 = 0.000001;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// This lint warns about using characters which are not commonly used, and may
+    /// cause visual confusion.
+    ///
+    /// This lint is triggered by identifiers that contain a codepoint that is
+    /// not part of the set of "Allowed" codepoints as described by [Unicodeยฎ
+    /// Technical Standard #39 Unicode Security Mechanisms Section 3.1 General
+    /// Security Profile for Identifiers][TR39Allowed].
+    ///
+    /// Note that the set of uncommon codepoints may change over time. Beware
+    /// that if you "forbid" this lint that existing code may fail in the
+    /// future.
+    ///
+    /// [TR39Allowed]: https://www.unicode.org/reports/tr39/#General_Security_Profile
+    pub UNCOMMON_CODEPOINTS,
+    Warn,
+    "detects uncommon Unicode codepoints in identifiers",
+    crate_level_only
+}
+
+declare_lint! {
+    /// The `confusable_idents` lint detects visually confusable pairs between
+    /// identifiers.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// // Latin Capital Letter E With Caron
+    /// pub const ฤš: i32 = 1;
+    /// // Latin Capital Letter E With Breve
+    /// pub const ฤ”: i32 = 2;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// This lint warns when different identifiers may appear visually similar,
+    /// which can cause confusion.
+    ///
+    /// The confusable detection algorithm is based on [Unicodeยฎ Technical
+    /// Standard #39 Unicode Security Mechanisms Section 4 Confusable
+    /// Detection][TR39Confusable]. For every distinct identifier X execute
+    /// the function `skeleton(X)`. If there exist two distinct identifiers X
+    /// and Y in the same crate where `skeleton(X) = skeleton(Y)` report it.
+    /// The compiler uses the same mechanism to check if an identifier is too
+    /// similar to a keyword.
+    ///
+    /// Note that the set of confusable characters may change over time.
+    /// Beware that if you "forbid" this lint that existing code may fail in
+    /// the future.
+    ///
+    /// [TR39Confusable]: https://www.unicode.org/reports/tr39/#Confusable_Detection
+    pub CONFUSABLE_IDENTS,
+    Warn,
+    "detects visually confusable pairs between identifiers",
+    crate_level_only
+}
+
+declare_lint! {
+    /// The `mixed_script_confusables` lint detects visually confusable
+    /// characters in identifiers between different [scripts].
+    ///
+    /// [scripts]: https://en.wikipedia.org/wiki/Script_(Unicode)
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// // The Japanese katakana character ใ‚จ can be confused with the Han character ๅทฅ.
+    /// const ใ‚จ: &'static str = "ใ‚ขใ‚คใ‚ฆ";
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// This lint warns when characters between different scripts may appear
+    /// visually similar, which can cause confusion.
+    ///
+    /// If the crate contains other identifiers in the same script that have
+    /// non-confusable characters, then this lint will *not* be issued. For
+    /// example, if the example given above has another identifier with
+    /// katakana characters (such as `let ใ‚ซใ‚ฟใ‚ซใƒŠ = 123;`), then this indicates
+    /// that you are intentionally using katakana, and it will not warn about
+    /// it.
+    ///
+    /// Note that the set of confusable characters may change over time.
+    /// Beware that if you "forbid" this lint that existing code may fail in
+    /// the future.
+    pub MIXED_SCRIPT_CONFUSABLES,
+    Warn,
+    "detects Unicode scripts whose mixed script confusables codepoints are solely used",
+    crate_level_only
+}
+
+declare_lint_pass!(NonAsciiIdents => [NON_ASCII_IDENTS, UNCOMMON_CODEPOINTS, CONFUSABLE_IDENTS, MIXED_SCRIPT_CONFUSABLES]);
+
+impl EarlyLintPass for NonAsciiIdents {
+    fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+        use rustc_session::lint::Level;
+        use rustc_span::Span;
+        use std::collections::BTreeMap;
+        use unicode_security::GeneralSecurityProfile;
+
+        let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).0 != Level::Allow;
+        let check_uncommon_codepoints =
+            cx.builder.lint_level(UNCOMMON_CODEPOINTS).0 != Level::Allow;
+        let check_confusable_idents = cx.builder.lint_level(CONFUSABLE_IDENTS).0 != Level::Allow;
+        let check_mixed_script_confusables =
+            cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).0 != Level::Allow;
+
+        if !check_non_ascii_idents
+            && !check_uncommon_codepoints
+            && !check_confusable_idents
+            && !check_mixed_script_confusables
+        {
+            return;
+        }
+
+        let mut has_non_ascii_idents = false;
+        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_span_lint(NON_ASCII_IDENTS, sp, IdentifierNonAsciiChar);
+            if check_uncommon_codepoints
+                && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed)
+            {
+                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: UnordMap<Symbol, (Symbol, Span, bool)> =
+                UnordMap::with_capacity(symbols.len());
+            let mut skeleton_buf = String::new();
+
+            for (&symbol, &sp) in symbols.iter() {
+                use unicode_security::confusable_detection::skeleton;
+
+                let symbol_str = symbol.as_str();
+                let is_ascii = symbol_str.is_ascii();
+
+                // Get the skeleton as a `Symbol`.
+                skeleton_buf.clear();
+                skeleton_buf.extend(skeleton(symbol_str));
+                let skeleton_sym = if *symbol_str == *skeleton_buf {
+                    symbol
+                } else {
+                    Symbol::intern(&skeleton_buf)
+                };
+
+                skeleton_map
+                    .entry(skeleton_sym)
+                    .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| {
+                        if !*existing_is_ascii || !is_ascii {
+                            cx.emit_span_lint(
+                                CONFUSABLE_IDENTS,
+                                sp,
+                                ConfusableIdentifierPair {
+                                    existing_sym: *existing_symbol,
+                                    sym: symbol,
+                                    label: *existing_span,
+                                    main_label: sp,
+                                },
+                            );
+                        }
+                        if *existing_is_ascii && !is_ascii {
+                            *existing_symbol = symbol;
+                            *existing_span = sp;
+                            *existing_is_ascii = is_ascii;
+                        }
+                    })
+                    .or_insert((symbol, sp, is_ascii));
+            }
+        }
+
+        if has_non_ascii_idents && check_mixed_script_confusables {
+            use unicode_security::is_potential_mixed_script_confusable_char;
+            use unicode_security::mixed_script::AugmentedScriptSet;
+
+            #[derive(Clone)]
+            enum ScriptSetUsage {
+                Suspicious(Vec<char>, Span),
+                Verified,
+            }
+
+            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);
+
+            let mut has_suspicious = false;
+            for (symbol, &sp) in symbols.iter() {
+                let symbol_str = symbol.as_str();
+                for ch in symbol_str.chars() {
+                    if ch.is_ascii() {
+                        // all ascii characters are covered by exception.
+                        continue;
+                    }
+                    if !GeneralSecurityProfile::identifier_allowed(ch) {
+                        // this character is covered by `uncommon_codepoints` lint.
+                        continue;
+                    }
+                    let augmented_script_set = AugmentedScriptSet::for_char(ch);
+                    script_states
+                        .entry(augmented_script_set)
+                        .and_modify(|existing_state| {
+                            if let ScriptSetUsage::Suspicious(ch_list, _) = existing_state {
+                                if is_potential_mixed_script_confusable_char(ch) {
+                                    ch_list.push(ch);
+                                } else {
+                                    *existing_state = ScriptSetUsage::Verified;
+                                }
+                            }
+                        })
+                        .or_insert_with(|| {
+                            if !is_potential_mixed_script_confusable_char(ch) {
+                                ScriptSetUsage::Verified
+                            } else {
+                                has_suspicious = true;
+                                ScriptSetUsage::Suspicious(vec![ch], sp)
+                            }
+                        });
+                }
+            }
+
+            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 {
+                        ScriptSetUsage::Verified => Some(*k),
+                        _ => None,
+                    })
+                    .collect::<Vec<_>>();
+
+                // we're sorting the output here.
+                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 };
+
+                    if augment_script_set.is_all() {
+                        continue;
+                    }
+
+                    for existing in verified_augmented_script_sets.iter() {
+                        if existing.is_all() {
+                            continue;
+                        }
+                        let mut intersect = *existing;
+                        intersect.intersect_with(augment_script_set);
+                        if !intersect.is_empty() && !intersect.is_all() {
+                            continue 'outerloop;
+                        }
+                    }
+
+                    // We sort primitive chars here and can use unstable sort
+                    ch_list.sort_unstable();
+                    ch_list.dedup();
+                    lint_reports.insert((sp, ch_list), augment_script_set);
+                }
+
+                for ((sp, ch_list), script_set) in lint_reports {
+                    let mut includes = String::new();
+                    for (idx, ch) in ch_list.into_iter().enumerate() {
+                        if idx != 0 {
+                            includes += ", ";
+                        }
+                        let char_info = format!("'{}' (U+{:04X})", ch, ch as u32);
+                        includes += &char_info;
+                    }
+                    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
new file mode 100644
index 00000000000..2dc2a0efdf0
--- /dev/null
+++ b/compiler/rustc_lint/src/non_fmt_panic.rs
@@ -0,0 +1,335 @@
+use crate::lints::{NonFmtPanicBraces, NonFmtPanicUnused};
+use crate::{fluent_generated as fluent, LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, LangItem};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_middle::bug;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_parse_format::{ParseMode, Parser, Piece};
+use rustc_session::lint::FutureIncompatibilityReason;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::edition::Edition;
+use rustc_span::{hygiene, sym, symbol::kw, InnerSpan, Span, Symbol};
+use rustc_trait_selection::infer::InferCtxtExt;
+
+declare_lint! {
+    /// The `non_fmt_panics` lint detects `panic!(..)` invocations where the first
+    /// argument is not a formatting string.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,no_run,edition2018
+    /// panic!("{}");
+    /// panic!(123);
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
+    /// That means that `panic!("{}")` panics with the message `"{}"` instead
+    /// of using it as a formatting string, and `panic!(123)` will panic with
+    /// an `i32` as message.
+    ///
+    /// Rust 2021 always interprets the first argument as format string.
+    NON_FMT_PANICS,
+    Warn,
+    "detect single-argument panic!() invocations in which the argument is not a format string",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
+        explain_reason: false,
+    };
+    report_in_external_macro
+}
+
+declare_lint_pass!(NonPanicFmt => [NON_FMT_PANICS]);
+
+impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+        if let hir::ExprKind::Call(f, [arg]) = &expr.kind {
+            if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() {
+                let f_diagnostic_name = cx.tcx.get_diagnostic_name(def_id);
+
+                if cx.tcx.is_lang_item(def_id, LangItem::BeginPanic)
+                    || cx.tcx.is_lang_item(def_id, LangItem::Panic)
+                    || f_diagnostic_name == Some(sym::panic_str_2015)
+                {
+                    if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
+                        if matches!(
+                            cx.tcx.get_diagnostic_name(id),
+                            Some(sym::core_panic_2015_macro | sym::std_panic_2015_macro)
+                        ) {
+                            check_panic(cx, f, arg);
+                        }
+                    }
+                } else if f_diagnostic_name == Some(sym::unreachable_display) {
+                    if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
+                        if cx.tcx.is_diagnostic_item(sym::unreachable_2015_macro, id) {
+                            check_panic(
+                                cx,
+                                f,
+                                // This is safe because we checked above that the callee is indeed
+                                // unreachable_display
+                                match &arg.kind {
+                                    // Get the borrowed arg not the borrow
+                                    hir::ExprKind::AddrOf(ast::BorrowKind::Ref, _, arg) => arg,
+                                    _ => bug!("call to unreachable_display without borrow"),
+                                },
+                            );
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) {
+    if let hir::ExprKind::Lit(lit) = &arg.kind {
+        if let ast::LitKind::Str(sym, _) = lit.node {
+            // The argument is a string literal.
+            check_panic_str(cx, f, arg, sym.as_str());
+            return;
+        }
+    }
+
+    // The argument is *not* a string literal.
+
+    let (span, panic, symbol) = panic_call(cx, f);
+
+    if in_external_macro(cx.sess(), span) {
+        // Nothing that can be done about it in the current crate.
+        return;
+    }
+
+    // Find the span of the argument to `panic!()` or `unreachable!`, before expansion in the
+    // case of `panic!(some_macro!())` or `unreachable!(some_macro!())`.
+    // We don't use source_callsite(), because this `panic!(..)` might itself
+    // be expanded from another macro, in which case we want to stop at that
+    // expansion.
+    let mut arg_span = arg.span;
+    let mut arg_macro = None;
+    while !span.contains(arg_span) {
+        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.span_lint(NON_FMT_PANICS, arg_span, |lint| {
+        lint.primary_message(fluent::lint_non_fmt_panic);
+        lint.arg("name", symbol);
+        lint.note(fluent::lint_note);
+        lint.note(fluent::lint_more_info_note);
+        if !is_arg_inside_call(arg_span, span) {
+            // No clue where this argument is coming from.
+            return;
+        }
+        if arg_macro.is_some_and(|id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
+            // A case of `panic!(format!(..))`.
+            lint.note(fluent::lint_supports_fmt_note);
+            if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
+                lint.multipart_suggestion(
+                    fluent::lint_supports_fmt_suggestion,
+                    vec![
+                        (arg_span.until(open.shrink_to_hi()), "".into()),
+                        (close.until(arg_span.shrink_to_hi()), "".into()),
+                    ],
+                    Applicability::MachineApplicable,
+                );
+            }
+        } else {
+            let ty = cx.typeck_results().expr_ty(arg);
+            // If this is a &str or String, we can confidently give the `"{}", ` suggestion.
+            let is_str = matches!(
+                ty.kind(),
+                ty::Ref(_, r, _) if r.is_str(),
+            ) || matches!(
+                ty.ty_adt_def(),
+                Some(ty_def) if cx.tcx.is_lang_item(ty_def.did(), LangItem::String),
+            );
+
+            let infcx = cx.tcx.infer_ctxt().build();
+            let suggest_display = is_str
+                || cx.tcx.get_diagnostic_item(sym::Display).is_some_and(|t| {
+                    infcx.type_implements_trait(t, [ty], cx.param_env).may_apply()
+                });
+            let suggest_debug = !suggest_display
+                && cx.tcx.get_diagnostic_item(sym::Debug).is_some_and(|t| {
+                    infcx.type_implements_trait(t, [ty], cx.param_env).may_apply()
+                });
+
+            let suggest_panic_any = !is_str && panic == sym::std_panic_macro;
+
+            let fmt_applicability = if suggest_panic_any {
+                // If we can use panic_any, use that as the MachineApplicable suggestion.
+                Applicability::MaybeIncorrect
+            } else {
+                // If we don't suggest panic_any, using a format string is our best bet.
+                Applicability::MachineApplicable
+            };
+
+            if suggest_display {
+                lint.span_suggestion_verbose(
+                    arg_span.shrink_to_lo(),
+                    fluent::lint_display_suggestion,
+                    "\"{}\", ",
+                    fmt_applicability,
+                );
+            } else if suggest_debug {
+                lint.arg("ty", ty);
+                lint.span_suggestion_verbose(
+                    arg_span.shrink_to_lo(),
+                    fluent::lint_debug_suggestion,
+                    "\"{:?}\", ",
+                    fmt_applicability,
+                );
+            }
+
+            if suggest_panic_any {
+                if let Some((open, close, del)) = find_delimiters(cx, span) {
+                    lint.arg("already_suggested", suggest_display || suggest_debug);
+                    lint.multipart_suggestion(
+                        fluent::lint_panic_suggestion,
+                        if del == '(' {
+                            vec![(span.until(open), "std::panic::panic_any".into())]
+                        } else {
+                            vec![
+                                (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()),
+                                (close, ")".into()),
+                            ]
+                        },
+                        Applicability::MachineApplicable,
+                    );
+                }
+            }
+        }
+    });
+}
+
+fn check_panic_str<'tcx>(
+    cx: &LateContext<'tcx>,
+    f: &'tcx hir::Expr<'tcx>,
+    arg: &'tcx hir::Expr<'tcx>,
+    fmt: &str,
+) {
+    if !fmt.contains(&['{', '}']) {
+        // No brace, no problem.
+        return;
+    }
+
+    let (span, _, _) = panic_call(cx, f);
+
+    if in_external_macro(cx.sess(), span) && in_external_macro(cx.sess(), arg.span) {
+        // Nothing that can be done about it in the current crate.
+        return;
+    }
+
+    let fmt_span = arg.span.source_callsite();
+
+    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('"'));
+            (Some(snippet), style)
+        }
+        Err(_) => (None, None),
+    };
+
+    let mut fmt_parser = Parser::new(fmt, style, snippet.clone(), false, ParseMode::Format);
+    let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();
+
+    if n_arguments > 0 && fmt_parser.errors.is_empty() {
+        let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
+            [] => vec![fmt_span],
+            v => v
+                .iter()
+                .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end)))
+                .collect(),
+        };
+        cx.emit_span_lint(
+            NON_FMT_PANICS,
+            arg_spans,
+            NonFmtPanicUnused {
+                count: n_arguments,
+                suggestion: is_arg_inside_call(arg.span, span).then_some(arg.span),
+            },
+        );
+    } else {
+        let brace_spans: Option<Vec<_>> =
+            snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| {
+                s.char_indices()
+                    .filter(|&(_, c)| c == '{' || c == '}')
+                    .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }))
+                    .collect()
+            });
+        let count = brace_spans.as_ref().map(|v| v.len()).unwrap_or(/* any number >1 */ 2);
+        cx.emit_span_lint(
+            NON_FMT_PANICS,
+            brace_spans.unwrap_or_else(|| vec![span]),
+            NonFmtPanicBraces {
+                count,
+                suggestion: is_arg_inside_call(arg.span, span).then_some(arg.span.shrink_to_lo()),
+            },
+        );
+    }
+}
+
+/// 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().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((
+        span.from_inner(InnerSpan { start: open, end: open + 1 }),
+        span.from_inner(InnerSpan { start: close, end: close + 1 }),
+        open_ch,
+    ))
+}
+
+fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol, Symbol) {
+    let mut expn = f.span.ctxt().outer_expn_data();
+
+    let mut panic_macro = kw::Empty;
+
+    // Unwrap more levels of macro expansion, as panic_2015!()
+    // was likely expanded from panic!() and possibly from
+    // [debug_]assert!().
+    loop {
+        let parent = expn.call_site.ctxt().outer_expn_data();
+        let Some(id) = parent.macro_def_id else { break };
+        let Some(name) = cx.tcx.get_diagnostic_name(id) else { break };
+        if !matches!(
+            name,
+            sym::core_panic_macro
+                | sym::std_panic_macro
+                | sym::assert_macro
+                | sym::debug_assert_macro
+                | sym::unreachable_macro
+        ) {
+            break;
+        }
+        expn = parent;
+        panic_macro = name;
+    }
+
+    let macro_symbol =
+        if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind { symbol } else { sym::panic };
+    (expn.call_site, panic_macro, macro_symbol)
+}
+
+fn is_arg_inside_call(arg: Span, call: Span) -> bool {
+    // We only add suggestions if the argument we're looking at appears inside the
+    // panic call in the source file, to avoid invalid suggestions when macros are involved.
+    // We specifically check for the spans to not be identical, as that happens sometimes when
+    // proc_macros lie about spans and apply the same span to all the tokens they produce.
+    call.contains(arg) && !call.source_equal(arg)
+}
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..2f8eea6cd18
--- /dev/null
+++ b/compiler/rustc_lint/src/non_local_def.rs
@@ -0,0 +1,545 @@
+use rustc_errors::MultiSpan;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::HirId;
+use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, TyKind};
+use rustc_hir::{Path, QPath};
+use rustc_infer::infer::InferCtxt;
+use rustc_infer::traits::{Obligation, ObligationCause};
+use rustc_middle::ty::{self, Binder, Ty, TyCtxt, TypeFoldable, TypeFolder};
+use rustc_middle::ty::{EarlyBinder, TraitRef, TypeSuperFoldable};
+use rustc_session::{declare_lint, impl_lint_pass};
+use rustc_span::def_id::{DefId, LOCAL_CRATE};
+use rustc_span::Span;
+use rustc_span::{sym, symbol::kw, ExpnKind, MacroKind, Symbol};
+use rustc_trait_selection::error_reporting::traits::ambiguity::{
+    compute_applicable_impls_for_diagnostics, CandidateSource,
+};
+use rustc_trait_selection::infer::TyCtxtInferExt;
+
+use crate::fluent_generated as fluent;
+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 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: &Body<'tcx>) {
+        self.body_depth += 1;
+    }
+
+    fn check_body_post(&mut self, _cx: &LateContext<'tcx>, _body: &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 def_id = item.owner_id.def_id.into();
+        let parent = cx.tcx.parent(def_id);
+        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
+            }
+        };
+
+        // determining if we are in a doctest context can't currently be determined
+        // by the code itself (there are no specific attributes), but fortunately rustdoc
+        // sets a perma-unstable env var for libtest so we just reuse that for now
+        let is_at_toplevel_doctest =
+            || self.body_depth == 2 && std::env::var("UNSTABLE_RUSTDOC_TEST_PATH").is_ok();
+
+        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
+                };
+
+                // Part 1: Is the Self type local?
+                let self_ty_has_local_parent =
+                    ty_has_local_parent(&impl_.self_ty.kind, cx, parent, parent_parent);
+
+                if self_ty_has_local_parent {
+                    return;
+                }
+
+                // Part 2: Is the Trait local?
+                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 of_trait_has_local_parent {
+                    return;
+                }
+
+                // Part 3: Is the impl definition leaking outside it's defining scope?
+                //
+                // We always consider inherent impls to be leaking.
+                let impl_has_enough_non_local_candidates = cx
+                    .tcx
+                    .impl_trait_ref(def_id)
+                    .map(|binder| {
+                        impl_trait_ref_has_enough_non_local_candidates(
+                            cx.tcx,
+                            item.span,
+                            def_id,
+                            binder,
+                            |did| did_has_local_parent(did, cx.tcx, parent, parent_parent),
+                        )
+                    })
+                    .unwrap_or(false);
+
+                if impl_has_enough_non_local_candidates {
+                    return;
+                }
+
+                // Get the span of the parent const item ident (if it's a not a const anon).
+                //
+                // Used to suggest changing the const item to a const anon.
+                let span_for_const_anon_suggestion = if 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
+                };
+
+                let const_anon = matches!(parent_def_kind, DefKind::Const | DefKind::Static { .. })
+                    .then_some(span_for_const_anon_suggestion);
+
+                let may_remove = match &impl_.self_ty.kind {
+                    TyKind::Ptr(mut_ty) | TyKind::Ref(_, mut_ty)
+                        if ty_has_local_parent(&mut_ty.ty.kind, cx, parent, parent_parent) =>
+                    {
+                        let type_ =
+                            if matches!(impl_.self_ty.kind, TyKind::Ptr(_)) { "*" } else { "&" };
+                        let part = format!("{}{}", type_, mut_ty.mutbl.prefix_str());
+                        Some((impl_.self_ty.span.shrink_to_lo().until(mut_ty.ty.span), part))
+                    }
+                    _ => None,
+                };
+
+                let impl_span = item.span.shrink_to_lo().to(impl_.self_ty.span);
+                let mut ms = MultiSpan::from_span(impl_span);
+
+                let (self_ty_span, self_ty_str) =
+                    self_ty_kind_for_diagnostic(&impl_.self_ty, cx.tcx);
+
+                ms.push_span_label(
+                    self_ty_span,
+                    fluent::lint_non_local_definitions_self_ty_not_local,
+                );
+                let of_trait_str = if let Some(of_trait) = &impl_.of_trait {
+                    ms.push_span_label(
+                        path_span_without_args(&of_trait.path),
+                        fluent::lint_non_local_definitions_of_trait_not_local,
+                    );
+                    Some(path_name_to_string(&of_trait.path))
+                } else {
+                    None
+                };
+
+                let (doctest, move_to) = if is_at_toplevel_doctest() {
+                    (true, None)
+                } else {
+                    let mut collector = PathCollector { paths: Vec::new() };
+                    collector.visit_ty(&impl_.self_ty);
+                    if let Some(of_trait) = &impl_.of_trait {
+                        collector.visit_trait_ref(of_trait);
+                    }
+                    collector.visit_generics(&impl_.generics);
+
+                    let mut may_move: Vec<Span> = collector
+                        .paths
+                        .into_iter()
+                        .filter_map(|path| {
+                            if let Some(did) = path.res.opt_def_id()
+                                && did_has_local_parent(did, cx.tcx, parent, parent_parent)
+                            {
+                                Some(cx.tcx.def_span(did))
+                            } else {
+                                None
+                            }
+                        })
+                        .collect();
+                    may_move.sort();
+                    may_move.dedup();
+
+                    let move_to = if may_move.is_empty() {
+                        ms.push_span_label(
+                            cx.tcx.def_span(parent),
+                            fluent::lint_non_local_definitions_impl_move_help,
+                        );
+                        None
+                    } else {
+                        Some((cx.tcx.def_span(parent), may_move))
+                    };
+
+                    (false, move_to)
+                };
+
+                let macro_to_change =
+                    if let ExpnKind::Macro(kind, name) = item.span.ctxt().outer_expn_data().kind {
+                        Some((name.to_string(), kind.descr()))
+                    } else {
+                        None
+                    };
+
+                cx.emit_span_lint(
+                    NON_LOCAL_DEFINITIONS,
+                    ms,
+                    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,
+                        self_ty_str,
+                        of_trait_str,
+                        move_to,
+                        doctest,
+                        may_remove,
+                        has_trait: impl_.of_trait.is_some(),
+                        macro_to_change,
+                    },
+                )
+            }
+            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(),
+                        doctest: is_at_toplevel_doctest(),
+                    },
+                )
+            }
+            _ => {}
+        }
+    }
+}
+
+// Detecting if the impl definition is leaking outside of its defining scope.
+//
+// Rule: for each impl, instantiate all local types with inference vars and
+// then assemble candidates for that goal, if there are more than 1 (non-private
+// impls), it does not leak.
+//
+// https://github.com/rust-lang/rust/issues/121621#issuecomment-1976826895
+fn impl_trait_ref_has_enough_non_local_candidates<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    infer_span: Span,
+    trait_def_id: DefId,
+    binder: EarlyBinder<'tcx, TraitRef<'tcx>>,
+    mut did_has_local_parent: impl FnMut(DefId) -> bool,
+) -> bool {
+    let infcx = tcx
+        .infer_ctxt()
+        // We use the new trait solver since the obligation we are trying to
+        // prove here may overflow and those are fatal in the old trait solver.
+        // Which is unacceptable for a lint.
+        //
+        // Thanksfully the part we use here are very similar to the
+        // new-trait-solver-as-coherence, which is in stabilization.
+        //
+        // https://github.com/rust-lang/rust/issues/123573
+        .with_next_trait_solver(true)
+        .build();
+
+    let trait_ref = binder.instantiate(tcx, infcx.fresh_args_for_item(infer_span, trait_def_id));
+
+    let trait_ref = trait_ref.fold_with(&mut ReplaceLocalTypesWithInfer {
+        infcx: &infcx,
+        infer_span,
+        did_has_local_parent: &mut did_has_local_parent,
+    });
+
+    let poly_trait_obligation = Obligation::new(
+        tcx,
+        ObligationCause::dummy(),
+        ty::ParamEnv::empty(),
+        Binder::dummy(trait_ref),
+    );
+
+    let ambiguities = compute_applicable_impls_for_diagnostics(&infcx, &poly_trait_obligation);
+
+    let mut it = ambiguities.iter().filter(|ambi| match ambi {
+        CandidateSource::DefId(did) => !did_has_local_parent(*did),
+        CandidateSource::ParamEnv(_) => unreachable!(),
+    });
+
+    let _ = it.next();
+    it.next().is_some()
+}
+
+/// Replace every local type by inference variable.
+///
+/// ```text
+/// <Global<Local> as std::cmp::PartialEq<Global<Local>>>
+/// to
+/// <Global<_> as std::cmp::PartialEq<Global<_>>>
+/// ```
+struct ReplaceLocalTypesWithInfer<'a, 'tcx, F: FnMut(DefId) -> bool> {
+    infcx: &'a InferCtxt<'tcx>,
+    did_has_local_parent: F,
+    infer_span: Span,
+}
+
+impl<'a, 'tcx, F: FnMut(DefId) -> bool> TypeFolder<TyCtxt<'tcx>>
+    for ReplaceLocalTypesWithInfer<'a, 'tcx, F>
+{
+    fn cx(&self) -> TyCtxt<'tcx> {
+        self.infcx.tcx
+    }
+
+    fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
+        if let Some(def) = t.ty_adt_def()
+            && (self.did_has_local_parent)(def.did())
+        {
+            self.infcx.next_ty_var(self.infer_span)
+        } else {
+            t.super_fold_with(self)
+        }
+    }
+}
+
+/// Simple hir::Path collector
+struct PathCollector<'tcx> {
+    paths: Vec<Path<'tcx>>,
+}
+
+impl<'tcx> Visitor<'tcx> for PathCollector<'tcx> {
+    fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) {
+        self.paths.push(path.clone()); // need to clone, bc of the restricted lifetime
+        intravisit::walk_path(self, path)
+    }
+}
+
+/// Given a `Ty` we check if the (outermost) type is local.
+fn ty_has_local_parent(
+    ty_kind: &TyKind<'_>,
+    cx: &LateContext<'_>,
+    impl_parent: DefId,
+    impl_parent_parent: Option<DefId>,
+) -> bool {
+    match ty_kind {
+        TyKind::Path(QPath::Resolved(_, ty_path)) => {
+            path_has_local_parent(ty_path, cx, impl_parent, impl_parent_parent)
+        }
+        TyKind::TraitObject([principle_poly_trait_ref, ..], _, _) => path_has_local_parent(
+            principle_poly_trait_ref.trait_ref.path,
+            cx,
+            impl_parent,
+            impl_parent_parent,
+        ),
+        TyKind::TraitObject([], _, _)
+        | TyKind::InferDelegation(_, _)
+        | TyKind::Slice(_)
+        | TyKind::Array(_, _)
+        | TyKind::Ptr(_)
+        | TyKind::Ref(_, _)
+        | TyKind::BareFn(_)
+        | TyKind::Never
+        | TyKind::Tup(_)
+        | TyKind::Path(_)
+        | TyKind::Pat(..)
+        | TyKind::AnonAdt(_)
+        | TyKind::OpaqueDef(_, _, _)
+        | TyKind::Typeof(_)
+        | TyKind::Infer
+        | TyKind::Err(_) => false,
+    }
+}
+
+/// 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>>
+///    ^^^^^^^^^^^^^^^^^^^^^^^
+/// ```
+#[inline]
+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_has_local_parent(did, cx.tcx, impl_parent, impl_parent_parent))
+}
+
+/// Given a def id and a parent impl def id, this checks if the parent
+/// def id (modulo modules) correspond to the def id of the parent impl definition.
+#[inline]
+fn did_has_local_parent(
+    did: DefId,
+    tcx: TyCtxt<'_>,
+    impl_parent: DefId,
+    impl_parent_parent: Option<DefId>,
+) -> bool {
+    did.is_local()
+        && if let Some(did_parent) = tcx.opt_parent(did) {
+            did_parent == impl_parent
+                || Some(did_parent) == impl_parent_parent
+                || !did_parent.is_crate_root()
+                    && tcx.def_kind(did_parent) == DefKind::Mod
+                    && did_has_local_parent(did_parent, tcx, impl_parent, impl_parent_parent)
+        } else {
+            false
+        }
+}
+
+/// Return for a given `Path` the span until the last args
+fn path_span_without_args(path: &Path<'_>) -> Span {
+    if let Some(args) = &path.segments.last().unwrap().args {
+        path.span.until(args.span_ext)
+    } else {
+        path.span
+    }
+}
+
+/// Return a "error message-able" ident for the last segment of the `Path`
+fn path_name_to_string(path: &Path<'_>) -> String {
+    path.segments.last().unwrap().ident.name.to_ident_string()
+}
+
+/// Compute the `Span` and visual representation for the `Self` we want to point at;
+/// It follows part of the actual logic of non-local, and if possible return the least
+/// amount possible for the span and representation.
+fn self_ty_kind_for_diagnostic(ty: &rustc_hir::Ty<'_>, tcx: TyCtxt<'_>) -> (Span, String) {
+    match ty.kind {
+        TyKind::Path(QPath::Resolved(_, ty_path)) => (
+            path_span_without_args(ty_path),
+            ty_path
+                .res
+                .opt_def_id()
+                .map(|did| tcx.opt_item_name(did))
+                .flatten()
+                .as_ref()
+                .map(|s| Symbol::as_str(s))
+                .unwrap_or("<unnameable>")
+                .to_string(),
+        ),
+        TyKind::TraitObject([principle_poly_trait_ref, ..], _, _) => {
+            let path = &principle_poly_trait_ref.trait_ref.path;
+            (
+                path_span_without_args(path),
+                path.res
+                    .opt_def_id()
+                    .map(|did| tcx.opt_item_name(did))
+                    .flatten()
+                    .as_ref()
+                    .map(|s| Symbol::as_str(s))
+                    .unwrap_or("<unnameable>")
+                    .to_string(),
+            )
+        }
+        _ => (ty.span, rustc_hir_pretty::ty_to_string(&tcx, ty)),
+    }
+}
diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs
new file mode 100644
index 00000000000..d64f4447162
--- /dev/null
+++ b/compiler/rustc_lint/src/nonstandard_style.rs
@@ -0,0 +1,554 @@
+use crate::lints::{
+    NonCamelCaseType, NonCamelCaseTypeSub, NonSnakeCaseDiag, NonSnakeCaseDiagSub,
+    NonUpperCaseGlobal, NonUpperCaseGlobalSub,
+};
+use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_attr as attr;
+use rustc_hir as hir;
+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_session::{declare_lint, declare_lint_pass};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::{BytePos, Span};
+use rustc_target::spec::abi::Abi;
+
+#[derive(PartialEq)]
+pub enum MethodLateContext {
+    TraitAutoImpl,
+    TraitImpl,
+    PlainImpl,
+}
+
+pub fn method_context(cx: &LateContext<'_>, id: LocalDefId) -> MethodLateContext {
+    let item = cx.tcx.associated_item(id);
+    match item.container {
+        ty::TraitContainer => MethodLateContext::TraitAutoImpl,
+        ty::ImplContainer => match cx.tcx.impl_trait_ref(item.container_id(cx.tcx)) {
+            Some(_) => MethodLateContext::TraitImpl,
+            None => MethodLateContext::PlainImpl,
+        },
+    }
+}
+
+fn assoc_item_in_trait_impl(cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) -> bool {
+    let item = cx.tcx.associated_item(ii.owner_id);
+    item.trait_item_def_id.is_some()
+}
+
+declare_lint! {
+    /// The `non_camel_case_types` lint detects types, variants, traits and
+    /// type parameters that don't have camel case names.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// struct my_struct;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The preferred style for these identifiers is to use "camel case", such
+    /// as `MyStruct`, where the first letter should not be lowercase, and
+    /// should not use underscores between letters. Underscores are allowed at
+    /// the beginning and end of the identifier, as well as between
+    /// non-letters (such as `X86_64`).
+    pub NON_CAMEL_CASE_TYPES,
+    Warn,
+    "types, variants, traits and type parameters should have camel case names"
+}
+
+declare_lint_pass!(NonCamelCaseTypes => [NON_CAMEL_CASE_TYPES]);
+
+/// Some unicode characters *have* case, are considered upper case or lower case, but they *can't*
+/// be upper cased or lower cased. For the purposes of the lint suggestion, we care about being able
+/// to change the char's case.
+fn char_has_case(c: char) -> bool {
+    let mut l = c.to_lowercase();
+    let mut u = c.to_uppercase();
+    while let Some(l) = l.next() {
+        match u.next() {
+            Some(u) if l != u => return true,
+            _ => {}
+        }
+    }
+    u.next().is_some()
+}
+
+fn is_camel_case(name: &str) -> bool {
+    let name = name.trim_matches('_');
+    if name.is_empty() {
+        return true;
+    }
+
+    // start with a non-lowercase letter rather than non-uppercase
+    // ones (some scripts don't have a concept of upper/lowercase)
+    !name.chars().next().unwrap().is_lowercase()
+        && !name.contains("__")
+        && !name.chars().collect::<Vec<_>>().array_windows().any(|&[fst, snd]| {
+            // contains a capitalisable character followed by, or preceded by, an underscore
+            char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_'
+        })
+}
+
+fn to_camel_case(s: &str) -> String {
+    s.trim_matches('_')
+        .split('_')
+        .filter(|component| !component.is_empty())
+        .map(|component| {
+            let mut camel_cased_component = String::new();
+
+            let mut new_word = true;
+            let mut prev_is_lower_case = true;
+
+            for c in component.chars() {
+                // Preserve the case if an uppercase letter follows a lowercase letter, so that
+                // `camelCase` is converted to `CamelCase`.
+                if prev_is_lower_case && c.is_uppercase() {
+                    new_word = true;
+                }
+
+                if new_word {
+                    camel_cased_component.extend(c.to_uppercase());
+                } else {
+                    camel_cased_component.extend(c.to_lowercase());
+                }
+
+                prev_is_lower_case = c.is_lowercase();
+                new_word = false;
+            }
+
+            camel_cased_component
+        })
+        .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| {
+            // separate two components with an underscore if their boundary cannot
+            // be distinguished using an uppercase/lowercase case distinction
+            let join = if let Some(prev) = prev {
+                let l = prev.chars().last().unwrap();
+                let f = next.chars().next().unwrap();
+                !char_has_case(l) && !char_has_case(f)
+            } else {
+                false
+            };
+            (acc + if join { "_" } else { "" } + &next, Some(next))
+        })
+        .0
+}
+
+impl NonCamelCaseTypes {
+    fn check_case(&self, cx: &EarlyContext<'_>, sort: &str, ident: &Ident) {
+        let name = ident.name.as_str();
+
+        if !is_camel_case(name) {
+            let cc = to_camel_case(name);
+            let sub = if *name != cc {
+                NonCamelCaseTypeSub::Suggestion { span: ident.span, replace: cc }
+            } else {
+                NonCamelCaseTypeSub::Label { span: ident.span }
+            };
+            cx.emit_span_lint(
+                NON_CAMEL_CASE_TYPES,
+                ident.span,
+                NonCamelCaseType { sort, name, sub },
+            );
+        }
+    }
+}
+
+impl EarlyLintPass for NonCamelCaseTypes {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
+        let has_repr_c = it
+            .attrs
+            .iter()
+            .any(|attr| attr::find_repr_attrs(cx.sess(), attr).contains(&attr::ReprC));
+
+        if has_repr_c {
+            return;
+        }
+
+        match &it.kind {
+            ast::ItemKind::TyAlias(..)
+            | ast::ItemKind::Enum(..)
+            | ast::ItemKind::Struct(..)
+            | ast::ItemKind::Union(..) => self.check_case(cx, "type", &it.ident),
+            ast::ItemKind::Trait(..) => self.check_case(cx, "trait", &it.ident),
+            ast::ItemKind::TraitAlias(..) => self.check_case(cx, "trait alias", &it.ident),
+
+            // N.B. This check is only for inherent associated types, so that we don't lint against
+            // trait impls where we should have warned for the trait definition already.
+            ast::ItemKind::Impl(box ast::Impl { of_trait: None, items, .. }) => {
+                for it in items {
+                    // FIXME: this doesn't respect `#[allow(..)]` on the item itself.
+                    if let ast::AssocItemKind::Type(..) = it.kind {
+                        self.check_case(cx, "associated type", &it.ident);
+                    }
+                }
+            }
+            _ => (),
+        }
+    }
+
+    fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
+        if let ast::AssocItemKind::Type(..) = it.kind {
+            self.check_case(cx, "associated type", &it.ident);
+        }
+    }
+
+    fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
+        self.check_case(cx, "variant", &v.ident);
+    }
+
+    fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) {
+        if let ast::GenericParamKind::Type { .. } = param.kind {
+            self.check_case(cx, "type parameter", &param.ident);
+        }
+    }
+}
+
+declare_lint! {
+    /// The `non_snake_case` lint detects variables, methods, functions,
+    /// lifetime parameters and modules that don't have snake case names.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// let MY_VALUE = 5;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The preferred style for these identifiers is to use "snake case",
+    /// where all the characters are in lowercase, with words separated with a
+    /// single underscore, such as `my_value`.
+    pub NON_SNAKE_CASE,
+    Warn,
+    "variables, methods, functions, lifetime parameters and modules should have snake case names"
+}
+
+declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]);
+
+impl NonSnakeCase {
+    fn to_snake_case(mut str: &str) -> String {
+        let mut words = vec![];
+        // Preserve leading underscores
+        str = str.trim_start_matches(|c: char| {
+            if c == '_' {
+                words.push(String::new());
+                true
+            } else {
+                false
+            }
+        });
+        for s in str.split('_') {
+            let mut last_upper = false;
+            let mut buf = String::new();
+            if s.is_empty() {
+                continue;
+            }
+            for ch in s.chars() {
+                if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
+                    words.push(buf);
+                    buf = String::new();
+                }
+                last_upper = ch.is_uppercase();
+                buf.extend(ch.to_lowercase());
+            }
+            words.push(buf);
+        }
+        words.join("_")
+    }
+
+    /// Checks if a given identifier is snake case, and reports a diagnostic if not.
+    fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) {
+        fn is_snake_case(ident: &str) -> bool {
+            if ident.is_empty() {
+                return true;
+            }
+            let ident = ident.trim_start_matches('\'');
+            let ident = ident.trim_matches('_');
+
+            let mut allow_underscore = true;
+            ident.chars().all(|c| {
+                allow_underscore = match c {
+                    '_' if !allow_underscore => return false,
+                    '_' => false,
+                    // It would be more obvious to use `c.is_lowercase()`,
+                    // but some characters do not have a lowercase form
+                    c if !c.is_uppercase() => true,
+                    _ => return false,
+                };
+                true
+            })
+        }
+
+        let name = ident.name.as_str();
+
+        if !is_snake_case(name) {
+            let span = ident.span;
+            let sc = NonSnakeCase::to_snake_case(name);
+            // We cannot provide meaningful suggestions
+            // if the characters are in the category of "Uppercase Letter".
+            let sub = if name != sc {
+                // We have a valid span in almost all cases, but we don't have one when linting a
+                // crate name provided via the command line.
+                if !span.is_dummy() {
+                    let sc_ident = Ident::from_str_and_span(&sc, span);
+                    if sc_ident.is_reserved() {
+                        // We shouldn't suggest a reserved identifier to fix non-snake-case
+                        // identifiers. Instead, recommend renaming the identifier entirely or, if
+                        // permitted, escaping it to create a raw identifier.
+                        if sc_ident.name.can_be_raw() {
+                            NonSnakeCaseDiagSub::RenameOrConvertSuggestion {
+                                span,
+                                suggestion: sc_ident,
+                            }
+                        } else {
+                            NonSnakeCaseDiagSub::SuggestionAndNote { span }
+                        }
+                    } else {
+                        NonSnakeCaseDiagSub::ConvertSuggestion { span, suggestion: sc.clone() }
+                    }
+                } else {
+                    NonSnakeCaseDiagSub::Help
+                }
+            } else {
+                NonSnakeCaseDiagSub::Label { span }
+            };
+            cx.emit_span_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub });
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
+    fn check_mod(&mut self, cx: &LateContext<'_>, _: &'tcx hir::Mod<'tcx>, id: hir::HirId) {
+        if id != hir::CRATE_HIR_ID {
+            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 {
+            attr::find_by_name(cx.tcx.hir().attrs(hir::CRATE_HIR_ID), sym::crate_name)
+                .and_then(|attr| attr.meta())
+                .and_then(|meta| {
+                    meta.name_value_literal().and_then(|lit| {
+                        if let ast::LitKind::Str(name, ..) = lit.kind {
+                            // Discard the double quotes surrounding the literal.
+                            let sp = cx
+                                .sess()
+                                .source_map()
+                                .span_to_snippet(lit.span)
+                                .ok()
+                                .and_then(|snippet| {
+                                    let left = snippet.find('"')?;
+                                    let right =
+                                        snippet.rfind('"').map(|pos| snippet.len() - pos)?;
+
+                                    Some(
+                                        lit.span
+                                            .with_lo(lit.span.lo() + BytePos(left as u32 + 1))
+                                            .with_hi(lit.span.hi() - BytePos(right as u32)),
+                                    )
+                                })
+                                .unwrap_or(lit.span);
+
+                            Some(Ident::new(name, sp))
+                        } else {
+                            None
+                        }
+                    })
+                })
+        };
+
+        if let Some(ident) = &crate_ident {
+            self.check_snake_case(cx, "crate", ident);
+        }
+    }
+
+    fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
+        if let GenericParamKind::Lifetime { .. } = param.kind {
+            self.check_snake_case(cx, "lifetime", &param.name.ident());
+        }
+    }
+
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'_>,
+        fk: FnKind<'_>,
+        _: &hir::FnDecl<'_>,
+        _: &hir::Body<'_>,
+        _: Span,
+        id: LocalDefId,
+    ) {
+        match &fk {
+            FnKind::Method(ident, sig, ..) => match method_context(cx, id) {
+                MethodLateContext::PlainImpl => {
+                    if sig.header.abi != Abi::Rust && cx.tcx.has_attr(id, sym::no_mangle) {
+                        return;
+                    }
+                    self.check_snake_case(cx, "method", ident);
+                }
+                MethodLateContext::TraitAutoImpl => {
+                    self.check_snake_case(cx, "trait method", ident);
+                }
+                _ => (),
+            },
+            FnKind::ItemFn(ident, _, header) => {
+                // Skip foreign-ABI #[no_mangle] functions (Issue #31924)
+                if header.abi != Abi::Rust && cx.tcx.has_attr(id, sym::no_mangle) {
+                    return;
+                }
+                self.check_snake_case(cx, "function", ident);
+            }
+            FnKind::Closure => (),
+        }
+    }
+
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        if let hir::ItemKind::Mod(_) = it.kind {
+            self.check_snake_case(cx, "module", &it.ident);
+        }
+    }
+
+    fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) {
+        if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind {
+            self.check_snake_case(cx, "trait method", &item.ident);
+            for param_name in pnames {
+                self.check_snake_case(cx, "variable", param_name);
+            }
+        }
+    }
+
+    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.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.
+                    self.check_snake_case(cx, "variable", &ident);
+                }
+                return;
+            }
+            self.check_snake_case(cx, "variable", &ident);
+        }
+    }
+
+    fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) {
+        for sf in s.fields() {
+            self.check_snake_case(cx, "structure field", &sf.ident);
+        }
+    }
+}
+
+declare_lint! {
+    /// The `non_upper_case_globals` lint detects static items that don't have
+    /// uppercase identifiers.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// static max_points: i32 = 5;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The preferred style is for static item names to use all uppercase
+    /// letters such as `MAX_POINTS`.
+    pub NON_UPPER_CASE_GLOBALS,
+    Warn,
+    "static constants should have uppercase identifiers"
+}
+
+declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]);
+
+impl NonUpperCaseGlobals {
+    fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) {
+        let name = ident.name.as_str();
+        if name.chars().any(|c| c.is_lowercase()) {
+            let uc = NonSnakeCase::to_snake_case(name).to_uppercase();
+            // We cannot provide meaningful suggestions
+            // if the characters are in the category of "Lowercase Letter".
+            let sub = if *name != uc {
+                NonUpperCaseGlobalSub::Suggestion { span: ident.span, replace: uc }
+            } else {
+                NonUpperCaseGlobalSub::Label { span: ident.span }
+            };
+            cx.emit_span_lint(
+                NON_UPPER_CASE_GLOBALS,
+                ident.span,
+                NonUpperCaseGlobal { sort, name, sub },
+            );
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        let attrs = cx.tcx.hir().attrs(it.hir_id());
+        match it.kind {
+            hir::ItemKind::Static(..) if !attr::contains_name(attrs, sym::no_mangle) => {
+                NonUpperCaseGlobals::check_upper_case(cx, "static variable", &it.ident);
+            }
+            hir::ItemKind::Const(..) => {
+                NonUpperCaseGlobals::check_upper_case(cx, "constant", &it.ident);
+            }
+            _ => {}
+        }
+    }
+
+    fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) {
+        if let hir::TraitItemKind::Const(..) = ti.kind {
+            NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ti.ident);
+        }
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) {
+        if let hir::ImplItemKind::Const(..) = ii.kind
+            && !assoc_item_in_trait_impl(cx, ii)
+        {
+            NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ii.ident);
+        }
+    }
+
+    fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
+        // Lint for constants that look like binding identifiers (#7526)
+        if let PatKind::Path(hir::QPath::Resolved(None, path)) = p.kind {
+            if let Res::Def(DefKind::Const, _) = path.res {
+                if path.segments.len() == 1 {
+                    NonUpperCaseGlobals::check_upper_case(
+                        cx,
+                        "constant in pattern",
+                        &path.segments[0].ident,
+                    );
+                }
+            }
+        }
+    }
+
+    fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
+        if let GenericParamKind::Const { is_host_effect, .. } = param.kind {
+            // `host` params are explicitly allowed to be lowercase.
+            if is_host_effect {
+                return;
+            }
+            NonUpperCaseGlobals::check_upper_case(cx, "const parameter", &param.name.ident());
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/compiler/rustc_lint/src/nonstandard_style/tests.rs b/compiler/rustc_lint/src/nonstandard_style/tests.rs
new file mode 100644
index 00000000000..39c525b8623
--- /dev/null
+++ b/compiler/rustc_lint/src/nonstandard_style/tests.rs
@@ -0,0 +1,21 @@
+use super::{is_camel_case, to_camel_case};
+
+#[test]
+fn camel_case() {
+    assert!(!is_camel_case("userData"));
+    assert_eq!(to_camel_case("userData"), "UserData");
+
+    assert!(is_camel_case("X86_64"));
+
+    assert!(!is_camel_case("X86__64"));
+    assert_eq!(to_camel_case("X86__64"), "X86_64");
+
+    assert!(!is_camel_case("Abc_123"));
+    assert_eq!(to_camel_case("Abc_123"), "Abc123");
+
+    assert!(!is_camel_case("A1_b2_c3"));
+    assert_eq!(to_camel_case("A1_b2_c3"), "A1B2C3");
+
+    assert!(!is_camel_case("ONE_TWO_THREE"));
+    assert_eq!(to_camel_case("ONE_TWO_THREE"), "OneTwoThree");
+}
diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs
new file mode 100644
index 00000000000..307e4bebe9a
--- /dev/null
+++ b/compiler/rustc_lint/src/noop_method_call.rs
@@ -0,0 +1,161 @@
+use crate::context::LintContext;
+use crate::lints::{
+    NoopMethodCallDiag, SuspiciousDoubleRefCloneDiag, SuspiciousDoubleRefDerefDiag,
+};
+use crate::LateContext;
+use crate::LateLintPass;
+use rustc_hir::def::DefKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_middle::ty;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::symbol::sym;
+
+declare_lint! {
+    /// The `noop_method_call` lint detects specific calls to noop methods
+    /// such as a calling `<&T as Clone>::clone` where `T: !Clone`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # #![allow(unused)]
+    /// struct Foo;
+    /// let foo = &Foo;
+    /// let clone: &Foo = foo.clone();
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Some method calls are noops meaning that they do nothing. Usually such methods
+    /// are the result of blanket implementations that happen to create some method invocations
+    /// that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but
+    /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything
+    /// as references are copy. This lint detects these calls and warns the user about them.
+    pub NOOP_METHOD_CALL,
+    Warn,
+    "detects the use of well-known noop methods"
+}
+
+declare_lint! {
+    /// The `suspicious_double_ref_op` lint checks for usage of `.clone()`/`.borrow()`/`.deref()`
+    /// on an `&&T` when `T: !Deref/Borrow/Clone`, which means the call will return the inner `&T`,
+    /// instead of performing the operation on the underlying `T` and can be confusing.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # #![allow(unused)]
+    /// struct Foo;
+    /// let foo = &&Foo;
+    /// let clone: &Foo = foo.clone();
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Since `Foo` doesn't implement `Clone`, running `.clone()` only dereferences the double
+    /// reference, instead of cloning the inner type which should be what was intended.
+    pub SUSPICIOUS_DOUBLE_REF_OP,
+    Warn,
+    "suspicious call of trait method on `&&T`"
+}
+
+declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]);
+
+impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        // We only care about method calls.
+        let ExprKind::MethodCall(call, receiver, _, call_span) = &expr.kind else {
+            return;
+        };
+
+        if call_span.from_expansion() {
+            return;
+        }
+
+        // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
+        // traits and ignore any other method call.
+
+        let Some((DefKind::AssocFn, did)) = cx.typeck_results().type_dependent_def(expr.hir_id)
+        else {
+            return;
+        };
+
+        let Some(trait_id) = cx.tcx.trait_of_item(did) else { return };
+
+        let Some(trait_) = cx.tcx.get_diagnostic_name(trait_id) else { return };
+
+        if !matches!(trait_, sym::Borrow | sym::Clone | sym::Deref) {
+            return;
+        };
+
+        let args = cx
+            .tcx
+            .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_args(expr.hir_id));
+        // Resolve the trait method instance.
+        let Ok(Some(i)) = ty::Instance::try_resolve(cx.tcx, cx.param_env, did, args) else {
+            return;
+        };
+        // (Re)check that it implements the noop diagnostic.
+        let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return };
+        if !matches!(
+            name,
+            sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref
+        ) {
+            return;
+        }
+
+        let receiver_ty = cx.typeck_results().expr_ty(receiver);
+        let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
+        let arg_adjustments = cx.typeck_results().expr_adjustments(receiver);
+
+        // If there is any user defined auto-deref step, then we don't want to warn.
+        // https://github.com/rust-lang/rust-clippy/issues/9272
+        if arg_adjustments.iter().any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) {
+            return;
+        }
+
+        let expr_span = expr.span;
+        let span = expr_span.with_lo(receiver.span.hi());
+
+        let orig_ty = expr_ty.peel_refs();
+
+        if receiver_ty == expr_ty {
+            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,
+                    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_span_lint(
+                    SUSPICIOUS_DOUBLE_REF_OP,
+                    span,
+                    SuspiciousDoubleRefCloneDiag { ty: expr_ty },
+                ),
+                sym::noop_method_deref => cx.emit_span_lint(
+                    SUSPICIOUS_DOUBLE_REF_OP,
+                    span,
+                    SuspiciousDoubleRefDerefDiag { ty: expr_ty },
+                ),
+                _ => return,
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs
new file mode 100644
index 00000000000..fdb71ad41a7
--- /dev/null
+++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs
@@ -0,0 +1,209 @@
+use rustc_hir as hir;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_macros::{LintDiagnostic, Subdiagnostic};
+use rustc_middle::ty::print::{PrintTraitPredicateExt as _, TraitPredPrintModifiersAndPath};
+use rustc_middle::ty::{self, fold::BottomUpFolder, Ty, TypeFoldable};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::{symbol::kw, Span};
+use rustc_trait_selection::traits::{self, ObligationCtxt};
+
+use crate::{LateContext, LateLintPass, LintContext};
+
+declare_lint! {
+    /// The `opaque_hidden_inferred_bound` lint detects cases in which nested
+    /// `impl Trait` in associated type bounds are not written generally enough
+    /// to satisfy the bounds of the associated type.
+    ///
+    /// ### Explanation
+    ///
+    /// This functionality was removed in #97346, but then rolled back in #99860
+    /// because it caused regressions.
+    ///
+    /// We plan on reintroducing this as a hard error, but in the meantime,
+    /// this lint serves to warn and suggest fixes for any use-cases which rely
+    /// on this behavior.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![feature(type_alias_impl_trait)]
+    ///
+    /// trait Duh {}
+    ///
+    /// impl Duh for i32 {}
+    ///
+    /// trait Trait {
+    ///     type Assoc: Duh;
+    /// }
+    ///
+    /// impl<F: Duh> Trait for F {
+    ///     type Assoc = F;
+    /// }
+    ///
+    /// type Tait = impl Sized;
+    ///
+    /// fn test() -> impl Trait<Assoc = Tait> {
+    ///     42
+    /// }
+    ///
+    /// fn main() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// In this example, `test` declares that the associated type `Assoc` for
+    /// `impl Trait` is `impl Sized`, which does not satisfy the bound `Duh`
+    /// on the associated type.
+    ///
+    /// Although the hidden type, `i32` does satisfy this bound, we do not
+    /// consider the return type to be well-formed with this lint. It can be
+    /// fixed by changing `Tait = impl Sized` into `Tait = impl Sized + Duh`.
+    pub OPAQUE_HIDDEN_INFERRED_BOUND,
+    Warn,
+    "detects the use of nested `impl Trait` types in associated type bounds that are not general enough"
+}
+
+declare_lint_pass!(OpaqueHiddenInferredBound => [OPAQUE_HIDDEN_INFERRED_BOUND]);
+
+impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        let hir::ItemKind::OpaqueTy(opaque) = &item.kind else {
+            return;
+        };
+        let def_id = item.owner_id.def_id.to_def_id();
+        let infcx = &cx.tcx.infer_ctxt().build();
+        // For every projection predicate in the opaque type's explicit bounds,
+        // check that the type that we're assigning actually satisfies the bounds
+        // of the associated type.
+        for (pred, pred_span) in cx.tcx.explicit_item_bounds(def_id).iter_identity_copied() {
+            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.as_type() 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: `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_from_args(
+                    cx.tcx,
+                    proj.projection_term.def_id,
+                    proj.projection_term.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_term.def_id)
+                    .iter_instantiated_copied(cx.tcx, proj.projection_term.args)
+                {
+                    let assoc_pred = assoc_pred.fold_with(proj_replacer);
+
+                    let ocx = ObligationCtxt::new(infcx);
+                    let assoc_pred =
+                        ocx.normalize(&traits::ObligationCause::dummy(), cx.param_env, assoc_pred);
+                    if !ocx.select_all_or_error().is_empty() {
+                        // Can't normalize for some reason...?
+                        continue;
+                    }
+
+                    ocx.register_obligation(traits::Obligation::new(
+                        cx.tcx,
+                        traits::ObligationCause::dummy(),
+                        cx.param_env,
+                        assoc_pred,
+                    ));
+
+                    // 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 !ocx.select_all_or_error().is_empty() {
+                        // 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,
+                            },
+                        );
+                    }
+                }
+            });
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_opaque_hidden_inferred_bound)]
+struct OpaqueHiddenInferredBoundLint<'tcx> {
+    ty: Ty<'tcx>,
+    proj_ty: Ty<'tcx>,
+    #[label(lint_specifically)]
+    assoc_pred_span: Span,
+    #[subdiagnostic]
+    add_bound: Option<AddBound<'tcx>>,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    lint_opaque_hidden_inferred_bound_sugg,
+    style = "verbose",
+    applicability = "machine-applicable",
+    code = " + {trait_ref}"
+)]
+struct AddBound<'tcx> {
+    #[primary_span]
+    suggest_span: Span,
+    #[skip_arg]
+    trait_ref: TraitPredPrintModifiersAndPath<'tcx>,
+}
diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs
new file mode 100644
index 00000000000..c1f5cd45dc8
--- /dev/null
+++ b/compiler/rustc_lint/src/pass_by_value.rs
@@ -0,0 +1,93 @@
+use crate::lints::PassByValueDiag;
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{GenericArg, PathSegment, QPath, TyKind};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_tool_lint! {
+    /// The `rustc_pass_by_value` lint marks a type with `#[rustc_pass_by_value]` requiring it to
+    /// always be passed by value. This is usually used for types that are thin wrappers around
+    /// references, so there is no benefit to an extra layer of indirection. (Example: `Ty` which
+    /// is a reference to an `Interned<TyKind>`)
+    pub rustc::PASS_BY_VALUE,
+    Warn,
+    "pass by reference of a type flagged as `#[rustc_pass_by_value]`",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(PassByValue => [PASS_BY_VALUE]);
+
+impl<'tcx> LateLintPass<'tcx> for PassByValue {
+    fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) {
+        match &ty.kind {
+            TyKind::Ref(_, hir::MutTy { ty: inner_ty, mutbl: hir::Mutability::Not }) => {
+                if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
+                    if cx.tcx.impl_trait_ref(impl_did).is_some() {
+                        return;
+                    }
+                }
+                if let Some(t) = path_for_pass_by_value(cx, inner_ty) {
+                    cx.emit_span_lint(
+                        PASS_BY_VALUE,
+                        ty.span,
+                        PassByValueDiag { ty: t, suggestion: ty.span },
+                    );
+                }
+            }
+            _ => {}
+        }
+    }
+}
+
+fn path_for_pass_by_value(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Option<String> {
+    if let TyKind::Path(QPath::Resolved(_, path)) = &ty.kind {
+        match path.res {
+            Res::Def(_, def_id) if cx.tcx.has_attr(def_id, sym::rustc_pass_by_value) => {
+                let name = cx.tcx.item_name(def_id).to_ident_string();
+                let path_segment = path.segments.last().unwrap();
+                return Some(format!("{}{}", name, gen_args(cx, path_segment)));
+            }
+            Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
+                if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() {
+                    if cx.tcx.has_attr(adt.did(), sym::rustc_pass_by_value) {
+                        return Some(cx.tcx.def_path_str_with_args(adt.did(), args));
+                    }
+                }
+            }
+            _ => (),
+        }
+    }
+
+    None
+}
+
+fn gen_args(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> String {
+    if let Some(args) = &segment.args {
+        let params = args
+            .args
+            .iter()
+            .map(|arg| match arg {
+                GenericArg::Lifetime(lt) => lt.to_string(),
+                GenericArg::Type(ty) => {
+                    cx.tcx.sess.source_map().span_to_snippet(ty.span).unwrap_or_else(|_| "_".into())
+                }
+                GenericArg::Const(c) => cx
+                    .tcx
+                    .sess
+                    .source_map()
+                    .span_to_snippet(c.value.span)
+                    .unwrap_or_else(|_| "_".into()),
+                GenericArg::Infer(_) => String::from("_"),
+            })
+            .collect::<Vec<_>>();
+
+        if !params.is_empty() {
+            return format!("<{}>", params.join(", "));
+        }
+    }
+
+    String::new()
+}
diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs
new file mode 100644
index 00000000000..2a843977990
--- /dev/null
+++ b/compiler/rustc_lint/src/passes.rs
@@ -0,0 +1,241 @@
+use crate::context::{EarlyContext, LateContext};
+
+use rustc_session::lint::builtin::HardwiredLints;
+use rustc_session::lint::LintPass;
+
+#[macro_export]
+macro_rules! late_lint_methods {
+    ($macro:path, $args:tt) => (
+        $macro!($args, [
+            fn check_body(a: &rustc_hir::Body<'tcx>);
+            fn check_body_post(a: &rustc_hir::Body<'tcx>);
+            fn check_crate();
+            fn check_crate_post();
+            fn check_mod(a: &'tcx rustc_hir::Mod<'tcx>, b: rustc_hir::HirId);
+            fn check_foreign_item(a: &'tcx rustc_hir::ForeignItem<'tcx>);
+            fn check_item(a: &'tcx rustc_hir::Item<'tcx>);
+            fn check_item_post(a: &'tcx rustc_hir::Item<'tcx>);
+            fn check_local(a: &'tcx rustc_hir::LetStmt<'tcx>);
+            fn check_block(a: &'tcx rustc_hir::Block<'tcx>);
+            fn check_block_post(a: &'tcx rustc_hir::Block<'tcx>);
+            fn check_stmt(a: &'tcx rustc_hir::Stmt<'tcx>);
+            fn check_arm(a: &'tcx rustc_hir::Arm<'tcx>);
+            fn check_pat(a: &'tcx rustc_hir::Pat<'tcx>);
+            fn check_expr(a: &'tcx rustc_hir::Expr<'tcx>);
+            fn check_expr_post(a: &'tcx rustc_hir::Expr<'tcx>);
+            fn check_ty(a: &'tcx rustc_hir::Ty<'tcx>);
+            fn check_generic_param(a: &'tcx rustc_hir::GenericParam<'tcx>);
+            fn check_generics(a: &'tcx rustc_hir::Generics<'tcx>);
+            fn check_poly_trait_ref(a: &'tcx rustc_hir::PolyTraitRef<'tcx>);
+            fn check_fn(
+                a: rustc_hir::intravisit::FnKind<'tcx>,
+                b: &'tcx rustc_hir::FnDecl<'tcx>,
+                c: &'tcx rustc_hir::Body<'tcx>,
+                d: rustc_span::Span,
+                e: rustc_span::def_id::LocalDefId);
+            fn check_trait_item(a: &'tcx rustc_hir::TraitItem<'tcx>);
+            fn check_impl_item(a: &'tcx rustc_hir::ImplItem<'tcx>);
+            fn check_impl_item_post(a: &'tcx rustc_hir::ImplItem<'tcx>);
+            fn check_struct_def(a: &'tcx rustc_hir::VariantData<'tcx>);
+            fn check_field_def(a: &'tcx rustc_hir::FieldDef<'tcx>);
+            fn check_variant(a: &'tcx rustc_hir::Variant<'tcx>);
+            fn check_path(a: &rustc_hir::Path<'tcx>, b: rustc_hir::HirId);
+            fn check_attribute(a: &'tcx rustc_ast::Attribute);
+            fn check_attributes(a: &'tcx [rustc_ast::Attribute]);
+            fn check_attributes_post(a: &'tcx [rustc_ast::Attribute]);
+        ]);
+    )
+}
+
+/// Trait for types providing lint checks.
+///
+/// Each `check` method checks a single syntax node, and should not
+/// invoke methods recursively (unlike `Visitor`). By default they
+/// do nothing.
+//
+// FIXME: eliminate the duplication with `Visitor`. But this also
+// contains a few lint-specific methods with no equivalent in `Visitor`.
+
+macro_rules! declare_late_lint_pass {
+    ([], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        pub trait LateLintPass<'tcx>: LintPass {
+            $(#[inline(always)] fn $name(&mut self, _: &LateContext<'tcx>, $(_: $arg),*) {})*
+        }
+    )
+}
+
+// Declare the `LateLintPass` trait, which contains empty default definitions
+// for all the `check_*` methods.
+late_lint_methods!(declare_late_lint_pass, []);
+
+impl LateLintPass<'_> for HardwiredLints {}
+
+#[macro_export]
+macro_rules! expand_combined_late_lint_pass_method {
+    ([$($pass:ident),*], $self: ident, $name: ident, $params:tt) => ({
+        $($self.$pass.$name $params;)*
+    })
+}
+
+#[macro_export]
+macro_rules! expand_combined_late_lint_pass_methods {
+    ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        $(fn $name(&mut self, context: &$crate::LateContext<'tcx>, $($param: $arg),*) {
+            $crate::expand_combined_late_lint_pass_method!($passes, self, $name, (context, $($param),*));
+        })*
+    )
+}
+
+/// Combines multiple lints passes into a single lint pass, at compile time,
+/// for maximum speed. Each `check_foo` method in `$methods` within this pass
+/// simply calls `check_foo` once per `$pass`. Compare with
+/// `LateLintPassObjects`, which is similar, but combines lint passes at
+/// runtime.
+#[macro_export]
+macro_rules! declare_combined_late_lint_pass {
+    ([$v:vis $name:ident, [$($pass:ident: $constructor:expr,)*]], $methods:tt) => (
+        #[allow(non_snake_case)]
+        $v struct $name {
+            $($pass: $pass,)*
+        }
+
+        impl $name {
+            $v fn new() -> Self {
+                Self {
+                    $($pass: $constructor,)*
+                }
+            }
+
+            $v fn get_lints() -> $crate::LintVec {
+                let mut lints = Vec::new();
+                $(lints.extend_from_slice(&$pass::get_lints());)*
+                lints
+            }
+        }
+
+        impl<'tcx> $crate::LateLintPass<'tcx> for $name {
+            $crate::expand_combined_late_lint_pass_methods!([$($pass),*], $methods);
+        }
+
+        #[allow(rustc::lint_pass_impl_without_macro)]
+        impl $crate::LintPass for $name {
+            fn name(&self) -> &'static str {
+                panic!()
+            }
+        }
+    )
+}
+
+#[macro_export]
+macro_rules! early_lint_methods {
+    ($macro:path, $args:tt) => (
+        $macro!($args, [
+            fn check_param(a: &rustc_ast::Param);
+            fn check_ident(a: rustc_span::symbol::Ident);
+            fn check_crate(a: &rustc_ast::Crate);
+            fn check_crate_post(a: &rustc_ast::Crate);
+            fn check_item(a: &rustc_ast::Item);
+            fn check_item_post(a: &rustc_ast::Item);
+            fn check_local(a: &rustc_ast::Local);
+            fn check_block(a: &rustc_ast::Block);
+            fn check_stmt(a: &rustc_ast::Stmt);
+            fn check_arm(a: &rustc_ast::Arm);
+            fn check_pat(a: &rustc_ast::Pat);
+            fn check_pat_post(a: &rustc_ast::Pat);
+            fn check_expr(a: &rustc_ast::Expr);
+            fn check_expr_post(a: &rustc_ast::Expr);
+            fn check_ty(a: &rustc_ast::Ty);
+            fn check_generic_arg(a: &rustc_ast::GenericArg);
+            fn check_generic_param(a: &rustc_ast::GenericParam);
+            fn check_generics(a: &rustc_ast::Generics);
+            fn check_poly_trait_ref(a: &rustc_ast::PolyTraitRef);
+            fn check_fn(
+                a: rustc_ast::visit::FnKind<'_>,
+                c: rustc_span::Span,
+                d_: rustc_ast::NodeId);
+            fn check_trait_item(a: &rustc_ast::AssocItem);
+            fn check_impl_item(a: &rustc_ast::AssocItem);
+            fn check_variant(a: &rustc_ast::Variant);
+            fn check_attribute(a: &rustc_ast::Attribute);
+            fn check_attributes(a: &[rustc_ast::Attribute]);
+            fn check_attributes_post(a: &[rustc_ast::Attribute]);
+            fn check_mac_def(a: &rustc_ast::MacroDef);
+            fn check_mac(a: &rustc_ast::MacCall);
+
+            fn enter_where_predicate(a: &rustc_ast::WherePredicate);
+            fn exit_where_predicate(a: &rustc_ast::WherePredicate);
+        ]);
+    )
+}
+
+macro_rules! declare_early_lint_pass {
+    ([], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        pub trait EarlyLintPass: LintPass {
+            $(#[inline(always)] fn $name(&mut self, _: &EarlyContext<'_>, $(_: $arg),*) {})*
+        }
+    )
+}
+
+// Declare the `EarlyLintPass` trait, which contains empty default definitions
+// for all the `check_*` methods.
+early_lint_methods!(declare_early_lint_pass, []);
+
+#[macro_export]
+macro_rules! expand_combined_early_lint_pass_method {
+    ([$($pass:ident),*], $self: ident, $name: ident, $params:tt) => ({
+        $($self.$pass.$name $params;)*
+    })
+}
+
+#[macro_export]
+macro_rules! expand_combined_early_lint_pass_methods {
+    ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        $(fn $name(&mut self, context: &$crate::EarlyContext<'_>, $($param: $arg),*) {
+            $crate::expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*));
+        })*
+    )
+}
+
+/// Combines multiple lints passes into a single lint pass, at compile time,
+/// for maximum speed. Each `check_foo` method in `$methods` within this pass
+/// simply calls `check_foo` once per `$pass`. Compare with
+/// `EarlyLintPassObjects`, which is similar, but combines lint passes at
+/// runtime.
+#[macro_export]
+macro_rules! declare_combined_early_lint_pass {
+    ([$v:vis $name:ident, [$($pass:ident: $constructor:expr,)*]], $methods:tt) => (
+        #[allow(non_snake_case)]
+        $v struct $name {
+            $($pass: $pass,)*
+        }
+
+        impl $name {
+            $v fn new() -> Self {
+                Self {
+                    $($pass: $constructor,)*
+                }
+            }
+
+            $v fn get_lints() -> $crate::LintVec {
+                let mut lints = Vec::new();
+                $(lints.extend_from_slice(&$pass::get_lints());)*
+                lints
+            }
+        }
+
+        impl $crate::EarlyLintPass for $name {
+            $crate::expand_combined_early_lint_pass_methods!([$($pass),*], $methods);
+        }
+
+        #[allow(rustc::lint_pass_impl_without_macro)]
+        impl $crate::LintPass for $name {
+            fn name(&self) -> &'static str {
+                panic!()
+            }
+        }
+    )
+}
+
+/// A lint pass boxed up as a trait object.
+pub(crate) type EarlyLintPassObject = Box<dyn EarlyLintPass + 'static>;
+pub(crate) type LateLintPassObject<'tcx> = Box<dyn LateLintPass<'tcx> + 'tcx>;
diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs
new file mode 100644
index 00000000000..8038115ef51
--- /dev/null
+++ b/compiler/rustc_lint/src/ptr_nulls.rs
@@ -0,0 +1,157 @@
+use crate::{lints::PtrNullChecksDiag, LateContext, LateLintPass, LintContext};
+use rustc_ast::LitKind;
+use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::sym;
+
+declare_lint! {
+    /// The `useless_ptr_null_checks` lint checks for useless null checks against pointers
+    /// obtained from non-null types.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # fn test() {}
+    /// let fn_ptr: fn() = /* somehow obtained nullable function pointer */
+    /// #   test;
+    ///
+    /// if (fn_ptr as *const ()).is_null() { /* ... */ }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Function pointers and references are assumed to be non-null, checking them for null
+    /// will always return false.
+    USELESS_PTR_NULL_CHECKS,
+    Warn,
+    "useless checking of non-null-typed pointer"
+}
+
+declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS]);
+
+/// This function checks if the expression is from a series of consecutive casts,
+/// ie. `(my_fn as *const _ as *mut _).cast_mut()` and whether the original expression is either
+/// a fn ptr, a reference, or a function call whose definition is
+/// annotated with `#![rustc_never_returns_null_ptr]`.
+/// If this situation is present, the function returns the appropriate diagnostic.
+fn incorrect_check<'a, 'tcx: 'a>(
+    cx: &'a LateContext<'tcx>,
+    mut e: &'a Expr<'a>,
+) -> Option<PtrNullChecksDiag<'tcx>> {
+    let mut had_at_least_one_cast = false;
+    loop {
+        e = e.peel_blocks();
+        if let ExprKind::MethodCall(_, _expr, [], _) = e.kind
+            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
+            && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr)
+            && let Some(fn_name) = cx.tcx.opt_item_ident(def_id)
+        {
+            return Some(PtrNullChecksDiag::FnRet { fn_name });
+        } else if let ExprKind::Call(path, _args) = e.kind
+            && let ExprKind::Path(ref qpath) = path.kind
+            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+            && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr)
+            && let Some(fn_name) = cx.tcx.opt_item_ident(def_id)
+        {
+            return Some(PtrNullChecksDiag::FnRet { fn_name });
+        }
+        e = if let ExprKind::Cast(expr, t) = e.kind
+            && let TyKind::Ptr(_) = t.kind
+        {
+            had_at_least_one_cast = true;
+            expr
+        } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
+            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
+            && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_cast | sym::ptr_cast_mut))
+        {
+            had_at_least_one_cast = true;
+            expr
+        } else if had_at_least_one_cast {
+            let orig_ty = cx.typeck_results().expr_ty(e);
+            return if orig_ty.is_fn() {
+                Some(PtrNullChecksDiag::FnPtr { orig_ty, label: e.span })
+            } else if orig_ty.is_ref() {
+                Some(PtrNullChecksDiag::Ref { orig_ty, label: e.span })
+            } else {
+                None
+            };
+        } else {
+            return None;
+        };
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        match expr.kind {
+            // Catching:
+            // <*<const/mut> <ty>>::is_null(fn_ptr as *<const/mut> <ty>)
+            ExprKind::Call(path, [arg])
+                if let ExprKind::Path(ref qpath) = path.kind
+                    && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+                    && matches!(
+                        cx.tcx.get_diagnostic_name(def_id),
+                        Some(sym::ptr_const_is_null | sym::ptr_is_null)
+                    )
+                    && let Some(diag) = incorrect_check(cx, arg) =>
+            {
+                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
+            }
+
+            // Catching:
+            // (fn_ptr as *<const/mut> <ty>).is_null()
+            ExprKind::MethodCall(_, receiver, _, _)
+                if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
+                    && matches!(
+                        cx.tcx.get_diagnostic_name(def_id),
+                        Some(sym::ptr_const_is_null | sym::ptr_is_null)
+                    )
+                    && let Some(diag) = incorrect_check(cx, receiver) =>
+            {
+                cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
+            }
+
+            ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => {
+                let to_check: &Expr<'_>;
+                let diag: PtrNullChecksDiag<'_>;
+                if let Some(ddiag) = incorrect_check(cx, left) {
+                    to_check = right;
+                    diag = ddiag;
+                } else if let Some(ddiag) = incorrect_check(cx, right) {
+                    to_check = left;
+                    diag = ddiag;
+                } else {
+                    return;
+                }
+
+                match to_check.kind {
+                    // Catching:
+                    // (fn_ptr as *<const/mut> <ty>) == (0 as <ty>)
+                    ExprKind::Cast(cast_expr, _)
+                        if let ExprKind::Lit(spanned) = cast_expr.kind
+                            && let LitKind::Int(v, _) = spanned.node
+                            && v == 0 =>
+                    {
+                        cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag)
+                    }
+
+                    // Catching:
+                    // (fn_ptr as *<const/mut> <ty>) == std::ptr::null()
+                    ExprKind::Call(path, [])
+                        if let ExprKind::Path(ref qpath) = path.kind
+                            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+                            && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
+                            && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) =>
+                    {
+                        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
new file mode 100644
index 00000000000..ef08e79e24a
--- /dev/null
+++ b/compiler/rustc_lint/src/redundant_semicolon.rs
@@ -0,0 +1,57 @@
+use crate::{lints::RedundantSemicolonsDiag, EarlyContext, EarlyLintPass, LintContext};
+use rustc_ast::{Block, StmtKind};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::Span;
+
+declare_lint! {
+    /// The `redundant_semicolons` lint detects unnecessary trailing
+    /// semicolons.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// let _ = 123;;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Extra semicolons are not needed, and may be removed to avoid confusion
+    /// and visual clutter.
+    pub REDUNDANT_SEMICOLONS,
+    Warn,
+    "detects unnecessary trailing semicolons"
+}
+
+declare_lint_pass!(RedundantSemicolons => [REDUNDANT_SEMICOLONS]);
+
+impl EarlyLintPass for RedundantSemicolons {
+    fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
+        let mut seq = None;
+        for stmt in block.stmts.iter() {
+            match (&stmt.kind, &mut seq) {
+                (StmtKind::Empty, None) => seq = Some((stmt.span, false)),
+                (StmtKind::Empty, Some(seq)) => *seq = (seq.0.to(stmt.span), true),
+                (_, seq) => maybe_lint_redundant_semis(cx, seq),
+            }
+        }
+        maybe_lint_redundant_semis(cx, &mut seq);
+    }
+}
+
+fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) {
+    if let Some((span, multiple)) = seq.take() {
+        // FIXME: Find a better way of ignoring the trailing
+        // semicolon from macro expansion
+        if span == rustc_span::DUMMY_SP {
+            return;
+        }
+
+        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
new file mode 100644
index 00000000000..34153e3a220
--- /dev/null
+++ b/compiler/rustc_lint/src/reference_casting.rs
@@ -0,0 +1,281 @@
+use rustc_ast::Mutability;
+use rustc_hir::{Expr, ExprKind, UnOp};
+use rustc_middle::ty::layout::LayoutOf as _;
+use rustc_middle::ty::{self, layout::TyAndLayout};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::sym;
+
+use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext};
+
+declare_lint! {
+    /// The `invalid_reference_casting` lint checks for casts of `&T` to `&mut T`
+    /// without using interior mutability.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// fn x(r: &i32) {
+    ///     unsafe {
+    ///         *(r as *const i32 as *mut i32) += 1;
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Casting `&T` to `&mut T` without using interior mutability is undefined behavior,
+    /// as it's a violation of Rust reference aliasing requirements.
+    ///
+    /// `UnsafeCell` is the only way to obtain aliasable data that is considered
+    /// mutable.
+    INVALID_REFERENCE_CASTING,
+    Deny,
+    "casts of `&T` to `&mut T` without interior mutability"
+}
+
+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) {
+            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))
+            };
+
+            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_span_lint(
+                    INVALID_REFERENCE_CASTING,
+                    expr.span,
+                    if pat == PatternKind::Assign {
+                        InvalidReferenceCastingDiag::AssignToRef {
+                            orig_cast,
+                            ty_has_interior_mutability,
+                        }
+                    } else {
+                        InvalidReferenceCastingDiag::BorrowAsMut {
+                            orig_cast,
+                            ty_has_interior_mutability,
+                        }
+                    },
+                );
+            }
+
+            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(),
+                    },
+                );
+            }
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum PatternKind {
+    Borrow { mutbl: Mutability },
+    Assign,
+}
+
+fn borrow_or_assign<'tcx>(
+    cx: &LateContext<'tcx>,
+    e: &'tcx Expr<'tcx>,
+) -> Option<(&'tcx Expr<'tcx>, PatternKind)> {
+    fn deref_assign_or_addr_of<'tcx>(
+        expr: &'tcx Expr<'tcx>,
+    ) -> Option<(&'tcx Expr<'tcx>, PatternKind)> {
+        // &(mut) <expr>
+        let (inner, pat) = if let ExprKind::AddrOf(_, mutbl, expr) = expr.kind {
+            (expr, PatternKind::Borrow { mutbl })
+        // <expr> = ...
+        } else if let ExprKind::Assign(expr, _, _) = expr.kind {
+            (expr, PatternKind::Assign)
+        // <expr> += ...
+        } else if let ExprKind::AssignOp(_, expr, _) = expr.kind {
+            (expr, PatternKind::Assign)
+        } else {
+            return None;
+        };
+
+        // *<inner>
+        let ExprKind::Unary(UnOp::Deref, e) = &inner.kind else {
+            return None;
+        };
+        Some((e, pat))
+    }
+
+    fn ptr_write<'tcx>(
+        cx: &LateContext<'tcx>,
+        e: &'tcx Expr<'tcx>,
+    ) -> Option<(&'tcx Expr<'tcx>, PatternKind)> {
+        if let ExprKind::Call(path, [arg_ptr, _arg_val]) = e.kind
+            && let ExprKind::Path(ref qpath) = path.kind
+            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+            && matches!(
+                cx.tcx.get_diagnostic_name(def_id),
+                Some(sym::ptr_write | sym::ptr_write_volatile | sym::ptr_write_unaligned)
+            )
+        {
+            Some((arg_ptr, PatternKind::Assign))
+        } else {
+            None
+        }
+    }
+
+    deref_assign_or_addr_of(e).or_else(|| ptr_write(cx, e))
+}
+
+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);
+
+    // Bail out early if the end type is **not** a mutable pointer.
+    if !matches!(end_ty.kind(), ty::RawPtr(_, Mutability::Mut)) {
+        return None;
+    }
+
+    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() {
+        // If an UnsafeCell method is involved, we need to additionally check the
+        // inner type for the presence of the Freeze trait (ie does NOT contain
+        // an UnsafeCell), since in that case we would incorrectly lint on valid casts.
+        //
+        // Except on the presence of non concrete skeleton types (ie generics)
+        // since there is no way to make it safe for arbitrary types.
+        let inner_ty_has_interior_mutability =
+            !inner_ty.is_freeze(cx.tcx, cx.param_env) && inner_ty.has_concrete_skeleton();
+        (!need_check_freeze || !inner_ty_has_interior_mutability)
+            .then_some(inner_ty_has_interior_mutability)
+    } else {
+        None
+    }
+}
+
+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(inner_end_ty, _) = 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 };
+
+    // if the current expr looks like this `&mut expr[index]` then just looking
+    // at `expr[index]` won't give us the underlying allocation, so we just skip it
+    // the same logic applies field access `&mut expr.field` and reborrows `&mut *expr`.
+    if let ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(UnOp::Deref, ..) =
+        e_alloc.kind
+    {
+        return None;
+    }
+
+    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;
+
+    loop {
+        e = e.peel_blocks();
+        // <expr> as ...
+        e = if let ExprKind::Cast(expr, _) = e.kind {
+            expr
+        // <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const()
+        } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
+            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
+            && matches!(
+                cx.tcx.get_diagnostic_name(def_id),
+                Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const)
+            )
+        {
+            expr
+        // ptr::from_ref(<expr>), UnsafeCell::raw_get(<expr>) or mem::transmute<_, _>(<expr>)
+        } else if let ExprKind::Call(path, [arg]) = e.kind
+            && let ExprKind::Path(ref qpath) = path.kind
+            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+            && matches!(
+                cx.tcx.get_diagnostic_name(def_id),
+                Some(sym::ptr_from_ref | sym::unsafe_cell_raw_get | sym::transmute)
+            )
+        {
+            if cx.tcx.is_diagnostic_item(sym::unsafe_cell_raw_get, def_id) {
+                gone_trough_unsafe_cell_raw_get = true;
+            }
+            arg
+        } else {
+            let init = cx.expr_or_init(e);
+            if init.hir_id != e.hir_id {
+                init
+            } else {
+                break;
+            }
+        };
+    }
+
+    (e, gone_trough_unsafe_cell_raw_get)
+}
diff --git a/compiler/rustc_lint/src/shadowed_into_iter.rs b/compiler/rustc_lint/src/shadowed_into_iter.rs
new file mode 100644
index 00000000000..da2b5878b19
--- /dev/null
+++ b/compiler/rustc_lint/src/shadowed_into_iter.rs
@@ -0,0 +1,157 @@
+use crate::lints::{ShadowedIntoIterDiag, ShadowedIntoIterDiagSub};
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_hir as hir;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::lint::FutureIncompatibilityReason;
+use rustc_session::{declare_lint, impl_lint_pass};
+use rustc_span::edition::Edition;
+
+declare_lint! {
+    /// The `array_into_iter` lint detects calling `into_iter` on arrays.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2018
+    /// # #![allow(unused)]
+    /// [1, 2, 3].into_iter().for_each(|n| { *n; });
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid
+    /// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still
+    /// behave as `(&array).into_iter()`, returning an iterator over
+    /// references, just like in Rust 1.52 and earlier.
+    /// This only applies to the method call syntax `array.into_iter()`, not to
+    /// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`.
+    pub ARRAY_INTO_ITER,
+    Warn,
+    "detects calling `into_iter` on arrays in Rust 2015 and 2018",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
+        reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
+    };
+}
+
+declare_lint! {
+    /// The `boxed_slice_into_iter` lint detects calling `into_iter` on boxed slices.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2021
+    /// # #![allow(unused)]
+    /// vec![1, 2, 3].into_boxed_slice().into_iter().for_each(|n| { *n; });
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Since Rust 1.80.0, boxed slices implement `IntoIterator`. However, to avoid
+    /// breakage, `boxed_slice.into_iter()` in Rust 2015, 2018, and 2021 code will still
+    /// behave as `(&boxed_slice).into_iter()`, returning an iterator over
+    /// references, just like in Rust 1.79.0 and earlier.
+    /// This only applies to the method call syntax `boxed_slice.into_iter()`, not to
+    /// any other syntax such as `for _ in boxed_slice` or `IntoIterator::into_iter(boxed_slice)`.
+    pub BOXED_SLICE_INTO_ITER,
+    Warn,
+    "detects calling `into_iter` on boxed slices in Rust 2015, 2018, and 2021",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
+    };
+}
+
+#[derive(Copy, Clone)]
+pub struct ShadowedIntoIter;
+
+impl_lint_pass!(ShadowedIntoIter => [ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER]);
+
+impl<'tcx> LateLintPass<'tcx> for ShadowedIntoIter {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+        let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind else {
+            return;
+        };
+
+        // Check if the method call actually calls the libcore
+        // `IntoIterator::into_iter`.
+        let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
+            return;
+        };
+        if Some(method_def_id) != cx.tcx.lang_items().into_iter_fn() {
+            return;
+        }
+
+        // As this is a method call expression, we have at least one argument.
+        let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
+        let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
+
+        let adjusted_receiver_tys: Vec<_> =
+            [receiver_ty].into_iter().chain(adjustments.iter().map(|adj| adj.target)).collect();
+
+        fn is_ref_to_array(ty: Ty<'_>) -> bool {
+            if let ty::Ref(_, pointee_ty, _) = *ty.kind() { pointee_ty.is_array() } else { false }
+        }
+        fn is_boxed_slice(ty: Ty<'_>) -> bool {
+            ty.is_box() && ty.boxed_ty().is_slice()
+        }
+        fn is_ref_to_boxed_slice(ty: Ty<'_>) -> bool {
+            if let ty::Ref(_, pointee_ty, _) = *ty.kind() {
+                is_boxed_slice(pointee_ty)
+            } else {
+                false
+            }
+        }
+
+        let (lint, target, edition, can_suggest_ufcs) =
+            if is_ref_to_array(*adjusted_receiver_tys.last().unwrap())
+                && let Some(idx) = adjusted_receiver_tys
+                    .iter()
+                    .copied()
+                    .take_while(|ty| !is_ref_to_array(*ty))
+                    .position(|ty| ty.is_array())
+            {
+                (ARRAY_INTO_ITER, "[T; N]", "2021", idx == 0)
+            } else if is_ref_to_boxed_slice(*adjusted_receiver_tys.last().unwrap())
+                && let Some(idx) = adjusted_receiver_tys
+                    .iter()
+                    .copied()
+                    .take_while(|ty| !is_ref_to_boxed_slice(*ty))
+                    .position(|ty| is_boxed_slice(ty))
+            {
+                (BOXED_SLICE_INTO_ITER, "Box<[T]>", "2024", idx == 0)
+            } else {
+                return;
+            };
+
+        // If this expression comes from the `IntoIter::into_iter` inside of a for loop,
+        // we should just suggest removing the `.into_iter()` or changing it to `.iter()`
+        // to disambiguate if we want to iterate by-value or by-ref.
+        let sub = if let Some((_, hir::Node::Expr(parent_expr))) =
+            cx.tcx.hir().parent_iter(expr.hir_id).nth(1)
+            && let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) =
+                &parent_expr.kind
+            && let hir::ExprKind::Call(path, [_]) = &arg.kind
+            && let hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoIterIntoIter, ..)) =
+                &path.kind
+        {
+            Some(ShadowedIntoIterDiagSub::RemoveIntoIter {
+                span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+            })
+        } else if can_suggest_ufcs {
+            Some(ShadowedIntoIterDiagSub::UseExplicitIntoIter {
+                start_span: expr.span.shrink_to_lo(),
+                end_span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+            })
+        } else {
+            None
+        };
+
+        cx.emit_span_lint(
+            lint,
+            call.ident.span,
+            ShadowedIntoIterDiag { target, edition, suggestion: call.ident.span, sub },
+        );
+    }
+}
diff --git a/compiler/rustc_lint/src/tests.rs b/compiler/rustc_lint/src/tests.rs
new file mode 100644
index 00000000000..4fd054cb717
--- /dev/null
+++ b/compiler/rustc_lint/src/tests.rs
@@ -0,0 +1,26 @@
+use crate::levels::parse_lint_and_tool_name;
+use rustc_span::{create_default_session_globals_then, Symbol};
+
+#[test]
+fn parse_lint_no_tool() {
+    create_default_session_globals_then(|| {
+        assert_eq!(parse_lint_and_tool_name("foo"), (None, "foo"))
+    });
+}
+
+#[test]
+fn parse_lint_with_tool() {
+    create_default_session_globals_then(|| {
+        assert_eq!(parse_lint_and_tool_name("clippy::foo"), (Some(Symbol::intern("clippy")), "foo"))
+    });
+}
+
+#[test]
+fn parse_lint_multiple_path() {
+    create_default_session_globals_then(|| {
+        assert_eq!(
+            parse_lint_and_tool_name("clippy::foo::bar"),
+            (Some(Symbol::intern("clippy")), "foo::bar")
+        )
+    });
+}
diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs
new file mode 100644
index 00000000000..6983e7abbd6
--- /dev/null
+++ b/compiler/rustc_lint/src/traits.rs
@@ -0,0 +1,124 @@
+use crate::lints::{DropGlue, DropTraitConstraintsDiag};
+use crate::LateContext;
+use crate::LateLintPass;
+use crate::LintContext;
+use rustc_hir::{self as hir, LangItem};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::symbol::sym;
+
+declare_lint! {
+    /// The `drop_bounds` lint checks for generics with `std::ops::Drop` as
+    /// bounds.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// fn foo<T: Drop>() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// A generic trait bound of the form `T: Drop` is most likely misleading
+    /// and not what the programmer intended (they probably should have used
+    /// `std::mem::needs_drop` instead).
+    ///
+    /// `Drop` bounds do not actually indicate whether a type can be trivially
+    /// dropped or not, because a composite type containing `Drop` types does
+    /// not necessarily implement `Drop` itself. Naรฏvely, one might be tempted
+    /// to write an implementation that assumes that a type can be trivially
+    /// dropped while also supplying a specialization for `T: Drop` that
+    /// actually calls the destructor. However, this breaks down e.g. when `T`
+    /// is `String`, which does not implement `Drop` itself but contains a
+    /// `Vec`, which does implement `Drop`, so assuming `T` can be trivially
+    /// dropped would lead to a memory leak here.
+    ///
+    /// Furthermore, the `Drop` trait only contains one method, `Drop::drop`,
+    /// which may not be called explicitly in user code (`E0040`), so there is
+    /// really no use case for using `Drop` in trait bounds, save perhaps for
+    /// some obscure corner cases, which can use `#[allow(drop_bounds)]`.
+    pub DROP_BOUNDS,
+    Warn,
+    "bounds of the form `T: Drop` are most likely incorrect"
+}
+
+declare_lint! {
+    /// The `dyn_drop` lint checks for trait objects with `std::ops::Drop`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// fn foo(_x: Box<dyn Drop>) {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// A trait object bound of the form `dyn Drop` is most likely misleading
+    /// and not what the programmer intended.
+    ///
+    /// `Drop` bounds do not actually indicate whether a type can be trivially
+    /// dropped or not, because a composite type containing `Drop` types does
+    /// not necessarily implement `Drop` itself. Naรฏvely, one might be tempted
+    /// to write a deferred drop system, to pull cleaning up memory out of a
+    /// latency-sensitive code path, using `dyn Drop` trait objects. However,
+    /// this breaks down e.g. when `T` is `String`, which does not implement
+    /// `Drop`, but should probably be accepted.
+    ///
+    /// To write a trait object bound that accepts anything, use a placeholder
+    /// trait with a blanket implementation.
+    ///
+    /// ```rust
+    /// trait Placeholder {}
+    /// impl<T> Placeholder for T {}
+    /// fn foo(_x: Box<dyn Placeholder>) {}
+    /// ```
+    pub DYN_DROP,
+    Warn,
+    "trait objects of the form `dyn Drop` are useless"
+}
+
+declare_lint_pass!(
+    /// Lint for bounds of the form `T: Drop`, which usually
+    /// indicate an attempt to emulate `std::mem::needs_drop`.
+    DropTraitConstraints => [DROP_BOUNDS, DYN_DROP]
+);
+
+impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        use rustc_middle::ty::ClauseKind;
+
+        let predicates = cx.tcx.explicit_predicates_of(item.owner_id);
+        for &(predicate, span) in predicates.predicates {
+            let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() else {
+                continue;
+            };
+            let def_id = trait_predicate.trait_ref.def_id;
+            if cx.tcx.is_lang_item(def_id, LangItem::Drop) {
+                // Explicitly allow `impl Drop`, a drop-guards-as-unnameable-type pattern.
+                if trait_predicate.trait_ref.self_ty().is_impl_trait() {
+                    continue;
+                }
+                let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return };
+                cx.emit_span_lint(
+                    DROP_BOUNDS,
+                    span,
+                    DropTraitConstraintsDiag { predicate, tcx: cx.tcx, def_id },
+                );
+            }
+        }
+    }
+
+    fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) {
+        let hir::TyKind::TraitObject(bounds, _lifetime, _syntax) = &ty.kind else { return };
+        for bound in &bounds[..] {
+            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_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
new file mode 100644
index 00000000000..c0364b35716
--- /dev/null
+++ b/compiler/rustc_lint/src/types.rs
@@ -0,0 +1,2059 @@
+use crate::{
+    fluent_generated as fluent,
+    lints::{
+        AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion,
+        AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad,
+        AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons,
+        InvalidNanComparisonsSuggestion, OnlyCastu8ToChar, OverflowingBinHex,
+        OverflowingBinHexSign, OverflowingBinHexSignBitSub, OverflowingBinHexSub, OverflowingInt,
+        OverflowingIntHelp, OverflowingLiteral, OverflowingUInt, RangeEndpointOutOfRange,
+        UnusedComparisons, UseInclusiveRange, VariantSizeDifferencesDiag,
+    },
+};
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_attr as attr;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::DiagMessage;
+use rustc_hir as hir;
+use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
+use rustc_middle::bug;
+use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton};
+use rustc_middle::ty::GenericArgsRef;
+use rustc_middle::ty::{
+    self, AdtKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
+};
+use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::source_map;
+use rustc_span::symbol::sym;
+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 std::iter;
+use std::ops::ControlFlow;
+use tracing::debug;
+
+declare_lint! {
+    /// The `unused_comparisons` lint detects comparisons made useless by
+    /// limits of the types involved.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// fn foo(x: u8) {
+    ///     x >= 0;
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// A useless comparison may indicate a mistake, and should be fixed or
+    /// removed.
+    UNUSED_COMPARISONS,
+    Warn,
+    "comparisons made useless by limits of the types involved"
+}
+
+declare_lint! {
+    /// The `overflowing_literals` lint detects literal out of range for its
+    /// type.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// let x: u8 = 1000;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// It is usually a mistake to use a literal that overflows the type where
+    /// it is used. Either use a literal that is within range, or change the
+    /// type to be within the range of the literal.
+    OVERFLOWING_LITERALS,
+    Deny,
+    "literal out of range for its type"
+}
+
+declare_lint! {
+    /// The `variant_size_differences` lint detects enums with widely varying
+    /// variant sizes.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(variant_size_differences)]
+    /// enum En {
+    ///     V0(u8),
+    ///     VBig([u8; 1024]),
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// It can be a mistake to add a variant to an enum that is much larger
+    /// than the other variants, bloating the overall size required for all
+    /// variants. This can impact performance and memory usage. This is
+    /// triggered if one variant is more than 3 times larger than the
+    /// second-largest variant.
+    ///
+    /// Consider placing the large variant's contents on the heap (for example
+    /// via [`Box`]) to keep the overall size of the enum itself down.
+    ///
+    /// This lint is "allow" by default because it can be noisy, and may not be
+    /// an actual problem. Decisions about this should be guided with
+    /// profiling and benchmarking.
+    ///
+    /// [`Box`]: https://doc.rust-lang.org/std/boxed/index.html
+    VARIANT_SIZE_DIFFERENCES,
+    Allow,
+    "detects enums with widely varying variant sizes"
+}
+
+declare_lint! {
+    /// The `invalid_nan_comparisons` lint checks comparison with `f32::NAN` or `f64::NAN`
+    /// as one of the operand.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// let a = 2.3f32;
+    /// if a == f32::NAN {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// NaN does not compare meaningfully to anything โ€“ not
+    /// even itself โ€“ so those comparisons are always false.
+    INVALID_NAN_COMPARISONS,
+    Warn,
+    "detects invalid floating point NaN comparisons"
+}
+
+declare_lint! {
+    /// The `ambiguous_wide_pointer_comparisons` lint checks comparison
+    /// of `*const/*mut ?Sized` as the operands.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # struct A;
+    /// # struct B;
+    ///
+    /// # trait T {}
+    /// # impl T for A {}
+    /// # impl T for B {}
+    ///
+    /// let ab = (A, B);
+    /// let a = &ab.0 as *const dyn T;
+    /// let b = &ab.1 as *const dyn T;
+    ///
+    /// let _ = a == b;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The comparison includes metadata which may not be expected.
+    AMBIGUOUS_WIDE_POINTER_COMPARISONS,
+    Warn,
+    "detects ambiguous wide pointer comparisons"
+}
+
+#[derive(Copy, Clone)]
+pub struct TypeLimits {
+    /// Id of the last visited negated expression
+    negated_expr_id: Option<hir::HirId>,
+    /// Span of the last visited negated expression
+    negated_expr_span: Option<Span>,
+}
+
+impl_lint_pass!(TypeLimits => [
+    UNUSED_COMPARISONS,
+    OVERFLOWING_LITERALS,
+    INVALID_NAN_COMPARISONS,
+    AMBIGUOUS_WIDE_POINTER_COMPARISONS
+]);
+
+impl TypeLimits {
+    pub fn new() -> TypeLimits {
+        TypeLimits { negated_expr_id: None, negated_expr_span: None }
+    }
+}
+
+/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint (`expr..MAX+1`).
+/// Returns `true` iff the lint was emitted.
+fn lint_overflowing_range_endpoint<'tcx>(
+    cx: &LateContext<'tcx>,
+    lit: &hir::Lit,
+    lit_val: u128,
+    max: u128,
+    expr: &'tcx hir::Expr<'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.parent_hir_node(expr.hir_id)
+        && let ExprKind::Cast(_, _) = par_expr.kind
+    {
+        (par_expr, expr.span)
+    } else {
+        (expr, expr.span)
+    };
+
+    // We only want to handle exclusive (`..`) ranges,
+    // which are represented as `ExprKind::Struct`.
+    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;
+    };
+    let ExprKind::Struct(_, eps, _) = &struct_expr.kind else { return false };
+    if eps.len() != 2 {
+        return false;
+    }
+
+    // We can suggest using an inclusive range
+    // (`..=`) instead only if it is the `end` that is
+    // overflowing and only by 1.
+    if !(eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max) {
+        return false;
+    };
+
+    use rustc_ast::{LitIntType, LitKind};
+    let suffix = match lit.node {
+        LitKind::Int(_, LitIntType::Signed(s)) => s.name_str(),
+        LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str(),
+        LitKind::Int(_, LitIntType::Unsuffixed) => "",
+        _ => bug!(),
+    };
+
+    let sub_sugg = if expr.span.lo() == lit_span.lo() {
+        let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) else { return false };
+        UseInclusiveRange::WithoutParen {
+            sugg: struct_expr.span.shrink_to_lo().to(lit_span.shrink_to_hi()),
+            start,
+            literal: lit_val - 1,
+            suffix,
+        }
+    } else {
+        UseInclusiveRange::WithParen {
+            eq_sugg: expr.span.shrink_to_lo(),
+            lit_sugg: lit_span,
+            literal: lit_val - 1,
+            suffix,
+        }
+    };
+
+    cx.emit_span_lint(
+        OVERFLOWING_LITERALS,
+        struct_expr.span,
+        RangeEndpointOutOfRange { ty, sub: sub_sugg },
+    );
+
+    // We've just emitted a lint, special cased for `(...)..MAX+1` ranges,
+    // return `true` so the callers don't also emit a lint
+    true
+}
+
+// For `isize` & `usize`, be conservative with the warnings, so that the
+// warnings are consistent between 32- and 64-bit platforms.
+fn int_ty_range(int_ty: ty::IntTy) -> (i128, i128) {
+    match int_ty {
+        ty::IntTy::Isize => (i64::MIN.into(), i64::MAX.into()),
+        ty::IntTy::I8 => (i8::MIN.into(), i8::MAX.into()),
+        ty::IntTy::I16 => (i16::MIN.into(), i16::MAX.into()),
+        ty::IntTy::I32 => (i32::MIN.into(), i32::MAX.into()),
+        ty::IntTy::I64 => (i64::MIN.into(), i64::MAX.into()),
+        ty::IntTy::I128 => (i128::MIN, i128::MAX),
+    }
+}
+
+fn uint_ty_range(uint_ty: ty::UintTy) -> (u128, u128) {
+    let max = match uint_ty {
+        ty::UintTy::Usize => u64::MAX.into(),
+        ty::UintTy::U8 => u8::MAX.into(),
+        ty::UintTy::U16 => u16::MAX.into(),
+        ty::UintTy::U32 => u32::MAX.into(),
+        ty::UintTy::U64 => u64::MAX.into(),
+        ty::UintTy::U128 => u128::MAX,
+    };
+    (0, max)
+}
+
+fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> {
+    let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?;
+    let firstch = src.chars().next()?;
+
+    if firstch == '0' {
+        match src.chars().nth(1) {
+            Some('x' | 'b') => return Some(src),
+            _ => return None,
+        }
+    }
+
+    None
+}
+
+fn report_bin_hex_error(
+    cx: &LateContext<'_>,
+    expr: &hir::Expr<'_>,
+    ty: attr::IntType,
+    size: Size,
+    repr_str: String,
+    val: u128,
+    negative: bool,
+) {
+    let (t, actually) = match ty {
+        attr::IntType::SignedInt(t) => {
+            let actually = if negative {
+                -(size.sign_extend(val) as i128)
+            } else {
+                size.sign_extend(val) as i128
+            };
+            (t.name_str(), actually.to_string())
+        }
+        attr::IntType::UnsignedInt(t) => {
+            let actually = size.truncate(val);
+            (t.name_str(), actually.to_string())
+        }
+    };
+    let sign =
+        if negative { OverflowingBinHexSign::Negative } else { OverflowingBinHexSign::Positive };
+    let sub = get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative).map(
+        |suggestion_ty| {
+            if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
+                let (sans_suffix, _) = repr_str.split_at(pos);
+                OverflowingBinHexSub::Suggestion { span: expr.span, suggestion_ty, sans_suffix }
+            } else {
+                OverflowingBinHexSub::Help { suggestion_ty }
+            }
+        },
+    );
+    let sign_bit_sub = (!negative)
+        .then(|| {
+            let ty::Int(int_ty) = cx.typeck_results().node_type(expr.hir_id).kind() else {
+                return None;
+            };
+
+            let Some(bit_width) = int_ty.bit_width() else {
+                return None; // isize case
+            };
+
+            // Skip if sign bit is not set
+            if (val & (1 << (bit_width - 1))) == 0 {
+                return None;
+            }
+
+            let lit_no_suffix =
+                if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
+                    repr_str.split_at(pos).0
+                } else {
+                    &repr_str
+                };
+
+            Some(OverflowingBinHexSignBitSub {
+                span: expr.span,
+                lit_no_suffix,
+                negative_val: actually.clone(),
+                int_ty: int_ty.name_str(),
+                uint_ty: int_ty.to_unsigned().name_str(),
+            })
+        })
+        .flatten();
+
+    cx.emit_span_lint(
+        OVERFLOWING_LITERALS,
+        expr.span,
+        OverflowingBinHex {
+            ty: t,
+            lit: repr_str.clone(),
+            dec: val,
+            actually,
+            sign,
+            sub,
+            sign_bit_sub,
+        },
+    )
+}
+
+// This function finds the next fitting type and generates a suggestion string.
+// It searches for fitting types in the following way (`X < Y`):
+//  - `iX`: if literal fits in `uX` => `uX`, else => `iY`
+//  - `-iX` => `iY`
+//  - `uX` => `uY`
+//
+// No suggestion for: `isize`, `usize`.
+fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> {
+    use ty::IntTy::*;
+    use ty::UintTy::*;
+    macro_rules! find_fit {
+        ($ty:expr, $val:expr, $negative:expr,
+         $($type:ident => [$($utypes:expr),*] => [$($itypes:expr),*]),+) => {
+            {
+                let _neg = if negative { 1 } else { 0 };
+                match $ty {
+                    $($type => {
+                        $(if !negative && val <= uint_ty_range($utypes).1 {
+                            return Some($utypes.name_str())
+                        })*
+                        $(if val <= int_ty_range($itypes).1 as u128 + _neg {
+                            return Some($itypes.name_str())
+                        })*
+                        None
+                    },)+
+                    _ => None
+                }
+            }
+        }
+    }
+    match t.kind() {
+        ty::Int(i) => find_fit!(i, val, negative,
+                      I8 => [U8] => [I16, I32, I64, I128],
+                      I16 => [U16] => [I32, I64, I128],
+                      I32 => [U32] => [I64, I128],
+                      I64 => [U64] => [I128],
+                      I128 => [U128] => []),
+        ty::Uint(u) => find_fit!(u, val, negative,
+                      U8 => [U8, U16, U32, U64, U128] => [],
+                      U16 => [U16, U32, U64, U128] => [],
+                      U32 => [U32, U64, U128] => [],
+                      U64 => [U64, U128] => [],
+                      U128 => [U128] => []),
+        _ => None,
+    }
+}
+
+fn lint_int_literal<'tcx>(
+    cx: &LateContext<'tcx>,
+    type_limits: &TypeLimits,
+    e: &'tcx hir::Expr<'tcx>,
+    lit: &hir::Lit,
+    t: ty::IntTy,
+    v: u128,
+) {
+    let int_type = t.normalize(cx.sess().target.pointer_width);
+    let (min, max) = int_ty_range(int_type);
+    let max = max as u128;
+    let negative = type_limits.negated_expr_id == Some(e.hir_id);
+
+    // Detect literal value out of range [min, max] inclusive
+    // avoiding use of -min to prevent overflow/panic
+    if (negative && v > max + 1) || (!negative && v > max) {
+        if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
+            report_bin_hex_error(
+                cx,
+                e,
+                attr::IntType::SignedInt(ty::ast_int_ty(t)),
+                Integer::from_int_ty(cx, t).size(),
+                repr_str,
+                v,
+                negative,
+            );
+            return;
+        }
+
+        if lint_overflowing_range_endpoint(cx, lit, v, max, e, t.name_str()) {
+            // The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`.
+            return;
+        }
+
+        let span = if negative { type_limits.negated_expr_span.unwrap() } else { e.span };
+        let lit =
+            cx.sess().source_map().span_to_snippet(span).expect("must get snippet from literal");
+        let help = get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative)
+            .map(|suggestion_ty| OverflowingIntHelp { suggestion_ty });
+
+        cx.emit_span_lint(
+            OVERFLOWING_LITERALS,
+            span,
+            OverflowingInt { ty: t.name_str(), lit, min, max, help },
+        );
+    }
+}
+
+fn lint_uint_literal<'tcx>(
+    cx: &LateContext<'tcx>,
+    e: &'tcx hir::Expr<'tcx>,
+    lit: &hir::Lit,
+    t: ty::UintTy,
+) {
+    let uint_type = t.normalize(cx.sess().target.pointer_width);
+    let (min, max) = uint_ty_range(uint_type);
+    let lit_val: u128 = match lit.node {
+        // _v is u8, within range by definition
+        ast::LitKind::Byte(_v) => return,
+        ast::LitKind::Int(v, _) => v.get(),
+        _ => bug!(),
+    };
+    if lit_val < min || lit_val > max {
+        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_span_lint(
+                            OVERFLOWING_LITERALS,
+                            par_e.span,
+                            OnlyCastu8ToChar { span: par_e.span, literal: lit_val },
+                        );
+                        return;
+                    }
+                }
+                _ => {}
+            }
+        }
+        if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, t.name_str()) {
+            // The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`.
+            return;
+        }
+        if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
+            report_bin_hex_error(
+                cx,
+                e,
+                attr::IntType::UnsignedInt(ty::ast_uint_ty(t)),
+                Integer::from_uint_ty(cx, t).size(),
+                repr_str,
+                lit_val,
+                false,
+            );
+            return;
+        }
+        cx.emit_span_lint(
+            OVERFLOWING_LITERALS,
+            e.span,
+            OverflowingUInt {
+                ty: t.name_str(),
+                lit: cx
+                    .sess()
+                    .source_map()
+                    .span_to_snippet(lit.span)
+                    .expect("must get snippet from literal"),
+                min,
+                max,
+            },
+        );
+    }
+}
+
+fn lint_literal<'tcx>(
+    cx: &LateContext<'tcx>,
+    type_limits: &TypeLimits,
+    e: &'tcx hir::Expr<'tcx>,
+    lit: &hir::Lit,
+) {
+    match *cx.typeck_results().node_type(e.hir_id).kind() {
+        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.get())
+                }
+                _ => bug!(),
+            };
+        }
+        ty::Uint(t) => lint_uint_literal(cx, e, lit, t),
+        ty::Float(t) => {
+            let is_infinite = match lit.node {
+                ast::LitKind::Float(v, _) => match t {
+                    // FIXME(f16_f128): add this check once `is_infinite` is reliable (ABI
+                    // issues resolved).
+                    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_span_lint(
+                    OVERFLOWING_LITERALS,
+                    e.span,
+                    OverflowingLiteral {
+                        ty: t.name_str(),
+                        lit: cx
+                            .sess()
+                            .source_map()
+                            .span_to_snippet(lit.span)
+                            .expect("must get snippet from literal"),
+                    },
+                );
+            }
+        }
+        _ => {}
+    }
+}
+
+fn lint_nan<'tcx>(
+    cx: &LateContext<'tcx>,
+    e: &'tcx hir::Expr<'tcx>,
+    binop: hir::BinOp,
+    l: &'tcx hir::Expr<'tcx>,
+    r: &'tcx hir::Expr<'tcx>,
+) {
+    fn is_nan(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+        let expr = expr.peel_blocks().peel_borrows();
+        match expr.kind {
+            ExprKind::Path(qpath) => {
+                let Some(def_id) = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id()
+                else {
+                    return false;
+                };
+
+                matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::f32_nan | sym::f64_nan))
+            }
+            _ => false,
+        }
+    }
+
+    fn eq_ne(
+        cx: &LateContext<'_>,
+        e: &hir::Expr<'_>,
+        l: &hir::Expr<'_>,
+        r: &hir::Expr<'_>,
+        f: impl FnOnce(Span, Span) -> InvalidNanComparisonsSuggestion,
+    ) -> InvalidNanComparisons {
+        // FIXME(#72505): This suggestion can be restored if `f{32,64}::is_nan` is made const.
+        let suggestion = (!cx.tcx.hir().is_inside_const_context(e.hir_id)).then(|| {
+            if let Some(l_span) = l.span.find_ancestor_inside(e.span)
+                && let Some(r_span) = r.span.find_ancestor_inside(e.span)
+            {
+                f(l_span, r_span)
+            } else {
+                InvalidNanComparisonsSuggestion::Spanless
+            }
+        });
+
+        InvalidNanComparisons::EqNe { suggestion }
+    }
+
+    let lint = match binop.node {
+        hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, l) => {
+            eq_ne(cx, e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful {
+                nan_plus_binop: l_span.until(r_span),
+                float: r_span.shrink_to_hi(),
+                neg: (binop.node == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()),
+            })
+        }
+        hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, r) => {
+            eq_ne(cx, e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful {
+                nan_plus_binop: l_span.shrink_to_hi().to(r_span),
+                float: l_span.shrink_to_hi(),
+                neg: (binop.node == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()),
+            })
+        }
+        hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge
+            if is_nan(cx, l) || is_nan(cx, r) =>
+        {
+            InvalidNanComparisons::LtLeGtGe
+        }
+        _ => return,
+    };
+
+    cx.emit_span_lint(INVALID_NAN_COMPARISONS, e.span, lint);
+}
+
+#[derive(Debug, PartialEq)]
+enum ComparisonOp {
+    BinOp(hir::BinOpKind),
+    Other,
+}
+
+fn lint_wide_pointer<'tcx>(
+    cx: &LateContext<'tcx>,
+    e: &'tcx hir::Expr<'tcx>,
+    cmpop: ComparisonOp,
+    l: &'tcx hir::Expr<'tcx>,
+    r: &'tcx hir::Expr<'tcx>,
+) {
+    let ptr_unsized = |mut ty: Ty<'tcx>| -> Option<(
+        /* number of refs */ usize,
+        /* modifiers */ String,
+        /* is dyn */ bool,
+    )> {
+        let mut refs = 0;
+        // here we remove any "implicit" references and count the number
+        // of them to correctly suggest the right number of deref
+        while let ty::Ref(_, inner_ty, _) = ty.kind() {
+            ty = *inner_ty;
+            refs += 1;
+        }
+
+        // get the inner type of a pointer (or akin)
+        let mut modifiers = String::new();
+        ty = match ty.kind() {
+            ty::RawPtr(ty, _) => *ty,
+            ty::Adt(def, args) if cx.tcx.is_diagnostic_item(sym::NonNull, def.did()) => {
+                modifiers.push_str(".as_ptr()");
+                args.type_at(0)
+            }
+            _ => return None,
+        };
+
+        (!ty.is_sized(cx.tcx, cx.param_env))
+            .then(|| (refs, modifiers, matches!(ty.kind(), ty::Dynamic(_, _, ty::Dyn))))
+    };
+
+    // the left and right operands can have references, remove any explicit references
+    let l = l.peel_borrows();
+    let r = r.peel_borrows();
+
+    let Some(l_ty) = cx.typeck_results().expr_ty_opt(l) else {
+        return;
+    };
+    let Some(r_ty) = cx.typeck_results().expr_ty_opt(r) else {
+        return;
+    };
+
+    let Some((l_ty_refs, l_modifiers, l_inner_ty_is_dyn)) = ptr_unsized(l_ty) else {
+        return;
+    };
+    let Some((r_ty_refs, r_modifiers, r_inner_ty_is_dyn)) = ptr_unsized(r_ty) else {
+        return;
+    };
+
+    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_span_lint(
+            AMBIGUOUS_WIDE_POINTER_COMPARISONS,
+            e.span,
+            AmbiguousWidePointerComparisons::Spanless,
+        );
+    };
+
+    let ne = if cmpop == ComparisonOp::BinOp(hir::BinOpKind::Ne) { "!" } else { "" };
+    let is_eq_ne = matches!(cmpop, ComparisonOp::BinOp(hir::BinOpKind::Eq | hir::BinOpKind::Ne));
+    let is_dyn_comparison = l_inner_ty_is_dyn && r_inner_ty_is_dyn;
+
+    let left = e.span.shrink_to_lo().until(l_span.shrink_to_lo());
+    let middle = l_span.shrink_to_hi().until(r_span.shrink_to_lo());
+    let right = r_span.shrink_to_hi().until(e.span.shrink_to_hi());
+
+    let deref_left = &*"*".repeat(l_ty_refs);
+    let deref_right = &*"*".repeat(r_ty_refs);
+
+    let l_modifiers = &*l_modifiers;
+    let r_modifiers = &*r_modifiers;
+
+    cx.emit_span_lint(
+        AMBIGUOUS_WIDE_POINTER_COMPARISONS,
+        e.span,
+        AmbiguousWidePointerComparisons::Spanful {
+            addr_metadata_suggestion: (is_eq_ne && !is_dyn_comparison).then(|| {
+                AmbiguousWidePointerComparisonsAddrMetadataSuggestion {
+                    ne,
+                    deref_left,
+                    deref_right,
+                    l_modifiers,
+                    r_modifiers,
+                    left,
+                    middle,
+                    right,
+                }
+            }),
+            addr_suggestion: if is_eq_ne {
+                AmbiguousWidePointerComparisonsAddrSuggestion::AddrEq {
+                    ne,
+                    deref_left,
+                    deref_right,
+                    l_modifiers,
+                    r_modifiers,
+                    left,
+                    middle,
+                    right,
+                }
+            } else {
+                AmbiguousWidePointerComparisonsAddrSuggestion::Cast {
+                    deref_left,
+                    deref_right,
+                    l_modifiers,
+                    r_modifiers,
+                    paren_left: if l_ty_refs != 0 { ")" } else { "" },
+                    paren_right: if r_ty_refs != 0 { ")" } else { "" },
+                    left_before: (l_ty_refs != 0).then_some(l_span.shrink_to_lo()),
+                    left_after: l_span.shrink_to_hi(),
+                    right_before: (r_ty_refs != 0).then_some(r_span.shrink_to_lo()),
+                    right_after: r_span.shrink_to_hi(),
+                }
+            },
+        },
+    );
+}
+
+impl<'tcx> LateLintPass<'tcx> for TypeLimits {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
+        match e.kind {
+            hir::ExprKind::Unary(hir::UnOp::Neg, expr) => {
+                // Propagate negation, if the negation itself isn't negated
+                if self.negated_expr_id != Some(e.hir_id) {
+                    self.negated_expr_id = Some(expr.hir_id);
+                    self.negated_expr_span = Some(e.span);
+                }
+            }
+            hir::ExprKind::Binary(binop, ref l, ref r) => {
+                if is_comparison(binop) {
+                    if !check_limits(cx, binop, l, r) {
+                        cx.emit_span_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
+                    } else {
+                        lint_nan(cx, e, binop, l, r);
+                        lint_wide_pointer(cx, e, ComparisonOp::BinOp(binop.node), l, r);
+                    }
+                }
+            }
+            hir::ExprKind::Lit(lit) => lint_literal(cx, self, e, lit),
+            hir::ExprKind::Call(path, [l, r])
+                if let ExprKind::Path(ref qpath) = path.kind
+                    && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+                    && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
+                    && let Some(cmpop) = diag_item_cmpop(diag_item) =>
+            {
+                lint_wide_pointer(cx, e, cmpop, l, r);
+            }
+            hir::ExprKind::MethodCall(_, l, [r], _)
+                if let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
+                    && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
+                    && let Some(cmpop) = diag_item_cmpop(diag_item) =>
+            {
+                lint_wide_pointer(cx, e, cmpop, l, r);
+            }
+            _ => {}
+        };
+
+        fn is_valid<T: PartialOrd>(binop: hir::BinOp, v: T, min: T, max: T) -> bool {
+            match binop.node {
+                hir::BinOpKind::Lt => v > min && v <= max,
+                hir::BinOpKind::Le => v >= min && v < max,
+                hir::BinOpKind::Gt => v >= min && v < max,
+                hir::BinOpKind::Ge => v > min && v <= max,
+                hir::BinOpKind::Eq | hir::BinOpKind::Ne => v >= min && v <= max,
+                _ => bug!(),
+            }
+        }
+
+        fn rev_binop(binop: hir::BinOp) -> hir::BinOp {
+            source_map::respan(
+                binop.span,
+                match binop.node {
+                    hir::BinOpKind::Lt => hir::BinOpKind::Gt,
+                    hir::BinOpKind::Le => hir::BinOpKind::Ge,
+                    hir::BinOpKind::Gt => hir::BinOpKind::Lt,
+                    hir::BinOpKind::Ge => hir::BinOpKind::Le,
+                    _ => return binop,
+                },
+            )
+        }
+
+        fn check_limits(
+            cx: &LateContext<'_>,
+            binop: hir::BinOp,
+            l: &hir::Expr<'_>,
+            r: &hir::Expr<'_>,
+        ) -> bool {
+            let (lit, expr, swap) = match (&l.kind, &r.kind) {
+                (&hir::ExprKind::Lit(_), _) => (l, r, true),
+                (_, &hir::ExprKind::Lit(_)) => (r, l, false),
+                _ => return true,
+            };
+            // Normalize the binop so that the literal is always on the RHS in
+            // the comparison
+            let norm_binop = if swap { rev_binop(binop) } else { binop };
+            match *cx.typeck_results().node_type(expr.hir_id).kind() {
+                ty::Int(int_ty) => {
+                    let (min, max) = int_ty_range(int_ty);
+                    let lit_val: i128 = match lit.kind {
+                        hir::ExprKind::Lit(li) => match li.node {
+                            ast::LitKind::Int(
+                                v,
+                                ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed,
+                            ) => v.get() as i128,
+                            _ => return true,
+                        },
+                        _ => bug!(),
+                    };
+                    is_valid(norm_binop, lit_val, min, max)
+                }
+                ty::Uint(uint_ty) => {
+                    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.get(),
+                            _ => return true,
+                        },
+                        _ => bug!(),
+                    };
+                    is_valid(norm_binop, lit_val, min, max)
+                }
+                _ => true,
+            }
+        }
+
+        fn is_comparison(binop: hir::BinOp) -> bool {
+            matches!(
+                binop.node,
+                hir::BinOpKind::Eq
+                    | hir::BinOpKind::Lt
+                    | hir::BinOpKind::Le
+                    | hir::BinOpKind::Ne
+                    | hir::BinOpKind::Ge
+                    | hir::BinOpKind::Gt
+            )
+        }
+
+        fn diag_item_cmpop(diag_item: Symbol) -> Option<ComparisonOp> {
+            Some(match diag_item {
+                sym::cmp_ord_max => ComparisonOp::Other,
+                sym::cmp_ord_min => ComparisonOp::Other,
+                sym::ord_cmp_method => ComparisonOp::Other,
+                sym::cmp_partialeq_eq => ComparisonOp::BinOp(hir::BinOpKind::Eq),
+                sym::cmp_partialeq_ne => ComparisonOp::BinOp(hir::BinOpKind::Ne),
+                sym::cmp_partialord_cmp => ComparisonOp::Other,
+                sym::cmp_partialord_ge => ComparisonOp::BinOp(hir::BinOpKind::Ge),
+                sym::cmp_partialord_gt => ComparisonOp::BinOp(hir::BinOpKind::Gt),
+                sym::cmp_partialord_le => ComparisonOp::BinOp(hir::BinOpKind::Le),
+                sym::cmp_partialord_lt => ComparisonOp::BinOp(hir::BinOpKind::Lt),
+                _ => return None,
+            })
+        }
+    }
+}
+
+declare_lint! {
+    /// The `improper_ctypes` lint detects incorrect use of types in foreign
+    /// modules.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// extern "C" {
+    ///     static STATIC: String;
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The compiler has several checks to verify that types used in `extern`
+    /// blocks are safe and follow certain rules to ensure proper
+    /// compatibility with the foreign interfaces. This lint is issued when it
+    /// detects a probable mistake in a definition. The lint usually should
+    /// provide a description of the issue, along with possibly a hint on how
+    /// to resolve it.
+    IMPROPER_CTYPES,
+    Warn,
+    "proper use of libc types in foreign modules"
+}
+
+declare_lint_pass!(ImproperCTypesDeclarations => [IMPROPER_CTYPES]);
+
+declare_lint! {
+    /// The `improper_ctypes_definitions` lint detects incorrect use of
+    /// [`extern` function] definitions.
+    ///
+    /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # #![allow(unused)]
+    /// pub extern "C" fn str_type(p: &str) { }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// There are many parameter and return types that may be specified in an
+    /// `extern` function that are not compatible with the given ABI. This
+    /// lint is an alert that these types should not be used. The lint usually
+    /// should provide a description of the issue, along with possibly a hint
+    /// on how to resolve it.
+    IMPROPER_CTYPES_DEFINITIONS,
+    Warn,
+    "proper use of libc types in foreign item definitions"
+}
+
+declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]);
+
+#[derive(Clone, Copy)]
+pub(crate) enum CItemKind {
+    Declaration,
+    Definition,
+}
+
+struct ImproperCTypesVisitor<'a, 'tcx> {
+    cx: &'a LateContext<'tcx>,
+    mode: CItemKind,
+}
+
+enum FfiResult<'tcx> {
+    FfiSafe,
+    FfiPhantom(Ty<'tcx>),
+    FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option<DiagMessage> },
+}
+
+pub(crate) fn nonnull_optimization_guaranteed<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    def: ty::AdtDef<'tcx>,
+) -> bool {
+    tcx.has_attr(def.did(), sym::rustc_nonnull_optimization_guaranteed)
+}
+
+/// `repr(transparent)` structs can have a single non-1-ZST field, this function returns that
+/// field.
+pub fn transparent_newtype_field<'a, 'tcx>(
+    tcx: TyCtxt<'tcx>,
+    variant: &'a ty::VariantDef,
+) -> Option<&'a ty::FieldDef> {
+    let param_env = tcx.param_env(variant.def_id);
+    variant.fields.iter().find(|field| {
+        let field_ty = tcx.type_of(field.did).instantiate_identity();
+        let is_1zst = tcx.layout_of(param_env.and(field_ty)).is_ok_and(|layout| layout.is_1zst());
+        !is_1zst
+    })
+}
+
+/// Is type known to be non-null?
+fn ty_is_known_nonnull<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    ty: Ty<'tcx>,
+    mode: CItemKind,
+) -> bool {
+    let ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty);
+
+    match ty.kind() {
+        ty::FnPtr(_) => true,
+        ty::Ref(..) => true,
+        ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true,
+        ty::Adt(def, args) if def.repr().transparent() && !def.is_union() => {
+            let marked_non_null = nonnull_optimization_guaranteed(tcx, *def);
+
+            if marked_non_null {
+                return true;
+            }
+
+            // `UnsafeCell` has its niche hidden.
+            if def.is_unsafe_cell() {
+                return false;
+            }
+
+            def.variants()
+                .iter()
+                .filter_map(|variant| transparent_newtype_field(tcx, variant))
+                .any(|field| ty_is_known_nonnull(tcx, param_env, field.ty(tcx, args), mode))
+        }
+        _ => false,
+    }
+}
+
+/// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type.
+/// If the type passed in was not scalar, returns None.
+fn get_nullable_type<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    ty: Ty<'tcx>,
+) -> Option<Ty<'tcx>> {
+    let ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty);
+
+    Some(match *ty.kind() {
+        ty::Adt(field_def, field_args) => {
+            let inner_field_ty = {
+                let mut first_non_zst_ty =
+                    field_def.variants().iter().filter_map(|v| transparent_newtype_field(tcx, v));
+                debug_assert_eq!(
+                    first_non_zst_ty.clone().count(),
+                    1,
+                    "Wrong number of fields for transparent type"
+                );
+                first_non_zst_ty
+                    .next_back()
+                    .expect("No non-zst fields in transparent type.")
+                    .ty(tcx, field_args)
+            };
+            return get_nullable_type(tcx, param_env, inner_field_ty);
+        }
+        ty::Int(ty) => Ty::new_int(tcx, ty),
+        ty::Uint(ty) => Ty::new_uint(tcx, ty),
+        ty::RawPtr(ty, mutbl) => Ty::new_ptr(tcx, ty, mutbl),
+        // As these types are always non-null, the nullable equivalent of
+        // `Option<T>` of these types are their raw pointer counterparts.
+        ty::Ref(_region, ty, mutbl) => Ty::new_ptr(tcx, ty, mutbl),
+        // There is no nullable equivalent for Rust's function pointers,
+        // you must use an `Option<fn(..) -> _>` to represent it.
+        ty::FnPtr(..) => ty,
+        // We should only ever reach this case if `ty_is_known_nonnull` is
+        // extended to other types.
+        ref unhandled => {
+            debug!(
+                "get_nullable_type: Unhandled scalar kind: {:?} while checking {:?}",
+                unhandled, ty
+            );
+            return None;
+        }
+    })
+}
+
+/// A type is niche-optimization candidate iff:
+/// - Is a zero-sized type with alignment 1 (a โ€œ1-ZSTโ€).
+/// - Has no fields.
+/// - Does not have the `#[non_exhaustive]` attribute.
+fn is_niche_optimization_candidate<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    ty: Ty<'tcx>,
+) -> bool {
+    if tcx.layout_of(param_env.and(ty)).is_ok_and(|layout| !layout.is_1zst()) {
+        return false;
+    }
+
+    match ty.kind() {
+        ty::Adt(ty_def, _) => {
+            let non_exhaustive = ty_def.is_variant_list_non_exhaustive();
+            let empty = (ty_def.is_struct() && ty_def.all_fields().next().is_none())
+                || (ty_def.is_enum() && ty_def.variants().is_empty());
+
+            !non_exhaustive && empty
+        }
+        ty::Tuple(tys) => tys.is_empty(),
+        _ => false,
+    }
+}
+
+/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it
+/// can, return the type that `ty` can be safely converted to, otherwise return `None`.
+/// Currently restricted to function pointers, boxes, references, `core::num::NonZero`,
+/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes.
+/// FIXME: This duplicates code in codegen.
+pub(crate) fn repr_nullable_ptr<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    ty: Ty<'tcx>,
+    ckind: CItemKind,
+) -> Option<Ty<'tcx>> {
+    debug!("is_repr_nullable_ptr(tcx, ty = {:?})", ty);
+    if let ty::Adt(ty_def, args) = ty.kind() {
+        let field_ty = match &ty_def.variants().raw[..] {
+            [var_one, var_two] => match (&var_one.fields.raw[..], &var_two.fields.raw[..]) {
+                ([], [field]) | ([field], []) => field.ty(tcx, args),
+                ([field1], [field2]) => {
+                    if !tcx.features().result_ffi_guarantees {
+                        return None;
+                    }
+
+                    let ty1 = field1.ty(tcx, args);
+                    let ty2 = field2.ty(tcx, args);
+
+                    if is_niche_optimization_candidate(tcx, param_env, ty1) {
+                        ty2
+                    } else if is_niche_optimization_candidate(tcx, param_env, ty2) {
+                        ty1
+                    } else {
+                        return None;
+                    }
+                }
+                _ => return None,
+            },
+            _ => return None,
+        };
+
+        if !ty_is_known_nonnull(tcx, param_env, field_ty, ckind) {
+            return None;
+        }
+
+        // At this point, the field's type is known to be nonnull and the parent enum is Option-like.
+        // If the computed size for the field and the enum are different, the nonnull optimization isn't
+        // being applied (and we've got a problem somewhere).
+        let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, param_env).ok();
+        if !compute_size_skeleton(ty)?.same_size(compute_size_skeleton(field_ty)?) {
+            bug!("improper_ctypes: Option nonnull optimization not applied?");
+        }
+
+        // Return the nullable type this Option-like enum can be safely represented with.
+        let field_ty_layout = tcx.layout_of(param_env.and(field_ty));
+        if field_ty_layout.is_err() && !field_ty.has_non_region_param() {
+            bug!("should be able to compute the layout of non-polymorphic type");
+        }
+
+        let field_ty_abi = &field_ty_layout.ok()?.abi;
+        if let Abi::Scalar(field_ty_scalar) = field_ty_abi {
+            match field_ty_scalar.valid_range(&tcx) {
+                WrappingRange { start: 0, end }
+                    if end == field_ty_scalar.size(&tcx).unsigned_int_max() - 1 =>
+                {
+                    return Some(get_nullable_type(tcx, param_env, field_ty).unwrap());
+                }
+                WrappingRange { start: 1, .. } => {
+                    return Some(get_nullable_type(tcx, param_env, field_ty).unwrap());
+                }
+                WrappingRange { start, end } => {
+                    unreachable!("Unhandled start and end range: ({}, {})", start, end)
+                }
+            };
+        }
+    }
+    None
+}
+
+impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
+    /// Check if the type is array and emit an unsafe type lint.
+    fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
+        if let ty::Array(..) = ty.kind() {
+            self.emit_ffi_unsafe_type_lint(
+                ty,
+                sp,
+                fluent::lint_improper_ctypes_array_reason,
+                Some(fluent::lint_improper_ctypes_array_help),
+            );
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Checks if the given field's type is "ffi-safe".
+    fn check_field_type_for_ffi(
+        &self,
+        cache: &mut FxHashSet<Ty<'tcx>>,
+        field: &ty::FieldDef,
+        args: GenericArgsRef<'tcx>,
+    ) -> FfiResult<'tcx> {
+        let field_ty = field.ty(self.cx.tcx, args);
+        let field_ty = self
+            .cx
+            .tcx
+            .try_normalize_erasing_regions(self.cx.param_env, field_ty)
+            .unwrap_or(field_ty);
+        self.check_type_for_ffi(cache, field_ty)
+    }
+
+    /// Checks if the given `VariantDef`'s field types are "ffi-safe".
+    fn check_variant_for_ffi(
+        &self,
+        cache: &mut FxHashSet<Ty<'tcx>>,
+        ty: Ty<'tcx>,
+        def: ty::AdtDef<'tcx>,
+        variant: &ty::VariantDef,
+        args: GenericArgsRef<'tcx>,
+    ) -> FfiResult<'tcx> {
+        use FfiResult::*;
+        let transparent_with_all_zst_fields = if def.repr().transparent() {
+            if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) {
+                // Transparent newtypes have at most one non-ZST field which needs to be checked..
+                match self.check_field_type_for_ffi(cache, field, args) {
+                    FfiUnsafe { ty, .. } if ty.is_unit() => (),
+                    r => return r,
+                }
+
+                false
+            } else {
+                // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
+                // `PhantomData`).
+                true
+            }
+        } else {
+            false
+        };
+
+        // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
+        let mut all_phantom = !variant.fields.is_empty();
+        for field in &variant.fields {
+            all_phantom &= match self.check_field_type_for_ffi(cache, field, args) {
+                FfiSafe => false,
+                // `()` fields are FFI-safe!
+                FfiUnsafe { ty, .. } if ty.is_unit() => false,
+                FfiPhantom(..) => true,
+                r @ FfiUnsafe { .. } => return r,
+            }
+        }
+
+        if all_phantom {
+            FfiPhantom(ty)
+        } else if transparent_with_all_zst_fields {
+            FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None }
+        } else {
+            FfiSafe
+        }
+    }
+
+    /// Checks if the given type is "ffi-safe" (has a stable, well-defined
+    /// representation which can be exported to C code).
+    fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> {
+        use FfiResult::*;
+
+        let tcx = self.cx.tcx;
+
+        // Protect against infinite recursion, for example
+        // `struct S(*mut S);`.
+        // FIXME: A recursion limit is necessary as well, for irregular
+        // recursive types.
+        if !cache.insert(ty) {
+            return FfiSafe;
+        }
+
+        match *ty.kind() {
+            ty::Adt(def, args) => {
+                if def.is_box() && matches!(self.mode, CItemKind::Definition) {
+                    if ty.boxed_ty().is_sized(tcx, self.cx.param_env) {
+                        return FfiSafe;
+                    } else {
+                        return FfiUnsafe {
+                            ty,
+                            reason: fluent::lint_improper_ctypes_box,
+                            help: None,
+                        };
+                    }
+                }
+                if def.is_phantom_data() {
+                    return FfiPhantom(ty);
+                }
+                match def.adt_kind() {
+                    AdtKind::Struct | AdtKind::Union => {
+                        if !def.repr().c() && !def.repr().transparent() {
+                            return FfiUnsafe {
+                                ty,
+                                reason: if def.is_struct() {
+                                    fluent::lint_improper_ctypes_struct_layout_reason
+                                } else {
+                                    fluent::lint_improper_ctypes_union_layout_reason
+                                },
+                                help: if def.is_struct() {
+                                    Some(fluent::lint_improper_ctypes_struct_layout_help)
+                                } else {
+                                    Some(fluent::lint_improper_ctypes_union_layout_help)
+                                },
+                            };
+                        }
+
+                        let is_non_exhaustive =
+                            def.non_enum_variant().is_field_list_non_exhaustive();
+                        if is_non_exhaustive && !def.did().is_local() {
+                            return FfiUnsafe {
+                                ty,
+                                reason: if def.is_struct() {
+                                    fluent::lint_improper_ctypes_struct_non_exhaustive
+                                } else {
+                                    fluent::lint_improper_ctypes_union_non_exhaustive
+                                },
+                                help: None,
+                            };
+                        }
+
+                        if def.non_enum_variant().fields.is_empty() {
+                            return FfiUnsafe {
+                                ty,
+                                reason: if def.is_struct() {
+                                    fluent::lint_improper_ctypes_struct_fieldless_reason
+                                } else {
+                                    fluent::lint_improper_ctypes_union_fieldless_reason
+                                },
+                                help: if def.is_struct() {
+                                    Some(fluent::lint_improper_ctypes_struct_fieldless_help)
+                                } else {
+                                    Some(fluent::lint_improper_ctypes_union_fieldless_help)
+                                },
+                            };
+                        }
+
+                        self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), args)
+                    }
+                    AdtKind::Enum => {
+                        if def.variants().is_empty() {
+                            // Empty enums are okay... although sort of useless.
+                            return FfiSafe;
+                        }
+
+                        if def.is_variant_list_non_exhaustive() && !def.did().is_local() {
+                            return FfiUnsafe {
+                                ty,
+                                reason: fluent::lint_improper_ctypes_non_exhaustive,
+                                help: None,
+                            };
+                        }
+
+                        // Check for a repr() attribute to specify the size of the
+                        // discriminant.
+                        if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none()
+                        {
+                            // Special-case types like `Option<extern fn()>` and `Result<extern fn(), ()>`
+                            if let Some(ty) =
+                                repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode)
+                            {
+                                return self.check_type_for_ffi(cache, ty);
+                            }
+
+                            return FfiUnsafe {
+                                ty,
+                                reason: fluent::lint_improper_ctypes_enum_repr_reason,
+                                help: Some(fluent::lint_improper_ctypes_enum_repr_help),
+                            };
+                        }
+
+                        // Check the contained variants.
+                        for variant in def.variants() {
+                            let is_non_exhaustive = variant.is_field_list_non_exhaustive();
+                            if is_non_exhaustive && !variant.def_id.is_local() {
+                                return FfiUnsafe {
+                                    ty,
+                                    reason: fluent::lint_improper_ctypes_non_exhaustive_variant,
+                                    help: None,
+                                };
+                            }
+
+                            match self.check_variant_for_ffi(cache, ty, def, variant, args) {
+                                FfiSafe => (),
+                                r => return r,
+                            }
+                        }
+
+                        FfiSafe
+                    }
+                }
+            }
+
+            ty::Char => FfiUnsafe {
+                ty,
+                reason: fluent::lint_improper_ctypes_char_reason,
+                help: Some(fluent::lint_improper_ctypes_char_help),
+            },
+
+            ty::Pat(..) => FfiUnsafe {
+                ty,
+                reason: fluent::lint_improper_ctypes_pat_reason,
+                help: Some(fluent::lint_improper_ctypes_pat_help),
+            },
+
+            ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => {
+                FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_128bit, help: None }
+            }
+
+            // Primitive types with a stable representation.
+            ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe,
+
+            ty::Slice(_) => FfiUnsafe {
+                ty,
+                reason: fluent::lint_improper_ctypes_slice_reason,
+                help: Some(fluent::lint_improper_ctypes_slice_help),
+            },
+
+            ty::Dynamic(..) => {
+                FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_dyn, help: None }
+            }
+
+            ty::Str => FfiUnsafe {
+                ty,
+                reason: fluent::lint_improper_ctypes_str_reason,
+                help: Some(fluent::lint_improper_ctypes_str_help),
+            },
+
+            ty::Tuple(..) => FfiUnsafe {
+                ty,
+                reason: fluent::lint_improper_ctypes_tuple_reason,
+                help: Some(fluent::lint_improper_ctypes_tuple_help),
+            },
+
+            ty::RawPtr(ty, _) | ty::Ref(_, ty, _)
+                if {
+                    matches!(self.mode, CItemKind::Definition)
+                        && ty.is_sized(self.cx.tcx, self.cx.param_env)
+                } =>
+            {
+                FfiSafe
+            }
+
+            ty::RawPtr(ty, _)
+                if match ty.kind() {
+                    ty::Tuple(tuple) => tuple.is_empty(),
+                    _ => false,
+                } =>
+            {
+                FfiSafe
+            }
+
+            ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(cache, ty),
+
+            ty::Array(inner_ty, _) => self.check_type_for_ffi(cache, inner_ty),
+
+            ty::FnPtr(sig) => {
+                if self.is_internal_abi(sig.abi()) {
+                    return FfiUnsafe {
+                        ty,
+                        reason: fluent::lint_improper_ctypes_fnptr_reason,
+                        help: Some(fluent::lint_improper_ctypes_fnptr_help),
+                    };
+                }
+
+                let sig = tcx.instantiate_bound_regions_with_erased(sig);
+                for arg in sig.inputs() {
+                    match self.check_type_for_ffi(cache, *arg) {
+                        FfiSafe => {}
+                        r => return r,
+                    }
+                }
+
+                let ret_ty = sig.output();
+                if ret_ty.is_unit() {
+                    return FfiSafe;
+                }
+
+                self.check_type_for_ffi(cache, ret_ty)
+            }
+
+            ty::Foreign(..) => FfiSafe,
+
+            // While opaque types are checked for earlier, if a projection in a struct field
+            // normalizes to an opaque type, then it will reach this branch.
+            ty::Alias(ty::Opaque, ..) => {
+                FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_opaque, help: None }
+            }
+
+            // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe,
+            //  so they are currently ignored for the purposes of this lint.
+            ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent, ..)
+                if matches!(self.mode, CItemKind::Definition) =>
+            {
+                FfiSafe
+            }
+
+            ty::Param(..)
+            | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..)
+            | ty::Infer(..)
+            | ty::Bound(..)
+            | ty::Error(_)
+            | ty::Closure(..)
+            | ty::CoroutineClosure(..)
+            | ty::Coroutine(..)
+            | ty::CoroutineWitness(..)
+            | ty::Placeholder(..)
+            | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty),
+        }
+    }
+
+    fn emit_ffi_unsafe_type_lint(
+        &mut self,
+        ty: Ty<'tcx>,
+        sp: Span,
+        note: DiagMessage,
+        help: Option<DiagMessage>,
+    ) {
+        let lint = match self.mode {
+            CItemKind::Declaration => IMPROPER_CTYPES,
+            CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS,
+        };
+        let desc = match self.mode {
+            CItemKind::Declaration => "block",
+            CItemKind::Definition => "fn",
+        };
+        let span_note = if let ty::Adt(def, _) = ty.kind()
+            && let Some(sp) = self.cx.tcx.hir().span_if_local(def.did())
+        {
+            Some(sp)
+        } else {
+            None
+        };
+        self.cx.emit_span_lint(
+            lint,
+            sp,
+            ImproperCTypes { ty, desc, label: sp, help, note, span_note },
+        );
+    }
+
+    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 Result = ControlFlow<Ty<'tcx>>;
+
+            fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
+                if !ty.has_opaque_types() {
+                    return ControlFlow::Continue(());
+                }
+
+                if let ty::Alias(ty::Opaque, ..) = ty.kind() {
+                    ControlFlow::Break(ty)
+                } else {
+                    ty.super_visit_with(self)
+                }
+            }
+        }
+
+        if let Some(ty) = self
+            .cx
+            .tcx
+            .try_normalize_erasing_regions(self.cx.param_env, ty)
+            .unwrap_or(ty)
+            .visit_with(&mut ProhibitOpaqueTypes)
+            .break_value()
+        {
+            self.emit_ffi_unsafe_type_lint(ty, sp, fluent::lint_improper_ctypes_opaque, None);
+            true
+        } else {
+            false
+        }
+    }
+
+    fn check_type_for_ffi_and_report_errors(
+        &mut self,
+        sp: Span,
+        ty: Ty<'tcx>,
+        is_static: bool,
+        is_return_type: bool,
+    ) {
+        if self.check_for_opaque_ty(sp, ty) {
+            // We've already emitted an error due to an opaque type.
+            return;
+        }
+
+        let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.param_env, ty).unwrap_or(ty);
+
+        // C doesn't really support passing arrays by value - the only way to pass an array by value
+        // is through a struct. So, first test that the top level isn't an array, and then
+        // recursively check the types inside.
+        if !is_static && self.check_for_array_ty(sp, ty) {
+            return;
+        }
+
+        // Don't report FFI errors for unit return types. This check exists here, and not in
+        // the caller (where it would make more sense) so that normalization has definitely
+        // happened.
+        if is_return_type && ty.is_unit() {
+            return;
+        }
+
+        match self.check_type_for_ffi(&mut FxHashSet::default(), ty) {
+            FfiResult::FfiSafe => {}
+            FfiResult::FfiPhantom(ty) => {
+                self.emit_ffi_unsafe_type_lint(
+                    ty,
+                    sp,
+                    fluent::lint_improper_ctypes_only_phantomdata,
+                    None,
+                );
+            }
+            FfiResult::FfiUnsafe { ty, reason, help } => {
+                self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
+            }
+        }
+    }
+
+    /// Check if a function's argument types and result type are "ffi-safe".
+    ///
+    /// For a external ABI function, argument types and the result type are walked to find fn-ptr
+    /// types that have external ABIs, as these still need checked.
+    fn check_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) {
+        let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity();
+        let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig);
+
+        for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
+            for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) {
+                self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, false);
+            }
+        }
+
+        if let hir::FnRetTy::Return(ret_hir) = decl.output {
+            for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(ret_hir, sig.output()) {
+                self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, true);
+            }
+        }
+    }
+
+    /// Check if a function's argument types and result type are "ffi-safe".
+    fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) {
+        let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity();
+        let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig);
+
+        for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
+            self.check_type_for_ffi_and_report_errors(input_hir.span, *input_ty, false, false);
+        }
+
+        if let hir::FnRetTy::Return(ret_hir) = decl.output {
+            self.check_type_for_ffi_and_report_errors(ret_hir.span, sig.output(), false, true);
+        }
+    }
+
+    fn check_foreign_static(&mut self, id: hir::OwnerId, span: Span) {
+        let ty = self.cx.tcx.type_of(id).instantiate_identity();
+        self.check_type_for_ffi_and_report_errors(span, ty, true, false);
+    }
+
+    fn is_internal_abi(&self, abi: SpecAbi) -> bool {
+        matches!(abi, SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic)
+    }
+
+    /// Find any fn-ptr types with external ABIs in `ty`.
+    ///
+    /// For example, `Option<extern "C" fn()>` returns `extern "C" fn()`
+    fn find_fn_ptr_ty_with_external_abi(
+        &self,
+        hir_ty: &hir::Ty<'tcx>,
+        ty: Ty<'tcx>,
+    ) -> Vec<(Ty<'tcx>, Span)> {
+        struct FnPtrFinder<'parent, 'a, 'tcx> {
+            visitor: &'parent ImproperCTypesVisitor<'a, 'tcx>,
+            spans: Vec<Span>,
+            tys: Vec<Ty<'tcx>>,
+        }
+
+        impl<'parent, 'a, 'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'parent, 'a, 'tcx> {
+            fn visit_ty(&mut self, ty: &'_ hir::Ty<'_>) {
+                debug!(?ty);
+                if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind
+                    && !self.visitor.is_internal_abi(*abi)
+                {
+                    self.spans.push(ty.span);
+                }
+
+                hir::intravisit::walk_ty(self, ty)
+            }
+        }
+
+        impl<'vis, 'a, 'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'vis, 'a, 'tcx> {
+            type Result = ControlFlow<Ty<'tcx>>;
+
+            fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
+                if let ty::FnPtr(sig) = ty.kind()
+                    && !self.visitor.is_internal_abi(sig.abi())
+                {
+                    self.tys.push(ty);
+                }
+
+                ty.super_visit_with(self)
+            }
+        }
+
+        let mut visitor = FnPtrFinder { visitor: &*self, spans: Vec::new(), tys: Vec::new() };
+        ty.visit_with(&mut visitor);
+        hir::intravisit::Visitor::visit_ty(&mut visitor, hir_ty);
+
+        iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)).collect()
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations {
+    fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) {
+        let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration };
+        let abi = cx.tcx.hir().get_foreign_abi(it.hir_id());
+
+        match it.kind {
+            hir::ForeignItemKind::Fn(decl, _, _, _) if !vis.is_internal_abi(abi) => {
+                vis.check_foreign_fn(it.owner_id.def_id, decl);
+            }
+            hir::ForeignItemKind::Static(ty, _, _) if !vis.is_internal_abi(abi) => {
+                vis.check_foreign_static(it.owner_id, ty.span);
+            }
+            hir::ForeignItemKind::Fn(decl, _, _, _) => vis.check_fn(it.owner_id.def_id, decl),
+            hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (),
+        }
+    }
+}
+
+impl ImproperCTypesDefinitions {
+    fn check_ty_maybe_containing_foreign_fnptr<'tcx>(
+        &mut self,
+        cx: &LateContext<'tcx>,
+        hir_ty: &'tcx hir::Ty<'_>,
+        ty: Ty<'tcx>,
+    ) {
+        let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
+        for (fn_ptr_ty, span) in vis.find_fn_ptr_ty_with_external_abi(hir_ty, ty) {
+            vis.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, true, false);
+        }
+    }
+}
+
+/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in
+/// `extern "C" { }` blocks):
+///
+/// - `extern "<abi>" fn` definitions are checked in the same way as the
+///   `ImproperCtypesDeclarations` visitor checks functions if `<abi>` is external (e.g. "C").
+/// - All other items which contain types (e.g. other functions, struct definitions, etc) are
+///   checked for extern fn-ptrs with external ABIs.
+impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        match item.kind {
+            hir::ItemKind::Static(ty, ..)
+            | hir::ItemKind::Const(ty, ..)
+            | hir::ItemKind::TyAlias(ty, ..) => {
+                self.check_ty_maybe_containing_foreign_fnptr(
+                    cx,
+                    ty,
+                    cx.tcx.type_of(item.owner_id).instantiate_identity(),
+                );
+            }
+            // See `check_fn`..
+            hir::ItemKind::Fn(..) => {}
+            // See `check_field_def`..
+            hir::ItemKind::Union(..) | hir::ItemKind::Struct(..) | hir::ItemKind::Enum(..) => {}
+            // Doesn't define something that can contain a external type to be checked.
+            hir::ItemKind::Impl(..)
+            | hir::ItemKind::TraitAlias(..)
+            | hir::ItemKind::Trait(..)
+            | hir::ItemKind::OpaqueTy(..)
+            | hir::ItemKind::GlobalAsm(..)
+            | hir::ItemKind::ForeignMod { .. }
+            | hir::ItemKind::Mod(..)
+            | hir::ItemKind::Macro(..)
+            | hir::ItemKind::Use(..)
+            | hir::ItemKind::ExternCrate(..) => {}
+        }
+    }
+
+    fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) {
+        self.check_ty_maybe_containing_foreign_fnptr(
+            cx,
+            field.ty,
+            cx.tcx.type_of(field.def_id).instantiate_identity(),
+        );
+    }
+
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'tcx>,
+        kind: hir::intravisit::FnKind<'tcx>,
+        decl: &'tcx hir::FnDecl<'_>,
+        _: &'tcx hir::Body<'_>,
+        _: Span,
+        id: LocalDefId,
+    ) {
+        use hir::intravisit::FnKind;
+
+        let abi = match kind {
+            FnKind::ItemFn(_, _, header, ..) => header.abi,
+            FnKind::Method(_, sig, ..) => sig.header.abi,
+            _ => return,
+        };
+
+        let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
+        if vis.is_internal_abi(abi) {
+            vis.check_fn(id, decl);
+        } else {
+            vis.check_foreign_fn(id, decl);
+        }
+    }
+}
+
+declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]);
+
+impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind {
+            let t = cx.tcx.type_of(it.owner_id).instantiate_identity();
+            let ty = cx.tcx.erase_regions(t);
+            let Ok(layout) = cx.layout_of(ty) else { return };
+            let Variants::Multiple { tag_encoding: TagEncoding::Direct, tag, ref variants, .. } =
+                &layout.variants
+            else {
+                return;
+            };
+
+            let tag_size = tag.size(&cx.tcx).bytes();
+
+            debug!(
+                "enum `{}` is {} bytes large with layout:\n{:#?}",
+                t,
+                layout.size.bytes(),
+                layout
+            );
+
+            let (largest, slargest, largest_index) = iter::zip(enum_definition.variants, variants)
+                .map(|(variant, variant_layout)| {
+                    // Subtract the size of the enum tag.
+                    let bytes = variant_layout.size.bytes().saturating_sub(tag_size);
+
+                    debug!("- variant `{}` is {} bytes large", variant.ident, bytes);
+                    bytes
+                })
+                .enumerate()
+                .fold((0, 0, 0), |(l, s, li), (idx, size)| {
+                    if size > l {
+                        (size, l, idx)
+                    } else if size > s {
+                        (l, size, li)
+                    } else {
+                        (l, s, li)
+                    }
+                });
+
+            // 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_span_lint(
+                    VARIANT_SIZE_DIFFERENCES,
+                    enum_definition.variants[largest_index].span,
+                    VariantSizeDifferencesDiag { largest },
+                );
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `invalid_atomic_ordering` lint detects passing an `Ordering`
+    /// to an atomic operation that does not support that ordering.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// # use core::sync::atomic::{AtomicU8, Ordering};
+    /// let atom = AtomicU8::new(0);
+    /// let value = atom.load(Ordering::Release);
+    /// # let _ = value;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Some atomic operations are only supported for a subset of the
+    /// `atomic::Ordering` variants. Passing an unsupported variant will cause
+    /// an unconditional panic at runtime, which is detected by this lint.
+    ///
+    /// This lint will trigger in the following cases: (where `AtomicType` is an
+    /// atomic type from `core::sync::atomic`, such as `AtomicBool`,
+    /// `AtomicPtr`, `AtomicUsize`, or any of the other integer atomics).
+    ///
+    /// - Passing `Ordering::Acquire` or `Ordering::AcqRel` to
+    ///   `AtomicType::store`.
+    ///
+    /// - Passing `Ordering::Release` or `Ordering::AcqRel` to
+    ///   `AtomicType::load`.
+    ///
+    /// - Passing `Ordering::Relaxed` to `core::sync::atomic::fence` or
+    ///   `core::sync::atomic::compiler_fence`.
+    ///
+    /// - Passing `Ordering::Release` or `Ordering::AcqRel` as the failure
+    ///   ordering for any of `AtomicType::compare_exchange`,
+    ///   `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update`.
+    INVALID_ATOMIC_ORDERING,
+    Deny,
+    "usage of invalid atomic ordering in atomic operations and memory fences"
+}
+
+declare_lint_pass!(InvalidAtomicOrdering => [INVALID_ATOMIC_ORDERING]);
+
+impl InvalidAtomicOrdering {
+    fn inherent_atomic_method_call<'hir>(
+        cx: &LateContext<'_>,
+        expr: &Expr<'hir>,
+        recognized_names: &[Symbol], // used for fast path calculation
+    ) -> Option<(Symbol, &'hir [Expr<'hir>])> {
+        const ATOMIC_TYPES: &[Symbol] = &[
+            sym::AtomicBool,
+            sym::AtomicPtr,
+            sym::AtomicUsize,
+            sym::AtomicU8,
+            sym::AtomicU16,
+            sym::AtomicU32,
+            sym::AtomicU64,
+            sym::AtomicU128,
+            sym::AtomicIsize,
+            sym::AtomicI8,
+            sym::AtomicI16,
+            sym::AtomicI32,
+            sym::AtomicI64,
+            sym::AtomicI128,
+        ];
+        if let ExprKind::MethodCall(method_path, _, args, _) = &expr.kind
+            && recognized_names.contains(&method_path.ident.name)
+            && let Some(m_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
+            && let Some(impl_did) = cx.tcx.impl_of_method(m_def_id)
+            && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def()
+            // skip extension traits, only lint functions from the standard library
+            && cx.tcx.trait_id_of_impl(impl_did).is_none()
+            && let parent = cx.tcx.parent(adt.did())
+            && cx.tcx.is_diagnostic_item(sym::atomic_mod, parent)
+            && ATOMIC_TYPES.contains(&cx.tcx.item_name(adt.did()))
+        {
+            return Some((method_path.ident.name, args));
+        }
+        None
+    }
+
+    fn match_ordering(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<Symbol> {
+        let ExprKind::Path(ref ord_qpath) = ord_arg.kind else { return None };
+        let did = cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id()?;
+        let tcx = cx.tcx;
+        let atomic_ordering = tcx.get_diagnostic_item(sym::Ordering);
+        let name = tcx.item_name(did);
+        let parent = tcx.parent(did);
+        [sym::Relaxed, sym::Release, sym::Acquire, sym::AcqRel, sym::SeqCst].into_iter().find(
+            |&ordering| {
+                name == ordering
+                    && (Some(parent) == atomic_ordering
+                            // needed in case this is a ctor, not a variant
+                            || tcx.opt_parent(parent) == atomic_ordering)
+            },
+        )
+    }
+
+    fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
+        if let Some((method, args)) =
+            Self::inherent_atomic_method_call(cx, expr, &[sym::load, sym::store])
+            && let Some((ordering_arg, invalid_ordering)) = match method {
+                sym::load => Some((&args[0], sym::Release)),
+                sym::store => Some((&args[1], sym::Acquire)),
+                _ => None,
+            }
+            && let Some(ordering) = Self::match_ordering(cx, ordering_arg)
+            && (ordering == invalid_ordering || ordering == sym::AcqRel)
+        {
+            if method == sym::load {
+                cx.emit_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, AtomicOrderingLoad);
+            } else {
+                cx.emit_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, AtomicOrderingStore);
+            };
+        }
+    }
+
+    fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) {
+        if let ExprKind::Call(func, args) = expr.kind
+            && let ExprKind::Path(ref func_qpath) = func.kind
+            && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id()
+            && 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_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, AtomicOrderingFence);
+        }
+    }
+
+    fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) {
+        let Some((method, args)) = Self::inherent_atomic_method_call(
+            cx,
+            expr,
+            &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak],
+        ) else {
+            return;
+        };
+
+        let fail_order_arg = match method {
+            sym::fetch_update => &args[1],
+            sym::compare_exchange | sym::compare_exchange_weak => &args[3],
+            _ => return,
+        };
+
+        let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return };
+
+        if matches!(fail_ordering, sym::Release | sym::AcqRel) {
+            cx.emit_span_lint(
+                INVALID_ATOMIC_ORDERING,
+                fail_order_arg.span,
+                InvalidAtomicOrderingDiag { method, fail_order_arg_span: fail_order_arg.span },
+            );
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for InvalidAtomicOrdering {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        Self::check_atomic_load_store(cx, expr);
+        Self::check_memory_fence(cx, expr);
+        Self::check_atomic_compare_exchange(cx, expr);
+    }
+}
diff --git a/compiler/rustc_lint/src/unit_bindings.rs b/compiler/rustc_lint/src/unit_bindings.rs
new file mode 100644
index 00000000000..8202bfa805a
--- /dev/null
+++ b/compiler/rustc_lint/src/unit_bindings.rs
@@ -0,0 +1,72 @@
+use crate::lints::UnitBindingsDiag;
+use crate::{LateLintPass, LintContext};
+use rustc_hir as hir;
+use rustc_session::{declare_lint, declare_lint_pass};
+
+declare_lint! {
+    /// The `unit_bindings` lint detects cases where bindings are useless because they have
+    /// the unit type `()` as their inferred type. The lint is suppressed if the user explicitly
+    /// annotates the let binding with the unit type `()`, or if the let binding uses an underscore
+    /// wildcard pattern, i.e. `let _ = expr`, or if the binding is produced from macro expansions.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(unit_bindings)]
+    ///
+    /// fn foo() {
+    ///     println!("do work");
+    /// }
+    ///
+    /// pub fn main() {
+    ///     let x = foo(); // useless binding
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Creating a local binding with the unit type `()` does not do much and can be a sign of a
+    /// user error, such as in this example:
+    ///
+    /// ```rust,no_run
+    /// fn main() {
+    ///     let mut x = [1, 2, 3];
+    ///     x[0] = 5;
+    ///     let y = x.sort(); // useless binding as `sort` returns `()` and not the sorted array.
+    ///     println!("{:?}", y); // prints "()"
+    /// }
+    /// ```
+    pub UNIT_BINDINGS,
+    Allow,
+    "binding is useless because it has the unit `()` type"
+}
+
+declare_lint_pass!(UnitBindings => [UNIT_BINDINGS]);
+
+impl<'tcx> LateLintPass<'tcx> for UnitBindings {
+    fn check_local(&mut self, cx: &crate::LateContext<'tcx>, local: &'tcx hir::LetStmt<'tcx>) {
+        // Suppress warning if user:
+        // - explicitly ascribes a type to the pattern
+        // - explicitly wrote `let pat = ();`
+        // - explicitly wrote `let () = init;`.
+        if !local.span.from_expansion()
+            && let Some(tyck_results) = cx.maybe_typeck_results()
+            && let Some(init) = local.init
+            && let init_ty = tyck_results.expr_ty(init)
+            && let local_ty = tyck_results.node_type(local.hir_id)
+            && init_ty == cx.tcx.types.unit
+            && local_ty == cx.tcx.types.unit
+            && local.ty.is_none()
+            && !matches!(init.kind, hir::ExprKind::Tup([]))
+            && !matches!(local.pat.kind, hir::PatKind::Tuple([], ..))
+        {
+            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
new file mode 100644
index 00000000000..65d42ed8054
--- /dev/null
+++ b/compiler/rustc_lint/src/unused.rs
@@ -0,0 +1,1623 @@
+use crate::lints::{
+    PathStatementDrop, PathStatementDropSub, PathStatementNoEffect, UnusedAllocationDiag,
+    UnusedAllocationMutDiag, UnusedClosure, UnusedCoroutine, UnusedDef, UnusedDefSuggestion,
+    UnusedDelim, UnusedDelimSuggestion, UnusedImportBracesDiag, UnusedOp, UnusedOpSuggestion,
+    UnusedResult,
+};
+use crate::Lint;
+use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_ast::util::{classify, parser};
+use rustc_ast::{ExprKind, StmtKind};
+use rustc_errors::{pluralize, MultiSpan};
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{self as hir, LangItem};
+use rustc_infer::traits::util::elaborate;
+use rustc_middle::ty::adjustment;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
+use rustc_span::symbol::Symbol;
+use rustc_span::symbol::{kw, sym};
+use rustc_span::{BytePos, Span};
+use std::iter;
+use std::ops::ControlFlow;
+use tracing::instrument;
+
+declare_lint! {
+    /// The `unused_must_use` lint detects unused result of a type flagged as
+    /// `#[must_use]`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// fn returns_result() -> Result<(), ()> {
+    ///     Ok(())
+    /// }
+    ///
+    /// fn main() {
+    ///     returns_result();
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The `#[must_use]` attribute is an indicator that it is a mistake to
+    /// ignore the value. See [the reference] for more details.
+    ///
+    /// [the reference]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
+    pub UNUSED_MUST_USE,
+    Warn,
+    "unused result of a type flagged as `#[must_use]`",
+    report_in_external_macro
+}
+
+declare_lint! {
+    /// The `unused_results` lint checks for the unused result of an
+    /// expression in a statement.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(unused_results)]
+    /// fn foo<T>() -> T { panic!() }
+    ///
+    /// fn main() {
+    ///     foo::<usize>();
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Ignoring the return value of a function may indicate a mistake. In
+    /// cases were it is almost certain that the result should be used, it is
+    /// recommended to annotate the function with the [`must_use` attribute].
+    /// Failure to use such a return value will trigger the [`unused_must_use`
+    /// lint] which is warn-by-default. The `unused_results` lint is
+    /// essentially the same, but triggers for *all* return values.
+    ///
+    /// This lint is "allow" by default because it can be noisy, and may not be
+    /// an actual problem. For example, calling the `remove` method of a `Vec`
+    /// or `HashMap` returns the previous value, which you may not care about.
+    /// Using this lint would require explicitly ignoring or discarding such
+    /// values.
+    ///
+    /// [`must_use` attribute]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
+    /// [`unused_must_use` lint]: warn-by-default.html#unused-must-use
+    pub UNUSED_RESULTS,
+    Allow,
+    "unused result of an expression in a statement"
+}
+
+declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedResults {
+    fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
+        let hir::StmtKind::Semi(mut expr) = s.kind else {
+            return;
+        };
+
+        let mut expr_is_from_block = false;
+        while let hir::ExprKind::Block(blk, ..) = expr.kind
+            && let hir::Block { expr: Some(e), .. } = blk
+        {
+            expr = e;
+            expr_is_from_block = true;
+        }
+
+        if let hir::ExprKind::Ret(..) = expr.kind {
+            return;
+        }
+
+        if let hir::ExprKind::Match(await_expr, _arms, hir::MatchSource::AwaitDesugar) = expr.kind
+            && let ty = cx.typeck_results().expr_ty(await_expr)
+            && let ty::Alias(ty::Opaque, ty::AliasTy { def_id: future_def_id, .. }) = ty.kind()
+            && cx.tcx.ty_is_opaque_future(ty)
+            && let async_fn_def_id = cx.tcx.parent(*future_def_id)
+            && matches!(cx.tcx.def_kind(async_fn_def_id), DefKind::Fn | DefKind::AssocFn)
+            // Check that this `impl Future` actually comes from an `async fn`
+            && cx.tcx.asyncness(async_fn_def_id).is_async()
+            && check_must_use_def(
+                cx,
+                async_fn_def_id,
+                expr.span,
+                "output of future returned by ",
+                "",
+                expr_is_from_block,
+            )
+        {
+            // We have a bare `foo().await;` on an opaque type from an async function that was
+            // annotated with `#[must_use]`.
+            return;
+        }
+
+        let ty = cx.typeck_results().expr_ty(expr);
+
+        let must_use_result = is_ty_must_use(cx, ty, expr, expr.span);
+        let type_lint_emitted_or_suppressed = match must_use_result {
+            Some(path) => {
+                emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block);
+                true
+            }
+            None => false,
+        };
+
+        let fn_warned = check_fn_must_use(cx, expr, expr_is_from_block);
+
+        if !fn_warned && type_lint_emitted_or_suppressed {
+            // We don't warn about unused unit or uninhabited types.
+            // (See https://github.com/rust-lang/rust/issues/43806 for details.)
+            return;
+        }
+
+        let must_use_op = match expr.kind {
+            // Hardcoding operators here seemed more expedient than the
+            // refactoring that would be needed to look up the `#[must_use]`
+            // attribute which does exist on the comparison trait methods
+            hir::ExprKind::Binary(bin_op, ..) => match bin_op.node {
+                hir::BinOpKind::Eq
+                | hir::BinOpKind::Lt
+                | hir::BinOpKind::Le
+                | hir::BinOpKind::Ne
+                | hir::BinOpKind::Ge
+                | hir::BinOpKind::Gt => Some("comparison"),
+                hir::BinOpKind::Add
+                | hir::BinOpKind::Sub
+                | hir::BinOpKind::Div
+                | hir::BinOpKind::Mul
+                | hir::BinOpKind::Rem => Some("arithmetic operation"),
+                hir::BinOpKind::And | hir::BinOpKind::Or => Some("logical operation"),
+                hir::BinOpKind::BitXor
+                | hir::BinOpKind::BitAnd
+                | hir::BinOpKind::BitOr
+                | hir::BinOpKind::Shl
+                | hir::BinOpKind::Shr => Some("bitwise operation"),
+            },
+            hir::ExprKind::AddrOf(..) => Some("borrow"),
+            hir::ExprKind::OffsetOf(..) => Some("`offset_of` call"),
+            hir::ExprKind::Unary(..) => Some("unary operation"),
+            _ => None,
+        };
+
+        let mut op_warned = false;
+
+        if let Some(must_use_op) = must_use_op {
+            cx.emit_span_lint(
+                UNUSED_MUST_USE,
+                expr.span,
+                UnusedOp {
+                    op: must_use_op,
+                    label: expr.span,
+                    suggestion: if expr_is_from_block {
+                        UnusedOpSuggestion::BlockTailExpr {
+                            before_span: expr.span.shrink_to_lo(),
+                            after_span: expr.span.shrink_to_hi(),
+                        }
+                    } else {
+                        UnusedOpSuggestion::NormalExpr { span: expr.span.shrink_to_lo() }
+                    },
+                },
+            );
+            op_warned = true;
+        }
+
+        if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) {
+            cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty });
+        }
+
+        fn check_fn_must_use(
+            cx: &LateContext<'_>,
+            expr: &hir::Expr<'_>,
+            expr_is_from_block: bool,
+        ) -> bool {
+            let maybe_def_id = match expr.kind {
+                hir::ExprKind::Call(callee, _) => {
+                    match callee.kind {
+                        hir::ExprKind::Path(ref qpath) => {
+                            match cx.qpath_res(qpath, callee.hir_id) {
+                                Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id),
+                                // `Res::Local` if it was a closure, for which we
+                                // do not currently support must-use linting
+                                _ => None,
+                            }
+                        }
+                        _ => None,
+                    }
+                }
+                hir::ExprKind::MethodCall(..) => {
+                    cx.typeck_results().type_dependent_def_id(expr.hir_id)
+                }
+                _ => None,
+            };
+            if let Some(def_id) = maybe_def_id {
+                check_must_use_def(
+                    cx,
+                    def_id,
+                    expr.span,
+                    "return value of ",
+                    "",
+                    expr_is_from_block,
+                )
+            } else {
+                false
+            }
+        }
+
+        /// A path through a type to a must_use source. Contains useful info for the lint.
+        #[derive(Debug)]
+        enum MustUsePath {
+            /// Suppress must_use checking.
+            Suppressed,
+            /// The root of the normal must_use lint with an optional message.
+            Def(Span, DefId, Option<Symbol>),
+            Boxed(Box<Self>),
+            Pinned(Box<Self>),
+            Opaque(Box<Self>),
+            TraitObject(Box<Self>),
+            TupleElement(Vec<(usize, Self)>),
+            Array(Box<Self>, u64),
+            /// The root of the unused_closures lint.
+            Closure(Span),
+            /// The root of the unused_coroutines lint.
+            Coroutine(Span),
+        }
+
+        #[instrument(skip(cx, expr), level = "debug", ret)]
+        fn is_ty_must_use<'tcx>(
+            cx: &LateContext<'tcx>,
+            ty: Ty<'tcx>,
+            expr: &hir::Expr<'_>,
+            span: Span,
+        ) -> Option<MustUsePath> {
+            if ty.is_unit()
+                || !ty.is_inhabited_from(
+                    cx.tcx,
+                    cx.tcx.parent_module(expr.hir_id).to_def_id(),
+                    cx.param_env,
+                )
+            {
+                return Some(MustUsePath::Suppressed);
+            }
+
+            match *ty.kind() {
+                ty::Adt(..) if ty.is_box() => {
+                    let boxed_ty = ty.boxed_ty();
+                    is_ty_must_use(cx, boxed_ty, expr, span)
+                        .map(|inner| MustUsePath::Boxed(Box::new(inner)))
+                }
+                ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => {
+                    let pinned_ty = args.type_at(0);
+                    is_ty_must_use(cx, pinned_ty, expr, span)
+                        .map(|inner| MustUsePath::Pinned(Box::new(inner)))
+                }
+                ty::Adt(def, _) => is_def_must_use(cx, def.did(), span),
+                ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => {
+                    elaborate(
+                        cx.tcx,
+                        cx.tcx.explicit_item_super_predicates(def).iter_identity_copied(),
+                    )
+                    // We only care about self bounds for the impl-trait
+                    .filter_only_self()
+                    .find_map(|(pred, _span)| {
+                        // We only look at the `DefId`, so it is safe to skip the binder here.
+                        if let ty::ClauseKind::Trait(ref poly_trait_predicate) =
+                            pred.kind().skip_binder()
+                        {
+                            let def_id = poly_trait_predicate.trait_ref.def_id;
+
+                            is_def_must_use(cx, def_id, span)
+                        } else {
+                            None
+                        }
+                    })
+                    .map(|inner| MustUsePath::Opaque(Box::new(inner)))
+                }
+                ty::Dynamic(binders, _, _) => binders.iter().find_map(|predicate| {
+                    if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder()
+                    {
+                        let def_id = trait_ref.def_id;
+                        is_def_must_use(cx, def_id, span)
+                            .map(|inner| MustUsePath::TraitObject(Box::new(inner)))
+                    } else {
+                        None
+                    }
+                }),
+                ty::Tuple(tys) => {
+                    let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind {
+                        debug_assert_eq!(elem_exprs.len(), tys.len());
+                        elem_exprs
+                    } else {
+                        &[]
+                    };
+
+                    // Default to `expr`.
+                    let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr));
+
+                    let nested_must_use = tys
+                        .iter()
+                        .zip(elem_exprs)
+                        .enumerate()
+                        .filter_map(|(i, (ty, expr))| {
+                            is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path))
+                        })
+                        .collect::<Vec<_>>();
+
+                    if !nested_must_use.is_empty() {
+                        Some(MustUsePath::TupleElement(nested_must_use))
+                    } else {
+                        None
+                    }
+                }
+                ty::Array(ty, len) => match len.try_eval_target_usize(cx.tcx, cx.param_env) {
+                    // If the array is empty we don't lint, to avoid false positives
+                    Some(0) | None => None,
+                    // If the array is definitely non-empty, we can do `#[must_use]` checking.
+                    Some(len) => is_ty_must_use(cx, ty, expr, span)
+                        .map(|inner| MustUsePath::Array(Box::new(inner), len)),
+                },
+                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) {
+                        let def_id = cx.tcx.lang_items().future_trait()?;
+                        is_def_must_use(cx, def_id, span)
+                            .map(|inner| MustUsePath::Opaque(Box::new(inner)))
+                    } else {
+                        None
+                    };
+                    must_use.or(Some(MustUsePath::Coroutine(span)))
+                }
+                _ => None,
+            }
+        }
+
+        fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option<MustUsePath> {
+            if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) {
+                // check for #[must_use = "..."]
+                let reason = attr.value_str();
+                Some(MustUsePath::Def(span, def_id, reason))
+            } else {
+                None
+            }
+        }
+
+        // Returns whether further errors should be suppressed because either a lint has been
+        // emitted or the type should be ignored.
+        fn check_must_use_def(
+            cx: &LateContext<'_>,
+            def_id: DefId,
+            span: Span,
+            descr_pre_path: &str,
+            descr_post_path: &str,
+            expr_is_from_block: bool,
+        ) -> bool {
+            is_def_must_use(cx, def_id, span)
+                .map(|must_use_path| {
+                    emit_must_use_untranslated(
+                        cx,
+                        &must_use_path,
+                        descr_pre_path,
+                        descr_post_path,
+                        1,
+                        false,
+                        expr_is_from_block,
+                    )
+                })
+                .is_some()
+        }
+
+        #[instrument(skip(cx), level = "debug")]
+        fn emit_must_use_untranslated(
+            cx: &LateContext<'_>,
+            path: &MustUsePath,
+            descr_pre: &str,
+            descr_post: &str,
+            plural_len: usize,
+            is_inner: bool,
+            expr_is_from_block: bool,
+        ) {
+            let plural_suffix = pluralize!(plural_len);
+
+            match path {
+                MustUsePath::Suppressed => {}
+                MustUsePath::Boxed(path) => {
+                    let descr_pre = &format!("{descr_pre}boxed ");
+                    emit_must_use_untranslated(
+                        cx,
+                        path,
+                        descr_pre,
+                        descr_post,
+                        plural_len,
+                        true,
+                        expr_is_from_block,
+                    );
+                }
+                MustUsePath::Pinned(path) => {
+                    let descr_pre = &format!("{descr_pre}pinned ");
+                    emit_must_use_untranslated(
+                        cx,
+                        path,
+                        descr_pre,
+                        descr_post,
+                        plural_len,
+                        true,
+                        expr_is_from_block,
+                    );
+                }
+                MustUsePath::Opaque(path) => {
+                    let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of ");
+                    emit_must_use_untranslated(
+                        cx,
+                        path,
+                        descr_pre,
+                        descr_post,
+                        plural_len,
+                        true,
+                        expr_is_from_block,
+                    );
+                }
+                MustUsePath::TraitObject(path) => {
+                    let descr_post = &format!(" trait object{plural_suffix}{descr_post}");
+                    emit_must_use_untranslated(
+                        cx,
+                        path,
+                        descr_pre,
+                        descr_post,
+                        plural_len,
+                        true,
+                        expr_is_from_block,
+                    );
+                }
+                MustUsePath::TupleElement(elems) => {
+                    for (index, path) in elems {
+                        let descr_post = &format!(" in tuple element {index}");
+                        emit_must_use_untranslated(
+                            cx,
+                            path,
+                            descr_pre,
+                            descr_post,
+                            plural_len,
+                            true,
+                            expr_is_from_block,
+                        );
+                    }
+                }
+                MustUsePath::Array(path, len) => {
+                    let descr_pre = &format!("{descr_pre}array{plural_suffix} of ");
+                    emit_must_use_untranslated(
+                        cx,
+                        path,
+                        descr_pre,
+                        descr_post,
+                        plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)),
+                        true,
+                        expr_is_from_block,
+                    );
+                }
+                MustUsePath::Closure(span) => {
+                    cx.emit_span_lint(
+                        UNUSED_MUST_USE,
+                        *span,
+                        UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post },
+                    );
+                }
+                MustUsePath::Coroutine(span) => {
+                    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_span_lint(
+                        UNUSED_MUST_USE,
+                        *span,
+                        UnusedDef {
+                            pre: descr_pre,
+                            post: descr_post,
+                            cx,
+                            def_id: *def_id,
+                            note: *reason,
+                            suggestion: (!is_inner).then_some(if expr_is_from_block {
+                                UnusedDefSuggestion::BlockTailExpr {
+                                    before_span: span.shrink_to_lo(),
+                                    after_span: span.shrink_to_hi(),
+                                }
+                            } else {
+                                UnusedDefSuggestion::NormalExpr { span: span.shrink_to_lo() }
+                            }),
+                        },
+                    );
+                }
+            }
+        }
+    }
+}
+
+declare_lint! {
+    /// The `path_statements` lint detects path statements with no effect.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// let x = 42;
+    ///
+    /// x;
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// It is usually a mistake to have a statement that has no effect.
+    pub PATH_STATEMENTS,
+    Warn,
+    "path statements with no effect"
+}
+
+declare_lint_pass!(PathStatements => [PATH_STATEMENTS]);
+
+impl<'tcx> LateLintPass<'tcx> for PathStatements {
+    fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
+        if let hir::StmtKind::Semi(expr) = s.kind {
+            if let hir::ExprKind::Path(_) = expr.kind {
+                let ty = cx.typeck_results().expr_ty(expr);
+                if ty.needs_drop(cx.tcx, cx.param_env) {
+                    let sub = if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span)
+                    {
+                        PathStatementDropSub::Suggestion { span: s.span, snippet }
+                    } else {
+                        PathStatementDropSub::Help { span: s.span }
+                    };
+                    cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementDrop { sub })
+                } else {
+                    cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementNoEffect);
+                }
+            }
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum UnusedDelimsCtx {
+    FunctionArg,
+    MethodArg,
+    AssignedValue,
+    AssignedValueLetElse,
+    IfCond,
+    WhileCond,
+    ForIterExpr,
+    MatchScrutineeExpr,
+    ReturnValue,
+    BlockRetValue,
+    LetScrutineeExpr,
+    ArrayLenExpr,
+    AnonConst,
+    MatchArmExpr,
+    IndexExpr,
+}
+
+impl From<UnusedDelimsCtx> for &'static str {
+    fn from(ctx: UnusedDelimsCtx) -> &'static str {
+        match ctx {
+            UnusedDelimsCtx::FunctionArg => "function argument",
+            UnusedDelimsCtx::MethodArg => "method argument",
+            UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => {
+                "assigned value"
+            }
+            UnusedDelimsCtx::IfCond => "`if` condition",
+            UnusedDelimsCtx::WhileCond => "`while` condition",
+            UnusedDelimsCtx::ForIterExpr => "`for` iterator expression",
+            UnusedDelimsCtx::MatchScrutineeExpr => "`match` scrutinee expression",
+            UnusedDelimsCtx::ReturnValue => "`return` value",
+            UnusedDelimsCtx::BlockRetValue => "block return value",
+            UnusedDelimsCtx::LetScrutineeExpr => "`let` scrutinee expression",
+            UnusedDelimsCtx::ArrayLenExpr | UnusedDelimsCtx::AnonConst => "const expression",
+            UnusedDelimsCtx::MatchArmExpr => "match arm expression",
+            UnusedDelimsCtx::IndexExpr => "index expression",
+        }
+    }
+}
+
+/// Used by both `UnusedParens` and `UnusedBraces` to prevent code duplication.
+trait UnusedDelimLint {
+    const DELIM_STR: &'static str;
+
+    /// Due to `ref` pattern, there can be a difference between using
+    /// `{ expr }` and `expr` in pattern-matching contexts. This means
+    /// that we should only lint `unused_parens` and not `unused_braces`
+    /// in this case.
+    ///
+    /// ```rust
+    /// let mut a = 7;
+    /// let ref b = { a }; // We actually borrow a copy of `a` here.
+    /// a += 1; // By mutating `a` we invalidate any borrows of `a`.
+    /// assert_eq!(b + 1, a); // `b` does not borrow `a`, so we can still use it here.
+    /// ```
+    const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool;
+
+    // this cannot be a constant is it refers to a static.
+    fn lint(&self) -> &'static Lint;
+
+    fn check_unused_delims_expr(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        followed_by_block: bool,
+        left_pos: Option<BytePos>,
+        right_pos: Option<BytePos>,
+        is_kw: bool,
+    );
+
+    fn is_expr_delims_necessary(
+        inner: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        followed_by_block: 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,
+                _ if classify::expr_trailing_brace(inner).is_some() => return true,
+                _ => {}
+            }
+        }
+
+        // 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) }`.
+        //
+        // FIXME: https://github.com/rust-lang/rust/issues/119426
+        // The syntax tree in this code is from after macro expansion, so the
+        // current implementation has both false negatives and false positives
+        // related to expressions containing macros.
+        //
+        //     macro_rules! m1 {
+        //         () => {
+        //             1
+        //         };
+        //     }
+        //
+        //     fn f1() -> u8 {
+        //         // Lint says parens are not needed, but they are.
+        //         (m1! {} + 1)
+        //     }
+        //
+        //     macro_rules! m2 {
+        //         () => {
+        //             loop { break 1; }
+        //         };
+        //     }
+        //
+        //     fn f2() -> u8 {
+        //         // Lint says parens are needed, but they are not.
+        //         (m2!() + 1)
+        //     }
+        {
+            let mut innermost = inner;
+            loop {
+                innermost = match &innermost.kind {
+                    ExprKind::Binary(_op, lhs, _rhs) => lhs,
+                    ExprKind::Call(fn_, _params) => fn_,
+                    ExprKind::Cast(expr, _ty) => expr,
+                    ExprKind::Type(expr, _ty) => expr,
+                    ExprKind::Index(base, _subscript, _) => base,
+                    _ => break,
+                };
+                if !classify::expr_requires_semi_to_be_stmt(innermost) {
+                    return true;
+                }
+            }
+        }
+
+        // Check if RHS needs parens to prevent false-positives in cases like `if (() == return)
+        // {}`.
+        if !followed_by_block {
+            return false;
+        }
+
+        // Check if we need parens for `match &( Struct { feild:  }) {}`.
+        {
+            let mut innermost = inner;
+            loop {
+                innermost = match &innermost.kind {
+                    ExprKind::AddrOf(_, _, expr) => expr,
+                    _ => {
+                        if parser::contains_exterior_struct_lit(innermost) {
+                            return true;
+                        } else {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        let mut innermost = inner;
+        loop {
+            innermost = match &innermost.kind {
+                ExprKind::Unary(_op, expr) => expr,
+                ExprKind::Binary(_op, _lhs, rhs) => rhs,
+                ExprKind::AssignOp(_op, _lhs, rhs) => rhs,
+                ExprKind::Assign(_lhs, rhs, _span) => rhs,
+
+                ExprKind::Ret(_) | ExprKind::Yield(..) | ExprKind::Yeet(..) => return true,
+
+                ExprKind::Break(_label, None) => return false,
+                ExprKind::Break(_label, Some(break_expr)) => {
+                    return matches!(break_expr.kind, ExprKind::Block(..));
+                }
+
+                ExprKind::Range(_lhs, Some(rhs), _limits) => {
+                    return matches!(rhs.kind, ExprKind::Block(..));
+                }
+
+                _ => return parser::contains_exterior_struct_lit(inner),
+            }
+        }
+    }
+
+    fn emit_unused_delims_expr(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        left_pos: Option<BytePos>,
+        right_pos: Option<BytePos>,
+        is_kw: bool,
+    ) {
+        // If `value` has `ExprKind::Err`, unused delim lint can be broken.
+        // For example, the following code caused ICE.
+        // This is because the `ExprKind::Call` in `value` has `ExprKind::Err` as its argument
+        // and this leads to wrong spans. #104897
+        //
+        // ```
+        // fn f(){(print!(รก
+        // ```
+        use rustc_ast::visit::{walk_expr, Visitor};
+        struct ErrExprVisitor;
+        impl<'ast> Visitor<'ast> for ErrExprVisitor {
+            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)
+                }
+            }
+        }
+        if ErrExprVisitor.visit_expr(value).is_break() {
+            return;
+        }
+        let spans = match value.kind {
+            ast::ExprKind::Block(ref block, None) if block.stmts.len() == 1 => block.stmts[0]
+                .span
+                .find_ancestor_inside(value.span)
+                .map(|span| (value.span.with_hi(span.lo()), value.span.with_lo(span.hi()))),
+            ast::ExprKind::Paren(ref expr) => {
+                expr.span.find_ancestor_inside(value.span).map(|expr_span| {
+                    (value.span.with_hi(expr_span.lo()), value.span.with_lo(expr_span.hi()))
+                })
+            }
+            _ => return,
+        };
+        let keep_space = (
+            left_pos.is_some_and(|s| s >= value.span.lo()),
+            right_pos.is_some_and(|s| s <= value.span.hi()),
+        );
+        self.emit_unused_delims(cx, value.span, spans, ctx.into(), keep_space, is_kw);
+    }
+
+    fn emit_unused_delims(
+        &self,
+        cx: &EarlyContext<'_>,
+        value_span: Span,
+        spans: Option<(Span, Span)>,
+        msg: &str,
+        keep_space: (bool, bool),
+        is_kw: bool,
+    ) {
+        let primary_span = if let Some((lo, hi)) = spans {
+            if hi.is_empty() {
+                // do not point at delims that do not exist
+                return;
+            }
+            MultiSpan::from(vec![lo, hi])
+        } else {
+            MultiSpan::from(value_span)
+        };
+        let suggestion = spans.map(|(lo, hi)| {
+            let sm = cx.sess().source_map();
+            let lo_replace = if (keep_space.0 || is_kw)
+                && let Ok(snip) = sm.span_to_prev_source(lo)
+                && !snip.ends_with(' ')
+            {
+                " "
+            } else {
+                ""
+            };
+
+            let hi_replace = if keep_space.1
+                && let Ok(snip) = sm.span_to_next_source(hi)
+                && !snip.starts_with(' ')
+            {
+                " "
+            } else {
+                ""
+            };
+            UnusedDelimSuggestion {
+                start_span: lo,
+                start_replace: lo_replace,
+                end_span: hi,
+                end_replace: hi_replace,
+            }
+        });
+        cx.emit_span_lint(
+            self.lint(),
+            primary_span,
+            UnusedDelim { delim: Self::DELIM_STR, item: msg, suggestion },
+        );
+    }
+
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        use rustc_ast::ExprKind::*;
+        let (value, ctx, followed_by_block, left_pos, right_pos, is_kw) = match e.kind {
+            // Do not lint `unused_braces` in `if let` expressions.
+            If(ref cond, ref block, _)
+                if !matches!(cond.kind, Let(..)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
+            {
+                let left = e.span.lo() + rustc_span::BytePos(2);
+                let right = block.span.lo();
+                (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right), true)
+            }
+
+            // Do not lint `unused_braces` in `while let` expressions.
+            While(ref cond, ref block, ..)
+                if !matches!(cond.kind, Let(..)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
+            {
+                let left = e.span.lo() + rustc_span::BytePos(5);
+                let right = block.span.lo();
+                (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right), true)
+            }
+
+            ForLoop { ref iter, ref body, .. } => {
+                (iter, UnusedDelimsCtx::ForIterExpr, true, None, Some(body.span.lo()), true)
+            }
+
+            Match(ref head, _, ast::MatchKind::Prefix)
+                if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
+            {
+                let left = e.span.lo() + rustc_span::BytePos(5);
+                (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None, true)
+            }
+
+            Ret(Some(ref value)) => {
+                let left = e.span.lo() + rustc_span::BytePos(3);
+                (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None, true)
+            }
+
+            Index(_, ref value, _) => (value, UnusedDelimsCtx::IndexExpr, false, None, None, false),
+
+            Assign(_, ref value, _) | AssignOp(.., ref value) => {
+                (value, UnusedDelimsCtx::AssignedValue, false, None, None, false)
+            }
+            // either function/method call, or something this lint doesn't care about
+            ref call_or_other => {
+                let (args_to_check, ctx) = match *call_or_other {
+                    Call(_, ref args) => (&args[..], UnusedDelimsCtx::FunctionArg),
+                    MethodCall(ref call) => (&call.args[..], UnusedDelimsCtx::MethodArg),
+                    // actual catch-all arm
+                    _ => {
+                        return;
+                    }
+                };
+                // Don't lint if this is a nested macro expansion: otherwise, the lint could
+                // trigger in situations that macro authors shouldn't have to care about, e.g.,
+                // when a parenthesized token tree matched in one macro expansion is matched as
+                // an expression in another and used as a fn/method argument (Issue #47775)
+                if e.span.ctxt().outer_expn_data().call_site.from_expansion() {
+                    return;
+                }
+                for arg in args_to_check {
+                    self.check_unused_delims_expr(cx, arg, ctx, false, None, None, false);
+                }
+                return;
+            }
+        };
+        self.check_unused_delims_expr(
+            cx,
+            value,
+            ctx,
+            followed_by_block,
+            left_pos,
+            right_pos,
+            is_kw,
+        );
+    }
+
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+        match s.kind {
+            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,
+                        Some(_) => UnusedDelimsCtx::AssignedValueLetElse,
+                    };
+                    self.check_unused_delims_expr(cx, init, ctx, false, None, None, false);
+                }
+            }
+            StmtKind::Expr(ref expr) => {
+                self.check_unused_delims_expr(
+                    cx,
+                    expr,
+                    UnusedDelimsCtx::BlockRetValue,
+                    false,
+                    None,
+                    None,
+                    false,
+                );
+            }
+            _ => {}
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        use ast::ItemKind::*;
+
+        if let Const(box ast::ConstItem { expr: Some(expr), .. })
+        | Static(box ast::StaticItem { expr: Some(expr), .. }) = &item.kind
+        {
+            self.check_unused_delims_expr(
+                cx,
+                expr,
+                UnusedDelimsCtx::AssignedValue,
+                false,
+                None,
+                None,
+                false,
+            );
+        }
+    }
+}
+
+declare_lint! {
+    /// The `unused_parens` lint detects `if`, `match`, `while` and `return`
+    /// with parentheses; they do not need them.
+    ///
+    /// ### Examples
+    ///
+    /// ```rust
+    /// if(true) {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The parentheses are not needed, and should be removed. This is the
+    /// preferred style for writing these expressions.
+    pub(super) UNUSED_PARENS,
+    Warn,
+    "`if`, `match`, `while` and `return` do not need parentheses"
+}
+
+pub struct UnusedParens {
+    with_self_ty_parens: bool,
+    /// `1 as (i32) < 2` parses to ExprKind::Lt
+    /// `1 as i32 < 2` parses to i32::<2[missing angle bracket]
+    parens_in_cast_in_lt: Vec<ast::NodeId>,
+}
+
+impl UnusedParens {
+    pub fn new() -> Self {
+        Self { with_self_ty_parens: false, parens_in_cast_in_lt: Vec::new() }
+    }
+}
+
+impl_lint_pass!(UnusedParens => [UNUSED_PARENS]);
+
+impl UnusedDelimLint for UnusedParens {
+    const DELIM_STR: &'static str = "parentheses";
+
+    const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = true;
+
+    fn lint(&self) -> &'static Lint {
+        UNUSED_PARENS
+    }
+
+    fn check_unused_delims_expr(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        followed_by_block: bool,
+        left_pos: Option<BytePos>,
+        right_pos: Option<BytePos>,
+        is_kw: bool,
+    ) {
+        match value.kind {
+            ast::ExprKind::Paren(ref inner) => {
+                if !Self::is_expr_delims_necessary(inner, ctx, followed_by_block)
+                    && value.attrs.is_empty()
+                    && !value.span.from_expansion()
+                    && (ctx != UnusedDelimsCtx::LetScrutineeExpr
+                        || !matches!(inner.kind, ast::ExprKind::Binary(
+                                rustc_span::source_map::Spanned { node, .. },
+                                _,
+                                _,
+                            ) if node.is_lazy()))
+                {
+                    self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw)
+                }
+            }
+            ast::ExprKind::Let(_, ref expr, _, _) => {
+                self.check_unused_delims_expr(
+                    cx,
+                    expr,
+                    UnusedDelimsCtx::LetScrutineeExpr,
+                    followed_by_block,
+                    None,
+                    None,
+                    false,
+                );
+            }
+            _ => {}
+        }
+    }
+}
+
+impl UnusedParens {
+    fn check_unused_parens_pat(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Pat,
+        avoid_or: bool,
+        avoid_mut: bool,
+        keep_space: (bool, bool),
+    ) {
+        use ast::{BindingMode, PatKind};
+
+        if let PatKind::Paren(inner) = &value.kind {
+            match inner.kind {
+                // The lint visitor will visit each subpattern of `p`. We do not want to lint
+                // any range pattern no matter where it occurs in the pattern. For something like
+                // `&(a..=b)`, there is a recursive `check_pat` on `a` and `b`, but we will assume
+                // that if there are unnecessary parens they serve a purpose of readability.
+                PatKind::Range(..) => return,
+                // Avoid `p0 | .. | pn` if we should.
+                PatKind::Or(..) if avoid_or => return,
+                // Avoid `mut x` and `mut x @ p` if we should:
+                PatKind::Ident(BindingMode::MUT, ..) if avoid_mut => {
+                    return;
+                }
+                // Otherwise proceed with linting.
+                _ => {}
+            }
+            let spans = if !value.span.from_expansion() {
+                inner
+                    .span
+                    .find_ancestor_inside(value.span)
+                    .map(|inner| (value.span.with_hi(inner.lo()), value.span.with_lo(inner.hi())))
+            } else {
+                None
+            };
+            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 Some(ty_id) = self.cast_followed_by_lt(e) {
+            self.parens_in_cast_in_lt.push(ty_id);
+        }
+
+        match e.kind {
+            ExprKind::Let(ref pat, _, _, _) | ExprKind::ForLoop { ref pat, .. } => {
+                self.check_unused_parens_pat(cx, pat, false, false, (true, true));
+            }
+            // We ignore parens in cases like `if (((let Some(0) = Some(1))))` because we already
+            // handle a hard error for them during AST lowering in `lower_expr_mut`, but we still
+            // want to complain about things like `if let 42 = (42)`.
+            ExprKind::If(ref cond, ref block, ref else_)
+                if matches!(cond.peel_parens().kind, ExprKind::Let(..)) =>
+            {
+                self.check_unused_delims_expr(
+                    cx,
+                    cond.peel_parens(),
+                    UnusedDelimsCtx::LetScrutineeExpr,
+                    true,
+                    None,
+                    None,
+                    true,
+                );
+                for stmt in &block.stmts {
+                    <Self as UnusedDelimLint>::check_stmt(self, cx, stmt);
+                }
+                if let Some(e) = else_ {
+                    <Self as UnusedDelimLint>::check_expr(self, cx, e);
+                }
+                return;
+            }
+            ExprKind::Match(ref _expr, ref arm, _) => {
+                for a in arm {
+                    if let Some(body) = &a.body {
+                        self.check_unused_delims_expr(
+                            cx,
+                            body,
+                            UnusedDelimsCtx::MatchArmExpr,
+                            false,
+                            None,
+                            None,
+                            true,
+                        );
+                    }
+                }
+            }
+            _ => {}
+        }
+
+        <Self as UnusedDelimLint>::check_expr(self, cx, e)
+    }
+
+    fn check_expr_post(&mut self, _cx: &EarlyContext<'_>, e: &ast::Expr) {
+        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,
+                "check_expr, check_ty, and check_expr_post are called, in that order, by the visitor"
+            );
+        }
+    }
+
+    fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &ast::Pat) {
+        use ast::{Mutability, PatKind::*};
+        let keep_space = (false, false);
+        match &p.kind {
+            // 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(..) | 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);
+            },
+            Struct(_, _, fps, _) => for f in fps {
+                self.check_unused_parens_pat(cx, &f.pat, false, false, keep_space);
+            },
+            // Avoid linting on `i @ (p0 | .. | pn)` and `box (p0 | .. | pn)`, #64106.
+            Ident(.., Some(p)) | Box(p) | Deref(p) => self.check_unused_parens_pat(cx, p, true, false, keep_space),
+            // Avoid linting on `&(mut x)` as `&mut x` has a different meaning, #55342.
+            // Also avoid linting on `& mut? (p0 | .. | pn)`, #64106.
+            Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not, keep_space),
+        }
+    }
+
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+        if let StmtKind::Let(ref local) = s.kind {
+            self.check_unused_parens_pat(cx, &local.pat, true, false, (true, false));
+        }
+
+        <Self as UnusedDelimLint>::check_stmt(self, cx, s)
+    }
+
+    fn check_param(&mut self, cx: &EarlyContext<'_>, param: &ast::Param) {
+        self.check_unused_parens_pat(cx, &param.pat, true, false, (false, false));
+    }
+
+    fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
+        self.check_unused_parens_pat(cx, &arm.pat, false, false, (false, false));
+    }
+
+    fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
+        if let ast::TyKind::Paren(_) = ty.kind
+            && Some(&ty.id) == self.parens_in_cast_in_lt.last()
+        {
+            return;
+        }
+        match &ty.kind {
+            ast::TyKind::Array(_, len) => {
+                self.check_unused_delims_expr(
+                    cx,
+                    &len.value,
+                    UnusedDelimsCtx::ArrayLenExpr,
+                    false,
+                    None,
+                    None,
+                    false,
+                );
+            }
+            ast::TyKind::Paren(r) => {
+                match &r.kind {
+                    ast::TyKind::TraitObject(..) => {}
+                    ast::TyKind::BareFn(b)
+                        if self.with_self_ty_parens && b.generic_params.len() > 0 => {}
+                    ast::TyKind::ImplTrait(_, bounds) if bounds.len() > 1 => {}
+                    _ => {
+                        let spans = if !ty.span.from_expansion() {
+                            r.span
+                                .find_ancestor_inside(ty.span)
+                                .map(|r| (ty.span.with_hi(r.lo()), ty.span.with_lo(r.hi())))
+                        } else {
+                            None
+                        };
+                        self.emit_unused_delims(cx, ty.span, spans, "type", (false, false), false);
+                    }
+                }
+                self.with_self_ty_parens = false;
+            }
+            _ => {}
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        <Self as UnusedDelimLint>::check_item(self, cx, item)
+    }
+
+    fn enter_where_predicate(&mut self, _: &EarlyContext<'_>, pred: &ast::WherePredicate) {
+        use rustc_ast::{WhereBoundPredicate, WherePredicate};
+        if let WherePredicate::BoundPredicate(WhereBoundPredicate {
+            bounded_ty,
+            bound_generic_params,
+            ..
+        }) = pred
+            && let ast::TyKind::Paren(_) = &bounded_ty.kind
+            && bound_generic_params.is_empty()
+        {
+            self.with_self_ty_parens = true;
+        }
+    }
+
+    fn exit_where_predicate(&mut self, _: &EarlyContext<'_>, _: &ast::WherePredicate) {
+        assert!(!self.with_self_ty_parens);
+    }
+}
+
+declare_lint! {
+    /// The `unused_braces` lint detects unnecessary braces around an
+    /// expression.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// if { true } {
+    ///     // ...
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// The braces are not needed, and should be removed. This is the
+    /// preferred style for writing these expressions.
+    pub(super) UNUSED_BRACES,
+    Warn,
+    "unnecessary braces around an expression"
+}
+
+declare_lint_pass!(UnusedBraces => [UNUSED_BRACES]);
+
+impl UnusedDelimLint for UnusedBraces {
+    const DELIM_STR: &'static str = "braces";
+
+    const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = false;
+
+    fn lint(&self) -> &'static Lint {
+        UNUSED_BRACES
+    }
+
+    fn check_unused_delims_expr(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        followed_by_block: bool,
+        left_pos: Option<BytePos>,
+        right_pos: Option<BytePos>,
+        is_kw: bool,
+    ) {
+        match value.kind {
+            ast::ExprKind::Block(ref inner, None)
+                if inner.rules == ast::BlockCheckMode::Default =>
+            {
+                // emit a warning under the following conditions:
+                //
+                // - the block does not have a label
+                // - the block is not `unsafe`
+                // - the block contains exactly one expression (do not lint `{ expr; }`)
+                // - `followed_by_block` is true and the internal expr may contain a `{`
+                // - the block is not multiline (do not lint multiline match arms)
+                //      ```
+                //      match expr {
+                //          Pattern => {
+                //              somewhat_long_expression
+                //          }
+                //          // ...
+                //      }
+                //      ```
+                // - the block has no attribute and was not created inside a macro
+                // - if the block is an `anon_const`, the inner expr must be a literal
+                //   not created by a macro, i.e. do not lint on:
+                //      ```
+                //      struct A<const N: usize>;
+                //      let _: A<{ 2 + 3 }>;
+                //      let _: A<{produces_literal!()}>;
+                //      ```
+                // 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, ctx, followed_by_block)
+                            && (ctx != UnusedDelimsCtx::AnonConst
+                                || (matches!(expr.kind, ast::ExprKind::Lit(_))
+                                    && !expr.span.from_expansion()))
+                            && !cx.sess().source_map().is_multiline(value.span)
+                            && value.attrs.is_empty()
+                            && !value.span.from_expansion()
+                            && !inner.span.from_expansion()
+                        {
+                            self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw)
+                        }
+                    }
+                }
+            }
+            ast::ExprKind::Let(_, ref expr, _, _) => {
+                self.check_unused_delims_expr(
+                    cx,
+                    expr,
+                    UnusedDelimsCtx::LetScrutineeExpr,
+                    followed_by_block,
+                    None,
+                    None,
+                    false,
+                );
+            }
+            _ => {}
+        }
+    }
+}
+
+impl EarlyLintPass for UnusedBraces {
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+        <Self as UnusedDelimLint>::check_stmt(self, cx, s)
+    }
+
+    #[inline]
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        <Self as UnusedDelimLint>::check_expr(self, cx, e);
+
+        if let ExprKind::Repeat(_, ref anon_const) = e.kind {
+            self.check_unused_delims_expr(
+                cx,
+                &anon_const.value,
+                UnusedDelimsCtx::AnonConst,
+                false,
+                None,
+                None,
+                false,
+            );
+        }
+    }
+
+    fn check_generic_arg(&mut self, cx: &EarlyContext<'_>, arg: &ast::GenericArg) {
+        if let ast::GenericArg::Const(ct) = arg {
+            self.check_unused_delims_expr(
+                cx,
+                &ct.value,
+                UnusedDelimsCtx::AnonConst,
+                false,
+                None,
+                None,
+                false,
+            );
+        }
+    }
+
+    fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
+        if let Some(anon_const) = &v.disr_expr {
+            self.check_unused_delims_expr(
+                cx,
+                &anon_const.value,
+                UnusedDelimsCtx::AnonConst,
+                false,
+                None,
+                None,
+                false,
+            );
+        }
+    }
+
+    fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
+        match ty.kind {
+            ast::TyKind::Array(_, ref len) => {
+                self.check_unused_delims_expr(
+                    cx,
+                    &len.value,
+                    UnusedDelimsCtx::ArrayLenExpr,
+                    false,
+                    None,
+                    None,
+                    false,
+                );
+            }
+
+            ast::TyKind::Typeof(ref anon_const) => {
+                self.check_unused_delims_expr(
+                    cx,
+                    &anon_const.value,
+                    UnusedDelimsCtx::AnonConst,
+                    false,
+                    None,
+                    None,
+                    false,
+                );
+            }
+
+            _ => {}
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        <Self as UnusedDelimLint>::check_item(self, cx, item)
+    }
+}
+
+declare_lint! {
+    /// The `unused_import_braces` lint catches unnecessary braces around an
+    /// imported item.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(unused_import_braces)]
+    /// use test::{A};
+    ///
+    /// pub mod test {
+    ///     pub struct A;
+    /// }
+    /// # fn main() {}
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// If there is only a single item, then remove the braces (`use test::A;`
+    /// for example).
+    ///
+    /// This lint is "allow" by default because it is only enforcing a
+    /// stylistic choice.
+    UNUSED_IMPORT_BRACES,
+    Allow,
+    "unnecessary braces around an imported item"
+}
+
+declare_lint_pass!(UnusedImportBraces => [UNUSED_IMPORT_BRACES]);
+
+impl UnusedImportBraces {
+    fn check_use_tree(&self, cx: &EarlyContext<'_>, use_tree: &ast::UseTree, item: &ast::Item) {
+        if let ast::UseTreeKind::Nested { ref items, .. } = use_tree.kind {
+            // Recursively check nested UseTrees
+            for (tree, _) in items {
+                self.check_use_tree(cx, tree, item);
+            }
+
+            // Trigger the lint only if there is one nested item
+            if items.len() != 1 {
+                return;
+            }
+
+            // Trigger the lint if the nested item is a non-self single item
+            let node_name = match items[0].0.kind {
+                ast::UseTreeKind::Simple(rename) => {
+                    let orig_ident = items[0].0.prefix.segments.last().unwrap().ident;
+                    if orig_ident.name == kw::SelfLower {
+                        return;
+                    }
+                    rename.unwrap_or(orig_ident).name
+                }
+                ast::UseTreeKind::Glob => Symbol::intern("*"),
+                ast::UseTreeKind::Nested { .. } => return,
+            };
+
+            cx.emit_span_lint(
+                UNUSED_IMPORT_BRACES,
+                item.span,
+                UnusedImportBracesDiag { node: node_name },
+            );
+        }
+    }
+}
+
+impl EarlyLintPass for UnusedImportBraces {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        if let ast::ItemKind::Use(ref use_tree) = item.kind {
+            self.check_use_tree(cx, use_tree, item);
+        }
+    }
+}
+
+declare_lint! {
+    /// The `unused_allocation` lint detects unnecessary allocations that can
+    /// be eliminated.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// fn main() {
+    ///     let a = Box::new([1, 2, 3]).len();
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// When a `box` expression is immediately coerced to a reference, then
+    /// the allocation is unnecessary, and a reference (using `&` or `&mut`)
+    /// should be used instead to avoid the allocation.
+    pub(super) UNUSED_ALLOCATION,
+    Warn,
+    "detects unnecessary allocations that can be eliminated"
+}
+
+declare_lint_pass!(UnusedAllocation => [UNUSED_ALLOCATION]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedAllocation {
+    fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
+        match e.kind {
+            hir::ExprKind::Call(path_expr, [_])
+                if let hir::ExprKind::Path(qpath) = &path_expr.kind
+                    && let Some(did) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()
+                    && cx.tcx.is_diagnostic_item(sym::box_new, did) => {}
+            _ => return,
+        }
+
+        for adj in cx.typeck_results().expr_adjustments(e) {
+            if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind {
+                match m {
+                    adjustment::AutoBorrowMutability::Not => {
+                        cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationDiag);
+                    }
+                    adjustment::AutoBorrowMutability::Mut { .. } => {
+                        cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationMutDiag);
+                    }
+                };
+            }
+        }
+    }
+}