about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2025-03-29 00:53:59 +0100
committerGuillaume Gomez <guillaume1.gomez@gmail.com>2025-09-27 11:29:48 +0200
commit7c00bccd3b3eb6717e3c801123107962e671e48f (patch)
treed09538e680b53fb8ec9fc632916181b77be4dade
parent959b450747f81e720be3a829665dd30e553e7fd7 (diff)
downloadrust-7c00bccd3b3eb6717e3c801123107962e671e48f.tar.gz
rust-7c00bccd3b3eb6717e3c801123107962e671e48f.zip
Implement RFC 3631
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs2
-rw-r--r--compiler/rustc_passes/messages.ftl9
-rw-r--r--compiler/rustc_passes/src/check_attr.rs53
-rw-r--r--compiler/rustc_passes/src/errors.rs14
-rw-r--r--compiler/rustc_span/src/symbol.rs4
-rw-r--r--library/alloc/src/lib.rs29
-rw-r--r--library/core/src/lib.rs69
-rw-r--r--library/std/src/lib.rs16
-rw-r--r--src/librustdoc/clean/cfg.rs30
-rw-r--r--src/librustdoc/clean/inline.rs15
-rw-r--r--src/librustdoc/clean/mod.rs10
-rw-r--r--src/librustdoc/clean/types.rs302
-rw-r--r--src/librustdoc/doctest/rust.rs11
-rw-r--r--src/librustdoc/formats/cache.rs2
-rw-r--r--src/librustdoc/passes/propagate_doc_cfg.rs134
-rw-r--r--src/librustdoc/visit_ast.rs27
16 files changed, 518 insertions, 209 deletions
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 9ab5b0b3547..fe9cc8e61ed 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -182,8 +182,6 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
 
                 gate_doc!(
                     "experimental" {
-                        cfg => doc_cfg
-                        cfg_hide => doc_cfg_hide
                         masked => doc_masked
                         notable_trait => doc_notable_trait
                     }
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index 870e0a90b54..19014f37c66 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -143,6 +143,15 @@ passes_doc_attribute_not_attribute =
 passes_doc_cfg_hide_takes_list =
     `#[doc(cfg_hide(...))]` takes a list of attributes
 
+passes_doc_auto_cfg_expects_hide_or_show =
+    `only "hide" or "show" are allowed in "#[doc(auto_cfg(...))]"`
+
+passes_doc_auto_cfg_hide_show_expects_list =
+    `#![doc(auto_cfg({$attr_name}(...)))]` only expects a list of items
+
+passes_doc_auto_cfg_wrong_literal =
+    `expected boolean for #[doc(auto_cfg = ...)]`
+
 passes_doc_expect_str =
     doc {$attr_name} attribute expects a string: #[doc({$attr_name} = "a")]
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 007353f136d..3dc232a35ec 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -1160,16 +1160,43 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    /// Check that the `#![doc(cfg_hide(...))]` attribute only contains a list of attributes.
-    ///
-    fn check_doc_cfg_hide(&self, meta: &MetaItemInner, hir_id: HirId) {
-        if meta.meta_item_list().is_none() {
-            self.tcx.emit_node_span_lint(
-                INVALID_DOC_ATTRIBUTES,
-                hir_id,
-                meta.span(),
-                errors::DocCfgHideTakesList,
-            );
+    /// Check that the `#![doc(auto_cfg(..))]` attribute has expected input.
+    fn check_doc_auto_cfg(&self, meta: &MetaItemInner, hir_id: HirId) {
+        let MetaItemInner::MetaItem(meta) = meta else {
+            unreachable!();
+        };
+        match &meta.kind {
+            MetaItemKind::Word => {}
+            MetaItemKind::NameValue(lit) => {
+                if !matches!(lit.kind, LitKind::Bool(_)) {
+                    self.tcx.emit_node_span_lint(
+                        INVALID_DOC_ATTRIBUTES,
+                        hir_id,
+                        meta.span,
+                        errors::DocAutoCfgWrongLiteral,
+                    );
+                }
+            }
+            MetaItemKind::List(list) => {
+                for item in list {
+                    let Some(attr_name) = item.name() else { continue };
+                    if attr_name != sym::hide && attr_name != sym::show {
+                        self.tcx.emit_node_span_lint(
+                            INVALID_DOC_ATTRIBUTES,
+                            hir_id,
+                            meta.span,
+                            errors::DocAutoCfgExpectsHideOrShow,
+                        );
+                    } else if item.meta_item_list().is_none() {
+                        self.tcx.emit_node_span_lint(
+                            INVALID_DOC_ATTRIBUTES,
+                            hir_id,
+                            meta.span,
+                            errors::DocAutoCfgHideShowExpectsList { attr_name: attr_name.as_str() },
+                        );
+                    }
+                }
+            }
         }
     }
 
@@ -1245,10 +1272,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                             self.check_attr_crate_level(attr, style, meta, hir_id);
                         }
 
-                        Some(sym::cfg_hide) => {
-                            if self.check_attr_crate_level(attr, style, meta, hir_id) {
-                                self.check_doc_cfg_hide(meta, hir_id);
-                            }
+                        Some(sym::auto_cfg) => {
+                            self.check_doc_auto_cfg(meta, hir_id);
                         }
 
                         Some(sym::inline | sym::no_inline) => {
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index cfd6b9e6dff..6cc0bd6ce48 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -309,8 +309,18 @@ pub(crate) struct DocTestLiteral;
 pub(crate) struct DocTestTakesList;
 
 #[derive(LintDiagnostic)]
-#[diag(passes_doc_cfg_hide_takes_list)]
-pub(crate) struct DocCfgHideTakesList;
+#[diag(passes_doc_auto_cfg_wrong_literal)]
+pub(crate) struct DocAutoCfgWrongLiteral;
+
+#[derive(LintDiagnostic)]
+#[diag(passes_doc_auto_cfg_expects_hide_or_show)]
+pub(crate) struct DocAutoCfgExpectsHideOrShow;
+
+#[derive(LintDiagnostic)]
+#[diag(passes_doc_auto_cfg_hide_show_expects_list)]
+pub(crate) struct DocAutoCfgHideShowExpectsList<'a> {
+    pub attr_name: &'a str,
+}
 
 #[derive(LintDiagnostic)]
 #[diag(passes_doc_test_unknown_any)]
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index faf32523baa..e80a98994db 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -545,6 +545,7 @@ symbols! {
         attributes,
         audit_that,
         augmented_assignments,
+        auto_cfg,
         auto_traits,
         autodiff,
         autodiff_forward,
@@ -1149,6 +1150,8 @@ symbols! {
         hashset_iter_ty,
         hexagon_target_feature,
         hidden,
+        hidden_cfg,
+        hide,
         hint,
         homogeneous_aggregate,
         host,
@@ -1987,6 +1990,7 @@ symbols! {
         shl_assign,
         shorter_tail_lifetimes,
         should_panic,
+        show,
         shr,
         shr_assign,
         sig_dfl,
diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs
index cba1ce40f75..243fdc2e843 100644
--- a/library/alloc/src/lib.rs
+++ b/library/alloc/src/lib.rs
@@ -64,14 +64,27 @@
     issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
     test(no_crate_inject, attr(allow(unused_variables), deny(warnings)))
 )]
-#![doc(cfg_hide(
-    not(test),
-    no_global_oom_handling,
-    not(no_global_oom_handling),
-    not(no_rc),
-    not(no_sync),
-    target_has_atomic = "ptr"
-))]
+#![cfg_attr(
+    bootstrap,
+    doc(cfg_hide(
+        not(test),
+        no_global_oom_handling,
+        not(no_global_oom_handling),
+        not(no_rc),
+        not(no_sync),
+        target_has_atomic = "ptr"
+    ))
+)]
+#![cfg_attr(
+    not(bootstrap),
+    doc(auto_cfg(hide(
+        test,
+        no_global_oom_handling,
+        no_rc,
+        no_sync,
+        target_has_atomic = "ptr"
+    )))
+)]
 #![doc(rust_logo)]
 #![feature(rustdoc_internals)]
 #![no_std]
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 5d52bfb1b12..32a3d6c7042 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -51,27 +51,54 @@
     test(attr(allow(dead_code, deprecated, unused_variables, unused_mut)))
 )]
 #![doc(rust_logo)]
