rustdoc/passes/
propagate_doc_cfg.rs

1//! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.
2
3use rustc_hir::Attribute;
4use rustc_hir::attrs::{AttributeKind, DocAttribute};
5use rustc_span::symbol::sym;
6
7use crate::clean::inline::{load_attrs, merge_attrs};
8use crate::clean::{CfgInfo, Crate, Item, 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() }.fold_crate(cr)
22    } else {
23        cr
24    }
25}
26
27struct CfgPropagator<'a, 'tcx> {
28    cx: &'a mut DocContext<'tcx>,
29    cfg_info: 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::Unparsed(normal) = attr
43            && let [ident] = &*normal.path.segments
44            && ident.name == sym::cfg_trace
45        {
46            // If it's a `cfg()` attribute, we keep it.
47            attrs.push(attr.clone());
48        }
49    }
50}
51
52impl CfgPropagator<'_, '_> {
53    // Some items need to merge their attributes with their parents' otherwise a few of them
54    // (mostly `cfg` ones) will be missing.
55    fn merge_with_parent_attributes(&mut self, item: &mut Item) {
56        let mut attrs = Vec::new();
57        // We only need to merge an item attributes with its parent's in case it's an impl as an
58        // impl might not be defined in the same module as the item it implements.
59        //
60        // Otherwise, `cfg_info` already tracks everything we need so nothing else to do!
61        if matches!(item.kind, ItemKind::ImplItem(_))
62            && let Some(mut next_def_id) = item.item_id.as_local_def_id()
63        {
64            while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) {
65                let x = load_attrs(self.cx, parent_def_id.to_def_id());
66                add_only_cfg_attributes(&mut attrs, x);
67                next_def_id = parent_def_id;
68            }
69        }
70
71        let (_, cfg) = merge_attrs(
72            self.cx,
73            item.attrs.other_attrs.as_slice(),
74            Some((&attrs, None)),
75            &mut self.cfg_info,
76        );
77        item.inner.cfg = cfg;
78    }
79}
80
81impl DocFolder for CfgPropagator<'_, '_> {
82    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
83        let old_cfg_info = self.cfg_info.clone();
84
85        self.merge_with_parent_attributes(&mut item);
86
87        let result = self.fold_item_recur(item);
88        self.cfg_info = old_cfg_info;
89
90        Some(result)
91    }
92}