about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_interface/src/passes.rs7
-rw-r--r--compiler/rustc_lint/src/builtin.rs463
-rw-r--r--compiler/rustc_lint/src/foreign_modules.rs402
-rw-r--r--compiler/rustc_lint/src/late.rs42
-rw-r--r--compiler/rustc_lint/src/levels.rs11
-rw-r--r--compiler/rustc_lint/src/lib.rs45
-rw-r--r--compiler/rustc_lint/src/types.rs43
-rw-r--r--compiler/rustc_middle/src/query/mod.rs5
-rw-r--r--src/librustdoc/core.rs8
-rw-r--r--tests/ui/lint/clashing-extern-fn.stderr124
-rw-r--r--tests/ui/lint/issue-111359.stderr16
-rw-r--r--tests/ui/lint/issue-1866.stderr4
-rw-r--r--tests/ui/lint/lint-attr-everywhere-late.stderr104
-rw-r--r--tests/ui/lint/lint-missing-doc.stderr36
-rw-r--r--tests/ui/lint/missing-doc-private-macro.stderr12
-rw-r--r--tests/ui/missing_debug_impls.rs2
16 files changed, 653 insertions, 671 deletions
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 6b3facd041c..a34fdf4ecc9 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -846,10 +846,11 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
                     },
                     {
                         sess.time("lint_checking", || {
-                            rustc_lint::check_crate(tcx, || {
-                                rustc_lint::BuiltinCombinedLateLintPass::new()
-                            });
+                            rustc_lint::check_crate(tcx);
                         });
+                    },
+                    {
+                        tcx.ensure().clashing_extern_declarations(());
                     }
                 );
             },
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 5e61073694f..c290462fc20 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -24,10 +24,9 @@ use crate::fluent_generated as fluent;
 use crate::{
     errors::BuiltinEllipsisInclusiveRangePatterns,
     lints::{
-        BuiltinAnonymousParams, BuiltinBoxPointers, BuiltinClashingExtern,
-        BuiltinClashingExternSub, BuiltinConstNoMangle, BuiltinDeprecatedAttrLink,
-        BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed, BuiltinDerefNullptr,
-        BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives,
+        BuiltinAnonymousParams, BuiltinBoxPointers, BuiltinConstNoMangle,
+        BuiltinDeprecatedAttrLink, BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed,
+        BuiltinDerefNullptr, BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives,
         BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures,
         BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
         BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
@@ -40,8 +39,7 @@ use crate::{
         BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
         BuiltinWhileTrue, SuggestChangingAssocTypes,
     },
-    types::{transparent_newtype_field, CItemKind},
-    EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext,
+    EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext,
 };
 use hir::IsAsync;
 use rustc_ast::attr;
@@ -49,28 +47,26 @@ 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_data_structures::fx::{FxHashMap, FxHashSet};
-use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_errors::{Applicability, DecorateLint, MultiSpan};
 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, LocalDefIdSet, CRATE_DEF_ID};
+use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
 use rustc_hir::intravisit::FnKind as HirFnKind;
-use rustc_hir::{Body, FnDecl, ForeignItemKind, GenericParamKind, Node, PatKind, PredicateOrigin};
+use rustc_hir::{Body, FnDecl, GenericParamKind, Node, PatKind, PredicateOrigin};
 use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::layout::{LayoutError, LayoutOf};
+use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::GenericArgKind;
 use rustc_middle::ty::TypeVisitableExt;
-use rustc_middle::ty::{self, Instance, Ty, TyCtxt, VariantDef};
+use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
 use rustc_session::config::ExpectedValues;
 use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason};
 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, FIRST_VARIANT};
+use rustc_target::abi::Abi;
 use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt};
 use rustc_trait_selection::traits::{self, misc::type_allowed_to_implement_copy};
 
@@ -461,10 +457,7 @@ declare_lint! {
     report_in_external_macro
 }
 
-pub struct MissingDoc {
-    /// Stack of whether `#[doc(hidden)]` is set at each level which has lint attributes.
-    doc_hidden_stack: Vec<bool>,
-}
+pub struct MissingDoc;
 
 impl_lint_pass!(MissingDoc => [MISSING_DOCS]);
 
