1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.
use std::sync::Arc;

use crate::clean::cfg::Cfg;
use crate::clean::inline::{load_attrs, merge_attrs};
use crate::clean::{Crate, Item, ItemKind};
use crate::core::DocContext;
use crate::fold::DocFolder;
use crate::passes::Pass;

use rustc_hir::def_id::LocalDefId;

pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass {
    name: "propagate-doc-cfg",
    run: propagate_doc_cfg,
    description: "propagates `#[doc(cfg(...))]` to child items",
};

pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
    CfgPropagator { parent_cfg: None, parent: None, cx }.fold_crate(cr)
}

struct CfgPropagator<'a, 'tcx> {
    parent_cfg: Option<Arc<Cfg>>,
    parent: Option<LocalDefId>,
    cx: &'a mut DocContext<'tcx>,
}

impl<'a, 'tcx> CfgPropagator<'a, 'tcx> {
    // Some items need to merge their attributes with their parents' otherwise a few of them
    // (mostly `cfg` ones) will be missing.
    fn merge_with_parent_attributes(&mut self, item: &mut Item) {
        let check_parent = match &*item.kind {
            // impl blocks can be in different modules with different cfg and we need to get them
            // as well.
            ItemKind::ImplItem(_) => false,
            kind if kind.is_non_assoc() => true,
            _ => return,
        };

        let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local())
            else { return };

        let hir = self.cx.tcx.hir();
        let hir_id = hir.local_def_id_to_hir_id(def_id);

        if check_parent {
            let expected_parent = hir.get_parent_item(hir_id);
            // If parents are different, it means that `item` is a reexport and we need
            // to compute the actual `cfg` by iterating through its "real" parents.
            if self.parent == Some(expected_parent.def_id) {
                return;
            }
        }

        let mut attrs = Vec::new();
        for (parent_hir_id, _) in hir.parent_iter(hir_id) {
            if let Some(def_id) = hir.opt_local_def_id(parent_hir_id) {
                attrs.extend_from_slice(load_attrs(self.cx, def_id.to_def_id()));
            }
        }
        let (_, cfg) = merge_attrs(self.cx, None, item.attrs.other_attrs.as_slice(), Some(&attrs));
        item.cfg = cfg;
    }
}

impl<'a, 'tcx> DocFolder for CfgPropagator<'a, 'tcx> {
    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
        let old_parent_cfg = self.parent_cfg.clone();

        self.merge_with_parent_attributes(&mut item);

        let new_cfg = match (self.parent_cfg.take(), item.cfg.take()) {
            (None, None) => None,
            (Some(rc), None) | (None, Some(rc)) => Some(rc),
            (Some(mut a), Some(b)) => {
                let b = Arc::try_unwrap(b).unwrap_or_else(|rc| Cfg::clone(&rc));
                *Arc::make_mut(&mut a) &= b;
                Some(a)
            }
        };
        self.parent_cfg = new_cfg.clone();
        item.cfg = new_cfg;

        let old_parent =
            if let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) {
                self.parent.replace(def_id)
            } else {
                self.parent.take()
            };
        let result = self.fold_item_recur(item);
        self.parent_cfg = old_parent_cfg;
        self.parent = old_parent;

        Some(result)
    }
}