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_parsing::{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;
15use crate::passes::Pass;
16
17pub(crate) const PROPAGATE_STABILITY: Pass = Pass {
18    name: "propagate-stability",
19    run: Some(propagate_stability),
20    description: "propagates stability to child items",
21};
22
23pub(crate) fn propagate_stability(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
24    let crate_stability = cx.tcx.lookup_stability(CRATE_DEF_ID);
25    StabilityPropagator { parent_stability: crate_stability, cx }.fold_crate(cr)
26}
27
28struct StabilityPropagator<'a, 'tcx> {
29    parent_stability: Option<Stability>,
30    cx: &'a mut DocContext<'tcx>,
31}
32
33impl DocFolder for StabilityPropagator<'_, '_> {
34    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
35        let parent_stability = self.parent_stability;
36
37        let stability = match item.item_id {
38            ItemId::DefId(def_id) => {
39                let item_stability = self.cx.tcx.lookup_stability(def_id);
40                let inline_stability =
41                    item.inline_stmt_id.and_then(|did| self.cx.tcx.lookup_stability(did));
42                let own_stability = if let Some(item_stab) = item_stability
43                    && let StabilityLevel::Stable { since: _, allowed_through_unstable_modules } =
44                        item_stab.level
45                    && let Some(mut inline_stab) = inline_stability
46                    && let StabilityLevel::Stable {
47                        since: inline_since,
48                        allowed_through_unstable_modules: _,
49                    } = inline_stab.level
50                {
51                    inline_stab.level = StabilityLevel::Stable {
52                        since: inline_since,
53                        allowed_through_unstable_modules,
54                    };
55                    Some(inline_stab)
56                } else {
57                    item_stability
58                };
59
60                let (ItemKind::StrippedItem(box kind) | kind) = &item.kind;
61                match kind {
62                    ItemKind::ExternCrateItem { .. }
63                    | ItemKind::ImportItem(..)
64                    | ItemKind::StructItem(..)
65                    | ItemKind::UnionItem(..)
66                    | ItemKind::EnumItem(..)
67                    | ItemKind::FunctionItem(..)
68                    | ItemKind::ModuleItem(..)
69                    | ItemKind::TypeAliasItem(..)
70                    | ItemKind::StaticItem(..)
71                    | ItemKind::TraitItem(..)
72                    | ItemKind::TraitAliasItem(..)
73                    | ItemKind::StructFieldItem(..)
74                    | ItemKind::VariantItem(..)
75                    | ItemKind::ForeignFunctionItem(..)
76                    | ItemKind::ForeignStaticItem(..)
77                    | ItemKind::ForeignTypeItem
78                    | ItemKind::MacroItem(..)
79                    | ItemKind::ProcMacroItem(..)
80                    | ItemKind::ConstantItem(..) => {
81                        // If any of the item's parents was stabilized later or is still unstable,
82                        // then use the parent's stability instead.
83                        merge_stability(own_stability, parent_stability)
84                    }
85
86                    // Don't inherit the parent's stability for these items, because they
87                    // are potentially accessible even if the parent is more unstable.
88                    ItemKind::ImplItem(..)
89                    | ItemKind::RequiredMethodItem(..)
90                    | ItemKind::MethodItem(..)
91                    | ItemKind::RequiredAssocConstItem(..)
92                    | ItemKind::ProvidedAssocConstItem(..)
93                    | ItemKind::ImplAssocConstItem(..)
94                    | ItemKind::RequiredAssocTypeItem(..)
95                    | ItemKind::AssocTypeItem(..)
96                    | ItemKind::PrimitiveItem(..)
97                    | ItemKind::KeywordItem => own_stability,
98
99                    ItemKind::StrippedItem(..) => unreachable!(),
100                }
101            }
102            ItemId::Auto { .. } | ItemId::Blanket { .. } => {
103                // For now, we do now show stability for synthesized impls.
104                None
105            }
106        };
107
108        item.inner.stability = stability;
109        self.parent_stability = stability;
110        let item = self.fold_item_recur(item);
111        self.parent_stability = parent_stability;
112
113        Some(item)
114    }
115}
116
117fn merge_stability(
118    own_stability: Option<Stability>,
119    parent_stability: Option<Stability>,
120) -> Option<Stability> {
121    if let Some(own_stab) = own_stability
122        && let StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: None } =
123            own_stab.level
124        && let Some(parent_stab) = parent_stability
125        && (parent_stab.is_unstable()
126            || parent_stab.stable_since().is_some_and(|parent_since| parent_since > own_since))
127    {
128        parent_stability
129    } else if let Some(mut own_stab) = own_stability
130        && let StabilityLevel::Stable { since, allowed_through_unstable_modules: Some(_) } =
131            own_stab.level
132        && parent_stability.is_some_and(|stab| stab.is_stable())
133    {
134        // this property does not apply transitively through re-exports
135        own_stab.level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None };
136        Some(own_stab)
137    } else {
138        own_stability
139    }
140}