@@ -493,14 +486,6 @@ fn has_doc(attr: &ast::Attribute) -> bool {
 }
 
 impl MissingDoc {
-    pub fn new() -> MissingDoc {
-        MissingDoc { doc_hidden_stack: vec![false] }
-    }
-
-    fn doc_hidden(&self) -> bool {
-        *self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
-    }
-
     fn check_missing_docs_attrs(
         &self,
         cx: &LateContext<'_>,
@@ -514,11 +499,6 @@ impl MissingDoc {
             return;
         }
 
-        // `#[doc(hidden)]` disables missing_docs check.
-        if self.doc_hidden() {
-            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`).
@@ -541,23 +521,6 @@ impl MissingDoc {
 }
 
 impl<'tcx> LateLintPass<'tcx> for MissingDoc {
-    #[inline]
-    fn enter_lint_attrs(&mut self, _cx: &LateContext<'_>, attrs: &[ast::Attribute]) {
-        let doc_hidden = self.doc_hidden()
-            || attrs.iter().any(|attr| {
-                attr.has_name(sym::doc)
-                    && match attr.meta_item_list() {
-                        None => false,
-                        Some(l) => attr::list_contains_name(&l, sym::hidden),
-                    }
-            });
-        self.doc_hidden_stack.push(doc_hidden);
-    }
-
-    fn exit_lint_attrs(&mut self, _: &LateContext<'_>, _attrs: &[ast::Attribute]) {
-        self.doc_hidden_stack.pop().expect("empty doc_hidden_stack");
-    }
-
     fn check_crate(&mut self, cx: &LateContext<'_>) {
         self.check_missing_docs_attrs(cx, CRATE_DEF_ID, "the", "crate");
     }
@@ -778,9 +741,7 @@ declare_lint! {
 }
 
 #[derive(Default)]
-pub struct MissingDebugImplementations {
-    impling_types: Option<LocalDefIdSet>,
-}
+pub(crate) struct MissingDebugImplementations;
 
 impl_lint_pass!(MissingDebugImplementations => [MISSING_DEBUG_IMPLEMENTATIONS]);
 
@@ -795,23 +756,20 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
             _ => return,
         }
 
-        let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else { return };
-
-        if self.impling_types.is_none() {
-            let mut impls = LocalDefIdSet::default();
-            cx.tcx.for_each_impl(debug, |d| {
-                if let Some(ty_def) = cx.tcx.type_of(d).instantiate_identity().ty_adt_def() {
-                    if let Some(def_id) = ty_def.did().as_local() {
-                        impls.insert(def_id);
-                    }
-                }
-            });
-
-            self.impling_types = Some(impls);
-            debug!("{:?}", self.impling_types);
+        // 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;
         }
 
-        if !self.impling_types.as_ref().unwrap().contains(&item.owner_id.def_id) {
+        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_spanned_lint(
                 MISSING_DEBUG_IMPLEMENTATIONS,
                 item.span,
@@ -2613,381 +2571,6 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
 }
 
 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"
-}
-
-pub 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: FxHashMap<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: FxHashMap::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::ForeignItem<'_>) -> 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)
-        }
-    }
-
-    /// 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::ForeignItem<'_>) -> SymbolName {
-        if let Some((overridden_link_name, overridden_link_name_span)) =
-            tcx.codegen_fn_attrs(fi.owner_id).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.owner_id, sym::link_name).unwrap().span)
-            })
-        {
-            SymbolName::Link(overridden_link_name, overridden_link_name_span)
-        } else {
-            SymbolName::Normal(fi.ident.name)
-        }
-    }
-
-    /// 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>(
-        cx: &LateContext<'tcx>,
-        a: Ty<'tcx>,
-        b: Ty<'tcx>,
-        ckind: CItemKind,
-    ) -> bool {
-        fn structurally_same_type_impl<'tcx>(
-            seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>,
-            cx: &LateContext<'tcx>,
-            a: Ty<'tcx>,
-            b: Ty<'tcx>,
-            ckind: CItemKind,
-        ) -> bool {
-            debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b);
-            let tcx = cx.tcx;
-
-            // 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 = crate::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) = 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::sty::TyKind::*;
-                let a_kind = a.kind();
-                let b_kind = b.kind();
-
-                let compare_layouts = |a, b| -> Result<bool, LayoutError<'tcx>> {
-                    debug!("compare_layouts({:?}, {:?})", a, b);
-                    let a_layout = &cx.layout_of(a)?.layout.abi();
-                    let b_layout = &cx.layout_of(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,
-                                        cx,
-                                        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, cx, *a_ty, *b_ty, ckind)
-                        }
-                        (Slice(a_ty), Slice(b_ty)) => {
-                            structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind)
-                        }
-                        (RawPtr(a_tymut), RawPtr(b_tymut)) => {
-                            a_tymut.mutbl == b_tymut.mutbl
-                                && structurally_same_type_impl(
-                                    seen_types, cx, a_tymut.ty, b_tymut.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, cx, *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.erase_late_bound_regions(a_poly_sig);
-                            let b_sig = tcx.erase_late_bound_regions(b_poly_sig);
-
-                            (a_sig.abi, a_sig.unsafety, a_sig.c_variadic)
-                                == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic)
-                                && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
-                                    structurally_same_type_impl(seen_types, cx, *a, *b, ckind)
-                                })
-                                && structurally_same_type_impl(
-                                    seen_types,
-                                    cx,
-                                    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, cx, 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(..))
-                        | (Generator(..), Generator(..))
-                        | (GeneratorWitness(..), GeneratorWitness(..))
-                        | (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) = crate::types::repr_nullable_ptr(cx, 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),
-                    }
-                })
-            }
-        }
-        let mut seen_types = FxHashSet::default();
-        structurally_same_type_impl(&mut seen_types, cx, a, b, ckind)
-    }
-}
-
-impl_lint_pass!(ClashingExternDeclarations => [CLASHING_EXTERN_DECLARATIONS]);
-
-impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations {
-    #[instrument(level = "trace", skip(self, cx))]
-    fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, this_fi: &hir::ForeignItem<'_>) {
-        if let ForeignItemKind::Fn(..) = this_fi.kind {
-            let tcx = cx.tcx;
-            if let Some(existing_did) = self.insert(tcx, this_fi) {
-                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 !Self::structurally_same_type(
-                    cx,
-                    existing_decl_ty,
-                    this_decl_ty,
-                    CItemKind::Declaration,
-                ) {
-                    let orig_fi = tcx.hir().expect_foreign_item(existing_did);
-                    let orig = Self::name_of_extern_decl(tcx, orig_fi);
-
-                    // 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.
-                    let get_relevant_span =
-                        |fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) {
-                            SymbolName::Normal(_) => fi.span,
-                            SymbolName::Link(_, annot_span) => fi.span.to(annot_span),
-                        };
-
-                    // Finally, emit the diagnostic.
-                    let this = this_fi.ident.name;
-                    let orig = orig.get_name();
-                    let previous_decl_label = get_relevant_span(orig_fi);
-                    let mismatch_label = get_relevant_span(this_fi);
-                    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_spanned_lint(
-                        CLASHING_EXTERN_DECLARATIONS,
-                        this_fi.hir_id(),
-                        get_relevant_span(this_fi),
-                        decorator,
-                    );
-                }
-            }
-        }
-    }
-}
-
-declare_lint! {
     /// The `deref_nullptr` lint detects when an null pointer is dereferenced,
     /// which causes [undefined behavior].
     ///
diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs
new file mode 100644
index 00000000000..7b291d558e0
--- /dev/null
+++ b/compiler/rustc_lint/src/foreign_modules.rs
@@ -0,0 +1,402 @@
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::stack::ensure_sufficient_stack;
+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::lint::{lint_array, LintArray};
+use rustc_span::{sym, Span, Symbol};
+use rustc_target::abi::FIRST_VARIANT;
+
+use crate::lints::{BuiltinClashingExtern, BuiltinClashingExternSub};
+use crate::types;
+
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers { clashing_extern_declarations, ..*providers };
+}
+
+pub(crate) fn get_lints() -> LintArray {
+    lint_array!(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: FxHashMap<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: FxHashMap::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_spanned_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 = FxHashSet::default();
+    structurally_same_type_impl(&mut seen_types, tcx, param_env, a, b, ckind)
+}
+
+fn structurally_same_type_impl<'tcx>(
+    seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>,
+    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::sty::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_tymut), RawPtr(b_tymut)) => {
+                    a_tymut.mutbl == b_tymut.mutbl
+                        && structurally_same_type_impl(
+                            seen_types, tcx, param_env, a_tymut.ty, b_tymut.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.erase_late_bound_regions(a_poly_sig);
+                    let b_sig = tcx.erase_late_bound_regions(b_poly_sig);
+
+                    (a_sig.abi, a_sig.unsafety, a_sig.c_variadic)
+                        == (b_sig.abi, b_sig.unsafety, 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(..))
+                | (Generator(..), Generator(..))
+                | (GeneratorWitness(..), GeneratorWitness(..))
+                | (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/late.rs b/compiler/rustc_lint/src/late.rs
index fb12ded71d6..3331dbad4a9 100644
--- a/compiler/rustc_lint/src/late.rs
+++ b/compiler/rustc_lint/src/late.rs
@@ -17,7 +17,7 @@
 use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore};
 use rustc_ast as ast;
 use rustc_data_structures::stack::ensure_sufficient_stack;
-use rustc_data_structures::sync::{join, DynSend};
+use rustc_data_structures::sync::join;
 use rustc_hir as hir;
 use rustc_hir::def_id::LocalDefId;
 use rustc_hir::intravisit as hir_visit;
@@ -336,7 +336,7 @@ macro_rules! impl_late_lint_pass {
 
 crate::late_lint_methods!(impl_late_lint_pass, []);
 
-pub(super) fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>(
+pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>(
     tcx: TyCtxt<'tcx>,
     module_def_id: LocalDefId,
     builtin_lints: T,
@@ -376,6 +376,12 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>(
     let mut cx = LateContextAndPass { context, pass };
 
     let (module, _span, hir_id) = tcx.hir().get_module(module_def_id);
+
+    // 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);
 
     // Visit the crate attributes
@@ -383,10 +389,19 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>(
         for attr in tcx.hir().attrs(hir::CRATE_HIR_ID).iter() {
             cx.visit_attribute(attr)
         }
+        lint_callback!(cx, check_crate_post,);
     }
 }
 
-fn late_lint_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>(tcx: TyCtxt<'tcx>, builtin_lints: T) {
+fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) {
+    // Note: `passes` is often empty.
+    let mut passes: Vec<_> =
+        unerased_lint_store(tcx).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect();
+
+    if passes.is_empty() {
+        return;
+    }
+
     let context = LateContext {
         tcx,
         enclosing_body: None,
@@ -399,18 +414,8 @@ fn late_lint_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>(tcx: TyCtxt<'tcx>, builti
         only_module: false,
     };
 
-    // Note: `passes` is often empty. In that case, it's faster to run
-    // `builtin_lints` directly rather than bundling it up into the
-    // `RuntimeCombinedLateLintPass`.
-    let mut passes: Vec<_> =
-        unerased_lint_store(tcx).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect();
-    if passes.is_empty() {
-        late_lint_crate_inner(tcx, context, builtin_lints);
-    } else {
-        passes.push(Box::new(builtin_lints));
-        let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] };
-        late_lint_crate_inner(tcx, context, pass);
-    }
+    let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] };
+    late_lint_crate_inner(tcx, context, pass);
 }
 
 fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>(
@@ -432,15 +437,12 @@ fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>(
 }
 
 /// Performs lint checking on a crate.
-pub fn check_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>(
-    tcx: TyCtxt<'tcx>,
-    builtin_lints: impl FnOnce() -> T + Send + DynSend,
-) {
+pub fn check_crate<'tcx>(tcx: TyCtxt<'tcx>) {
     join(
         || {
             tcx.sess.time("crate_lints", || {
                 // Run whole crate non-incremental lints
-                late_lint_crate(tcx, builtin_lints());
+                late_lint_crate(tcx);
             });
         },
         || {
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index fb407be1f02..18b178d8882 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -1,4 +1,5 @@
 use crate::{
+    builtin::MISSING_DOCS,
     context::{CheckLintNameResult, LintStore},
     fluent_generated as fluent,
     late::unerased_lint_store,
@@ -667,6 +668,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
                 continue;
             }
 
+            // `#[doc(hidden)]` disables missing_docs check.
+            if attr.has_name(sym::doc)
+                && attr
+                    .meta_item_list()
+                    .map_or(false, |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
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 80bf53ea866..42378951af3 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -59,6 +59,7 @@ mod enum_intrinsics_non_enums;
 mod errors;
 mod expect;
 mod for_loops_over_fallibles;
+mod foreign_modules;
 pub mod hidden_unicode_codepoints;
 mod internal;
 mod invalid_from_utf8;
@@ -125,11 +126,11 @@ use types::*;
 use unused::*;
 
 /// Useful for other parts of the compiler / Clippy.
-pub use builtin::SoftLints;
+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, unerased_lint_store};
+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};
@@ -140,11 +141,12 @@ 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: LocalDefId) {
-    late::late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new());
+    late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new());
 }
 
 early_lint_methods!(
@@ -182,25 +184,6 @@ early_lint_methods!(
     ]
 );
 
