rustdoc/passes/
propagate_doc_cfg.rs

1//! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.
2
3use rustc_ast::token::{Token, TokenKind};
4use rustc_ast::tokenstream::{TokenStream, TokenTree};
5use rustc_hir::{AttrArgs, Attribute};
6use rustc_span::symbol::sym;
7
8use crate::clean::inline::{load_attrs, merge_attrs};
9use crate::clean::{CfgInfo, 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    if cx.tcx.features().doc_cfg() {
22        CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr)
23    } else {
24        cr
25    }
26}
27
28struct CfgPropagator<'a, 'tcx> {
29    cx: &'a mut DocContext<'tcx>,
30    cfg_info: CfgInfo,
31}
32
33/// Returns true if the provided `token` is a `cfg` ident.
34fn is_cfg_token(token: &TokenTree) -> bool {
35    // We only keep `doc(cfg)` items.
36    matches!(token, TokenTree::Token(Token { kind: TokenKind::Ident(sym::cfg, _,), .. }, _,),)
37}
38
39/// We only want to keep `#[cfg()]` and `#[doc(cfg())]` attributes so we rebuild a vec of
40/// `TokenTree` with only the tokens we're interested into.
41fn filter_non_cfg_tokens_from_list(args_tokens: &TokenStream) -> Vec<TokenTree> {
42    let mut tokens = Vec::with_capacity(args_tokens.len());
43    let mut skip_next_delimited = false;
44    for token in args_tokens.iter() {
45        match token {
46            TokenTree::Delimited(..) => {
47                if !skip_next_delimited {
48                    tokens.push(token.clone());
49                }
50                skip_next_delimited = false;
51            }
52            token if is_cfg_token(token) => {
53                skip_next_delimited = false;
54                tokens.push(token.clone());
55            }
56            _ => {
57                skip_next_delimited = true;
58            }
59        }
60    }
61    tokens
62}
63
64/// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from
65/// it and put them into `attrs`.
66fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute]) {
67    for attr in new_attrs {
68        if attr.is_doc_comment() {
69            continue;
70        }
71        let mut attr = attr.clone();
72        if let Attribute::Unparsed(ref mut normal) = attr
73            && let [ident] = &*normal.path.segments
74        {
75            let ident = ident.name;
76            if ident == sym::doc
77                && let AttrArgs::Delimited(args) = &mut normal.args
78            {
79                let tokens = filter_non_cfg_tokens_from_list(&args.tokens);
80                args.tokens = TokenStream::new(tokens);
81                attrs.push(attr);
82            } else if ident == sym::cfg_trace {
83                // If it's a `cfg()` attribute, we keep it.
84                attrs.push(attr);
85            }
86        }
87    }
88}
89
90impl CfgPropagator<'_, '_> {
91    // Some items need to merge their attributes with their parents' otherwise a few of them
92    // (mostly `cfg` ones) will be missing.
93    fn merge_with_parent_attributes(&mut self, item: &mut Item) {
94        let mut attrs = Vec::new();
95        // We only need to merge an item attributes with its parent's in case it's an impl as an
96        // impl might not be defined in the same module as the item it implements.
97        //
98        // Otherwise, `cfg_info` already tracks everything we need so nothing else to do!
99        if matches!(item.kind, ItemKind::ImplItem(_))
100            && let Some(mut next_def_id) = item.item_id.as_local_def_id()
101        {
102            while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) {
103                let x = load_attrs(self.cx, parent_def_id.to_def_id());
104                add_only_cfg_attributes(&mut attrs, x);
105                next_def_id = parent_def_id;
106            }
107        }
108
109        let (_, cfg) = merge_attrs(
110            self.cx,
111            item.attrs.other_attrs.as_slice(),
112            Some((&attrs, None)),
113            &mut self.cfg_info,
114        );
115        item.inner.cfg = cfg;
116    }
117}
118
119impl DocFolder for CfgPropagator<'_, '_> {
120    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
121        let old_cfg_info = self.cfg_info.clone();
122
123        self.merge_with_parent_attributes(&mut item);
124
125        let result = self.fold_item_recur(item);
126        self.cfg_info = old_cfg_info;
127
128        Some(result)
129    }
130}