rustdoc/passes/
propagate_doc_cfg.rs

1//! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.
2
3use std::sync::Arc;
4
5use rustc_hir::def_id::LocalDefId;
6
7use crate::clean::cfg::Cfg;
8use crate::clean::inline::{load_attrs, merge_attrs};
9use crate::clean::{Crate, Item, 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    CfgPropagator { parent_cfg: None, parent: None, cx }.fold_crate(cr)
22}
23
24struct CfgPropagator<'a, 'tcx> {
25    parent_cfg: Option<Arc<Cfg>>,
26    parent: Option<LocalDefId>,
27    cx: &'a mut DocContext<'tcx>,
28}
29
30impl CfgPropagator<'_, '_> {
31    // Some items need to merge their attributes with their parents' otherwise a few of them
32    // (mostly `cfg` ones) will be missing.
33    fn merge_with_parent_attributes(&mut self, item: &mut Item) {
34        let check_parent = match &item.kind {
35            // impl blocks can be in different modules with different cfg and we need to get them
36            // as well.
37            ItemKind::ImplItem(_) => false,
38            kind if kind.is_non_assoc() => true,
39            _ => return,
40        };
41
42        let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else {
43            return;
44        };
45
46        if check_parent {
47            let expected_parent = self.cx.tcx.opt_local_parent(def_id);
48            // If parents are different, it means that `item` is a reexport and we need
49            // to compute the actual `cfg` by iterating through its "real" parents.
50            if self.parent.is_some() && self.parent == expected_parent {
51                return;
52            }
53        }
54
55        let mut attrs = Vec::new();
56        let mut next_def_id = def_id;
57        while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) {
58            attrs.extend_from_slice(load_attrs(self.cx, parent_def_id.to_def_id()));
59            next_def_id = parent_def_id;
60        }
61
62        let (_, cfg) =
63            merge_attrs(self.cx, item.attrs.other_attrs.as_slice(), Some((&attrs, None)));
64        item.cfg = cfg;
65    }
66}
67
68impl DocFolder for CfgPropagator<'_, '_> {
69    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
70        let old_parent_cfg = self.parent_cfg.clone();
71
72        self.merge_with_parent_attributes(&mut item);
73
74        let new_cfg = match (self.parent_cfg.take(), item.cfg.take()) {
75            (None, None) => None,
76            (Some(rc), None) | (None, Some(rc)) => Some(rc),
77            (Some(mut a), Some(b)) => {
78                let b = Arc::try_unwrap(b).unwrap_or_else(|rc| Cfg::clone(&rc));
79                *Arc::make_mut(&mut a) &= b;
80                Some(a)
81            }
82        };
83        self.parent_cfg = new_cfg.clone();
84        item.cfg = new_cfg;
85
86        let old_parent =
87            if let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) {
88                self.parent.replace(def_id)
89            } else {
90                self.parent.take()
91            };
92        let result = self.fold_item_recur(item);
93        self.parent_cfg = old_parent_cfg;
94        self.parent = old_parent;
95
96        Some(result)
97    }
98}