-// FIXME: Make a separate lint type which does not require typeck tables.
-
-late_lint_methods!(
-    declare_combined_late_lint_pass,
-    [
-        pub BuiltinCombinedLateLintPass,
-        [
-            // Tracks attributes of parents
-            MissingDoc: MissingDoc::new(),
-            // Builds a global list of all impls of `Debug`.
-            // FIXME: Turn the computation of types which implement Debug into a query
-            // and change this to a module lint pass
-            MissingDebugImplementations: MissingDebugImplementations::default(),
-            // Keeps a global list of foreign declarations.
-            ClashingExternDeclarations: ClashingExternDeclarations::new(),
-        ]
-    ]
-);
-
 late_lint_methods!(
     declare_combined_late_lint_pass,
     [
@@ -253,6 +236,8 @@ late_lint_methods!(
             OpaqueHiddenInferredBound: OpaqueHiddenInferredBound,
             MultipleSupertraitUpcastable: MultipleSupertraitUpcastable,
             MapUnitFn: MapUnitFn,
+            MissingDebugImplementations: MissingDebugImplementations,
+            MissingDoc: MissingDoc,
         ]
     ]
 );
@@ -281,7 +266,7 @@ fn register_builtins(store: &mut LintStore) {
     store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints());
     store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints());
     store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints());
