about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-07-18 16:27:59 +0000
committerbors <bors@rust-lang.org>2025-07-18 16:27:59 +0000
commit8f08b3a32478b8d0507732800ecb548a76e0fd0c (patch)
treeabdd62e27dec75c21bc2569e90a650de0513fb2c /compiler
parent82310651b93a594a3fd69015e1562186a080d94c (diff)
parent7662731d75ceb19f6242c61a6812c22809e6adc2 (diff)
downloadrust-8f08b3a32478b8d0507732800ecb548a76e0fd0c.tar.gz
rust-8f08b3a32478b8d0507732800ecb548a76e0fd0c.zip
Auto merge of #143845 - cjgillot:stability-query, r=jieyouxu
Split-up stability_index query

This PR aims to move deprecation and stability processing away from the monolithic `stability_index` query, and directly implement `lookup_{deprecation,stability,body_stability,const_stability}` queries.

The basic idea is to:
- move per-attribute sanity checks into `check_attr.rs`;
- move attribute compatibility checks into the `MissingStabilityAnnotations` visitor;
- progressively dismantle the `Annotator` visitor and the `stability_index` query.

The first commit contains functional change, and now warns when `#[automatically_derived]` is applied on a non-trait impl block. The other commits should not change visible behaviour.

Perf in https://github.com/rust-lang/rust/pull/143845#issuecomment-3066308630 shows small but consistent improvement, except for unused-warnings case. That case being a stress test, I'm leaning towards accepting the regression.

This PR changes `check_attr`, so has a high conflict rate on that file. This should not cause issues for review.
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_attr_data_structures/src/lib.rs4
-rw-r--r--compiler/rustc_attr_data_structures/src/stability.rs4
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/stability.rs8
-rw-r--r--compiler/rustc_hir/src/target.rs29
-rw-r--r--compiler/rustc_interface/src/passes.rs6
-rw-r--r--compiler/rustc_middle/src/middle/stability.rs45
-rw-r--r--compiler/rustc_middle/src/query/mod.rs19
-rw-r--r--compiler/rustc_middle/src/ty/context.rs6
-rw-r--r--compiler/rustc_passes/messages.ftl3
-rw-r--r--compiler/rustc_passes/src/check_attr.rs88
-rw-r--r--compiler/rustc_passes/src/errors.rs4
-rw-r--r--compiler/rustc_passes/src/lang_items.rs2
-rw-r--r--compiler/rustc_passes/src/lib_features.rs2
-rw-r--r--compiler/rustc_passes/src/stability.rs940
14 files changed, 507 insertions, 653 deletions
diff --git a/compiler/rustc_attr_data_structures/src/lib.rs b/compiler/rustc_attr_data_structures/src/lib.rs
index 8f8ce575a18..ecca0e39063 100644
--- a/compiler/rustc_attr_data_structures/src/lib.rs
+++ b/compiler/rustc_attr_data_structures/src/lib.rs
@@ -24,7 +24,7 @@ use rustc_ast::token::CommentKind;
 use rustc_ast::{AttrStyle, IntTy, UintTy};
 use rustc_ast_pretty::pp::Printer;
 use rustc_span::hygiene::Transparency;
-use rustc_span::{Span, Symbol};
+use rustc_span::{ErrorGuaranteed, Span, Symbol};
 pub use stability::*;
 use thin_vec::ThinVec;
 pub use version::*;
@@ -170,7 +170,7 @@ macro_rules! print_tup {
 }
 
 print_tup!(A B C D E F G H);
-print_skip!(Span, ());
+print_skip!(Span, (), ErrorGuaranteed);
 print_disp!(u16, bool, NonZero<u32>);
 print_debug!(Symbol, UintTy, IntTy, Align, AttrStyle, CommentKind, Transparency);
 
diff --git a/compiler/rustc_attr_data_structures/src/stability.rs b/compiler/rustc_attr_data_structures/src/stability.rs
index 218e771c745..bd31c062117 100644
--- a/compiler/rustc_attr_data_structures/src/stability.rs
+++ b/compiler/rustc_attr_data_structures/src/stability.rs
@@ -1,7 +1,7 @@
 use std::num::NonZero;
 
 use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute};
-use rustc_span::{Symbol, sym};
+use rustc_span::{ErrorGuaranteed, Symbol, sym};
 
 use crate::{PrintAttribute, RustcVersion};
 
@@ -153,7 +153,7 @@ pub enum StableSince {
     /// Stabilized in the upcoming version, whatever number that is.
     Current,
     /// Failed to parse a stabilization version.
-    Err,
+    Err(ErrorGuaranteed),
 }
 
 impl StabilityLevel {
diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs
index 8f405e5aad9..59337749c87 100644
--- a/compiler/rustc_attr_parsing/src/attributes/stability.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs
@@ -292,12 +292,12 @@ pub(crate) fn parse_stability<S: Stage>(
         } else if let Some(version) = parse_version(since) {
             StableSince::Version(version)
         } else {
-            cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
-            StableSince::Err
+            let err = cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span });
+            StableSince::Err(err)
         }
     } else {
-        cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
-        StableSince::Err
+        let err = cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span });
+        StableSince::Err(err)
     };
 
     match feature {
diff --git a/compiler/rustc_hir/src/target.rs b/compiler/rustc_hir/src/target.rs
index 601898023fc..d617f44f8d8 100644
--- a/compiler/rustc_hir/src/target.rs
+++ b/compiler/rustc_hir/src/target.rs
@@ -41,7 +41,7 @@ pub enum Target {
     Union,
     Trait,
     TraitAlias,
-    Impl,
+    Impl { of_trait: bool },
     Expression,
     Statement,
     Arm,
@@ -51,7 +51,7 @@ pub enum Target {
     ForeignFn,
     ForeignStatic,
     ForeignTy,
-    GenericParam(GenericParamKind),
+    GenericParam { kind: GenericParamKind, has_default: bool },
     MacroDef,
     Param,
     PatField,
@@ -86,14 +86,14 @@ impl Target {
             | Target::Union
             | Target::Trait
             | Target::TraitAlias
-            | Target::Impl
+            | Target::Impl { .. }
             | Target::Expression
             | Target::Statement
             | Target::Arm
             | Target::ForeignFn
             | Target::ForeignStatic
             | Target::ForeignTy
-            | Target::GenericParam(_)
+            | Target::GenericParam { .. }
             | Target::MacroDef
             | Target::Param
             | Target::PatField
@@ -119,7 +119,7 @@ impl Target {
             ItemKind::Union(..) => Target::Union,
             ItemKind::Trait(..) => Target::Trait,
             ItemKind::TraitAlias(..) => Target::TraitAlias,
-            ItemKind::Impl { .. } => Target::Impl,
+            ItemKind::Impl(imp_) => Target::Impl { of_trait: imp_.of_trait.is_some() },
         }
     }
 
@@ -141,7 +141,7 @@ impl Target {
             DefKind::Union => Target::Union,
             DefKind::Trait => Target::Trait,
             DefKind::TraitAlias => Target::TraitAlias,
-            DefKind::Impl { .. } => Target::Impl,
+            DefKind::Impl { of_trait } => Target::Impl { of_trait },
             _ => panic!("impossible case reached"),
         }
     }
@@ -169,11 +169,17 @@ impl Target {
 
     pub fn from_generic_param(generic_param: &hir::GenericParam<'_>) -> Target {
         match generic_param.kind {
-            hir::GenericParamKind::Type { .. } => Target::GenericParam(GenericParamKind::Type),
+            hir::GenericParamKind::Type { default, .. } => Target::GenericParam {
+                kind: GenericParamKind::Type,
+                has_default: default.is_some(),
+            },
             hir::GenericParamKind::Lifetime { .. } => {
-                Target::GenericParam(GenericParamKind::Lifetime)
+                Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: false }
             }
-            hir::GenericParamKind::Const { .. } => Target::GenericParam(GenericParamKind::Const),
+            hir::GenericParamKind::Const { default, .. } => Target::GenericParam {
+                kind: GenericParamKind::Const,
+                has_default: default.is_some(),
+            },
         }
     }
 
@@ -196,7 +202,8 @@ impl Target {
             Target::Union => "union",
             Target::Trait => "trait",
             Target::TraitAlias => "trait alias",
-            Target::Impl => "implementation block",
+            Target::Impl { of_trait: false } => "inherent implementation block",
+            Target::Impl { of_trait: true } => "trait implementation block",
             Target::Expression => "expression",
             Target::Statement => "statement",
             Target::Arm => "match arm",
@@ -210,7 +217,7 @@ impl Target {
             Target::ForeignFn => "foreign function",
             Target::ForeignStatic => "foreign static item",
             Target::ForeignTy => "foreign type",
-            Target::GenericParam(kind) => match kind {
+            Target::GenericParam { kind, has_default: _ } => match kind {
                 GenericParamKind::Type => "type parameter",
                 GenericParamKind::Lifetime => "lifetime parameter",
                 GenericParamKind::Const => "const parameter",
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index a438cde018c..fb6897c7d89 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -1055,17 +1055,11 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
                 });
             },
             {
-                sess.time("unused_lib_feature_checking", || {
-                    rustc_passes::stability::check_unused_or_stable_features(tcx)
-                });
-            },
-            {
                 // We force these queries to run,
                 // since they might not otherwise get called.
                 // This marks the corresponding crate-level attributes
                 // as used, and ensures that their values are valid.
                 tcx.ensure_ok().limits(());
-                tcx.ensure_ok().stability_index(());
             }
         );
     });
diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs
index 99faba7b2c0..dc9311188e8 100644
--- a/compiler/rustc_middle/src/middle/stability.rs
+++ b/compiler/rustc_middle/src/middle/stability.rs
@@ -7,10 +7,9 @@ use rustc_ast::NodeId;
 use rustc_attr_data_structures::{
     self as attrs, ConstStability, DefaultBodyStability, DeprecatedSince, Deprecation, Stability,
 };
-use rustc_data_structures::unord::UnordMap;
 use rustc_errors::{Applicability, Diag, EmissionGuarantee};
 use rustc_feature::GateIssue;
-use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap};
+use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::{self as hir, HirId};
 use rustc_macros::{Decodable, Encodable, HashStable, Subdiagnostic};
 use rustc_session::Session;
@@ -65,48 +64,6 @@ impl DeprecationEntry {
     }
 }
 
-/// A stability index, giving the stability level for items and methods.
-#[derive(HashStable, Debug)]
-pub struct Index {
-    /// This is mostly a cache, except the stabilities of local items
-    /// are filled by the annotator.
-    pub stab_map: LocalDefIdMap<Stability>,
-    pub const_stab_map: LocalDefIdMap<ConstStability>,
-    pub default_body_stab_map: LocalDefIdMap<DefaultBodyStability>,
-    pub depr_map: LocalDefIdMap<DeprecationEntry>,
-    /// Mapping from feature name to feature name based on the `implied_by` field of `#[unstable]`
-    /// attributes. If a `#[unstable(feature = "implier", implied_by = "impliee")]` attribute
-    /// exists, then this map will have a `impliee -> implier` entry.
-    ///
-    /// This mapping is necessary unless both the `#[stable]` and `#[unstable]` attributes should
-    /// specify their implications (both `implies` and `implied_by`). If only one of the two
-    /// attributes do (as in the current implementation, `implied_by` in `#[unstable]`), then this
-    /// mapping is necessary for diagnostics. When a "unnecessary feature attribute" error is
-    /// reported, only the `#[stable]` attribute information is available, so the map is necessary
-    /// to know that the feature implies another feature. If it were reversed, and the `#[stable]`
-    /// attribute had an `implies` meta item, then a map would be necessary when avoiding a "use of
-    /// unstable feature" error for a feature that was implied.
-    pub implications: UnordMap<Symbol, Symbol>,
-}
-
-impl Index {
-    pub fn local_stability(&self, def_id: LocalDefId) -> Option<Stability> {
-        self.stab_map.get(&def_id).copied()
-    }
-
-    pub fn local_const_stability(&self, def_id: LocalDefId) -> Option<ConstStability> {
-        self.const_stab_map.get(&def_id).copied()
-    }
-
-    pub fn local_default_body_stability(&self, def_id: LocalDefId) -> Option<DefaultBodyStability> {
-        self.default_body_stab_map.get(&def_id).copied()
-    }
-
-    pub fn local_deprecation_entry(&self, def_id: LocalDefId) -> Option<DeprecationEntry> {
-        self.depr_map.get(&def_id).cloned()
-    }
-}
-
 pub fn report_unstable(
     sess: &Session,
     feature: Symbol,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index d5c2b6de4ae..ae8c8259be4 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -112,7 +112,7 @@ use crate::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo};
 use crate::middle::lib_features::LibFeatures;
 use crate::middle::privacy::EffectiveVisibilities;
 use crate::middle::resolve_bound_vars::{ObjectLifetimeDefault, ResolveBoundVars, ResolvedArg};
-use crate::middle::stability::{self, DeprecationEntry};
+use crate::middle::stability::DeprecationEntry;
 use crate::mir::interpret::{
     EvalStaticInitializerRawResult, EvalToAllocationRawResult, EvalToConstValueResult,
     EvalToValTreeResult, GlobalId, LitToConstInput,
@@ -2171,6 +2171,18 @@ rustc_queries! {
         separate_provide_extern
         arena_cache
     }
+    /// Mapping from feature name to feature name based on the `implied_by` field of `#[unstable]`
+    /// attributes. If a `#[unstable(feature = "implier", implied_by = "impliee")]` attribute
+    /// exists, then this map will have a `impliee -> implier` entry.
+    ///
+    /// This mapping is necessary unless both the `#[stable]` and `#[unstable]` attributes should
+    /// specify their implications (both `implies` and `implied_by`). If only one of the two
+    /// attributes do (as in the current implementation, `implied_by` in `#[unstable]`), then this
+    /// mapping is necessary for diagnostics. When a "unnecessary feature attribute" error is
+    /// reported, only the `#[stable]` attribute information is available, so the map is necessary
+    /// to know that the feature implies another feature. If it were reversed, and the `#[stable]`
+    /// attribute had an `implies` meta item, then a map would be necessary when avoiding a "use of
+    /// unstable feature" error for a feature that was implied.
     query stability_implications(_: CrateNum) -> &'tcx UnordMap<Symbol, Symbol> {
         arena_cache
         desc { "calculating the implications between `#[unstable]` features defined in a crate" }
@@ -2277,11 +2289,6 @@ rustc_queries! {
         desc { "fetching potentially unused trait imports" }
     }
 
-    query stability_index(_: ()) -> &'tcx stability::Index {
-        arena_cache
-        eval_always
-        desc { "calculating the stability index for the local crate" }
-    }
     /// All available crates in the graph, including those that should not be user-facing
     /// (such as private crates).
     query crates(_: ()) -> &'tcx [CrateNum] {
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 81c13e55dd8..7e6bcfee025 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -65,7 +65,7 @@ use crate::infer::canonical::{CanonicalParamEnvCache, CanonicalVarKind, Canonica
 use crate::lint::lint_level;
 use crate::metadata::ModChild;
 use crate::middle::codegen_fn_attrs::{CodegenFnAttrs, TargetFeature};
-use crate::middle::{resolve_bound_vars, stability};
+use crate::middle::resolve_bound_vars;
 use crate::mir::interpret::{self, Allocation, ConstAllocation};
 use crate::mir::{Body, Local, Place, PlaceElem, ProjectionKind, Promoted};
 use crate::query::plumbing::QuerySystem;
@@ -1807,10 +1807,6 @@ impl<'tcx> TyCtxt<'tcx> {
         )
     }
 
-    pub fn stability(self) -> &'tcx stability::Index {
-        self.stability_index(())
-    }
-
     pub fn features(self) -> &'tcx rustc_feature::Features {
         self.features_query(())
     }
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index d1b856ca415..51e23edb9bb 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -560,7 +560,8 @@ passes_only_has_effect_on =
     `#[{$attr_name}]` only has an effect on {$target_name ->
         [function] functions
         [module] modules
-        [implementation_block] implementation blocks
+        [trait_implementation_block] trait implementation blocks
+        [inherent_implementation_block] inherent implementation blocks
         *[unspecified] (unspecified--this is a compiler bug)
     }
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 2766b14bb8d..3ec6a1124a6 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -11,11 +11,17 @@ use std::slice;
 
 use rustc_abi::{Align, ExternAbi, Size};
 use rustc_ast::{AttrStyle, LitKind, MetaItemInner, MetaItemKind, ast, join_path_syms};
-use rustc_attr_data_structures::{AttributeKind, InlineAttr, ReprAttr, find_attr};
+use rustc_attr_data_structures::{
+    AttributeKind, InlineAttr, PartialConstStability, ReprAttr, Stability, StabilityLevel,
+    find_attr,
+};
 use rustc_attr_parsing::{AttributeParser, Late};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{Applicability, DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey};
-use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
+use rustc_feature::{
+    ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP,
+    BuiltinAttribute,
+};
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LocalModDefId;
 use rustc_hir::intravisit::{self, Visitor};
@@ -36,6 +42,7 @@ use rustc_session::lint;
 use rustc_session::lint::builtin::{
     CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, INVALID_MACRO_EXPORT_ARGUMENTS,
     MALFORMED_DIAGNOSTIC_ATTRIBUTES, MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
+    USELESS_DEPRECATED,
 };
 use rustc_session::parse::feature_err;
 use rustc_span::edition::Edition;
