Skip to main content

rustdoc/passes/
propagate_stability.rs

1//! Propagates stability to child items.
2//!
3//! The purpose of this pass is to make items whose parents are "more unstable"
4//! than the item itself inherit the parent's stability.
5//! For example, [`core::error::Error`] is marked as stable since 1.0.0, but the
6//! [`core::error`] module is marked as stable since 1.81.0, so we want to show
7//! [`core::error::Error`] as stable since 1.81.0 as well.
8
9use rustc_attr_ir::{Stability, StabilityLevel};
10use rustc_hir::def_id::CRATE_DEF_ID;
11
12use crate::clean::{Crate, Item, ItemId, ItemKind};
13use crate::core::DocContext;
14use crate::fold::DocFolder;
15
16pub(super) fn propagate_stability(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
17    let crate_stability = cx.tcx.lookup_stability(CRATE_DEF_ID);
18    StabilityPropagator { parent_stability: crate_stability, cx }.fold_crate(cr)
19}
20
21struct StabilityPropagator<'a, 'tcx> {
22    parent_stability: Option<Stability>,
23    cx: &'a mut DocContext<'tcx>,
24}
25
26impl DocFolder for StabilityPropagator<'_, '_> {
27    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
28        let parent_stability = self.parent_stability;
29
30        let stability = match item.item_id {
31            ItemId::DefId(def_id) => {
32                let item_stability = self.cx.tcx.lookup_stability(def_id);
33                let inline_stability =
34                    item.inline_stmt_id.and_then(|did| self.cx.tcx.lookup_stability(did));
35                let is_glob_export = item.inline_stmt_id.map(|id| {
36                    let hir_id = self.cx.tcx.local_def_id_to_hir_id(id);
37                    matches!(
38                        self.cx.tcx.hir_node(hir_id),
39                        rustc_hir::Node::Item(rustc_hir::Item {
40                            kind: rustc_hir::ItemKind::Use(tree),
41                            ..
42                        }) | rustc_hir::Node::NestedUseTree(tree) if matches!(tree.kind, rustc_hir::UseKind::Glob)
43                    )
44                });
45                let own_stability = if let Some(item_stab) = item_stability
46                    && let StabilityLevel::Stable { since: _, allowed_through_unstable_modules } =
47                        item_stab.level
48                    && let Some(mut inline_stab) = inline_stability
49                    && let StabilityLevel::Stable {
50                        since: inline_since,
51                        allowed_through_unstable_modules: _,
52                    } = inline_stab.level
53                    && let Some(is_global_export) = is_glob_export
54                    && !is_global_export
55                {
56                    inline_stab.level = StabilityLevel::Stable {
57                        since: inline_since,
58                        allowed_through_unstable_modules,
59                    };
60                    Some(inline_stab)
61                } else {
62                    item_stability
63                };
64
65                match item.kind.unstripped() {
66                    ItemKind::ExternCrate { .. }
67                    | ItemKind::Import(..)
68                    | ItemKind::Struct(..)
69                    | ItemKind::Union(..)
70                    | ItemKind::Enum(..)
71                    | ItemKind::Function(..)
72                    | ItemKind::Module(..)
73                    | ItemKind::TypeAlias(..)
74                    | ItemKind::Static(..)
75                    | ItemKind::Trait(..)
76                    | ItemKind::TraitAlias(..)
77                    | ItemKind::StructField(..)
78                    | ItemKind::Variant(..)
79                    | ItemKind::ForeignFunction(..)
80                    | ItemKind::ForeignStatic(..)
81                    | ItemKind::ForeignType
82                    | ItemKind::DeclMacro(..)
83                    | ItemKind::ProcMacro(..)
84                    | ItemKind::Constant(..) => {
85                        // If any of the item's parents was stabilized later or is still unstable,
86                        // then use the parent's stability instead.
87                        merge_stability(own_stability, parent_stability)
88                    }
89
90                    // Don't inherit the parent's stability for these items, because they
91                    // are potentially accessible even if the parent is more unstable.
92                    ItemKind::Impl(..)
93                    | ItemKind::AssocFunction(..)
94                    | ItemKind::AssocConstant(..)
95                    | ItemKind::AssocType(..)
96                    | ItemKind::PrimitiveType(..)
97                    | ItemKind::Keyword
98                    | ItemKind::Attribute
99                    | ItemKind::PlaceholderImpl => own_stability,
100
101                    ItemKind::Stripped(..) => unreachable!(),
102                }
103            }
104            ItemId::Auto { .. } | ItemId::Blanket { .. } => {
105                // For now, we do now show stability for synthesized impls.
106                None
107            }
108        };
109
110        item.inner.stability = stability;
111        self.parent_stability = stability;
112        let item = self.fold_item_recur(item);
113        self.parent_stability = parent_stability;
114
115        Some(item)
116    }
117}
118
119fn merge_stability(
120    own_stability: Option<Stability>,
121    parent_stability: Option<Stability>,
122) -> Option<Stability> {
123    if let Some(own_stab) = own_stability
124        && let StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: None } =
125            own_stab.level
126        && let Some(parent_stab) = parent_stability
127        && (parent_stab.is_unstable()
128            || parent_stab.stable_since().is_some_and(|parent_since| parent_since > own_since))
129    {
130        parent_stability
131    } else if let Some(mut own_stab) = own_stability
132        && let StabilityLevel::Stable { since, allowed_through_unstable_modules: Some(_) } =
133            own_stab.level
134        && parent_stability.is_some_and(|stab| stab.is_stable())
135    {
136        // this property does not apply transitively through re-exports
137        own_stab.level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
138        Some(own_stab)
139    } else {
140        own_stability
141    }
142}