-    store.register_lints(&BuiltinCombinedLateLintPass::get_lints());
+    store.register_lints(&foreign_modules::get_lints());
 
     add_lint_group!(
         "nonstandard_style",
@@ -521,20 +506,20 @@ 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_pass(|_| Box::new(DefaultHashTypes));
+    store.register_late_mod_pass(|_| Box::new(DefaultHashTypes));
     store.register_lints(&QueryStability::get_lints());
-    store.register_late_pass(|_| Box::new(QueryStability));
+    store.register_late_mod_pass(|_| Box::new(QueryStability));
     store.register_lints(&ExistingDocKeyword::get_lints());
-    store.register_late_pass(|_| Box::new(ExistingDocKeyword));
+    store.register_late_mod_pass(|_| Box::new(ExistingDocKeyword));
     store.register_lints(&TyTyKind::get_lints());
-    store.register_late_pass(|_| Box::new(TyTyKind));
+    store.register_late_mod_pass(|_| Box::new(TyTyKind));
     store.register_lints(&Diagnostics::get_lints());
     store.register_early_pass(|| Box::new(Diagnostics));
-    store.register_late_pass(|_| Box::new(Diagnostics));
+    store.register_late_mod_pass(|_| Box::new(Diagnostics));
     store.register_lints(&BadOptAccess::get_lints());
-    store.register_late_pass(|_| Box::new(BadOptAccess));
+    store.register_late_mod_pass(|_| Box::new(BadOptAccess));
     store.register_lints(&PassByValue::get_lints());
-    store.register_late_pass(|_| Box::new(PassByValue));
+    store.register_late_mod_pass(|_| Box::new(PassByValue));
     // 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
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index 226d01b79a8..24bc16af2ee 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -815,8 +815,7 @@ pub fn transparent_newtype_field<'a, 'tcx>(
 }
 
 /// Is type known to be non-null?
-fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool {
-    let tcx = cx.tcx;
+fn ty_is_known_nonnull<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool {
     match ty.kind() {
         ty::FnPtr(_) => true,
         ty::Ref(..) => true,
@@ -835,8 +834,8 @@ fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKi
 
             def.variants()
                 .iter()
-                .filter_map(|variant| transparent_newtype_field(cx.tcx, variant))
-                .any(|field| ty_is_known_nonnull(cx, field.ty(tcx, args), mode))
+                .filter_map(|variant| transparent_newtype_field(tcx, variant))
+                .any(|field| ty_is_known_nonnull(tcx, field.ty(tcx, args), mode))
         }
         _ => false,
     }
@@ -844,15 +843,12 @@ fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKi
 
 /// 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>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
-    let tcx = cx.tcx;
+fn get_nullable_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
     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(cx.tcx, v));
+                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,
@@ -863,7 +859,7 @@ fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t
                     .expect("No non-zst fields in transparent type.")
                     .ty(tcx, field_args)
             };
-            return get_nullable_type(cx, inner_field_ty);
+            return get_nullable_type(tcx, inner_field_ty);
         }
         ty::Int(ty) => Ty::new_int(tcx, ty),
         ty::Uint(ty) => Ty::new_uint(tcx, ty),
@@ -895,43 +891,44 @@ fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t
 /// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes.
 /// FIXME: This duplicates code in codegen.
 pub(crate) fn repr_nullable_ptr<'tcx>(
-    cx: &LateContext<'tcx>,
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
     ty: Ty<'tcx>,
     ckind: CItemKind,
 ) -> Option<Ty<'tcx>> {
-    debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty);
+    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(cx.tcx, args),
+                ([], [field]) | ([field], []) => field.ty(tcx, args),
                 _ => return None,
             },
             _ => return None,
         };
 
-        if !ty_is_known_nonnull(cx, field_ty, ckind) {
+        if !ty_is_known_nonnull(tcx, 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, cx.tcx, cx.param_env).unwrap();
+        let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, param_env).unwrap();
         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_abi = &cx.layout_of(field_ty).unwrap().abi;
