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