diff options
| author | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2025-03-29 00:53:59 +0100 |
|---|---|---|
| committer | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2025-09-27 11:29:48 +0200 |
| commit | 7c00bccd3b3eb6717e3c801123107962e671e48f (patch) | |
| tree | d09538e680b53fb8ec9fc632916181b77be4dade | |
| parent | 959b450747f81e720be3a829665dd30e553e7fd7 (diff) | |
| download | rust-7c00bccd3b3eb6717e3c801123107962e671e48f.tar.gz rust-7c00bccd3b3eb6717e3c801123107962e671e48f.zip | |
Implement RFC 3631
| -rw-r--r-- | compiler/rustc_ast_passes/src/feature_gate.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_passes/messages.ftl | 9 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/check_attr.rs | 53 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/errors.rs | 14 | ||||
| -rw-r--r-- | compiler/rustc_span/src/symbol.rs | 4 | ||||
| -rw-r--r-- | library/alloc/src/lib.rs | 29 | ||||
| -rw-r--r-- | library/core/src/lib.rs | 69 | ||||
| -rw-r--r-- | library/std/src/lib.rs | 16 | ||||
| -rw-r--r-- | src/librustdoc/clean/cfg.rs | 30 | ||||
| -rw-r--r-- | src/librustdoc/clean/inline.rs | 15 | ||||
| -rw-r--r-- | src/librustdoc/clean/mod.rs | 10 | ||||
| -rw-r--r-- | src/librustdoc/clean/types.rs | 302 | ||||
| -rw-r--r-- | src/librustdoc/doctest/rust.rs | 11 | ||||
| -rw-r--r-- | src/librustdoc/formats/cache.rs | 2 | ||||
| -rw-r--r-- | src/librustdoc/passes/propagate_doc_cfg.rs | 134 | ||||
| -rw-r--r-- | src/librustdoc/visit_ast.rs | 27 |
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 } |
