about summary refs log tree commit diff
path: root/src/librustdoc/passes/propagate_doc_cfg.rs
blob: 4bcb60a9a40de5aef74358d295c01301df680e9b (plain)
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.

use rustc_ast::token::{Token, TokenKind};
use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_hir::{AttrArgs, Attribute};
use rustc_span::symbol::sym;

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

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

pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
    if cx.tcx.features().doc_cfg() {
        CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr)
    } else {
        cr
    }
}

struct CfgPropagator<'a, 'tcx> {
    cx: &'a mut DocContext<'tcx>,
    cfg_info: CfgInfo,
}

/// Returns true if the provided `token` is a `cfg` ident.
fn is_cfg_token(token: &TokenTree) -> bool {
    // We only keep `doc(cfg)` items.
    matches!(token, TokenTree::Token(Token { kind: TokenKind::Ident(sym::cfg, _,), .. }, _,),)
}

/// We only want to keep `#[cfg()]` and `#[doc(cfg())]` attributes so we rebuild a vec of
/// `TokenTree` with only the tokens we're interested into.
fn filter_non_cfg_tokens_from_list(args_tokens: &TokenStream) -> Vec<TokenTree> {
    let mut tokens = Vec::with_capacity(args_tokens.len());
    let mut skip_next_delimited = false;
    for token in args_tokens.iter() {
        match token {
            TokenTree::Delimited(..) => {
                if !skip_next_delimited {
                    tokens.push(token.clone());
                }
                skip_next_delimited = false;
            }
            token if is_cfg_token(token) => {
                skip_next_delimited = false;
                tokens.push(token.clone());
            }
            _ => {
                skip_next_delimited = true;
            }
        }
    }
    tokens
}

/// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from
/// it and put them into `attrs`.
fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute]) {
    for attr in new_attrs {
        if attr.is_doc_comment() {
            continue;
        }
        let mut attr = attr.clone();
        if let Attribute::Unparsed(ref mut normal) = attr
            && let [ident] = &*normal.path.segments
        {
            let ident = ident.name;
            if ident == sym::doc
                && let AttrArgs::Delimited(args) = &mut normal.args
            {
                let tokens = filter_non_cfg_tokens_from_list(&args.tokens);
                args.tokens = TokenStream::new(tokens);
                attrs.push(attr);
            } else if ident == sym::cfg_trace {
                // If it's a `cfg()` attribute, we keep it.
                attrs.push(attr);
            }
        }
    }
}

impl CfgPropagator<'_, '_> {
    // 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 mut attrs = Vec::new();
        // We only need to merge an item attributes with its parent's in case it's an impl as an
        // impl might not be defined in the same module as the item it implements.
        //
        // Otherwise, `cfg_info` already tracks everything we need so nothing else to do!
        if matches!(item.kind, ItemKind::ImplItem(_))
            && let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local())
        {
            let mut next_def_id = def_id;
            while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) {
                let x = load_attrs(self.cx, parent_def_id.to_def_id());
                add_only_cfg_attributes(&mut attrs, x);
                next_def_id = parent_def_id;
            }
        }

        let (_, cfg) = merge_attrs(
            self.cx,
            item.attrs.other_attrs.as_slice(),
            Some((&attrs, None)),
            &mut self.cfg_info,
        );
        item.inner.cfg = cfg;
    }
}

impl DocFolder for CfgPropagator<'_, '_> {
    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
        let old_cfg_info = self.cfg_info.clone();

        self.merge_with_parent_attributes(&mut item);

        let result = self.fold_item_recur(item);
        self.cfg_info = old_cfg_info;

        Some(result)
    }
}