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