-#![doc(cfg_hide(
-    no_fp_fmt_parse,
-    target_pointer_width = "16",
-    target_pointer_width = "32",
-    target_pointer_width = "64",
-    target_has_atomic = "8",
-    target_has_atomic = "16",
-    target_has_atomic = "32",
-    target_has_atomic = "64",
-    target_has_atomic = "ptr",
-    target_has_atomic_equal_alignment = "8",
-    target_has_atomic_equal_alignment = "16",
-    target_has_atomic_equal_alignment = "32",
-    target_has_atomic_equal_alignment = "64",
-    target_has_atomic_equal_alignment = "ptr",
-    target_has_atomic_load_store = "8",
-    target_has_atomic_load_store = "16",
-    target_has_atomic_load_store = "32",
-    target_has_atomic_load_store = "64",
-    target_has_atomic_load_store = "ptr",
-))]
+#![cfg_attr(
+    bootstrap,
+    doc(cfg_hide(
+        no_fp_fmt_parse,
+        target_pointer_width = "16",
+        target_pointer_width = "32",
+        target_pointer_width = "64",
+        target_has_atomic = "8",
+        target_has_atomic = "16",
+        target_has_atomic = "32",
+        target_has_atomic = "64",
+        target_has_atomic = "ptr",
+        target_has_atomic_equal_alignment = "8",
+        target_has_atomic_equal_alignment = "16",
+        target_has_atomic_equal_alignment = "32",
+        target_has_atomic_equal_alignment = "64",
+        target_has_atomic_equal_alignment = "ptr",
+        target_has_atomic_load_store = "8",
+        target_has_atomic_load_store = "16",
+        target_has_atomic_load_store = "32",
+        target_has_atomic_load_store = "64",
+        target_has_atomic_load_store = "ptr",
+    ))
+)]
+#![cfg_attr(
+    not(bootstrap),
+    doc(auto_cfg(hide(
+        no_fp_fmt_parse,
+        target_pointer_width = "16",
+        target_pointer_width = "32",
+        target_pointer_width = "64",
+        target_has_atomic = "8",
+        target_has_atomic = "16",
+        target_has_atomic = "32",
+        target_has_atomic = "64",
+        target_has_atomic = "ptr",
+        target_has_atomic_equal_alignment = "8",
+        target_has_atomic_equal_alignment = "16",
+        target_has_atomic_equal_alignment = "32",
+        target_has_atomic_equal_alignment = "64",
+        target_has_atomic_equal_alignment = "ptr",
+        target_has_atomic_load_store = "8",
+        target_has_atomic_load_store = "16",
+        target_has_atomic_load_store = "32",
+        target_has_atomic_load_store = "64",
+        target_has_atomic_load_store = "ptr",
+    )))
+)]
 #![no_core]
 #![rustc_coherence_is_core]
 #![rustc_preserve_ub_checks]
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 233e41aa345..93c91b61525 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -235,7 +235,21 @@
     test(attr(allow(dead_code, deprecated, unused_variables, unused_mut)))
 )]
 #![doc(rust_logo)]
