Skip to main content

rustdoc/passes/
propagate_doc_cfg.rs

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