about summary refs log tree commit diff
path: root/src/librustdoc/passes/propagate_doc_cfg.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/passes/propagate_doc_cfg.rs')
-rw-r--r--src/librustdoc/passes/propagate_doc_cfg.rs142
1 files changed, 87 insertions, 55 deletions
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
index eddafa9ba8e..d5b20f2b941 100644
--- a/src/librustdoc/passes/propagate_doc_cfg.rs
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -1,12 +1,12 @@
 //! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.
 
-use std::sync::Arc;
+use rustc_ast::token::{Token, TokenKind};
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_hir::{AttrArgs, Attribute};
+use rustc_span::symbol::sym;
 
-use rustc_hir::def_id::LocalDefId;
-
-use crate::clean::cfg::Cfg;
 use crate::clean::inline::{load_attrs, merge_attrs};
-use crate::clean::{Crate, Item, ItemKind};
+use crate::clean::{CfgInfo, Crate, Item, ItemKind};
 use crate::core::DocContext;
 use crate::fold::DocFolder;
 use crate::passes::Pass;
@@ -18,80 +18,112 @@ pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass {
 };
 
 pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
-    CfgPropagator { parent_cfg: None, parent: None, cx }.fold_crate(cr)
+    if cx.tcx.features().doc_cfg() {
+        CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr)
+    } else {
+        cr
+    }
 }
 
 struct CfgPropagator<'a, 'tcx> {
-    parent_cfg: Option<Arc<Cfg>>,
-    parent: Option<LocalDefId>,
     cx: &'a mut DocContext<'tcx>,
+    cfg_info: CfgInfo,
 }
 
-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 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,
-        };
+/// 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, _,), .. }, _,),)
+}
 
-        let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else {
-            return;
-        };
+/// 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
+}
 
-        if check_parent {
-            let expected_parent = self.cx.tcx.opt_local_parent(def_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.is_some() && self.parent == expected_parent {
-                return;
+/// 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();
-        let mut next_def_id = def_id;
-        while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) {
-            attrs.extend_from_slice(load_attrs(self.cx, parent_def_id.to_def_id()));
-            next_def_id = parent_def_id;
+        // 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)));
+        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_parent_cfg = self.parent_cfg.clone();
+        let old_cfg_info = self.cfg_info.clone();
 
         self.merge_with_parent_attributes(&mut item);
 
-        let new_cfg = match (self.parent_cfg.take(), item.inner.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.inner.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;
+        self.cfg_info = old_cfg_info;
 
         Some(result)
     }