blob: c0b48ab51c7ebb0199210ca6e680f483140d48ea [file] [log] [blame] [edit]
//! 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().is_some() {
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(mut next_def_id) = item.item_id.as_local_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)
}
}