-#![doc(cfg_hide(not(test), no_global_oom_handling, not(no_global_oom_handling)))]
+#![cfg_attr(
+    bootstrap,
+    doc(cfg_hide(
+        not(test),
+        no_global_oom_handling,
+        not(no_global_oom_handling)
+    ))
+)]
+#![cfg_attr(
+    not(bootstrap),
+    doc(auto_cfg(hide(
+        test,
+        no_global_oom_handling,
+    )))
+)]
 // Don't link to std. We are std.
 #![no_std]
 // Tell the compiler to link to either panic_abort or panic_unwind
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index 8feca1367fc..aa614b73dea 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -256,6 +256,36 @@ impl Cfg {
     fn omit_preposition(&self) -> bool {
         matches!(self, Cfg::True | Cfg::False)
     }
+
+    pub(crate) fn strip_hidden(&self, hidden: &FxHashSet<Cfg>) -> Option<Self> {
+        match self {
+            Self::True | Self::False => Some(self.clone()),
+            Self::Cfg(..) => {
+                if !hidden.contains(self) {
+                    Some(self.clone())
+                } else {
+                    None
+                }
+            }
+            Self::Not(cfg) => {
+                if let Some(cfg) = cfg.strip_hidden(hidden) {
+                    Some(Self::Not(Box::new(cfg)))
+                } else {
+                    None
+                }
+            }
+            Self::Any(cfgs) => {
+                let cfgs =
+                    cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>();
+                if cfgs.is_empty() { None } else { Some(Self::Any(cfgs)) }
+            }
+            Self::All(cfgs) => {
+                let cfgs =
+                    cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>();
+                if cfgs.is_empty() { None } else { Some(Self::All(cfgs)) }
+            }
+        }
+    }
 }
 
 impl ops::Not for Cfg {
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 8461e15c6c3..8ffa6033c9b 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -19,10 +19,10 @@ use tracing::{debug, trace};
 
 use super::{Item, extract_cfg_from_attrs};
 use crate::clean::{
-    self, Attributes, ImplKind, ItemId, Type, clean_bound_vars, clean_generics, clean_impl_item,
-    clean_middle_assoc_item, clean_middle_field, clean_middle_ty, clean_poly_fn_sig,
-    clean_trait_ref_with_constraints, clean_ty, clean_ty_alias_inner_type, clean_ty_generics,
-    clean_variant_def, utils,
+    self, Attributes, CfgInfo, ImplKind, ItemId, Type, clean_bound_vars, clean_generics,
+    clean_impl_item, clean_middle_assoc_item, clean_middle_field, clean_middle_ty,
+    clean_poly_fn_sig, clean_trait_ref_with_constraints, clean_ty, clean_ty_alias_inner_type,
+    clean_ty_generics, clean_variant_def, utils,
 };
 use crate::core::DocContext;
 use crate::formats::item_type::ItemType;
@@ -409,6 +409,7 @@ pub(crate) fn merge_attrs(
     cx: &mut DocContext<'_>,
     old_attrs: &[hir::Attribute],
     new_attrs: Option<(&[hir::Attribute], Option<LocalDefId>)>,
+    cfg_info: &mut CfgInfo,
 ) -> (clean::Attributes, Option<Arc<clean::cfg::Cfg>>) {
     // NOTE: If we have additional attributes (from a re-export),
     // always insert them first. This ensure that re-export
@@ -423,12 +424,12 @@ pub(crate) fn merge_attrs(
             } else {
                 Attributes::from_hir(&both)
             },
-            extract_cfg_from_attrs(both.iter(), cx.tcx, &cx.cache.hidden_cfg),
+            extract_cfg_from_attrs(both.iter(), cx.tcx, cfg_info),
         )
     } else {
         (
             Attributes::from_hir(old_attrs),
-            extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg),
+            extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, cfg_info),
         )
     }
 }
