//! 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 { 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, 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 { 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) } }