Skip to main content

rustdoc/passes/
propagate_doc_cfg.rs

1//! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.
2
3use rustc_data_structures::fx::FxHashMap;
4use rustc_hir::Attribute;
5use rustc_hir::attrs::{AttributeKind, DocAttribute};
6
7use crate::clean::inline::{load_attrs, merge_attrs};
8use crate::clean::{CfgInfo, Crate, Item, ItemId, ItemKind};
9use crate::core::DocContext;
10use crate::fold::DocFolder;
11use crate::passes::Pass;
12
13pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass {
14    name: "propagate-doc-cfg",
15    run: Some(propagate_doc_cfg),
16    description: "propagates `#[doc(cfg(...))]` to child items",
17};
18
19pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
20    if cx.tcx.features().doc_cfg() {
21        CfgPropagator { cx, cfg_info: CfgInfo::default(), impl_cfg_info: FxHashMap::default() }
22            .fold_crate(cr)
23    } else {
24        cr
25    }
26}
27
28struct CfgPropagator<'a, 'tcx> {
29    cx: &'a mut DocContext<'tcx>,
30    cfg_info: CfgInfo,
31
32    /// To ensure the `doc_cfg` feature works with how `rustdoc` handles impls, we need to store
33    /// the `cfg` info of `impl`s placeholder to use them later on the "real" impl item.
34    impl_cfg_info: FxHashMap<ItemId, CfgInfo>,
35}
36
37/// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from
38/// it and put them into `attrs`.
39fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute]) {
40    for attr in new_attrs {
41        if let Attribute::Parsed(AttributeKind::Doc(d)) = attr
42            && !d.cfg.is_empty()
43        {
44            let mut new_attr = DocAttribute::default();
45            new_attr.cfg = d.cfg.clone();
46            attrs.push(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr))));
47        } else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr {
48            // If it's a `cfg()` attribute, we keep it.
49            attrs.push(attr.clone());
50        }
51    }
52}
53
54/// This function goes through the attributes list (`new_attrs`) and extracts the attributes that
55/// affect the cfg state propagated to detached items.
56fn add_cfg_state_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute]) {
57    for attr in new_attrs {
58        if let Attribute::Parsed(AttributeKind::Doc(d)) = attr
59            && (!d.cfg.is_empty() || !d.auto_cfg.is_empty() || !d.auto_cfg_change.is_empty())
60        {
61            let mut new_attr = DocAttribute::default();
62            new_attr.cfg = d.cfg.clone();
63            new_attr.auto_cfg = d.auto_cfg.clone();
64            new_attr.auto_cfg_change = d.auto_cfg_change.clone();
65            attrs.push(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr))));
66        } else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr {
67            // If it's a `cfg()` attribute, we keep it.
68            attrs.push(attr.clone());
69        }
70    }
71}
72
73impl CfgPropagator<'_, '_> {
74    // Some items need to merge their attributes with their parents' otherwise a few of them
75    // (mostly `cfg` ones) will be missing.
76    fn merge_with_parent_attributes(&mut self, item: &mut Item) {
77        let mut attrs = Vec::new();
78        // We need to merge an item attributes with its parent's in case it's an impl as an
79        // impl might not be defined in the same module as the item it implements.
80        //
81        // Same if it's an inlined item: we need to get the full original `cfg`.
82        //
83        // Otherwise, `cfg_info` already tracks everything we need so nothing else to do!
84        if matches!(item.kind, ItemKind::ImplItem(_)) || item.inline_stmt_id.is_some() {
85            if let Some(mut next_def_id) = item.item_id.as_local_def_id() {
86                while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) {
87                    let x = load_attrs(self.cx.tcx, parent_def_id.to_def_id());
88                    add_only_cfg_attributes(&mut attrs, x);
89                    next_def_id = parent_def_id;
90                }
91            }
92        }
93        // We also need to merge an item attributes with its parent's in case it's a macro with
94        // the `#[macro_export]` attribute, because it might not be defined at crate root.
95        else if matches!(item.kind, ItemKind::MacroItem(_))
96            && item.inner.attrs.other_attrs.iter().any(|attr| {
97                matches!(
98                    attr,
99                    rustc_hir::Attribute::Parsed(
100                        rustc_hir::attrs::AttributeKind::MacroExport { .. }
101                    )
102                )
103            })
104        {
105            for parent_def_id in &item.cfg_parent_ids_for_detached_item(self.cx.tcx) {
106                let mut parent_attrs = Vec::new();
107                add_cfg_state_attributes(
108                    &mut parent_attrs,
109                    load_attrs(self.cx.tcx, parent_def_id.to_def_id()),
110                );
111                merge_attrs(self.cx.tcx, &[], Some((&parent_attrs, None)), &mut self.cfg_info);
112            }
113        }
114
115        let (_, cfg) = merge_attrs(
116            self.cx.tcx,
117            item.attrs.other_attrs.as_slice(),
118            Some((&attrs, None)),
119            &mut self.cfg_info,
120        );
121        item.inner.cfg = cfg;
122    }
123}
124
125impl DocFolder for CfgPropagator<'_, '_> {
126    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
127        let old_cfg_info = self.cfg_info.clone();
128
129        // If we have an impl, we check if it has an associated `cfg` "context", and if so we will
130        // use that context instead of the actual (wrong) one.
131        if let ItemKind::ImplItem(_) = item.kind
132            && let Some(cfg_info) = self.impl_cfg_info.remove(&item.item_id)
133        {
134            self.cfg_info = cfg_info;
135        }
136
137        if let ItemKind::PlaceholderImplItem = item.kind {
138            // If we have a placeholder impl, we store the current `cfg` "context" to be used
139            // on the actual impl later on (the impls are generated after we go through the whole
140            // AST so they're stored in the `krate` object at the end).
141            self.impl_cfg_info.insert(item.item_id, self.cfg_info.clone());
142        } else {
143            self.merge_with_parent_attributes(&mut item);
144        }
145
146        let result = self.fold_item_recur(item);
147        self.cfg_info = old_cfg_info;
148
149        Some(result)
150    }
151}