@@ -604,7 +605,7 @@ pub(crate) fn build_impl(
         });
     }
 
-    let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs);
+    let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs, &mut CfgInfo::default());
     trace!("merged_attrs={merged_attrs:?}");
 
     trace!(
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 0afb969d5c8..c6339dd4755 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -212,18 +212,10 @@ fn generate_item_with_correct_attrs(
         // We only keep the item's attributes.
         target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect()
     };
-    let cfg = extract_cfg_from_attrs(
-        attrs.iter().map(move |(attr, _)| match attr {
-            Cow::Borrowed(attr) => *attr,
-            Cow::Owned(attr) => attr,
-        }),
-        cx.tcx,
-        &cx.cache.hidden_cfg,
-    );
     let attrs = Attributes::from_hir_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false);
 
     let name = renamed.or(Some(name));
-    let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, cfg);
+    let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, None);
     // FIXME (GuillaumeGomez): Should we also make `inline_stmt_id` a `Vec` instead of an `Option`?
     item.inner.inline_stmt_id = import_ids.first().copied();
     item
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index ff513c71035..63e8357801c 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -6,7 +6,8 @@ use std::{fmt, iter};
 use arrayvec::ArrayVec;
 use itertools::Either;
 use rustc_abi::{ExternAbi, VariantIdx};
-use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
+use rustc_ast::ast::{LitKind, MetaItemInner, MetaItemKind};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
 use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation};
 use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId};