+        let field_ty_abi = &tcx.layout_of(param_env.and(field_ty)).unwrap().abi;
         if let Abi::Scalar(field_ty_scalar) = field_ty_abi {
-            match field_ty_scalar.valid_range(cx) {
+            match field_ty_scalar.valid_range(&tcx) {
                 WrappingRange { start: 0, end }
-                    if end == field_ty_scalar.size(&cx.tcx).unsigned_int_max() - 1 =>
+                    if end == field_ty_scalar.size(&tcx).unsigned_int_max() - 1 =>
                 {
-                    return Some(get_nullable_type(cx, field_ty).unwrap());
+                    return Some(get_nullable_type(tcx, field_ty).unwrap());
                 }
                 WrappingRange { start: 1, .. } => {
-                    return Some(get_nullable_type(cx, field_ty).unwrap());
+                    return Some(get_nullable_type(tcx, field_ty).unwrap());
                 }
                 WrappingRange { start, end } => {
                     unreachable!("Unhandled start and end range: ({}, {})", start, end)
@@ -1116,7 +1113,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                         if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none()
                         {
                             // Special-case types like `Option<extern fn()>`.
-                            if repr_nullable_ptr(self.cx, ty, self.mode).is_none() {
+                            if repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode)
+                                .is_none()
+                            {
                                 return FfiUnsafe {
                                     ty,
                                     reason: fluent::lint_improper_ctypes_enum_repr_reason,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index a02f9a9f796..35fe6ab99fb 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1596,6 +1596,11 @@ rustc_queries! {
         separate_provide_extern
     }
 
+    /// Lint against `extern fn` declarations having incompatible types.
+    query clashing_extern_declarations(_: ()) {
+        desc { "checking `extern fn` declarations are compatible" }
+    }
+
     /// Identifies the entry-point (e.g., the `main` function) for a given
     /// crate, returning `None` if there is no entry point (such as for library crates).
     query entry_fn(_: ()) -> Option<(DefId, EntryFnType)> {
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 12d620b5b18..d7da8120996 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -10,6 +10,7 @@ use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId};
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::{HirId, Path};
 use rustc_interface::interface;
+use rustc_lint::{late_lint_mod, MissingDoc};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
 use rustc_session::config::{self, CrateType, ErrorOutputType, ResolveDocLinks};
@@ -262,8 +263,9 @@ pub(crate) fn create_config(
         parse_sess_created: None,
         register_lints: Some(Box::new(crate::lint::register_lints)),
         override_queries: Some(|_sess, providers, _external_providers| {
+            // We do not register late module lints, so this only runs `MissingDoc`.
             // Most lints will require typechecking, so just don't run them.
-            providers.lint_mod = |_, _| {};
+            providers.lint_mod = |tcx, module_def_id| late_lint_mod(tcx, module_def_id, MissingDoc);
             // hack so that `used_trait_imports` won't try to call typeck
             providers.used_trait_imports = |_, _| {
                 static EMPTY_SET: LazyLock<UnordSet<LocalDefId>> = LazyLock::new(UnordSet::default);
@@ -317,9 +319,7 @@ pub(crate) fn run_global_ctxt(
         tcx.hir().for_each_module(|module| tcx.ensure().check_mod_item_types(module))
     });
     tcx.sess.abort_if_errors();
-    tcx.sess.time("missing_docs", || {
-        rustc_lint::check_crate(tcx, rustc_lint::builtin::MissingDoc::new);
-    });
+    tcx.sess.time("missing_docs", || rustc_lint::check_crate(tcx));
     tcx.sess.time("check_mod_attrs", || {
         tcx.hir().for_each_module(|module| tcx.ensure().check_mod_attrs(module))
     });
diff --git a/tests/ui/lint/clashing-extern-fn.stderr b/tests/ui/lint/clashing-extern-fn.stderr
index 5d457ba0ec7..0d269e599dd 100644
--- a/tests/ui/lint/clashing-extern-fn.stderr
+++ b/tests/ui/lint/clashing-extern-fn.stderr
@@ -1,11 +1,30 @@
+warning: `extern` block uses type `Option<TransparentNoNiche>`, which is not FFI-safe
+  --> $DIR/clashing-extern-fn.rs:433:55
+   |
+LL |             fn hidden_niche_transparent_no_niche() -> Option<TransparentNoNiche>;
+   |                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
+   |
+   = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
+   = note: enum has no representation hint
+   = note: `#[warn(improper_ctypes)]` on by default
+
+warning: `extern` block uses type `Option<UnsafeCell<NonZeroUsize>>`, which is not FFI-safe
+  --> $DIR/clashing-extern-fn.rs:437:46
+   |
+LL |             fn hidden_niche_unsafe_cell() -> Option<UnsafeCell<NonZeroUsize>>;
+   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
+   |
+   = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
+   = note: enum has no representation hint
+
 warning: `clash` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:14:13
    |
 LL |             fn clash(x: u8);
-   |             ---------------- `clash` previously declared here
+   |             --------------- `clash` previously declared here
 ...
 LL |             fn clash(x: u64);
-   |             ^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(u8)`
               found `unsafe extern "C" fn(u64)`
@@ -18,12 +37,11 @@ LL | #![warn(clashing_extern_declarations)]
 warning: `extern_link_name` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:52:9
    |
-LL | /     #[link_name = "extern_link_name"]
-LL | |     fn some_new_name(x: i16);
-   | |_____________________________- `extern_link_name` previously declared here
+LL |     #[link_name = "extern_link_name"]
+   |     --------------------------------- `extern_link_name` previously declared here
 ...
-LL |           fn extern_link_name(x: u32);
-   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+LL |         fn extern_link_name(x: u32);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(i16)`
               found `unsafe extern "C" fn(u32)`
@@ -31,13 +49,11 @@ LL |           fn extern_link_name(x: u32);
 warning: `some_other_extern_link_name` redeclares `some_other_new_name` with a different signature
   --> $DIR/clashing-extern-fn.rs:55:9
    |
-LL |       fn some_other_new_name(x: i16);
-   |       ------------------------------- `some_other_new_name` previously declared here
+LL |     fn some_other_new_name(x: i16);
+   |     ------------------------------ `some_other_new_name` previously declared here
 ...
-LL | /         #[link_name = "some_other_new_name"]
-LL | |
-LL | |         fn some_other_extern_link_name(x: u32);
-   | |_______________________________________________^ this signature doesn't match the previous declaration
+LL |         #[link_name = "some_other_new_name"]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(i16)`
               found `unsafe extern "C" fn(u32)`
@@ -45,14 +61,11 @@ LL | |         fn some_other_extern_link_name(x: u32);
 warning: `other_both_names_different` redeclares `link_name_same` with a different signature
   --> $DIR/clashing-extern-fn.rs:59:9
    |
-LL | /     #[link_name = "link_name_same"]
-LL | |     fn both_names_different(x: i16);
-   | |____________________________________- `link_name_same` previously declared here
+LL |     #[link_name = "link_name_same"]
+   |     ------------------------------- `link_name_same` previously declared here
 ...
-LL | /         #[link_name = "link_name_same"]
-LL | |
-LL | |         fn other_both_names_different(x: u32);
-   | |______________________________________________^ this signature doesn't match the previous declaration
+LL |         #[link_name = "link_name_same"]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(i16)`
               found `unsafe extern "C" fn(u32)`
@@ -61,10 +74,10 @@ warning: `different_mod` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:72:9
    |
 LL |         fn different_mod(x: u8);
-   |         ------------------------ `different_mod` previously declared here
+   |         ----------------------- `different_mod` previously declared here
 ...
 LL |         fn different_mod(x: u64);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(u8)`
               found `unsafe extern "C" fn(u64)`
@@ -73,10 +86,10 @@ warning: `variadic_decl` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:82:9
    |
 LL |     fn variadic_decl(x: u8, ...);
-   |     ----------------------------- `variadic_decl` previously declared here
+   |     ---------------------------- `variadic_decl` previously declared here
 ...
 LL |         fn variadic_decl(x: u8);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |         ^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(u8, ...)`
               found `unsafe extern "C" fn(u8)`
@@ -85,10 +98,10 @@ warning: `weigh_banana` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:142:13
    |
 LL |             fn weigh_banana(count: *const Banana) -> u64;
-   |             --------------------------------------------- `weigh_banana` previously declared here
+   |             -------------------------------------------- `weigh_banana` previously declared here
 ...
 LL |             fn weigh_banana(count: *const Banana) -> u64;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(*const one::Banana) -> u64`
               found `unsafe extern "C" fn(*const three::Banana) -> u64`
@@ -97,10 +110,10 @@ warning: `draw_point` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:171:13
    |
 LL |             fn draw_point(p: Point);
-   |             ------------------------ `draw_point` previously declared here
+   |             ----------------------- `draw_point` previously declared here
 ...
 LL |             fn draw_point(p: Point);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(sameish_members::a::Point)`
               found `unsafe extern "C" fn(sameish_members::b::Point)`
@@ -109,10 +122,10 @@ warning: `origin` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:197:13
    |
 LL |             fn origin() -> Point3;
-   |             ---------------------- `origin` previously declared here
+   |             --------------------- `origin` previously declared here
 ...
 LL |             fn origin() -> Point3;
-   |             ^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> same_sized_members_clash::a::Point3`
               found `unsafe extern "C" fn() -> same_sized_members_clash::b::Point3`
@@ -121,10 +134,10 @@ warning: `transparent_incorrect` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:220:13
    |
 LL |             fn transparent_incorrect() -> T;
-   |             -------------------------------- `transparent_incorrect` previously declared here
+   |             ------------------------------- `transparent_incorrect` previously declared here
 ...
 LL |             fn transparent_incorrect() -> isize;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> T`
               found `unsafe extern "C" fn() -> isize`
@@ -133,10 +146,10 @@ warning: `missing_return_type` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:259:13
    |
 LL |             fn missing_return_type() -> usize;
-   |             ---------------------------------- `missing_return_type` previously declared here
+   |             --------------------------------- `missing_return_type` previously declared here
 ...
 LL |             fn missing_return_type();
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> usize`
               found `unsafe extern "C" fn()`
@@ -145,10 +158,10 @@ warning: `non_zero_usize` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:277:13
    |
 LL |             fn non_zero_usize() -> core::num::NonZeroUsize;
-   |             ----------------------------------------------- `non_zero_usize` previously declared here
+   |             ---------------------------------------------- `non_zero_usize` previously declared here
 ...
 LL |             fn non_zero_usize() -> usize;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> NonZeroUsize`
               found `unsafe extern "C" fn() -> usize`
@@ -157,10 +170,10 @@ warning: `non_null_ptr` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:279:13
    |
 LL |             fn non_null_ptr() -> core::ptr::NonNull<usize>;
-   |             ----------------------------------------------- `non_null_ptr` previously declared here
+   |             ---------------------------------------------- `non_null_ptr` previously declared here
 ...
 LL |             fn non_null_ptr() -> *const usize;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> NonNull<usize>`
               found `unsafe extern "C" fn() -> *const usize`
@@ -169,10 +182,10 @@ warning: `option_non_zero_usize_incorrect` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:377:13
    |
 LL |             fn option_non_zero_usize_incorrect() -> usize;
-   |             ---------------------------------------------- `option_non_zero_usize_incorrect` previously declared here
+   |             --------------------------------------------- `option_non_zero_usize_incorrect` previously declared here
 ...
 LL |             fn option_non_zero_usize_incorrect() -> isize;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> usize`
               found `unsafe extern "C" fn() -> isize`
@@ -181,10 +194,10 @@ warning: `option_non_null_ptr_incorrect` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:379:13
    |
 LL |             fn option_non_null_ptr_incorrect() -> *const usize;
-   |             --------------------------------------------------- `option_non_null_ptr_incorrect` previously declared here
+   |             -------------------------------------------------- `option_non_null_ptr_incorrect` previously declared here
 ...
 LL |             fn option_non_null_ptr_incorrect() -> *const isize;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> *const usize`
               found `unsafe extern "C" fn() -> *const isize`
@@ -193,10 +206,10 @@ warning: `hidden_niche_transparent_no_niche` redeclared with a different signatu
   --> $DIR/clashing-extern-fn.rs:433:13
    |
 LL |             fn hidden_niche_transparent_no_niche() -> usize;
-   |             ------------------------------------------------ `hidden_niche_transparent_no_niche` previously declared here
+   |             ----------------------------------------------- `hidden_niche_transparent_no_niche` previously declared here
 ...
 LL |             fn hidden_niche_transparent_no_niche() -> Option<TransparentNoNiche>;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> usize`
               found `unsafe extern "C" fn() -> Option<TransparentNoNiche>`
@@ -205,32 +218,13 @@ warning: `hidden_niche_unsafe_cell` redeclared with a different signature
   --> $DIR/clashing-extern-fn.rs:437:13
    |
 LL |             fn hidden_niche_unsafe_cell() -> usize;
-   |             --------------------------------------- `hidden_niche_unsafe_cell` previously declared here
+   |             -------------------------------------- `hidden_niche_unsafe_cell` previously declared here
 ...
 LL |             fn hidden_niche_unsafe_cell() -> Option<UnsafeCell<NonZeroUsize>>;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn() -> usize`
               found `unsafe extern "C" fn() -> Option<UnsafeCell<NonZeroUsize>>`
 
-warning: `extern` block uses type `Option<TransparentNoNiche>`, which is not FFI-safe
-  --> $DIR/clashing-extern-fn.rs:433:55
-   |
-LL |             fn hidden_niche_transparent_no_niche() -> Option<TransparentNoNiche>;
-   |                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
-   |
-   = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
-   = note: enum has no representation hint
-   = note: `#[warn(improper_ctypes)]` on by default
-
-warning: `extern` block uses type `Option<UnsafeCell<NonZeroUsize>>`, which is not FFI-safe
-  --> $DIR/clashing-extern-fn.rs:437:46
-   |
-LL |             fn hidden_niche_unsafe_cell() -> Option<UnsafeCell<NonZeroUsize>>;
-   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
-   |
-   = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
-   = note: enum has no representation hint
-
 warning: 19 warnings emitted
 
diff --git a/tests/ui/lint/issue-111359.stderr b/tests/ui/lint/issue-111359.stderr
index 2296d8413d6..0aef5007a2b 100644
--- a/tests/ui/lint/issue-111359.stderr
+++ b/tests/ui/lint/issue-111359.stderr
@@ -1,26 +1,26 @@
-error: type does not implement `Debug`; consider adding `#[derive(Debug)]` or a manual implementation
+error: type could implement `Copy`; consider adding `impl Copy`
   --> $DIR/issue-111359.rs:7:5
    |
 LL |     pub struct BarPub;
    |     ^^^^^^^^^^^^^^^^^^
    |
 note: the lint level is defined here
-  --> $DIR/issue-111359.rs:1:8
+  --> $DIR/issue-111359.rs:2:8
    |
-LL | #[deny(missing_debug_implementations)]
-   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[deny(missing_copy_implementations)]
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: type could implement `Copy`; consider adding `impl Copy`
+error: type does not implement `Debug`; consider adding `#[derive(Debug)]` or a manual implementation
   --> $DIR/issue-111359.rs:7:5
    |
 LL |     pub struct BarPub;
    |     ^^^^^^^^^^^^^^^^^^
    |
 note: the lint level is defined here
-  --> $DIR/issue-111359.rs:2:8
+  --> $DIR/issue-111359.rs:1:8
    |
-LL | #[deny(missing_copy_implementations)]
-   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[deny(missing_debug_implementations)]
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/lint/issue-1866.stderr b/tests/ui/lint/issue-1866.stderr
index d19a1349668..36d323825a4 100644
--- a/tests/ui/lint/issue-1866.stderr
+++ b/tests/ui/lint/issue-1866.stderr
@@ -2,10 +2,10 @@ warning: `rust_task_is_unwinding` redeclared with a different signature
   --> $DIR/issue-1866.rs:23:13
    |
 LL |             pub fn rust_task_is_unwinding(rt: *const rust_task) -> bool;
-   |             ------------------------------------------------------------ `rust_task_is_unwinding` previously declared here
+   |             ----------------------------------------------------------- `rust_task_is_unwinding` previously declared here
 ...
 LL |             pub fn rust_task_is_unwinding(rt: *const rust_task) -> bool;
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
    |
    = note: expected `unsafe extern "C" fn(*const usize) -> bool`
               found `unsafe extern "C" fn(*const bool) -> bool`
diff --git a/tests/ui/lint/lint-attr-everywhere-late.stderr b/tests/ui/lint/lint-attr-everywhere-late.stderr
index 9587556b0c1..7fe078068fe 100644
--- a/tests/ui/lint/lint-attr-everywhere-late.stderr
+++ b/tests/ui/lint/lint-attr-everywhere-late.stderr
@@ -34,12 +34,6 @@ note: the lint level is defined here
 LL |     #![deny(missing_docs)]
    |             ^^^^^^^^^^^^
 
-error: missing documentation for a function
-  --> $DIR/lint-attr-everywhere-late.rs:47:5
-   |
-LL |     pub fn missing_inner() {}
-   |     ^^^^^^^^^^^^^^^^^^^^^^
-
 error: missing documentation for an associated function
   --> $DIR/lint-attr-everywhere-late.rs:54:5
    |
@@ -142,52 +136,6 @@ note: the lint level is defined here
 LL |     #[deny(missing_docs)]
    |            ^^^^^^^^^^^^
 
-error: missing documentation for a variant
-  --> $DIR/lint-attr-everywhere-late.rs:112:5
-   |
-LL |     Variant1,
-   |     ^^^^^^^^
-   |
-note: the lint level is defined here
-  --> $DIR/lint-attr-everywhere-late.rs:111:12
-   |
-LL |     #[deny(missing_docs)]
-   |            ^^^^^^^^^^^^
-
-error: `clashing1` redeclared with a different signature
-  --> $DIR/lint-attr-everywhere-late.rs:123:5
-   |
-LL |         fn clashing1();
-   |         --------------- `clashing1` previously declared here
-...
-LL |     fn clashing1(_: i32);
-   |     ^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
-   |
-   = note: expected `unsafe extern "C" fn()`
-              found `unsafe extern "C" fn(i32)`
-note: the lint level is defined here
-  --> $DIR/lint-attr-everywhere-late.rs:122:13
-   |
-LL |     #![deny(clashing_extern_declarations)]
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: `clashing2` redeclared with a different signature
-  --> $DIR/lint-attr-everywhere-late.rs:128:5
-   |
-LL |         fn clashing2();
-   |         --------------- `clashing2` previously declared here
-...
-LL |     fn clashing2(_: i32);
-   |     ^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
-   |
-   = note: expected `unsafe extern "C" fn()`
-              found `unsafe extern "C" fn(i32)`
-note: the lint level is defined here
-  --> $DIR/lint-attr-everywhere-late.rs:127:12
-   |
-LL |     #[deny(clashing_extern_declarations)]
-   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
 error: types that do not implement `Drop` can still have drop glue, consider instead using `std::mem::needs_drop` to detect whether a type is trivially dropped
   --> $DIR/lint-attr-everywhere-late.rs:93:38
    |
@@ -230,6 +178,18 @@ note: the lint level is defined here
 LL |     #[deny(overflowing_literals)] const ASSOC_CONST: u8 = 1000;
    |            ^^^^^^^^^^^^^^^^^^^^
 
+error: missing documentation for a variant
+  --> $DIR/lint-attr-everywhere-late.rs:112:5
+   |
+LL |     Variant1,
+   |     ^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/lint-attr-everywhere-late.rs:111:12
+   |
+LL |     #[deny(missing_docs)]
+   |            ^^^^^^^^^^^^
+
 error: variable `PARAM` should have a snake case name
   --> $DIR/lint-attr-everywhere-late.rs:131:37
    |
@@ -436,5 +396,45 @@ note: the lint level is defined here
 LL |     TupleStruct(#[deny(enum_intrinsics_non_enums)] discriminant::<i32>(&123));
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: missing documentation for a function
+  --> $DIR/lint-attr-everywhere-late.rs:47:5
+   |
+LL |     pub fn missing_inner() {}
+   |     ^^^^^^^^^^^^^^^^^^^^^^
+
+error: `clashing1` redeclared with a different signature
+  --> $DIR/lint-attr-everywhere-late.rs:123:5
+   |
+LL |         fn clashing1();
+   |         -------------- `clashing1` previously declared here
+...
+LL |     fn clashing1(_: i32);
+   |     ^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn()`
+              found `unsafe extern "C" fn(i32)`
+note: the lint level is defined here
+  --> $DIR/lint-attr-everywhere-late.rs:122:13
+   |
+LL |     #![deny(clashing_extern_declarations)]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `clashing2` redeclared with a different signature
+  --> $DIR/lint-attr-everywhere-late.rs:128:5
+   |
+LL |         fn clashing2();
+   |         -------------- `clashing2` previously declared here
+...
+LL |     fn clashing2(_: i32);
+   |     ^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn()`
+              found `unsafe extern "C" fn(i32)`
+note: the lint level is defined here
+  --> $DIR/lint-attr-everywhere-late.rs:127:12
+   |
+LL |     #[deny(clashing_extern_declarations)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
 error: aborting due to 32 previous errors
 
diff --git a/tests/ui/lint/lint-missing-doc.stderr b/tests/ui/lint/lint-missing-doc.stderr
index adcc21c44b2..4e9ee4f2769 100644
--- a/tests/ui/lint/lint-missing-doc.stderr
+++ b/tests/ui/lint/lint-missing-doc.stderr
@@ -113,24 +113,6 @@ LL | pub static BAR4: u32 = 0;
    | ^^^^^^^^^^^^^^^^^^^^
 
 error: missing documentation for a function
-  --> $DIR/lint-missing-doc.rs:174:5
-   |
-LL |     pub fn undocumented1() {}
-   |     ^^^^^^^^^^^^^^^^^^^^^^
-
-error: missing documentation for a function
-  --> $DIR/lint-missing-doc.rs:175:5
-   |
-LL |     pub fn undocumented2() {}
-   |     ^^^^^^^^^^^^^^^^^^^^^^
-
-error: missing documentation for a function
-  --> $DIR/lint-missing-doc.rs:181:9
-   |
-LL |         pub fn also_undocumented1() {}
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: missing documentation for a function
   --> $DIR/lint-missing-doc.rs:196:5
    |
 LL |     pub fn extern_fn_undocumented(f: f32) -> f32;
@@ -154,5 +136,23 @@ error: missing documentation for a trait alias
 LL | pub trait T = Sync;
    | ^^^^^^^^^^^
 
+error: missing documentation for a function
+  --> $DIR/lint-missing-doc.rs:174:5
+   |
+LL |     pub fn undocumented1() {}
+   |     ^^^^^^^^^^^^^^^^^^^^^^
+
+error: missing documentation for a function
+  --> $DIR/lint-missing-doc.rs:175:5
+   |
+LL |     pub fn undocumented2() {}
+   |     ^^^^^^^^^^^^^^^^^^^^^^
+
+error: missing documentation for a function
+  --> $DIR/lint-missing-doc.rs:181:9
+   |
+LL |         pub fn also_undocumented1() {}
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
 error: aborting due to 25 previous errors
 
diff --git a/tests/ui/lint/missing-doc-private-macro.stderr b/tests/ui/lint/missing-doc-private-macro.stderr
index 979b007d0ec..18c8ad2de6b 100644
--- a/tests/ui/lint/missing-doc-private-macro.stderr
+++ b/tests/ui/lint/missing-doc-private-macro.stderr
@@ -1,8 +1,8 @@
 error: missing documentation for a macro
-  --> $DIR/missing-doc-private-macro.rs:31:5
+  --> $DIR/missing-doc-private-macro.rs:37:1
    |
-LL |     macro_rules! exported_to_top_level {
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | pub macro top_level_pub_macro {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
 note: the lint level is defined here
   --> $DIR/missing-doc-private-macro.rs:5:9
@@ -11,10 +11,10 @@ LL | #![deny(missing_docs)]
    |         ^^^^^^^^^^^^
 
 error: missing documentation for a macro
-  --> $DIR/missing-doc-private-macro.rs:37:1
+  --> $DIR/missing-doc-private-macro.rs:31:5
    |
-LL | pub macro top_level_pub_macro {
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     macro_rules! exported_to_top_level {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/missing_debug_impls.rs b/tests/ui/missing_debug_impls.rs
index dc4dacfc468..ccad861c037 100644
--- a/tests/ui/missing_debug_impls.rs
+++ b/tests/ui/missing_debug_impls.rs
@@ -35,4 +35,4 @@ struct PrivateStruct;
 enum PrivateEnum {}
 
 #[derive(Debug)]
-struct GenericType<T>(T);
+pub struct GenericType<T>(T);