@@ -161,12 +168,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         sym::automatically_derived,
                         *attr_span,
                         target,
-                        Target::Impl,
+                        Target::Impl { of_trait: true },
                     ),
                 Attribute::Parsed(
-                    AttributeKind::Stability { span, .. }
-                    | AttributeKind::ConstStability { span, .. },
-                ) => self.check_stability_promotable(*span, target),
+                    AttributeKind::Stability {
+                        span: attr_span,
+                        stability: Stability { level, feature },
+                    }
+                    | AttributeKind::ConstStability {
+                        span: attr_span,
+                        stability: PartialConstStability { level, feature, .. },
+                    },
+                ) => self.check_stability(*attr_span, span, level, *feature, target),
                 Attribute::Parsed(AttributeKind::Inline(InlineAttr::Force { .. }, ..)) => {} // handled separately below
                 Attribute::Parsed(AttributeKind::Inline(kind, attr_span)) => {
                     self.check_inline(hir_id, *attr_span, span, kind, target)
@@ -494,7 +507,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         attr: &Attribute,
         item: Option<ItemLike<'_>>,
     ) {
-        if !matches!(target, Target::Impl)
+        if !matches!(target, Target::Impl { .. })
             || matches!(
                 item,
                 Some(ItemLike::Item(hir::Item {  kind: hir::ItemKind::Impl(_impl),.. }))
@@ -598,7 +611,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             Target::Fn
             | Target::Closure
             | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent)
-            | Target::Impl
+            | Target::Impl { .. }
             | Target::Mod => return,
 
             // These are "functions", but they aren't allowed because they don't
@@ -987,9 +1000,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         let span = meta.span();
         if let Some(location) = match target {
             Target::AssocTy => {
-                let parent_def_id = self.tcx.hir_get_parent_item(hir_id).def_id;
-                let containing_item = self.tcx.hir_expect_item(parent_def_id);
-                if Target::from_item(containing_item) == Target::Impl {
+                if let DefKind::Impl { .. } =
+                    self.tcx.def_kind(self.tcx.local_parent(hir_id.owner.def_id))
+                {
                     Some("type alias in implementation block")
                 } else {
                     None
@@ -1012,7 +1025,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             | Target::Arm
             | Target::ForeignMod
             | Target::Closure
-            | Target::Impl
+            | Target::Impl { .. }
             | Target::WherePredicate => Some(target.name()),
             Target::ExternCrate
             | Target::Use
@@ -1033,7 +1046,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             | Target::ForeignFn
             | Target::ForeignStatic
             | Target::ForeignTy
-            | Target::GenericParam(..)
+            | Target::GenericParam { .. }
             | Target::MacroDef
             | Target::PatField
             | Target::ExprField => None,
@@ -1590,7 +1603,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         let article = match target {
             Target::ExternCrate
             | Target::Enum
-            | Target::Impl
+            | Target::Impl { .. }
             | Target::Expression
             | Target::Arm
             | Target::AssocConst
@@ -2274,7 +2287,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         match target {
             // FIXME(staged_api): There's no reason we can't support more targets here. We're just
             // being conservative to begin with.
-            Target::Fn | Target::Impl => {}
+            Target::Fn | Target::Impl { .. } => {}
             Target::ExternCrate
             | Target::Use
             | Target::Static
@@ -2300,7 +2313,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             | Target::ForeignFn
             | Target::ForeignStatic
             | Target::ForeignTy
-            | Target::GenericParam(_)
+            | Target::GenericParam { .. }
             | Target::MacroDef
             | Target::Param
             | Target::PatField
@@ -2320,13 +2333,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_stability_promotable(&self, span: Span, target: Target) {
+    fn check_stability(
+        &self,
+        attr_span: Span,
+        item_span: Span,
+        level: &StabilityLevel,
+        feature: Symbol,
+        target: Target,
+    ) {
         match target {
             Target::Expression => {
-                self.dcx().emit_err(errors::StabilityPromotable { attr_span: span });
+                self.dcx().emit_err(errors::StabilityPromotable { attr_span });
             }
             _ => {}
         }
+
+        // Stable *language* features shouldn't be used as unstable library features.
+        // (Not doing this for stable library features is checked by tidy.)
+        if level.is_unstable()
+            && ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some()
+        {
+            self.tcx
+                .dcx()
+                .emit_err(errors::UnstableAttrForAlreadyStableFeature { attr_span, item_span });
+        }
     }
 
     fn check_link_ordinal(&self, attr_span: Span, _span: Span, target: Target) {
@@ -2354,6 +2384,28 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                     errors::Deprecated,
                 );
             }
+            Target::Impl { of_trait: true }
+            | Target::GenericParam { has_default: false, kind: _ } => {
+                self.tcx.emit_node_span_lint(
+                    USELESS_DEPRECATED,
+                    hir_id,
+                    attr.span(),
+                    errors::DeprecatedAnnotationHasNoEffect { span: attr.span() },
+                );
+            }
+            Target::AssocConst | Target::Method(..) | Target::AssocTy
+                if matches!(
+                    self.tcx.def_kind(self.tcx.local_parent(hir_id.owner.def_id)),
+                    DefKind::Impl { of_trait: true }
+                ) =>
+            {
+                self.tcx.emit_node_span_lint(
+                    USELESS_DEPRECATED,
+                    hir_id,
+                    attr.span(),
+                    errors::DeprecatedAnnotationHasNoEffect { span: attr.span() },
+                );
+            }
             _ => {}
         }
     }
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 37330c0ed6e..c6ab6b0d601 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1373,9 +1373,9 @@ pub(crate) struct UnstableAttrForAlreadyStableFeature {
     #[primary_span]
     #[label]
     #[help]
-    pub span: Span,
+    pub attr_span: Span,
     #[label(passes_item)]
-    pub item_sp: Span,
+    pub item_span: Span,
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_passes/src/lang_items.rs b/compiler/rustc_passes/src/lang_items.rs
index 3afed9784de..6fac01827a4 100644
--- a/compiler/rustc_passes/src/lang_items.rs
+++ b/compiler/rustc_passes/src/lang_items.rs
@@ -287,7 +287,7 @@ impl<'ast, 'tcx> visit::Visitor<'ast> for LanguageItemCollector<'ast, 'tcx> {
             ast::ItemKind::Union(..) => Target::Union,
             ast::ItemKind::Trait(_) => Target::Trait,
             ast::ItemKind::TraitAlias(..) => Target::TraitAlias,
-            ast::ItemKind::Impl(_) => Target::Impl,
+            ast::ItemKind::Impl(imp_) => Target::Impl { of_trait: imp_.of_trait.is_some() },
             ast::ItemKind::MacroDef(..) => Target::MacroDef,
             ast::ItemKind::MacCall(_) | ast::ItemKind::DelegationMac(_) => {
                 unreachable!("macros should have been expanded")
diff --git a/compiler/rustc_passes/src/lib_features.rs b/compiler/rustc_passes/src/lib_features.rs
index 127e0df1332..35d2a655991 100644
--- a/compiler/rustc_passes/src/lib_features.rs
+++ b/compiler/rustc_passes/src/lib_features.rs
@@ -44,7 +44,7 @@ impl<'tcx> LibFeatureCollector<'tcx> {
             StabilityLevel::Stable { since, .. } => FeatureStability::AcceptedSince(match since {
                 StableSince::Version(v) => Symbol::intern(&v.to_string()),
                 StableSince::Current => sym::env_CFG_RELEASE,
-                StableSince::Err => return None,
+                StableSince::Err(_) => return None,
             }),
         };
 
diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs
index e5530d52686..40999d622dc 100644
--- a/compiler/rustc_passes/src/stability.rs
+++ b/compiler/rustc_passes/src/stability.rs
@@ -1,37 +1,31 @@
 //! A pass that annotates every item and method with its stability level,
 //! propagating default levels lexically from parent to children ast nodes.
 
-use std::mem::replace;
 use std::num::NonZero;
 
 use rustc_ast_lowering::stability::extern_abi_stability;
 use rustc_attr_data_structures::{
-    self as attrs, AttributeKind, ConstStability, DeprecatedSince, PartialConstStability,
-    Stability, StabilityLevel, StableSince, UnstableReason, VERSION_PLACEHOLDER, find_attr,
+    self as attrs, AttributeKind, ConstStability, DefaultBodyStability, DeprecatedSince, Stability,
+    StabilityLevel, StableSince, UnstableReason, VERSION_PLACEHOLDER, find_attr,
 };
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet};
-use rustc_feature::{ACCEPTED_LANG_FEATURES, EnabledLangFeature, EnabledLibFeature};
+use rustc_feature::{EnabledLangFeature, EnabledLibFeature};
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId, LocalModDefId};
-use rustc_hir::hir_id::CRATE_HIR_ID;
 use rustc_hir::intravisit::{self, Visitor, VisitorExt};
 use rustc_hir::{self as hir, AmbigArg, FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::middle::lib_features::{FeatureStability, LibFeatures};
 use rustc_middle::middle::privacy::EffectiveVisibilities;
-use rustc_middle::middle::stability::{
-    AllowUnstable, Deprecated, DeprecationEntry, EvalResult, Index,
-};
-use rustc_middle::query::Providers;
+use rustc_middle::middle::stability::{AllowUnstable, Deprecated, DeprecationEntry, EvalResult};
+use rustc_middle::query::{LocalCrate, Providers};
 use rustc_middle::ty::TyCtxt;
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_session::lint;
-use rustc_session::lint::builtin::{
-    DEPRECATED, INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED,
-};
+use rustc_session::lint::builtin::{DEPRECATED, INEFFECTIVE_UNSTABLE_TRAIT_IMPL};
 use rustc_span::{Span, Symbol, sym};
-use tracing::{debug, info};
+use tracing::instrument;
 
 use crate::errors;
 
@@ -47,359 +41,263 @@ enum AnnotationKind {
     Container,
 }
 
-/// Whether to inherit deprecation flags for nested items. In most cases, we do want to inherit
-/// deprecation, because nested items rarely have individual deprecation attributes, and so
-/// should be treated as deprecated if their parent is. However, default generic parameters
-/// have separate deprecation attributes from their parents, so we do not wish to inherit
-/// deprecation in this case. For example, inheriting deprecation for `T` in `Foo<T>`
-/// would cause a duplicate warning arising from both `Foo` and `T` being deprecated.
-#[derive(Clone)]
-enum InheritDeprecation {
-    Yes,
-    No,
+fn inherit_deprecation(def_kind: DefKind) -> bool {
+    match def_kind {
+        DefKind::LifetimeParam | DefKind::TyParam | DefKind::ConstParam => false,
+        _ => true,
+    }
 }
 
-impl InheritDeprecation {
-    fn yes(&self) -> bool {
-        matches!(self, InheritDeprecation::Yes)
+fn inherit_const_stability(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
+    let def_kind = tcx.def_kind(def_id);
+    match def_kind {
+        DefKind::AssocFn | DefKind::AssocTy | DefKind::AssocConst => {
+            match tcx.def_kind(tcx.local_parent(def_id)) {
+                DefKind::Impl { of_trait: true } => true,
+                _ => false,
+            }
+        }
+        _ => false,
     }
 }
 
-/// Whether to inherit const stability flags for nested items. In most cases, we do not want to
-/// inherit const stability: just because an enclosing `fn` is const-stable does not mean
-/// all `extern` imports declared in it should be const-stable! However, trait methods
-/// inherit const stability attributes from their parent and do not have their own.
-enum InheritConstStability {
-    Yes,
-    No,
-}
+fn annotation_kind(tcx: TyCtxt<'_>, def_id: LocalDefId) -> AnnotationKind {
+    let def_kind = tcx.def_kind(def_id);
+    match def_kind {
+        // Inherent impls and foreign modules serve only as containers for other items,
+        // they don't have their own stability. They still can be annotated as unstable
+        // and propagate this unstability to children, but this annotation is completely
+        // optional. They inherit stability from their parents when unannotated.
+        DefKind::Impl { of_trait: false } | DefKind::ForeignMod => AnnotationKind::Container,
+        DefKind::Impl { of_trait: true } => AnnotationKind::DeprecationProhibited,
+
+        // Allow stability attributes on default generic arguments.
+        DefKind::TyParam | DefKind::ConstParam => {
+            match &tcx.hir_node_by_def_id(def_id).expect_generic_param().kind {
+                hir::GenericParamKind::Type { default: Some(_), .. }
+                | hir::GenericParamKind::Const { default: Some(_), .. } => {
+                    AnnotationKind::Container
+                }
+                _ => AnnotationKind::Prohibited,
+            }
+        }
+
+        // Impl items in trait impls cannot have stability.
+        DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst => {
+            match tcx.def_kind(tcx.local_parent(def_id)) {
+                DefKind::Impl { of_trait: true } => AnnotationKind::Prohibited,
+                _ => AnnotationKind::Required,
+            }
+        }
 
-impl InheritConstStability {
-    fn yes(&self) -> bool {
-        matches!(self, InheritConstStability::Yes)
+        _ => AnnotationKind::Required,
     }
 }
 
-enum InheritStability {
-    Yes,
-    No,
-}
+fn lookup_deprecation_entry(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<DeprecationEntry> {
+    let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
+    let depr = attrs::find_attr!(attrs,
+        AttributeKind::Deprecation { deprecation, span: _ } => *deprecation
+    );
 
-impl InheritStability {
-    fn yes(&self) -> bool {
-        matches!(self, InheritStability::Yes)
-    }
+    let Some(depr) = depr else {
+        if inherit_deprecation(tcx.def_kind(def_id)) {
+            let parent_id = tcx.opt_local_parent(def_id)?;
+            let parent_depr = tcx.lookup_deprecation_entry(parent_id)?;
+            return Some(parent_depr);
+        }
+
+        return None;
+    };
+
+    // `Deprecation` is just two pointers, no need to intern it
+    Some(DeprecationEntry::local(depr, def_id))
 }
 
-/// A private tree-walker for producing an `Index`.
-struct Annotator<'a, 'tcx> {
-    tcx: TyCtxt<'tcx>,
-    index: &'a mut Index,
-    parent_stab: Option<Stability>,
-    parent_const_stab: Option<ConstStability>,
-    parent_depr: Option<DeprecationEntry>,
-    in_trait_impl: bool,
+fn inherit_stability(def_kind: DefKind) -> bool {
+    match def_kind {
+        DefKind::Field | DefKind::Variant | DefKind::Ctor(..) => true,
+        _ => false,
+    }
 }
 
-impl<'a, 'tcx> Annotator<'a, 'tcx> {
-    /// Determine the stability for a node based on its attributes and inherited stability. The
-    /// stability is recorded in the index and used as the parent. If the node is a function,
-    /// `fn_sig` is its signature.
-    fn annotate<F>(
-        &mut self,
-        def_id: LocalDefId,
-        item_sp: Span,
-        fn_sig: Option<&'tcx hir::FnSig<'tcx>>,
-        kind: AnnotationKind,
-        inherit_deprecation: InheritDeprecation,
-        inherit_const_stability: InheritConstStability,
-        inherit_from_parent: InheritStability,
-        visit_children: F,
-    ) where
-        F: FnOnce(&mut Self),
-    {
-        let attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id));
-        debug!("annotate(id = {:?}, attrs = {:?})", def_id, attrs);
-
-        let depr = attrs::find_attr!(attrs, AttributeKind::Deprecation{deprecation, span} => (*deprecation, *span));
-        let const_stability_indirect = find_attr!(attrs, AttributeKind::ConstStabilityIndirect);
-
-        let mut is_deprecated = false;
-        if let Some((depr, span)) = &depr {
-            is_deprecated = true;
-
-            if matches!(kind, AnnotationKind::Prohibited | AnnotationKind::DeprecationProhibited) {
-                let hir_id = self.tcx.local_def_id_to_hir_id(def_id);
-                self.tcx.emit_node_span_lint(
-                    USELESS_DEPRECATED,
-                    hir_id,
-                    *span,
-                    errors::DeprecatedAnnotationHasNoEffect { span: *span },
-                );
-            }
+/// If the `-Z force-unstable-if-unmarked` flag is passed then we provide
+/// a parent stability annotation which indicates that this is private
+/// with the `rustc_private` feature. This is intended for use when
+/// compiling library and `rustc_*` crates themselves so we can leverage crates.io
+/// while maintaining the invariant that all sysroot crates are unstable
+/// by default and are unable to be used.
+const FORCE_UNSTABLE: Stability = Stability {
+    level: attrs::StabilityLevel::Unstable {
+        reason: UnstableReason::Default,
+        issue: NonZero::new(27812),
+        is_soft: false,
+        implied_by: None,
+        old_name: None,
+    },
+    feature: sym::rustc_private,
+};
 
-            // `Deprecation` is just two pointers, no need to intern it
-            let depr_entry = DeprecationEntry::local(*depr, def_id);
-            self.index.depr_map.insert(def_id, depr_entry);
-        } else if let Some(parent_depr) = self.parent_depr {
-            if inherit_deprecation.yes() {
-                is_deprecated = true;
-                info!("tagging child {:?} as deprecated from parent", def_id);
-                self.index.depr_map.insert(def_id, parent_depr);
-            }
+#[instrument(level = "debug", skip(tcx))]
+fn lookup_stability(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Stability> {
+    // Propagate unstability. This can happen even for non-staged-api crates in case
+    // -Zforce-unstable-if-unmarked is set.
+    if !tcx.features().staged_api() {
+        if !tcx.sess.opts.unstable_opts.force_unstable_if_unmarked {
+            return None;
         }
 
-        if !self.tcx.features().staged_api() {
-            // Propagate unstability. This can happen even for non-staged-api crates in case
-            // -Zforce-unstable-if-unmarked is set.
-            if let Some(stab) = self.parent_stab {
-                if inherit_deprecation.yes() && stab.is_unstable() {
-                    self.index.stab_map.insert(def_id, stab);
-                    if fn_sig.is_some_and(|s| s.header.is_const()) {
-                        self.index.const_stab_map.insert(
-                            def_id,
-                            ConstStability::unmarked(const_stability_indirect, stab),
-                        );
-                    }
-                }
-            }
+        let Some(parent) = tcx.opt_local_parent(def_id) else { return Some(FORCE_UNSTABLE) };
 
-            self.recurse_with_stability_attrs(
-                depr.map(|(d, _)| DeprecationEntry::local(d, def_id)),
-                None,
-                None,
-                visit_children,
-            );
-            return;
+        if inherit_deprecation(tcx.def_kind(def_id)) {
+            let parent = tcx.lookup_stability(parent)?;
+            if parent.is_unstable() {
+                return Some(parent);
+            }
         }
 
-        // # Regular and body stability
-        let stab = attrs::find_attr!(attrs, AttributeKind::Stability { stability, span } => (*stability, *span));
-        let body_stab =
-            attrs::find_attr!(attrs, AttributeKind::BodyStability { stability, .. } => *stability);
+        return None;
+    }
 
-        if let Some((depr, span)) = &depr
-            && depr.is_since_rustc_version()
-            && stab.is_none()
-        {
-            self.tcx.dcx().emit_err(errors::DeprecatedAttribute { span: *span });
-        }
+    // # Regular stability
+    let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
+    let stab =
+        attrs::find_attr!(attrs, AttributeKind::Stability { stability, span: _ } => *stability);
 
-        if let Some(body_stab) = body_stab {
-            // FIXME: check that this item can have body stability
+    if let Some(stab) = stab {
+        return Some(stab);
+    }
 
-            self.index.default_body_stab_map.insert(def_id, body_stab);
-            debug!(?self.index.default_body_stab_map);
+    if inherit_deprecation(tcx.def_kind(def_id)) {
+        let Some(parent) = tcx.opt_local_parent(def_id) else {
+            return tcx
+                .sess
+                .opts
+                .unstable_opts
+                .force_unstable_if_unmarked
+                .then_some(FORCE_UNSTABLE);
+        };
+        let parent = tcx.lookup_stability(parent)?;
+        if parent.is_unstable() || inherit_stability(tcx.def_kind(def_id)) {
+            return Some(parent);
         }
+    }
 
-        let stab = stab.map(|(stab, span)| {
-            // Error if prohibited, or can't inherit anything from a container.
-            if kind == AnnotationKind::Prohibited
-                || (kind == AnnotationKind::Container && stab.level.is_stable() && is_deprecated)
-            {
-                self.tcx.dcx().emit_err(errors::UselessStability { span, item_sp });
-            }
+    None
+}
 
-            debug!("annotate: found {:?}", stab);
+#[instrument(level = "debug", skip(tcx))]
+fn lookup_default_body_stability(
+    tcx: TyCtxt<'_>,
+    def_id: LocalDefId,
+) -> Option<DefaultBodyStability> {
+    if !tcx.features().staged_api() {
+        return None;
+    }
 
-            // Check if deprecated_since < stable_since. If it is,
-            // this is *almost surely* an accident.
-            if let (
-                &Some(DeprecatedSince::RustcVersion(dep_since)),
-                &attrs::StabilityLevel::Stable { since: stab_since, .. },
-            ) = (&depr.as_ref().map(|(d, _)| d.since), &stab.level)
-            {
-                match stab_since {
-                    StableSince::Current => {
-                        self.tcx
-                            .dcx()
-                            .emit_err(errors::CannotStabilizeDeprecated { span, item_sp });
-                    }
-                    StableSince::Version(stab_since) => {
-                        if dep_since < stab_since {
-                            self.tcx
-                                .dcx()
-                                .emit_err(errors::CannotStabilizeDeprecated { span, item_sp });
-                        }
-                    }
-                    StableSince::Err => {
-                        // An error already reported. Assume the unparseable stabilization
-                        // version is older than the deprecation version.
-                    }
-                }
-            }
+    let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
+    // FIXME: check that this item can have body stability
+    attrs::find_attr!(attrs, AttributeKind::BodyStability { stability, .. } => *stability)
+}
 
-            // Stable *language* features shouldn't be used as unstable library features.
-            // (Not doing this for stable library features is checked by tidy.)
-            if let Stability { level: StabilityLevel::Unstable { .. }, feature } = stab {
-                if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() {
-                    self.tcx
-                        .dcx()
-                        .emit_err(errors::UnstableAttrForAlreadyStableFeature { span, item_sp });
-                }
-            }
-            if let Stability {
-                level: StabilityLevel::Unstable { implied_by: Some(implied_by), .. },
-                feature,
-            } = stab
+#[instrument(level = "debug", skip(tcx))]
+fn lookup_const_stability(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<ConstStability> {
+    if !tcx.features().staged_api() {
+        // Propagate unstability. This can happen even for non-staged-api crates in case
+        // -Zforce-unstable-if-unmarked is set.
+        if inherit_deprecation(tcx.def_kind(def_id)) {
+            let parent = tcx.opt_local_parent(def_id)?;
+            let parent_stab = tcx.lookup_stability(parent)?;
+            if parent_stab.is_unstable()
+                && let Some(fn_sig) = tcx.hir_node_by_def_id(def_id).fn_sig()
+                && fn_sig.header.is_const()
             {
-                self.index.implications.insert(implied_by, feature);
-            }
-
-            self.index.stab_map.insert(def_id, stab);
-            stab
-        });
-
-        if stab.is_none() {
-            debug!("annotate: stab not found, parent = {:?}", self.parent_stab);
-            if let Some(stab) = self.parent_stab {
-                if inherit_deprecation.yes() && stab.is_unstable() || inherit_from_parent.yes() {
-                    self.index.stab_map.insert(def_id, stab);
-                }
+                let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
+                let const_stability_indirect =
+                    find_attr!(attrs, AttributeKind::ConstStabilityIndirect);
+                return Some(ConstStability::unmarked(const_stability_indirect, parent_stab));
             }
         }
 
-        let final_stab = self.index.stab_map.get(&def_id);
+        return None;
+    }
 
-        // # Const stability
+    let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
+    let const_stability_indirect = find_attr!(attrs, AttributeKind::ConstStabilityIndirect);
+    let const_stab = attrs::find_attr!(attrs, AttributeKind::ConstStability { stability, span: _ } => *stability);
+
+    // After checking the immediate attributes, get rid of the span and compute implied
+    // const stability: inherit feature gate from regular stability.
+    let mut const_stab = const_stab
+        .map(|const_stab| ConstStability::from_partial(const_stab, const_stability_indirect));
+
+    // If this is a const fn but not annotated with stability markers, see if we can inherit
+    // regular stability.
+    if let Some(fn_sig) = tcx.hir_node_by_def_id(def_id).fn_sig()
+        && fn_sig.header.is_const()
+        && const_stab.is_none()
+        // We only ever inherit unstable features.
+        && let Some(inherit_regular_stab) = tcx.lookup_stability(def_id)
+        && inherit_regular_stab.is_unstable()
+    {
+        const_stab = Some(ConstStability {
+            // We subject these implicitly-const functions to recursive const stability.
+            const_stable_indirect: true,
+            promotable: false,
+            level: inherit_regular_stab.level,
+            feature: inherit_regular_stab.feature,
+        });
+    }
 
-        let const_stab = attrs::find_attr!(attrs, AttributeKind::ConstStability { stability, span } => (*stability, *span));
+    if let Some(const_stab) = const_stab {
+        return Some(const_stab);
+    }
 
-        // If the current node is a function with const stability attributes (directly given or
-        // implied), check if the function/method is const or the parent impl block is const.
-        if let Some(fn_sig) = fn_sig
-            && !fn_sig.header.is_const()
-            && const_stab.is_some()
-        {
-            self.tcx.dcx().emit_err(errors::MissingConstErr { fn_sig_span: fn_sig.span });
+    // `impl const Trait for Type` items forward their const stability to their immediate children.
+    // FIXME(const_trait_impl): how is this supposed to interact with `#[rustc_const_stable_indirect]`?
+    // Currently, once that is set, we do not inherit anything from the parent any more.
+    if inherit_const_stability(tcx, def_id) {
+        let parent = tcx.opt_local_parent(def_id)?;
+        let parent = tcx.lookup_const_stability(parent)?;
+        if parent.is_const_unstable() {
+            return Some(parent);
         }
+    }
 
-        // If this is marked const *stable*, it must also be regular-stable.
-        if let Some((const_stab, const_span)) = const_stab
-            && let Some(fn_sig) = fn_sig
-            && const_stab.is_const_stable()
-            && !stab.is_some_and(|s| s.is_stable())
-        {
-            self.tcx
-                .dcx()
-                .emit_err(errors::ConstStableNotStable { fn_sig_span: fn_sig.span, const_span });
-        }
+    None
+}
 
-        // Stable *language* features shouldn't be used as unstable library features.
-        // (Not doing this for stable library features is checked by tidy.)
-        if let Some((
-            PartialConstStability { level: StabilityLevel::Unstable { .. }, feature, .. },
-            const_span,
-        )) = const_stab
-        {
-            if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() {
-                self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature {
-                    span: const_span,
-                    item_sp,
-                });
-            }
-        }
+/// A private tree-walker for producing an `Index`.
+struct Annotator<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    implications: UnordMap<Symbol, Symbol>,
+}
 
-        if let Some((stab, span)) = &const_stab
-            && stab.is_const_stable()
-            && const_stability_indirect
-        {
-            self.tcx.dcx().emit_err(errors::RustcConstStableIndirectPairing { span: *span });
+impl<'tcx> Annotator<'tcx> {
+    /// Determine the stability for a node based on its attributes and inherited stability. The
+    /// stability is recorded in the index and used as the parent. If the node is a function,
+    /// `fn_sig` is its signature.
+    #[instrument(level = "trace", skip(self))]
+    fn annotate(&mut self, def_id: LocalDefId) {
+        if !self.tcx.features().staged_api() {
+            return;
         }
 
-        // After checking the immediate attributes, get rid of the span and compute implied
-        // const stability: inherit feature gate from regular stability.
-        let mut const_stab = const_stab
-            .map(|(stab, _span)| ConstStability::from_partial(stab, const_stability_indirect));
-
-        // If this is a const fn but not annotated with stability markers, see if we can inherit regular stability.
-        if fn_sig.is_some_and(|s| s.header.is_const()) && const_stab.is_none() &&
-            // We only ever inherit unstable features.
-            let Some(inherit_regular_stab) =
-                final_stab.filter(|s| s.is_unstable())
+        if let Some(stability) = self.tcx.lookup_stability(def_id)
+            && let StabilityLevel::Unstable { implied_by: Some(implied_by), .. } = stability.level
         {
-            const_stab = Some(ConstStability {
-                // We subject these implicitly-const functions to recursive const stability.
-                const_stable_indirect: true,
-                promotable: false,
-                level: inherit_regular_stab.level,
-                feature: inherit_regular_stab.feature,
-            });
+            self.implications.insert(implied_by, stability.feature);
         }
 
-        // Now that everything is computed, insert it into the table.
-        const_stab.inspect(|const_stab| {
-            self.index.const_stab_map.insert(def_id, *const_stab);
-        });
-
-        if let Some(ConstStability {
-            level: StabilityLevel::Unstable { implied_by: Some(implied_by), .. },
-            feature,
-            ..
-        }) = const_stab
+        if let Some(stability) = self.tcx.lookup_const_stability(def_id)
+            && let StabilityLevel::Unstable { implied_by: Some(implied_by), .. } = stability.level
         {
-            self.index.implications.insert(implied_by, feature);
-        }
-
-        // `impl const Trait for Type` items forward their const stability to their
-        // immediate children.
-        // FIXME(const_trait_impl): how is this supposed to interact with `#[rustc_const_stable_indirect]`?
-        // Currently, once that is set, we do not inherit anything from the parent any more.
-        if const_stab.is_none() {
-            debug!("annotate: const_stab not found, parent = {:?}", self.parent_const_stab);
-            if let Some(parent) = self.parent_const_stab {
-                if parent.is_const_unstable() {
-                    self.index.const_stab_map.insert(def_id, parent);
-                }
-            }
-        }
-
-        self.recurse_with_stability_attrs(
-            depr.map(|(d, _)| DeprecationEntry::local(d, def_id)),
-            stab,
-            inherit_const_stability.yes().then_some(const_stab).flatten(),
-            visit_children,
-        );
-    }
-
-    fn recurse_with_stability_attrs(
-        &mut self,
-        depr: Option<DeprecationEntry>,
-        stab: Option<Stability>,
-        const_stab: Option<ConstStability>,
-        f: impl FnOnce(&mut Self),
-    ) {
-        // These will be `Some` if this item changes the corresponding stability attribute.
-        let mut replaced_parent_depr = None;
-        let mut replaced_parent_stab = None;
-        let mut replaced_parent_const_stab = None;
-
-        if let Some(depr) = depr {
-            replaced_parent_depr = Some(replace(&mut self.parent_depr, Some(depr)));
-        }
-        if let Some(stab) = stab {
-            replaced_parent_stab = Some(replace(&mut self.parent_stab, Some(stab)));
-        }
-        if let Some(const_stab) = const_stab {
-            replaced_parent_const_stab =
-                Some(replace(&mut self.parent_const_stab, Some(const_stab)));
-        }
-
-        f(self);
-
-        if let Some(orig_parent_depr) = replaced_parent_depr {
-            self.parent_depr = orig_parent_depr;
-        }
-        if let Some(orig_parent_stab) = replaced_parent_stab {
-            self.parent_stab = orig_parent_stab;
-        }
-        if let Some(orig_parent_const_stab) = replaced_parent_const_stab {
-            self.parent_const_stab = orig_parent_const_stab;
+            self.implications.insert(implied_by, stability.feature);
         }
     }
 }
 
-impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
+impl<'tcx> Visitor<'tcx> for Annotator<'tcx> {
     /// Because stability levels are scoped lexically, we want to walk
     /// nested items in the context of the outer item, so enable
     /// deep-walking.
@@ -410,184 +308,51 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
     }
 
     fn visit_item(&mut self, i: &'tcx Item<'tcx>) {
-        let orig_in_trait_impl = self.in_trait_impl;
-        let mut kind = AnnotationKind::Required;
-        let mut const_stab_inherit = InheritConstStability::No;
-        let mut fn_sig = None;
-
         match i.kind {
-            // Inherent impls and foreign modules serve only as containers for other items,
-            // they don't have their own stability. They still can be annotated as unstable
-            // and propagate this instability to children, but this annotation is completely
-            // optional. They inherit stability from their parents when unannotated.
-            hir::ItemKind::Impl(hir::Impl { of_trait: None, .. })
-            | hir::ItemKind::ForeignMod { .. } => {
-                self.in_trait_impl = false;
-                kind = AnnotationKind::Container;
-            }
-            hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) => {
-                self.in_trait_impl = true;
-                kind = AnnotationKind::DeprecationProhibited;
-                const_stab_inherit = InheritConstStability::Yes;
-            }
             hir::ItemKind::Struct(_, _, ref sd) => {
                 if let Some(ctor_def_id) = sd.ctor_def_id() {
-                    self.annotate(
-                        ctor_def_id,
-                        i.span,
-                        None,
-                        AnnotationKind::Required,
-                        InheritDeprecation::Yes,
-                        InheritConstStability::No,
-                        InheritStability::Yes,
-                        |_| {},
-                    )
+                    self.annotate(ctor_def_id);
                 }
             }
-            hir::ItemKind::Fn { sig: ref item_fn_sig, .. } => {
-                fn_sig = Some(item_fn_sig);
-            }
             _ => {}
         }
 
-        self.annotate(
-            i.owner_id.def_id,
-            i.span,
-            fn_sig,
-            kind,
-            InheritDeprecation::Yes,
-            const_stab_inherit,
-            InheritStability::No,
-            |v| intravisit::walk_item(v, i),
-        );
-        self.in_trait_impl = orig_in_trait_impl;
+        self.annotate(i.owner_id.def_id);
+        intravisit::walk_item(self, i)
     }
 
     fn visit_trait_item(&mut self, ti: &'tcx hir::TraitItem<'tcx>) {
-        let fn_sig = match ti.kind {
-            hir::TraitItemKind::Fn(ref fn_sig, _) => Some(fn_sig),
-            _ => None,
-        };
-
-        self.annotate(
-            ti.owner_id.def_id,
-            ti.span,
-            fn_sig,
-            AnnotationKind::Required,
-            InheritDeprecation::Yes,
-            InheritConstStability::No,
-            InheritStability::No,
-            |v| {
-                intravisit::walk_trait_item(v, ti);
-            },
-        );
+        self.annotate(ti.owner_id.def_id);
+        intravisit::walk_trait_item(self, ti);
     }
 
     fn visit_impl_item(&mut self, ii: &'tcx hir::ImplItem<'tcx>) {
-        let kind =
-            if self.in_trait_impl { AnnotationKind::Prohibited } else { AnnotationKind::Required };
-
-        let fn_sig = match ii.kind {
-            hir::ImplItemKind::Fn(ref fn_sig, _) => Some(fn_sig),
-            _ => None,
-        };
-
-        self.annotate(
-            ii.owner_id.def_id,
-            ii.span,
-            fn_sig,
-            kind,
-            InheritDeprecation::Yes,
-            InheritConstStability::No,
-            InheritStability::No,
-            |v| {
-                intravisit::walk_impl_item(v, ii);
-            },
-        );
+        self.annotate(ii.owner_id.def_id);
+        intravisit::walk_impl_item(self, ii);
     }
 
     fn visit_variant(&mut self, var: &'tcx Variant<'tcx>) {
-        self.annotate(
-            var.def_id,
-            var.span,
-            None,
-            AnnotationKind::Required,
-            InheritDeprecation::Yes,
-            InheritConstStability::No,
-            InheritStability::Yes,
-            |v| {
-                if let Some(ctor_def_id) = var.data.ctor_def_id() {
-                    v.annotate(
-                        ctor_def_id,
-                        var.span,
-                        None,
-                        AnnotationKind::Required,
-                        InheritDeprecation::Yes,
-                        InheritConstStability::No,
-                        InheritStability::Yes,
-                        |_| {},
-                    );
-                }
+        self.annotate(var.def_id);
+        if let Some(ctor_def_id) = var.data.ctor_def_id() {
+            self.annotate(ctor_def_id);
+        }
 
-                intravisit::walk_variant(v, var)
-            },
-        )
+        intravisit::walk_variant(self, var)
     }
 
     fn visit_field_def(&mut self, s: &'tcx FieldDef<'tcx>) {
-        self.annotate(
-            s.def_id,
-            s.span,
-            None,
-            AnnotationKind::Required,
-            InheritDeprecation::Yes,
-            InheritConstStability::No,
-            InheritStability::Yes,
-            |v| {
-                intravisit::walk_field_def(v, s);
-            },
-        );
+        self.annotate(s.def_id);
+        intravisit::walk_field_def(self, s);
     }
 
     fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) {
-        let fn_sig = match &i.kind {
-            rustc_hir::ForeignItemKind::Fn(fn_sig, ..) => Some(fn_sig),
-            _ => None,
-        };
-        self.annotate(
-            i.owner_id.def_id,
-            i.span,
-            fn_sig,
-            AnnotationKind::Required,
-            InheritDeprecation::Yes,
-            InheritConstStability::No,
-            InheritStability::No,
-            |v| {
-                intravisit::walk_foreign_item(v, i);
-            },
-        );
+        self.annotate(i.owner_id.def_id);
+        intravisit::walk_foreign_item(self, i);
     }
 
     fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
-        let kind = match &p.kind {
-            // Allow stability attributes on default generic arguments.
-            hir::GenericParamKind::Type { default: Some(_), .. }
-            | hir::GenericParamKind::Const { default: Some(_), .. } => AnnotationKind::Container,
-            _ => AnnotationKind::Prohibited,
-        };
-
-        self.annotate(
-            p.def_id,
-            p.span,
-            None,
-            kind,
-            InheritDeprecation::No,
-            InheritConstStability::No,
-            InheritStability::No,
-            |v| {
-                intravisit::walk_generic_param(v, p);
-            },
-        );
+        self.annotate(p.def_id);
+        intravisit::walk_generic_param(self, p);
     }
 }
 
@@ -597,18 +362,119 @@ struct MissingStabilityAnnotations<'tcx> {
 }
 
 impl<'tcx> MissingStabilityAnnotations<'tcx> {
-    fn check_missing_stability(&self, def_id: LocalDefId, span: Span) {
-        let stab = self.tcx.stability().local_stability(def_id);
+    /// Verify that deprecation and stability attributes make sense with one another.
+    #[instrument(level = "trace", skip(self))]
+    fn check_compatible_stability(&self, def_id: LocalDefId) {
+        if !self.tcx.features().staged_api() {
+            return;
+        }
+
+        let depr = self.tcx.lookup_deprecation_entry(def_id);
+        let stab = self.tcx.lookup_stability(def_id);
+        let const_stab = self.tcx.lookup_const_stability(def_id);
+
+        macro_rules! find_attr_span {
+            ($name:ident) => {{
+                let attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id));
+                attrs::find_attr!(attrs, AttributeKind::$name { span, .. } => *span)
+            }}
+        }
+
+        if stab.is_none()
+            && depr.map_or(false, |d| d.attr.is_since_rustc_version())
+            && let Some(span) = find_attr_span!(Deprecation)
+        {
+            self.tcx.dcx().emit_err(errors::DeprecatedAttribute { span });
+        }
+
+        if let Some(stab) = stab {
+            // Error if prohibited, or can't inherit anything from a container.
+            let kind = annotation_kind(self.tcx, def_id);
+            if kind == AnnotationKind::Prohibited
+                || (kind == AnnotationKind::Container && stab.level.is_stable() && depr.is_some())
+            {
+                if let Some(span) = find_attr_span!(Stability) {
+                    let item_sp = self.tcx.def_span(def_id);
+                    self.tcx.dcx().emit_err(errors::UselessStability { span, item_sp });
+                }
+            }
+
+            // Check if deprecated_since < stable_since. If it is,
+            // this is *almost surely* an accident.
+            if let Some(depr) = depr
+                && let DeprecatedSince::RustcVersion(dep_since) = depr.attr.since
+                && let attrs::StabilityLevel::Stable { since: stab_since, .. } = stab.level
+                && let Some(span) = find_attr_span!(Stability)
+            {
+                let item_sp = self.tcx.def_span(def_id);
+                match stab_since {
+                    StableSince::Current => {
+                        self.tcx
+                            .dcx()
+                            .emit_err(errors::CannotStabilizeDeprecated { span, item_sp });
+                    }
+                    StableSince::Version(stab_since) => {
+                        if dep_since < stab_since {
+                            self.tcx
+                                .dcx()
+                                .emit_err(errors::CannotStabilizeDeprecated { span, item_sp });
+                        }
+                    }
+                    StableSince::Err(_) => {
+                        // An error already reported. Assume the unparseable stabilization
+                        // version is older than the deprecation version.
+                    }
+                }
+            }
+        }
+
+        // If the current node is a function with const stability attributes (directly given or
+        // implied), check if the function/method is const or the parent impl block is const.
+        let fn_sig = self.tcx.hir_node_by_def_id(def_id).fn_sig();
+        if let Some(fn_sig) = fn_sig
+            && !fn_sig.header.is_const()
+            && const_stab.is_some()
+            && find_attr_span!(ConstStability).is_some()
+        {
+            self.tcx.dcx().emit_err(errors::MissingConstErr { fn_sig_span: fn_sig.span });
+        }
+
+        // If this is marked const *stable*, it must also be regular-stable.
+        if let Some(const_stab) = const_stab
+            && let Some(fn_sig) = fn_sig
+            && const_stab.is_const_stable()
+            && !stab.is_some_and(|s| s.is_stable())
+            && let Some(const_span) = find_attr_span!(ConstStability)
+        {
+            self.tcx
+                .dcx()
+                .emit_err(errors::ConstStableNotStable { fn_sig_span: fn_sig.span, const_span });
+        }
+
+        if let Some(stab) = &const_stab
+            && stab.is_const_stable()
+            && stab.const_stable_indirect
+            && let Some(span) = find_attr_span!(ConstStability)
+        {
+            self.tcx.dcx().emit_err(errors::RustcConstStableIndirectPairing { span });
+        }
+    }
+
+    #[instrument(level = "debug", skip(self))]
+    fn check_missing_stability(&self, def_id: LocalDefId) {
+        let stab = self.tcx.lookup_stability(def_id);
+        self.tcx.ensure_ok().lookup_const_stability(def_id);
         if !self.tcx.sess.is_test_crate()
             && stab.is_none()
             && self.effective_visibilities.is_reachable(def_id)
         {
             let descr = self.tcx.def_descr(def_id.to_def_id());
+            let span = self.tcx.def_span(def_id);
             self.tcx.dcx().emit_err(errors::MissingStabilityAttr { span, descr });
         }
     }
 
-    fn check_missing_const_stability(&self, def_id: LocalDefId, span: Span) {
+    fn check_missing_const_stability(&self, def_id: LocalDefId) {
         let is_const = self.tcx.is_const_fn(def_id.to_def_id())
             || (self.tcx.def_kind(def_id.to_def_id()) == DefKind::Trait
                 && self.tcx.is_const_trait(def_id.to_def_id()));
@@ -618,6 +484,7 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> {
             && self.effective_visibilities.is_reachable(def_id)
             && self.tcx.lookup_const_stability(def_id).is_none()
         {
+            let span = self.tcx.def_span(def_id);
             let descr = self.tcx.def_descr(def_id.to_def_id());
             self.tcx.dcx().emit_err(errors::MissingConstStabAttr { span, descr });
         }
@@ -632,6 +499,8 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
     }
 
     fn visit_item(&mut self, i: &'tcx Item<'tcx>) {
+        self.check_compatible_stability(i.owner_id.def_id);
+
         // Inherent impls and foreign modules serve only as containers for other items,
         // they don't have their own stability. They still can be annotated as unstable
         // and propagate this instability to children, but this annotation is completely
@@ -641,119 +510,97 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
             hir::ItemKind::Impl(hir::Impl { of_trait: None, .. })
                 | hir::ItemKind::ForeignMod { .. }
         ) {
-            self.check_missing_stability(i.owner_id.def_id, i.span);
+            self.check_missing_stability(i.owner_id.def_id);
         }
 
         // Ensure stable `const fn` have a const stability attribute.
-        self.check_missing_const_stability(i.owner_id.def_id, i.span);
+        self.check_missing_const_stability(i.owner_id.def_id);
 
         intravisit::walk_item(self, i)
     }
 
     fn visit_trait_item(&mut self, ti: &'tcx hir::TraitItem<'tcx>) {
-        self.check_missing_stability(ti.owner_id.def_id, ti.span);
+        self.check_compatible_stability(ti.owner_id.def_id);
+        self.check_missing_stability(ti.owner_id.def_id);
         intravisit::walk_trait_item(self, ti);
     }
 
     fn visit_impl_item(&mut self, ii: &'tcx hir::ImplItem<'tcx>) {
+        self.check_compatible_stability(ii.owner_id.def_id);
         let impl_def_id = self.tcx.hir_get_parent_item(ii.hir_id());
         if self.tcx.impl_trait_ref(impl_def_id).is_none() {
-            self.check_missing_stability(ii.owner_id.def_id, ii.span);
-            self.check_missing_const_stability(ii.owner_id.def_id, ii.span);
+            self.check_missing_stability(ii.owner_id.def_id);
+            self.check_missing_const_stability(ii.owner_id.def_id);
         }
         intravisit::walk_impl_item(self, ii);
     }
 
     fn visit_variant(&mut self, var: &'tcx Variant<'tcx>) {
-        self.check_missing_stability(var.def_id, var.span);
+        self.check_compatible_stability(var.def_id);
+        self.check_missing_stability(var.def_id);
         if let Some(ctor_def_id) = var.data.ctor_def_id() {
-            self.check_missing_stability(ctor_def_id, var.span);
+            self.check_missing_stability(ctor_def_id);
         }
         intravisit::walk_variant(self, var);
     }
 
     fn visit_field_def(&mut self, s: &'tcx FieldDef<'tcx>) {
-        self.check_missing_stability(s.def_id, s.span);
+        self.check_compatible_stability(s.def_id);
+        self.check_missing_stability(s.def_id);
         intravisit::walk_field_def(self, s);
     }
 
     fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) {
-        self.check_missing_stability(i.owner_id.def_id, i.span);
+        self.check_compatible_stability(i.owner_id.def_id);
+        self.check_missing_stability(i.owner_id.def_id);
         intravisit::walk_foreign_item(self, i);
     }
-    // Note that we don't need to `check_missing_stability` for default generic parameters,
-    // as we assume that any default generic parameters without attributes are automatically
-    // stable (assuming they have not inherited instability from their parent).
-}
 
-fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index {
-    let mut index = Index {
-        stab_map: Default::default(),
-        const_stab_map: Default::default(),
-        default_body_stab_map: Default::default(),
-        depr_map: Default::default(),
-        implications: Default::default(),
-    };
-
-    {
-        let mut annotator = Annotator {
-            tcx,
-            index: &mut index,
-            parent_stab: None,
-            parent_const_stab: None,
-            parent_depr: None,
-            in_trait_impl: false,
-        };
-
-        // If the `-Z force-unstable-if-unmarked` flag is passed then we provide
-        // a parent stability annotation which indicates that this is private
-        // with the `rustc_private` feature. This is intended for use when
-        // compiling `librustc_*` crates themselves so we can leverage crates.io
-        // while maintaining the invariant that all sysroot crates are unstable
-        // by default and are unable to be used.
-        if tcx.sess.opts.unstable_opts.force_unstable_if_unmarked {
-            let stability = Stability {
-                level: attrs::StabilityLevel::Unstable {
-                    reason: UnstableReason::Default,
-                    issue: NonZero::new(27812),
-                    is_soft: false,
-                    implied_by: None,
-                    old_name: None,
-                },
-                feature: sym::rustc_private,
-            };
-            annotator.parent_stab = Some(stability);
-        }
-
-        annotator.annotate(
-            CRATE_DEF_ID,
-            tcx.hir_span(CRATE_HIR_ID),
-            None,
-            AnnotationKind::Required,
-            InheritDeprecation::Yes,
-            InheritConstStability::No,
-            InheritStability::No,
-            |v| tcx.hir_walk_toplevel_module(v),
-        );
+    fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
+        self.check_compatible_stability(p.def_id);
+        // Note that we don't need to `check_missing_stability` for default generic parameters,
+        // as we assume that any default generic parameters without attributes are automatically
+        // stable (assuming they have not inherited instability from their parent).
+        intravisit::walk_generic_param(self, p);
     }
-    index
+}
+
+fn stability_implications(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> UnordMap<Symbol, Symbol> {
+    let mut annotator = Annotator { tcx, implications: Default::default() };
+    annotator.annotate(CRATE_DEF_ID);
+    tcx.hir_walk_toplevel_module(&mut annotator);
+    annotator.implications
 }
 
 /// Cross-references the feature names of unstable APIs with enabled
 /// features and possibly prints errors.
 fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
     tcx.hir_visit_item_likes_in_module(module_def_id, &mut Checker { tcx });
+
+    let is_staged_api =
+        tcx.sess.opts.unstable_opts.force_unstable_if_unmarked || tcx.features().staged_api();
+    if is_staged_api {
+        let effective_visibilities = &tcx.effective_visibilities(());
+        let mut missing = MissingStabilityAnnotations { tcx, effective_visibilities };
+        if module_def_id.is_top_level_module() {
+            missing.check_missing_stability(CRATE_DEF_ID);
+        }
+        tcx.hir_visit_item_likes_in_module(module_def_id, &mut missing);
+    }
+
+    if module_def_id.is_top_level_module() {
+        check_unused_or_stable_features(tcx)
+    }
 }
 
 pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers {
         check_mod_unstable_api_usage,
-        stability_index,
-        stability_implications: |tcx, _| tcx.stability().implications.clone(),
-        lookup_stability: |tcx, id| tcx.stability().local_stability(id),
-        lookup_const_stability: |tcx, id| tcx.stability().local_const_stability(id),
-        lookup_default_body_stability: |tcx, id| tcx.stability().local_default_body_stability(id),
-        lookup_deprecation_entry: |tcx, id| tcx.stability().local_deprecation_entry(id),
+        stability_implications,
+        lookup_stability,
+        lookup_const_stability,
+        lookup_default_body_stability,
+        lookup_deprecation_entry,
         ..*providers
     };
 }
@@ -1058,7 +905,7 @@ fn is_unstable_reexport(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
     };
     let def_id = owner.def_id;
 
-    let Some(stab) = tcx.stability().local_stability(def_id) else {
+    let Some(stab) = tcx.lookup_stability(def_id) else {
         return false;
     };
 
@@ -1127,16 +974,9 @@ impl<'tcx> Visitor<'tcx> for CheckTraitImplStable<'tcx> {
 /// Given the list of enabled features that were not language features (i.e., that
 /// were expected to be library features), and the list of features used from
 /// libraries, identify activated features that don't exist and error about them.
+// This is `pub` for rustdoc. rustc should call it through `check_mod_unstable_api_usage`.
 pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
-    let is_staged_api =
-        tcx.sess.opts.unstable_opts.force_unstable_if_unmarked || tcx.features().staged_api();
-    if is_staged_api {
-        let effective_visibilities = &tcx.effective_visibilities(());
-        let mut missing = MissingStabilityAnnotations { tcx, effective_visibilities };
-        missing.check_missing_stability(CRATE_DEF_ID, tcx.hir_span(CRATE_HIR_ID));
-        tcx.hir_walk_toplevel_module(&mut missing);
-        tcx.hir_visit_all_item_likes_in_crate(&mut missing);
-    }
+    let _prof_timer = tcx.sess.timer("unused_lib_feature_checking");
 
     let enabled_lang_features = tcx.features().enabled_lang_features();
     let mut lang_features = UnordSet::default();