@@ -467,7 +468,7 @@ impl Item {
             name,
             kind,
             Attributes::from_hir(hir_attrs),
-            extract_cfg_from_attrs(hir_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg),
+            None,
         )
     }
 
@@ -902,30 +903,6 @@ impl ItemKind {
             | AttributeItem => [].iter(),
         }
     }
-
-    /// Returns `true` if this item does not appear inside an impl block.
-    pub(crate) fn is_non_assoc(&self) -> bool {
-        matches!(
-            self,
-            StructItem(_)
-                | UnionItem(_)
-                | EnumItem(_)
-                | TraitItem(_)
-                | ModuleItem(_)
-                | ExternCrateItem { .. }
-                | FunctionItem(_)
-                | TypeAliasItem(_)
-                | StaticItem(_)
-                | ConstantItem(_)
-                | TraitAliasItem(_)
-                | ForeignFunctionItem(_, _)
-                | ForeignStaticItem(_, _)
-                | ForeignTypeItem
-                | MacroItem(_)
-                | ProcMacroItem(_)
-                | PrimitiveItem(_)
-        )
-    }
 }
 
 #[derive(Clone, Debug)]
@@ -945,14 +922,85 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator<Item = &'a hir::Attribute>>(
         .flatten()
 }
 
+#[derive(Clone, Debug)]
+pub(crate) struct CfgInfo {
+    hidden_cfg: FxHashSet<Cfg>,
+    current_cfg: Cfg,
+    doc_auto_cfg_active: bool,
+    parent_is_doc_cfg: bool,
+}
+
+impl Default for CfgInfo {
+    fn default() -> Self {
+        Self {
+            hidden_cfg: [
+                Cfg::Cfg(sym::test, None),
+                Cfg::Cfg(sym::doc, None),
+                Cfg::Cfg(sym::doctest, None),
+            ]
+            .into_iter()
+            .collect(),
+            current_cfg: Cfg::True,
+            doc_auto_cfg_active: true,
+            parent_is_doc_cfg: false,
+        }
+    }
+}
+
+fn show_hide_show_conflict_error(
+    tcx: TyCtxt<'_>,
+    item_span: rustc_span::Span,
+    previous: rustc_span::Span,
+) {
+    let mut diag = tcx.sess.dcx().struct_span_err(
+        item_span,
+        format!(
+            "same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item"
+        ),
+    );
+    diag.span_note(previous, "first change was here");
+    diag.emit();
+}
+
+fn handle_auto_cfg_hide_show(
+    tcx: TyCtxt<'_>,
+    cfg_info: &mut CfgInfo,
+    sub_attr: &MetaItemInner,
+    is_show: bool,
+    new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
+    new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
+) {
+    if let MetaItemInner::MetaItem(item) = sub_attr
+        && let MetaItemKind::List(items) = &item.kind
+    {
+        for item in items {
+            // Cfg parsing errors should already have been reported in `rustc_passes::check_attr`.
+            if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) {
+                if is_show {
+                    if let Some(span) = new_hide_attrs.get(&(key, value)) {
+                        show_hide_show_conflict_error(tcx, item.span(), *span);
+                    } else {
+                        new_show_attrs.insert((key, value), item.span());
+                    }
+                    cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value));
+                } else {
+                    if let Some(span) = new_show_attrs.get(&(key, value)) {
+                        show_hide_show_conflict_error(tcx, item.span(), *span);
+                    } else {
+                        new_hide_attrs.insert((key, value), item.span());
+                    }
+                    cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value));
+                }
+            }
+        }
+    }
+}
+
 pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>(
     attrs: I,
     tcx: TyCtxt<'_>,
-    hidden_cfg: &FxHashSet<Cfg>,
+    cfg_info: &mut CfgInfo,
 ) -> Option<Arc<Cfg>> {
-    let doc_cfg_active = tcx.features().doc_cfg();
-    let doc_auto_cfg_active = tcx.features().doc_auto_cfg();
-
     fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
         let mut iter = it.into_iter();
         let item = iter.next()?;
@@ -962,56 +1010,168 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
         Some(item)
     }
 
-    let mut cfg = if doc_cfg_active || doc_auto_cfg_active {
-        let mut doc_cfg = attrs
-            .clone()
-            .filter(|attr| attr.has_name(sym::doc))
-            .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
-            .filter(|attr| attr.has_name(sym::cfg))
-            .peekable();
-        if doc_cfg.peek().is_some() && doc_cfg_active {
-            let sess = tcx.sess;
-
-            doc_cfg.fold(Cfg::True, |mut cfg, item| {
-                if let Some(cfg_mi) =
-                    item.meta_item().and_then(|item| rustc_expand::config::parse_cfg(item, sess))
+    let mut new_show_attrs = FxHashMap::default();
+    let mut new_hide_attrs = FxHashMap::default();
+
+    let mut doc_cfg = attrs
+        .clone()
+        .filter(|attr| attr.has_name(sym::doc))
+        .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
+        .filter(|attr| attr.has_name(sym::cfg))
+        .peekable();
+    // If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes.
+    if doc_cfg.peek().is_some() {
+        let sess = tcx.sess;
+        // We overwrite existing `cfg`.
+        if !cfg_info.parent_is_doc_cfg {
+            cfg_info.current_cfg = Cfg::True;
+            cfg_info.parent_is_doc_cfg = true;
+        }
+        for attr in doc_cfg {
+            if let Some(cfg_mi) =
+                attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg(attr, sess))
+            {
+                match Cfg::parse(cfg_mi) {
+                    Ok(new_cfg) => cfg_info.current_cfg &= new_cfg,
+                    Err(e) => {
+                        sess.dcx().span_err(e.span, e.msg);
+                    }
+                }
+            }
+        }
+    } else {
+        cfg_info.parent_is_doc_cfg = false;
+    }
+
+    let mut changed_auto_active_status = None;
+
+    // First we get all `doc(auto_cfg)` attributes.
+    for attr in attrs.clone() {
+        if let Some(ident) = attr.ident()
+            && ident.name == sym::doc
+            && let Some(attrs) = attr.meta_item_list()
+        {
+            for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) {
+                let MetaItemInner::MetaItem(attr) = attr else {
+                    continue;
+                };
+                match &attr.kind {
+                    MetaItemKind::Word => {
+                        if let Some(first_change) = changed_auto_active_status {
+                            if !cfg_info.doc_auto_cfg_active {
+                                tcx.sess.dcx().struct_span_err(
+                                    vec![first_change, attr.span],
+                                    "`auto_cfg` was disabled and enabled more than once on the same item",
+                                ).emit();
+                                return None;
+                            }
+                        } else {
+                            changed_auto_active_status = Some(attr.span);
+                        }
+                        cfg_info.doc_auto_cfg_active = true;
+                    }
+                    MetaItemKind::NameValue(lit) => {
+                        if let LitKind::Bool(value) = lit.kind {
+                            if let Some(first_change) = changed_auto_active_status {
+                                if cfg_info.doc_auto_cfg_active != value {
+                                    tcx.sess.dcx().struct_span_err(
+                                        vec![first_change, attr.span],
+                                        "`auto_cfg` was disabled and enabled more than once on the same item",
+                                    ).emit();
+                                    return None;
+                                }
+                            } else {
+                                changed_auto_active_status = Some(attr.span);
+                            }
+                            cfg_info.doc_auto_cfg_active = value;
+                        }
+                    }
+                    MetaItemKind::List(sub_attrs) => {
+                        if let Some(first_change) = changed_auto_active_status {
+                            if !cfg_info.doc_auto_cfg_active {
+                                tcx.sess.dcx().struct_span_err(
+                                    vec![first_change, attr.span],
+                                    "`auto_cfg` was disabled and enabled more than once on the same item",
+                                ).emit();
+                                return None;
+                            }
+                        } else {
+                            changed_auto_active_status = Some(attr.span);
+                        }
+                        // Whatever happens next, the feature is enabled again.
+                        cfg_info.doc_auto_cfg_active = true;
+                        for sub_attr in sub_attrs.iter() {
+                            if let Some(ident) = sub_attr.ident()
+                                && (ident.name == sym::show || ident.name == sym::hide)
+                            {
+                                handle_auto_cfg_hide_show(
+                                    tcx,
+                                    cfg_info,
+                                    &sub_attr,
+                                    ident.name == sym::show,
+                                    &mut new_show_attrs,
+                                    &mut new_hide_attrs,
+                                );
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because
+    // `doc(cfg())` overrides `cfg()`).
+    for attr in attrs {
+        let Some(ident) = attr.ident() else { continue };
+        match ident.name {
+            sym::cfg | sym::cfg_trace if !cfg_info.parent_is_doc_cfg => {
+                if let Some(attr) = single(attr.meta_item_list()?)
+                    && let Ok(new_cfg) = Cfg::parse(&attr)
                 {
-                    match Cfg::parse(cfg_mi) {
-                        Ok(new_cfg) => cfg &= new_cfg,
-                        Err(e) => {
-                            sess.dcx().span_err(e.span, e.msg);
+                    cfg_info.current_cfg &= new_cfg;
+                }
+            }
+            // treat #[target_feature(enable = "feat")] attributes as if they were
+            // #[doc(cfg(target_feature = "feat"))] attributes as well
+            sym::target_feature
+                if let Some(attrs) = attr.meta_item_list() =>
+            {
+                for attr in attrs {
+                    if attr.has_name(sym::enable) && attr.value_str().is_some() {
+                        // Clone `enable = "feat"`, change to `target_feature = "feat"`.
+                        // Unwrap is safe because `value_str` succeeded above.
+                        let mut meta = attr.meta_item().unwrap().clone();
+                        meta.path =
+                            ast::Path::from_ident(Ident::with_dummy_span(sym::target_feature));
+
+                        if let Ok(feat_cfg) = Cfg::parse(&ast::MetaItemInner::MetaItem(meta)) {
+                            cfg_info.current_cfg &= feat_cfg;
                         }
                     }
                 }
-                cfg
-            })
-        } else if doc_auto_cfg_active {
-            // If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because
-            // `doc(cfg())` overrides `cfg()`).
-            attrs
-                .clone()
-                .filter(|attr| attr.has_name(sym::cfg_trace))
-                .filter_map(|attr| single(attr.meta_item_list()?))
-                .filter_map(|attr| Cfg::parse_without(attr.meta_item()?, hidden_cfg).ok().flatten())
-                .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg)
+            }
+            _ => {}
+        }
+    }
+
+    // If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing
+    // to be done here.
+    if !cfg_info.doc_auto_cfg_active && !cfg_info.parent_is_doc_cfg {
+        None
+    } else if cfg_info.parent_is_doc_cfg {
+        if cfg_info.current_cfg == Cfg::True {
+            None
         } else {
-            Cfg::True
+            Some(Arc::new(cfg_info.current_cfg.clone()))
         }
     } else {
-        Cfg::True
-    };
-
-    // treat #[target_feature(enable = "feat")] attributes as if they were
-    // #[doc(cfg(target_feature = "feat"))] attributes as well
-    if let Some(features) =
-        find_attr!(attrs, AttributeKind::TargetFeature { features, .. } => features)
-    {
-        for (feature, _) in features {
-            cfg &= Cfg::Cfg(sym::target_feature, Some(*feature));
+        // Since we always want to collect all `cfg` items, we remove the hidden ones afterward.
+        match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) {
+            None | Some(Cfg::True) => None,
+            Some(cfg) => Some(Arc::new(cfg)),
         }
     }
-
-    if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) }
 }
 
 pub(crate) trait NestedAttributesExt {
diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs
index f5ec828187a..b9367b88005 100644
--- a/src/librustdoc/doctest/rust.rs
+++ b/src/librustdoc/doctest/rust.rs
@@ -5,7 +5,6 @@ use std::env;
 use std::sync::Arc;
 
 use rustc_ast_pretty::pprust;
-use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
 use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit};
 use rustc_middle::hir::nested_filter;
@@ -15,7 +14,7 @@ use rustc_span::source_map::SourceMap;
 use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span, sym};
 
 use super::{DocTestVisitor, ScrapedDocTest};
-use crate::clean::{Attributes, extract_cfg_from_attrs};
+use crate::clean::{Attributes, CfgInfo, extract_cfg_from_attrs};
 use crate::html::markdown::{self, ErrorCodes, LangString, MdRelLine};
 
 struct RustCollector {
@@ -120,9 +119,11 @@ impl HirCollector<'_> {
         nested: F,
     ) {
         let ast_attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id));
-        if let Some(ref cfg) =
-            extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &FxHashSet::default())
-            && !cfg.matches(&self.tcx.sess.psess)
+        if let Some(ref cfg) = extract_cfg_from_attrs(
+            ast_attrs.iter(),
+            self.tcx,
+            &mut CfgInfo::default(),
+        ) && !cfg.matches(&self.tcx.sess.psess)
         {
             return;
         }
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index 29b4c4caaf8..a19d254ea95 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -125,8 +125,6 @@ pub(crate) struct Cache {
     ///
     /// Links are indexed by the DefId of the item they document.
     pub(crate) intra_doc_links: FxHashMap<ItemId, FxIndexSet<clean::ItemLink>>,
-    /// Cfg that have been hidden via #![doc(cfg_hide(...))]
-    pub(crate) hidden_cfg: FxHashSet<clean::cfg::Cfg>,
 
     /// Contains the list of `DefId`s which have been inlined. It is used when generating files
     /// to check if a stripped item should get its file generated or not: if it's inside a
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
index eddafa9ba8e..957a1f56c71 100644
--- a/src/librustdoc/passes/propagate_doc_cfg.rs
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -2,11 +2,15 @@
 
 use std::sync::Arc;
 
+use rustc_ast::token::{Token, TokenKind};
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
 use rustc_hir::def_id::LocalDefId;
+use rustc_hir::{AttrArgs, Attribute};
+use rustc_span::symbol::sym;
 
 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,70 +22,119 @@ 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)
+    CfgPropagator { parent_cfg: None, parent: None, cx, cfg_info: CfgInfo::default() }
+        .fold_crate(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,
-        };
+fn should_retain(token: &TokenTree) -> bool {
+    // We only keep `doc(cfg)` items.
+    matches!(
+        token,
+        TokenTree::Token(
+            Token {
+                kind: TokenKind::Ident(
+                    ident,
+                    _,
+                ),
+                ..
+            },
+            _,
+        ) if *ident == sym::cfg,
+    )
+}
 
-        let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else {
-            return;
-        };
+fn filter_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 should_retain(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;
+// We only care about `#[cfg()]` and `#[doc(cfg())]`, we discard everything else.
+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_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(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)));
+        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();
         let old_parent_cfg = self.parent_cfg.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;
+        self.parent_cfg = item.inner.cfg.clone();
 
         let old_parent =
             if let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) {
@@ -90,6 +143,7 @@ impl DocFolder for CfgPropagator<'_, '_> {
                 self.parent.take()
             };
         let result = self.fold_item_recur(item);
+        self.cfg_info = old_cfg_info;
         self.parent_cfg = old_parent_cfg;
         self.parent = old_parent;
 
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index cd28322f590..ac67871b0cb 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -17,7 +17,6 @@ use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
 use rustc_span::symbol::{Symbol, kw, sym};
 use tracing::debug;
 
-use crate::clean::cfg::Cfg;
 use crate::clean::utils::{inherits_doc_hidden, should_ignore_res};
 use crate::clean::{NestedAttributesExt, hir_attr_lists, reexport_chain};
 use crate::core;
@@ -178,32 +177,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             }
         }
 
-        self.cx.cache.hidden_cfg = self
-            .cx
-            .tcx
-            .hir_attrs(CRATE_HIR_ID)
-            .iter()
-            .filter(|attr| attr.has_name(sym::doc))
-            .flat_map(|attr| attr.meta_item_list().into_iter().flatten())
-            .filter(|attr| attr.has_name(sym::cfg_hide))
-            .flat_map(|attr| {
-                attr.meta_item_list()
-                    .unwrap_or(&[])
-                    .iter()
-                    .filter_map(|attr| {
-                        Cfg::parse(attr)
-                            .map_err(|e| self.cx.sess().dcx().span_err(e.span, e.msg))
-                            .ok()
-                    })
-                    .collect::<Vec<_>>()
-            })
-            .chain([
-                Cfg::Cfg(sym::test, None),
-                Cfg::Cfg(sym::doc, None),
-                Cfg::Cfg(sym::doctest, None),
-            ])
-            .collect();
-
         self.cx.cache.exact_paths = self.exact_paths;
         top_level_module
     }