From 7c00bccd3b3eb6717e3c801123107962e671e48f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 29 Mar 2025 00:53:59 +0100 Subject: Implement RFC 3631 --- compiler/rustc_ast_passes/src/feature_gate.rs | 2 - compiler/rustc_passes/messages.ftl | 9 + compiler/rustc_passes/src/check_attr.rs | 53 +++-- compiler/rustc_passes/src/errors.rs | 14 +- compiler/rustc_span/src/symbol.rs | 4 + library/alloc/src/lib.rs | 29 ++- library/core/src/lib.rs | 69 ++++-- library/std/src/lib.rs | 16 +- src/librustdoc/clean/cfg.rs | 30 +++ src/librustdoc/clean/inline.rs | 15 +- src/librustdoc/clean/mod.rs | 10 +- src/librustdoc/clean/types.rs | 302 ++++++++++++++++++++------ src/librustdoc/doctest/rust.rs | 11 +- src/librustdoc/formats/cache.rs | 2 - src/librustdoc/passes/propagate_doc_cfg.rs | 134 ++++++++---- 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) -> Option { + 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::>(); + 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::>(); + 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)>, + cfg_info: &mut CfgInfo, ) -> (clean::Attributes, Option>) { // 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>( .flatten() } +#[derive(Clone, Debug)] +pub(crate) struct CfgInfo { + hidden_cfg: FxHashSet, + 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), rustc_span::Span>, + new_hide_attrs: &mut FxHashMap<(Symbol, Option), 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 + Clone>( attrs: I, tcx: TyCtxt<'_>, - hidden_cfg: &FxHashSet, + cfg_info: &mut CfgInfo, ) -> Option> { - let doc_cfg_active = tcx.features().doc_cfg(); - let doc_auto_cfg_active = tcx.features().doc_auto_cfg(); - fn single(it: T) -> Option { let mut iter = it.into_iter(); let item = iter.next()?; @@ -962,56 +1010,168 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator 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>, - /// Cfg that have been hidden via #![doc(cfg_hide(...))] - pub(crate) hidden_cfg: FxHashSet, /// 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>, parent: Option, 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 { + 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, 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 { + 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::>() - }) - .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 } -- cgit 1.4.1-3-g733a5 From 6537278e114a98ea6f2c80d7d7ffacb2868be770 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 29 Mar 2025 00:54:22 +0100 Subject: Update rustdoc tests --- tests/rustdoc-ui/cfg-hide-show-conflict.rs | 2 ++ tests/rustdoc-ui/cfg-hide-show-conflict.stderr | 14 +++++++++++ tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs | 5 ++-- tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr | 11 ++++----- tests/rustdoc-ui/invalid-cfg.rs | 2 +- tests/rustdoc-ui/lints/doc_cfg_hide.rs | 9 ++----- tests/rustdoc-ui/lints/doc_cfg_hide.stderr | 29 +++++++---------------- tests/rustdoc/doc-cfg/doc-cfg-hide.rs | 8 +++---- tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs | 2 +- tests/rustdoc/doc_auto_cfg_reexports.rs | 21 ++++++++++++++++ 10 files changed, 61 insertions(+), 42 deletions(-) create mode 100644 tests/rustdoc-ui/cfg-hide-show-conflict.rs create mode 100644 tests/rustdoc-ui/cfg-hide-show-conflict.stderr create mode 100644 tests/rustdoc/doc_auto_cfg_reexports.rs diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.rs b/tests/rustdoc-ui/cfg-hide-show-conflict.rs new file mode 100644 index 00000000000..a8a50fe15c7 --- /dev/null +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.rs @@ -0,0 +1,2 @@ +#![doc(auto_cfg(hide(target_os = "linux")))] +#![doc(auto_cfg(show(windows, target_os = "linux")))] //~ ERROR diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr new file mode 100644 index 00000000000..d2d0564606a --- /dev/null +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr @@ -0,0 +1,14 @@ +error: same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item + --> $DIR/cfg-hide-show-conflict.rs:2:31 + | +LL | #![doc(auto_cfg(show(windows, target_os = "linux")))] + | ^^^^^^^^^^^^^^^^^^^ + | +note: first change was here + --> $DIR/cfg-hide-show-conflict.rs:1:22 + | +LL | #![doc(auto_cfg(hide(target_os = "linux")))] + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs b/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs index 17812018b9b..e49285a01b8 100644 --- a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs +++ b/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs @@ -1,5 +1,6 @@ -#![doc(cfg_hide(test))] -//~^ ERROR `#[doc(cfg_hide)]` is experimental +// FIXME: Remove this file once feature is removed + +#![doc(cfg_hide(test))] //~ ERROR #[cfg(not(test))] pub fn public_fn() {} diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr b/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr index 55135986ffe..b3eee2af7f7 100644 --- a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr @@ -1,13 +1,10 @@ -error[E0658]: `#[doc(cfg_hide)]` is experimental - --> $DIR/feature-gate-doc_cfg_hide.rs:1:1 +error: unknown `doc` attribute `cfg_hide` + --> $DIR/feature-gate-doc_cfg_hide.rs:3:8 | LL | #![doc(cfg_hide(test))] - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ | - = note: see issue #43781 for more information - = help: add `#![feature(doc_cfg_hide)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: `#[deny(invalid_doc_attributes)]` on by default error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0658`. diff --git a/tests/rustdoc-ui/invalid-cfg.rs b/tests/rustdoc-ui/invalid-cfg.rs index d237b8605c0..aff36286c53 100644 --- a/tests/rustdoc-ui/invalid-cfg.rs +++ b/tests/rustdoc-ui/invalid-cfg.rs @@ -1,4 +1,4 @@ #![feature(doc_cfg)] #[doc(cfg = "x")] //~ ERROR not followed by parentheses #[doc(cfg(x, y))] //~ ERROR multiple `cfg` predicates -struct S {} +pub struct S {} diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.rs b/tests/rustdoc-ui/lints/doc_cfg_hide.rs index 9a8bce2a92a..abf56318479 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.rs +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.rs @@ -1,7 +1,2 @@ -#![feature(doc_cfg_hide)] - -#![doc(cfg_hide = "test")] //~ ERROR -#![doc(cfg_hide)] //~ ERROR - -#[doc(cfg_hide(doc))] //~ ERROR -pub fn foo() {} +#![doc(auto_cfg(hide = "test"))] //~ ERROR +#![doc(auto_cfg(hide))] //~ ERROR diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr index 0c9d0879b0a..bb0e52eb666 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr @@ -1,27 +1,16 @@ -error: this attribute can only be applied at the crate level - --> $DIR/doc_cfg_hide.rs:6:7 +error: `#![doc(auto_cfg(hide(...)))]` only expects a list of items + --> $DIR/doc_cfg_hide.rs:1:8 | -LL | #[doc(cfg_hide(doc))] - | ^^^^^^^^^^^^^ +LL | #![doc(auto_cfg(hide = "test"))] + | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: read for more information = note: `#[deny(invalid_doc_attributes)]` on by default -help: to apply to the crate, use an inner attribute - | -LL | #![doc(cfg_hide(doc))] - | + - -error: `#[doc(cfg_hide(...))]` takes a list of attributes - --> $DIR/doc_cfg_hide.rs:3:8 - | -LL | #![doc(cfg_hide = "test")] - | ^^^^^^^^^^^^^^^^^ -error: `#[doc(cfg_hide(...))]` takes a list of attributes - --> $DIR/doc_cfg_hide.rs:4:8 +error: `#![doc(auto_cfg(hide(...)))]` only expects a list of items + --> $DIR/doc_cfg_hide.rs:2:8 | -LL | #![doc(cfg_hide)] - | ^^^^^^^^ +LL | #![doc(auto_cfg(hide))] + | ^^^^^^^^^^^^^^ -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors diff --git a/tests/rustdoc/doc-cfg/doc-cfg-hide.rs b/tests/rustdoc/doc-cfg/doc-cfg-hide.rs index ceb1f99fae0..cf906ede7e1 100644 --- a/tests/rustdoc/doc-cfg/doc-cfg-hide.rs +++ b/tests/rustdoc/doc-cfg/doc-cfg-hide.rs @@ -1,7 +1,7 @@ #![crate_name = "oud"] -#![feature(doc_auto_cfg, doc_cfg, doc_cfg_hide)] +#![feature(doc_auto_cfg, doc_cfg, doc_cfg_hide, no_core)] -#![doc(cfg_hide(feature = "solecism"))] +#![doc(auto_cfg(hide(feature = "solecism")))] //@ has 'oud/struct.Solecism.html' //@ count - '//*[@class="stab portability"]' 0 @@ -18,7 +18,7 @@ pub struct Scribacious; //@ has 'oud/struct.Hyperdulia.html' //@ count - '//*[@class="stab portability"]' 1 -//@ matches - '//*[@class="stab portability"]' 'crate feature hyperdulia' +//@ matches - '//*[@class="stab portability"]' 'crate features hyperdulia only' //@ compile-flags:--cfg feature="hyperdulia" #[cfg(feature = "solecism")] #[cfg(feature = "hyperdulia")] @@ -26,7 +26,7 @@ pub struct Hyperdulia; //@ has 'oud/struct.Oystercatcher.html' //@ count - '//*[@class="stab portability"]' 1 -//@ matches - '//*[@class="stab portability"]' 'crate feature oystercatcher only' +//@ matches - '//*[@class="stab portability"]' 'crate features oystercatcher only' //@ compile-flags:--cfg feature="oystercatcher" #[cfg(all(feature = "solecism", feature = "oystercatcher"))] pub struct Oystercatcher; diff --git a/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs b/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs index b5b8d0f427b..27923893bdd 100644 --- a/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs +++ b/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs @@ -2,6 +2,6 @@ #![crate_name = "xenogenous"] //@ has 'xenogenous/struct.Worricow.html' -//@ count - '//*[@class="stab portability"]' 0 +//@ count - '//*[@class="stab portability"]' 1 #[cfg(feature = "worricow")] pub struct Worricow; diff --git a/tests/rustdoc/doc_auto_cfg_reexports.rs b/tests/rustdoc/doc_auto_cfg_reexports.rs new file mode 100644 index 00000000000..f226c52e2eb --- /dev/null +++ b/tests/rustdoc/doc_auto_cfg_reexports.rs @@ -0,0 +1,21 @@ +// Checks that `cfg` are correctly applied on inlined reexports. + +#![crate_name = "foo"] + +// Check with `std` item. +//@ has 'foo/index.html' '//*[@class="stab portability"]' 'Non-moustache' +//@ has 'foo/struct.C.html' '//*[@class="stab portability"]' \ +// 'Available on non-crate feature moustache only.' +#[cfg(not(feature = "moustache"))] +pub use std::cell::RefCell as C; + +// Check with local item. +mod x { + pub struct B; +} + +//@ has 'foo/index.html' '//*[@class="stab portability"]' 'Non-pistache' +//@ has 'foo/struct.B.html' '//*[@class="stab portability"]' \ +// 'Available on non-crate feature pistache only.' +#[cfg(not(feature = "pistache"))] +pub use crate::x::B; -- cgit 1.4.1-3-g733a5 From 2d82c99f1e4a4e50b47f75d767b0d1d957357c1a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 1 Apr 2025 17:01:00 +0200 Subject: Add "global" rustdoc test for RFC 3631 --- tests/rustdoc/doc_auto_cfg.rs | 80 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/rustdoc/doc_auto_cfg.rs diff --git a/tests/rustdoc/doc_auto_cfg.rs b/tests/rustdoc/doc_auto_cfg.rs new file mode 100644 index 00000000000..cb7c0e3f595 --- /dev/null +++ b/tests/rustdoc/doc_auto_cfg.rs @@ -0,0 +1,80 @@ +// Test covering RFC 3631 features. + +#![crate_name = "foo"] +#![feature(no_core)] +#![no_core] +#![no_std] + +#![doc(auto_cfg(hide(feature = "hidden")))] + +//@ has 'foo/index.html' +//@ !has - '//*[@class="stab portability"]' 'Non-moustache' +//@ has - '//*[@class="stab portability"]' 'Non-pistache' +//@ count - '//*[@class="stab portability"]' 1 + +//@ has 'foo/m/index.html' +//@ count - '//*[@title="Available on non-crate feature `hidden` only"]' 2 +#[cfg(not(feature = "hidden"))] +pub mod m { + //@ count 'foo/m/struct.A.html' '//*[@class="stab portability"]' 0 + pub struct A; + + //@ has 'foo/m/inner/index.html' '//*[@class="stab portability"]' 'Available on non-crate feature hidden only.' + #[doc(auto_cfg(show(feature = "hidden")))] + pub mod inner { + //@ has 'foo/m/inner/struct.B.html' '//*[@class="stab portability"]' 'Available on non-crate feature hidden only.' + pub struct B; + + //@ count 'foo/m/inner/struct.A.html' '//*[@class="stab portability"]' 0 + #[doc(auto_cfg(hide(feature = "hidden")))] + pub struct A; + } + + //@ has 'foo/m/struct.B.html' '//*[@class="stab portability"]' 'Available on non-crate feature hidden only.' + #[doc(auto_cfg(show(feature = "hidden")))] + pub struct B; +} + +//@ count 'foo/n/index.html' '//*[@title="Available on non-crate feature `moustache` only"]' 3 +//@ count - '//dl/dt' 4 +#[cfg(not(feature = "moustache"))] +#[doc(auto_cfg = false)] +pub mod n { + // Should not have `moustache` listed. + //@ count 'foo/n/struct.X.html' '//*[@class="stab portability"]' 0 + pub struct X; + + // Should re-enable `auto_cfg` and make `moustache` listed. + //@ has 'foo/n/struct.Y.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature moustache only.' + #[doc(auto_cfg)] + pub struct Y; + + // Should re-enable `auto_cfg` and make `moustache` listed for itself + // and for `Y`. + //@ has 'foo/n/inner/index.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature moustache only.' + #[doc(auto_cfg = true)] + pub mod inner { + //@ has 'foo/n/inner/struct.Y.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature moustache only.' + pub struct Y; + } + + // Should re-enable `auto_cfg` and make `moustache` listed. + //@ has 'foo/n/struct.Z.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature moustache only.' + #[doc(auto_cfg(hide(feature = "hidden")))] + pub struct Z; +} + +// Checking inheritance. +//@ has 'foo/o/index.html' '//*[@class="stab portability"]' \ +// 'Available on non-crate feature pistache only.' +#[doc(cfg(not(feature = "pistache")))] +pub mod o { + //@ has 'foo/o/struct.A.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature pistache and non-crate feature tarte only.' + #[doc(cfg(not(feature = "tarte")))] + pub struct A; +} -- cgit 1.4.1-3-g733a5 From 63aefe0737d0df886955f5a3aa68db865cd97f1b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 1 Apr 2025 17:24:28 +0200 Subject: Strenghten checks for `doc(auto_cfg(show/hide))` attributes --- compiler/rustc_passes/messages.ftl | 3 +++ compiler/rustc_passes/src/check_attr.rs | 15 ++++++++++++++- compiler/rustc_passes/src/errors.rs | 6 ++++++ library/alloc/src/lib.rs | 1 - library/std/src/lib.rs | 8 +------- tests/rustdoc-ui/lints/doc_cfg_hide.rs | 1 + tests/rustdoc-ui/lints/doc_cfg_hide.stderr | 8 +++++++- 7 files changed, 32 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 19014f37c66..8326ddaf194 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -149,6 +149,9 @@ passes_doc_auto_cfg_expects_hide_or_show = passes_doc_auto_cfg_hide_show_expects_list = `#![doc(auto_cfg({$attr_name}(...)))]` only expects a list of items +passes_doc_auto_cfg_hide_show_unexpected_item = + `#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/values items + passes_doc_auto_cfg_wrong_literal = `expected boolean for #[doc(auto_cfg = ...)]` diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3dc232a35ec..38b67e2f9dc 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1187,7 +1187,20 @@ impl<'tcx> CheckAttrVisitor<'tcx> { meta.span, errors::DocAutoCfgExpectsHideOrShow, ); - } else if item.meta_item_list().is_none() { + } else if let Some(list) = item.meta_item_list() { + for item in list { + if item.meta_item_list().is_some() { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + item.span(), + errors::DocAutoCfgHideShowUnexpectedItem { + attr_name: attr_name.as_str(), + }, + ); + } + } + } else { self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, hir_id, diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 6cc0bd6ce48..1d2428c4f9a 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -322,6 +322,12 @@ pub(crate) struct DocAutoCfgHideShowExpectsList<'a> { pub attr_name: &'a str, } +#[derive(LintDiagnostic)] +#[diag(passes_doc_auto_cfg_hide_show_unexpected_item)] +pub(crate) struct DocAutoCfgHideShowUnexpectedItem<'a> { + pub attr_name: &'a str, +} + #[derive(LintDiagnostic)] #[diag(passes_doc_test_unknown_any)] pub(crate) struct DocTestUnknownAny { diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 243fdc2e843..dc5d243e882 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -78,7 +78,6 @@ #![cfg_attr( not(bootstrap), doc(auto_cfg(hide( - test, no_global_oom_handling, no_rc, no_sync, diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 93c91b61525..ecd354f599e 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -243,13 +243,7 @@ not(no_global_oom_handling) )) )] -#![cfg_attr( - not(bootstrap), - doc(auto_cfg(hide( - test, - no_global_oom_handling, - ))) -)] +#![cfg_attr(not(bootstrap), doc(auto_cfg(hide(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/tests/rustdoc-ui/lints/doc_cfg_hide.rs b/tests/rustdoc-ui/lints/doc_cfg_hide.rs index abf56318479..4f2625d00ce 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.rs +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.rs @@ -1,2 +1,3 @@ #![doc(auto_cfg(hide = "test"))] //~ ERROR #![doc(auto_cfg(hide))] //~ ERROR +#![doc(auto_cfg(hide(not(windows))))] //~ ERROR diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr index bb0e52eb666..22501d63c3f 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr @@ -12,5 +12,11 @@ error: `#![doc(auto_cfg(hide(...)))]` only expects a list of items LL | #![doc(auto_cfg(hide))] | ^^^^^^^^^^^^^^ -error: aborting due to 2 previous errors +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/values items + --> $DIR/doc_cfg_hide.rs:3:22 + | +LL | #![doc(auto_cfg(hide(not(windows))))] + | ^^^^^^^^^^^^ + +error: aborting due to 3 previous errors -- cgit 1.4.1-3-g733a5 From 4847eaf6d25473ad1b8dab4d8d6cd62520f55e0e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 1 Apr 2025 17:49:08 +0200 Subject: Remove useless code in `propagate_doc_cfg.rs` --- src/librustdoc/passes/propagate_doc_cfg.rs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 957a1f56c71..1425687cd26 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -1,14 +1,10 @@ //! 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::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::{CfgInfo, Crate, Item, ItemKind}; use crate::core::DocContext; @@ -22,13 +18,10 @@ 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, cfg_info: CfgInfo::default() } - .fold_crate(cr) + CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr) } struct CfgPropagator<'a, 'tcx> { - parent_cfg: Option>, - parent: Option, cx: &'a mut DocContext<'tcx>, cfg_info: CfgInfo, } @@ -131,21 +124,11 @@ impl CfgPropagator<'_, '_> { impl DocFolder for CfgPropagator<'_, '_> { fn fold_item(&mut self, mut item: Item) -> Option { let old_cfg_info = self.cfg_info.clone(); - let old_parent_cfg = self.parent_cfg.clone(); self.merge_with_parent_attributes(&mut item); - 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()) { - self.parent.replace(def_id) - } else { - 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; Some(result) } -- cgit 1.4.1-3-g733a5 From 18df897221c57941956f77ef1f92261cf983577d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 7 Apr 2025 16:18:27 +0200 Subject: Remove ui test for doc_cfg feature gate --- tests/ui/feature-gates/feature-gate-doc_cfg.rs | 6 +++++- tests/ui/feature-gates/feature-gate-doc_cfg.stderr | 13 ------------- 2 files changed, 5 insertions(+), 14 deletions(-) delete mode 100644 tests/ui/feature-gates/feature-gate-doc_cfg.stderr diff --git a/tests/ui/feature-gates/feature-gate-doc_cfg.rs b/tests/ui/feature-gates/feature-gate-doc_cfg.rs index b12b8a10571..83053e8c8bf 100644 --- a/tests/ui/feature-gates/feature-gate-doc_cfg.rs +++ b/tests/ui/feature-gates/feature-gate-doc_cfg.rs @@ -1,2 +1,6 @@ -#[doc(cfg(unix))] //~ ERROR: `#[doc(cfg)]` is experimental +//@ build-pass + +// FIXME: Remove this test once `doc_cfg` feature is completely removed. + +#[doc(cfg(unix))] fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-doc_cfg.stderr b/tests/ui/feature-gates/feature-gate-doc_cfg.stderr deleted file mode 100644 index 5315aaeeb3e..00000000000 --- a/tests/ui/feature-gates/feature-gate-doc_cfg.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0658]: `#[doc(cfg)]` is experimental - --> $DIR/feature-gate-doc_cfg.rs:1:1 - | -LL | #[doc(cfg(unix))] - | ^^^^^^^^^^^^^^^^^ - | - = note: see issue #43781 for more information - = help: add `#![feature(doc_cfg)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0658`. -- cgit 1.4.1-3-g733a5 From d6ba93a3fee59a08820ee09ab7b3096ccfe99219 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 7 Apr 2025 17:07:21 +0200 Subject: Remove `doc_cfg` related content from rustdoc book unstable features chapter --- src/doc/rustdoc/src/unstable-features.md | 82 -------------------------------- 1 file changed, 82 deletions(-) diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 4327a80cd08..a8856c2ecc7 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -56,88 +56,6 @@ It is also not emitted for foreign items, aliases, extern crates and imports. These features operate by extending the `#[doc]` attribute, and thus can be caught by the compiler and enabled with a `#![feature(...)]` attribute in your crate. -### `#[doc(cfg)]`: Recording what platforms or features are required for code to be present - - * Tracking issue: [#43781](https://github.com/rust-lang/rust/issues/43781) - -You can use `#[doc(cfg(...))]` to tell Rustdoc exactly which platform items appear on. -This has two effects: - -1. doctests will only run on the appropriate platforms, and -2. When Rustdoc renders documentation for that item, it will be accompanied by a banner explaining - that the item is only available on certain platforms. - -`#[doc(cfg)]` is intended to be used alongside [`#[cfg(doc)]`][cfg-doc]. -For example, `#[cfg(any(windows, doc))]` will preserve the item either on Windows or during the -documentation process. Then, adding a new attribute `#[doc(cfg(windows))]` will tell Rustdoc that -the item is supposed to be used on Windows. For example: - -```rust -#![feature(doc_cfg)] - -/// Token struct that can only be used on Windows. -#[cfg(any(windows, doc))] -#[doc(cfg(windows))] -pub struct WindowsToken; - -/// Token struct that can only be used on Unix. -#[cfg(any(unix, doc))] -#[doc(cfg(unix))] -pub struct UnixToken; - -/// Token struct that is only available with the `serde` feature -#[cfg(feature = "serde")] -#[doc(cfg(feature = "serde"))] -#[derive(serde::Deserialize)] -pub struct SerdeToken; -``` - -In this sample, the tokens will only appear on their respective platforms, but they will both appear -in documentation. - -`#[doc(cfg(...))]` was introduced to be used by the standard library and currently requires the -`#![feature(doc_cfg)]` feature gate. For more information, see [its chapter in the Unstable -Book][unstable-doc-cfg] and [its tracking issue][issue-doc-cfg]. - -### `doc_auto_cfg`: Automatically generate `#[doc(cfg)]` - - * Tracking issue: [#43781](https://github.com/rust-lang/rust/issues/43781) - -`doc_auto_cfg` is an extension to the `#[doc(cfg)]` feature. With it, you don't need to add -`#[doc(cfg(...)]` anymore unless you want to override the default behaviour. So if we take the -previous source code: - -```rust -#![feature(doc_auto_cfg)] - -/// Token struct that can only be used on Windows. -#[cfg(any(windows, doc))] -pub struct WindowsToken; - -/// Token struct that can only be used on Unix. -#[cfg(any(unix, doc))] -pub struct UnixToken; - -/// Token struct that is only available with the `serde` feature -#[cfg(feature = "serde")] -#[derive(serde::Deserialize)] -pub struct SerdeToken; -``` - -It'll render almost the same, the difference being that `doc` will also be displayed. To fix this, -you can use `doc_cfg_hide`: - -```rust -#![feature(doc_cfg_hide)] -#![doc(cfg_hide(doc))] -``` - -And `doc` won't show up anymore! - -[cfg-doc]: ./advanced-features.md -[unstable-doc-cfg]: ../unstable-book/language-features/doc-cfg.html -[issue-doc-cfg]: https://github.com/rust-lang/rust/issues/43781 - ### Adding your trait to the "Notable traits" dialog * Tracking issue: [#45040](https://github.com/rust-lang/rust/issues/45040) -- cgit 1.4.1-3-g733a5 From bb34332858fa6c675cbbaf421ad3986df22d86ef Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 11 Apr 2025 11:53:49 +0200 Subject: Update book for `doc_cfg` feature --- src/doc/rustdoc/src/unstable-features.md | 268 +++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index a8856c2ecc7..cfa1936f3d7 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -719,3 +719,271 @@ pass `--doctest-build-arg ARG` for each argument `ARG`. ## `--generate-macro-expansion`: Generate macros expansion toggles in source code This flag enables the generation of toggles to expand macros in the HTML source code pages. + +## `#[doc(cfg)]` + +This feature aims at providing rustdoc users the possibility to add visual markers to the rendered documentation to know under which conditions an item is available (currently possible through the following unstable features: `doc_cfg`, `doc_auto_cfg` and `doc_cfg_hide`). + +It does not aim to allow having a same item with different `cfg`s to appear more than once in the generated documentation. + +It does not aim to document items which are *inactive* under the current configuration (i.e., “`cfg`ed out”). + +This features adds the following attributes: + + * `#[doc(auto_cfg)]`/`#[doc(auto_cfg = true)]`/`#[doc(auto_cfg = false)]` + * `#[doc(cfg(...))]` + * `#![doc(auto_cfg(hide(...)))]` / `#[doc(auto_cfg(show(...)))]` + +All of these attributes can be added to a module or to the crate root, and they will be inherited by the child items unless another attribute overrides it. This is why "opposite" attributes like `auto_cfg(hide(...))` and `auto_cfg(show(...))` are provided: they allow a child item to override its parent. + +### `#[doc(auto_cfg)`/`#[doc(auto_cfg = true)]`/`#[doc(auto_cfg = false)]` + +By default, `#[doc(auto_cfg)]` is enabled at the crate-level. When it's enabled, Rustdoc will automatically display `cfg(...)` compatibility information as-if the same `#[doc(cfg(...))]` had been specified. + +This attribute impacts the item on which it is used and its descendants. + +So if we take back the previous example: + +```rust +#[cfg(feature = "futures-io")] +pub mod futures {} +``` + +There's no need to "duplicate" the `cfg` into a `doc(cfg())` to make Rustdoc display it. + +In some situations, the detailed conditional compilation rules used to implement the feature might not serve as good documentation (for example, the list of supported platforms might be very long, and it might be better to document them in one place). To turn it off, add the `#[doc(auto_cfg = false)]` attribute on the item. + +If no argument is specified (ie `#[doc(auto_cfg)]`), it's the same as writing `#[doc(auto_cfg = true)]`. + +### `#[doc(cfg(...))]` + +This attribute provides a standardized format to override `#[cfg()]` attributes to document conditionally available items. Example: + +```rust,ignore (nightly) +// the "real" cfg condition +#[cfg(feature = "futures-io")] +// the `doc(cfg())` so it's displayed to the readers +#[doc(cfg(feature = "futures-io"))] +pub mod futures {} +``` + +It will display in the documentation for this module: + +```text +This is supported on feature="futures-io" only. +``` + +You can use it to display information in generated documentation, whether or not there is a `#[cfg()]` attribute: + +```rust,ignore (nightly) +#[doc(cfg(feature = "futures-io"))] +pub mod futures {} +``` + +It will be displayed exactly the same as the previous code. + +This attribute has the same syntax as conditional compilation, but it only causes documentation to be added. This means `#[doc(cfg(not(windows)))]` will not cause your docs to be hidden on non-windows targets, even though `#[cfg(not(windows))]` does do that. + +If `doc(auto_cfg)` is enabled on the item, `doc(cfg)` will override it anyway so in the two previous examples, even if the `doc(auto_cfg)` feature was enabled, it would still display the same thing. + +This attribute works on modules and on items. + +### `#[doc(auto_cfg(hide(...)))]` + +This attribute is used to prevent some `cfg` to be generated in the visual markers. It only applies to `#[doc(auto_cfg = true)]`, not to `#[doc(cfg(...))]`. So in the previous example: + +```rust,ignore (nightly) +#[cfg(any(unix, feature = "futures-io"))] +pub mod futures {} +``` + +It currently displays both `unix` and `feature = "futures-io"` into the documentation, which is not great. To prevent the `unix` cfg to ever be displayed, you can use this attribute at the crate root level: + +```rust,ignore (nightly) +#![doc(auto_cfg(hide(unix)))] +``` + +Or directly on a given item/module as it covers any of the item's descendants: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(unix)))] +#[cfg(any(unix, feature = "futures-io"))] +pub mod futures { + // `futures` and all its descendants won't display "unix" in their cfgs. +} +``` + +Then, the `unix` cfg will never be displayed into the documentation. + +Rustdoc currently hides `doc` and `doctest` attributes by default and reserves the right to change the list of "hidden by default" attributes. + +The attribute accepts only a list of identifiers or key/value items. So you can write: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(unix, doctest, feature = "something")))] +#[doc(auto_cfg(hide()))] +``` + +But you cannot write: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(not(unix))))] +``` + +So if we use `doc(auto_cfg(hide(unix)))`, it means it will hide all mentions of `unix`: + +```rust,ignore (nightly) +#[cfg(unix)] // nothing displayed +#[cfg(any(unix))] // nothing displayed +#[cfg(any(unix, windows))] // only `windows` displayed +``` + +However, it only impacts the `unix` cfg, not the feature: + +```rust,ignore (nightly) +#[cfg(feature = "unix")] // `feature = "unix"` is displayed +``` + +If `cfg_auto(show(...))` and `cfg_auto(hide(...))` are used to show/hide a same `cfg` on a same item, it'll emit an error. Example: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(unix)))] +#[doc(auto_cfg(show(unix)))] // Error! +pub fn foo() {} +``` + +Using this attribute will re-enable `auto_cfg` if it was disabled at this location: + +```rust,ignore (nightly) +#[doc(auto_cfg = false)] // Disabling `auto_cfg` +pub fn foo() {} +``` + +And using `doc(auto_cfg)` will re-enable it: + +```rust,ignore (nightly) +#[doc(auto_cfg = false)] // Disabling `auto_cfg` +pub mod module { + #[doc(auto_cfg(hide(unix)))] // `auto_cfg` is re-enabled. + pub fn foo() {} +} +``` + +However, using `doc(auto_cfg = ...)` and `doc(auto_cfg(...))` on the same item will emit an error: + +```rust,ignore (nightly) +#[doc(auto_cfg = false)] +#[doc(auto_cfg(hide(unix)))] // error +pub fn foo() {} +``` + +The reason behind this is that `doc(auto_cfg = ...)` enables or disables the feature, whereas `doc(auto_cfg(...))` enables it unconditionally, making the first attribute to appear useless as it will be overidden by the next `doc(auto_cfg)` attribute. + +### `#[doc(auto_cfg(show(...)))]` + +This attribute does the opposite of `#[doc(auto_cfg(hide(...)))]`: if you used `#[doc(auto_cfg(hide(...)))]` and want to revert its effect on an item and its descendants, you can use `#[doc(auto_cfg(show(...)))]`. +It only applies to `#[doc(auto_cfg = true)]`, not to `#[doc(cfg(...))]`. + +For example: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(unix)))] +#[cfg(any(unix, feature = "futures-io"))] +pub mod futures { + // `futures` and all its descendants won't display "unix" in their cfgs. + #[doc(auto_cfg(show(unix)))] + pub mod child { + // `child` and all its descendants will display "unix" in their cfgs. + } +} +``` + +The attribute accepts only a list of identifiers or key/value items. So you can write: + +```rust,ignore (nightly) +#[doc(auto_cfg(show(unix, doctest, feature = "something")))] +#[doc(auto_cfg(show()))] +``` + +But you cannot write: + +```rust,ignore (nightly) +#[doc(auto_cfg(show(not(unix))))] +``` + +If `auto_cfg(show(...))` and `auto_cfg(hide(...))` are used to show/hide a same `cfg` on a same item, it'll emit an error. Example: + +```rust,ignore (nightly) +#[doc(auto_cfg(show(unix)))] +#[doc(auto_cfg(hide(unix)))] // Error! +pub fn foo() {} +``` + +Using this attribute will re-enable `auto_cfg` if it was disabled at this location: + +```rust,ignore (nightly) +#[doc(auto_cfg = false)] // Disabling `auto_cfg` +#[doc(auto_cfg(show(unix)))] // `auto_cfg` is re-enabled. +pub fn foo() {} +``` + +## Inheritance + +Rustdoc merges `cfg` attributes from parent modules to its children. For example, in this case, the module `non_unix` will describe the entire compatibility matrix for the module, and not just its directly attached information: + +```rust,ignore (nightly) +#[doc(cfg(any(windows, unix)))] +pub mod desktop { + #[doc(cfg(not(unix)))] + pub mod non_unix { + // ... + } +} +``` + +This code will display: + +```text +Available on (Windows or Unix) and non-Unix only. +``` + +### Re-exports and inlining + +`cfg` attributes of a re-export are never merged with the re-exported item(s) attributes except if the re-export has the `#[doc(inline)]` attribute. In this case, the `cfg` of the re-exported item will be merged with the re-export's. + +When talking about "attributes merge", we mean that if the re-export has `#[cfg(unix)]` and the re-exported item has `#[cfg(feature = "foo")]`, you will only see `cfg(unix)` on the re-export and only `cfg(feature = "foo")` on the re-exported item, unless the re-export has `#[doc(inline)]`, then you will only see the re-exported item with both `cfg(unix)` and `cfg(feature = "foo")`. + +Example: + +```rust,ignore (nightly) +#[doc(cfg(any(windows, unix)))] +pub mod desktop { + #[doc(cfg(not(unix)))] + pub mod non_unix { + // code + } +} + +#[doc(cfg(target_os = "freebsd"))] +pub use desktop::non_unix as non_unix_desktop; +#[doc(cfg(target_os = "macos"))] +#[doc(inline)] +pub use desktop::non_unix as inlined_non_unix_desktop; +``` + +In this example, `non_unix_desktop` will only display `cfg(target_os = "freeebsd")` and not display any `cfg` from `desktop::non_unix`. + +On the contrary, `inlined_non_unix_desktop` will have cfgs from both the re-export and the re-exported item. + +So that also means that if a crate re-exports a foreign item, unless it has `#[doc(inline)]`, the `cfg` and `doc(cfg)` attributes will not be visible: + +```rust,ignore (nightly) +// dep: +#[cfg(feature = "a")] +pub struct S; + +// crate using dep: + +// There will be no mention of `feature = "a"` in the documentation. +pub use dep::S as Y; +``` -- cgit 1.4.1-3-g733a5 From ed05315867b7dd17a456beaabc3faf36357f9447 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 11 Apr 2025 12:02:48 +0200 Subject: Rename `CfgInfo::doc_auto_cfg_active` into `auto_cfg_active` --- src/librustdoc/clean/types.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 63e8357801c..2ec97a57087 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -926,7 +926,7 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator>( pub(crate) struct CfgInfo { hidden_cfg: FxHashSet, current_cfg: Cfg, - doc_auto_cfg_active: bool, + auto_cfg_active: bool, parent_is_doc_cfg: bool, } @@ -941,7 +941,7 @@ impl Default for CfgInfo { .into_iter() .collect(), current_cfg: Cfg::True, - doc_auto_cfg_active: true, + auto_cfg_active: true, parent_is_doc_cfg: false, } } @@ -1058,7 +1058,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator match &attr.kind { MetaItemKind::Word => { if let Some(first_change) = changed_auto_active_status { - if !cfg_info.doc_auto_cfg_active { + if !cfg_info.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", @@ -1068,12 +1068,12 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator } else { changed_auto_active_status = Some(attr.span); } - cfg_info.doc_auto_cfg_active = true; + cfg_info.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 { + if cfg_info.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", @@ -1083,12 +1083,12 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator } else { changed_auto_active_status = Some(attr.span); } - cfg_info.doc_auto_cfg_active = value; + cfg_info.auto_cfg_active = value; } } MetaItemKind::List(sub_attrs) => { if let Some(first_change) = changed_auto_active_status { - if !cfg_info.doc_auto_cfg_active { + if !cfg_info.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", @@ -1099,7 +1099,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator changed_auto_active_status = Some(attr.span); } // Whatever happens next, the feature is enabled again. - cfg_info.doc_auto_cfg_active = true; + cfg_info.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) @@ -1157,7 +1157,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator // 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 { + if !cfg_info.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 { -- cgit 1.4.1-3-g733a5 From c06a076634e4feab47e133fe6325af9659bab082 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 11 Apr 2025 14:32:31 +0200 Subject: Put back the `doc_cfg` code behind a nightly feature --- compiler/rustc_ast_passes/src/feature_gate.rs | 1 + src/librustdoc/passes/propagate_doc_cfg.rs | 6 +++++- tests/rustdoc-ui/cfg-hide-show-conflict.rs | 1 + tests/rustdoc-ui/cfg-hide-show-conflict.stderr | 4 ++-- tests/rustdoc-ui/lints/doc_cfg_hide.rs | 1 + tests/rustdoc-ui/lints/doc_cfg_hide.stderr | 6 +++--- tests/rustdoc/doc-auto-cfg.rs | 2 +- tests/rustdoc/doc-cfg/doc-cfg-hide.rs | 2 +- tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs | 1 + tests/rustdoc/doc_auto_cfg.rs | 5 +---- tests/rustdoc/doc_auto_cfg_reexports.rs | 1 + tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs | 2 +- .../reexport/doc_auto_cfg-reexport-foreign-113982.rs | 2 +- .../reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs | 2 +- tests/ui/feature-gates/feature-gate-doc_cfg.rs | 6 +----- tests/ui/feature-gates/feature-gate-doc_cfg.stderr | 13 +++++++++++++ 16 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-doc_cfg.stderr diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index fe9cc8e61ed..35531378784 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -182,6 +182,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { gate_doc!( "experimental" { + cfg => doc_cfg masked => doc_masked notable_trait => doc_notable_trait } diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 1425687cd26..802f2fbe569 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -18,7 +18,11 @@ pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass { }; pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate { - CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr) + if cx.tcx.features().doc_cfg() { + CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr) + } else { + cr + } } struct CfgPropagator<'a, 'tcx> { diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.rs b/tests/rustdoc-ui/cfg-hide-show-conflict.rs index a8a50fe15c7..8e98b95c85b 100644 --- a/tests/rustdoc-ui/cfg-hide-show-conflict.rs +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.rs @@ -1,2 +1,3 @@ +#![feature(doc_cfg)] #![doc(auto_cfg(hide(target_os = "linux")))] #![doc(auto_cfg(show(windows, target_os = "linux")))] //~ ERROR diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr index d2d0564606a..22231e82cd7 100644 --- a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr @@ -1,11 +1,11 @@ error: same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item - --> $DIR/cfg-hide-show-conflict.rs:2:31 + --> $DIR/cfg-hide-show-conflict.rs:3:31 | LL | #![doc(auto_cfg(show(windows, target_os = "linux")))] | ^^^^^^^^^^^^^^^^^^^ | note: first change was here - --> $DIR/cfg-hide-show-conflict.rs:1:22 + --> $DIR/cfg-hide-show-conflict.rs:2:22 | LL | #![doc(auto_cfg(hide(target_os = "linux")))] | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.rs b/tests/rustdoc-ui/lints/doc_cfg_hide.rs index 4f2625d00ce..397b21393e5 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.rs +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.rs @@ -1,3 +1,4 @@ +#![feature(doc_cfg)] #![doc(auto_cfg(hide = "test"))] //~ ERROR #![doc(auto_cfg(hide))] //~ ERROR #![doc(auto_cfg(hide(not(windows))))] //~ ERROR diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr index 22501d63c3f..0e9db5a30d8 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr @@ -1,5 +1,5 @@ error: `#![doc(auto_cfg(hide(...)))]` only expects a list of items - --> $DIR/doc_cfg_hide.rs:1:8 + --> $DIR/doc_cfg_hide.rs:2:8 | LL | #![doc(auto_cfg(hide = "test"))] | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,13 +7,13 @@ LL | #![doc(auto_cfg(hide = "test"))] = note: `#[deny(invalid_doc_attributes)]` on by default error: `#![doc(auto_cfg(hide(...)))]` only expects a list of items - --> $DIR/doc_cfg_hide.rs:2:8 + --> $DIR/doc_cfg_hide.rs:3:8 | LL | #![doc(auto_cfg(hide))] | ^^^^^^^^^^^^^^ error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/values items - --> $DIR/doc_cfg_hide.rs:3:22 + --> $DIR/doc_cfg_hide.rs:4:22 | LL | #![doc(auto_cfg(hide(not(windows))))] | ^^^^^^^^^^^^ diff --git a/tests/rustdoc/doc-auto-cfg.rs b/tests/rustdoc/doc-auto-cfg.rs index b3fe8922fd7..e56cf18d08a 100644 --- a/tests/rustdoc/doc-auto-cfg.rs +++ b/tests/rustdoc/doc-auto-cfg.rs @@ -1,4 +1,4 @@ -#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] #![crate_name = "foo"] //@ has foo/fn.foo.html diff --git a/tests/rustdoc/doc-cfg/doc-cfg-hide.rs b/tests/rustdoc/doc-cfg/doc-cfg-hide.rs index cf906ede7e1..e919206d3a4 100644 --- a/tests/rustdoc/doc-cfg/doc-cfg-hide.rs +++ b/tests/rustdoc/doc-cfg/doc-cfg-hide.rs @@ -1,5 +1,5 @@ #![crate_name = "oud"] -#![feature(doc_auto_cfg, doc_cfg, doc_cfg_hide, no_core)] +#![feature(doc_cfg)] #![doc(auto_cfg(hide(feature = "solecism")))] diff --git a/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs b/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs index 27923893bdd..9ae8b8fca4f 100644 --- a/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs +++ b/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs @@ -1,4 +1,5 @@ //@ compile-flags:--cfg feature="worricow" +#![feature(doc_cfg)] #![crate_name = "xenogenous"] //@ has 'xenogenous/struct.Worricow.html' diff --git a/tests/rustdoc/doc_auto_cfg.rs b/tests/rustdoc/doc_auto_cfg.rs index cb7c0e3f595..19ef174c177 100644 --- a/tests/rustdoc/doc_auto_cfg.rs +++ b/tests/rustdoc/doc_auto_cfg.rs @@ -1,10 +1,7 @@ // Test covering RFC 3631 features. #![crate_name = "foo"] -#![feature(no_core)] -#![no_core] -#![no_std] - +#![feature(doc_cfg)] #![doc(auto_cfg(hide(feature = "hidden")))] //@ has 'foo/index.html' diff --git a/tests/rustdoc/doc_auto_cfg_reexports.rs b/tests/rustdoc/doc_auto_cfg_reexports.rs index f226c52e2eb..f6315e9d49d 100644 --- a/tests/rustdoc/doc_auto_cfg_reexports.rs +++ b/tests/rustdoc/doc_auto_cfg_reexports.rs @@ -1,6 +1,7 @@ // Checks that `cfg` are correctly applied on inlined reexports. #![crate_name = "foo"] +#![feature(doc_cfg)] // Check with `std` item. //@ has 'foo/index.html' '//*[@class="stab portability"]' 'Non-moustache' diff --git a/tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs b/tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs index f85d7b23637..f24ebcd52ac 100644 --- a/tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs +++ b/tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs @@ -1,6 +1,6 @@ // Regression test for . -#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] #![crate_type = "lib"] #![crate_name = "foo"] diff --git a/tests/rustdoc/reexport/doc_auto_cfg-reexport-foreign-113982.rs b/tests/rustdoc/reexport/doc_auto_cfg-reexport-foreign-113982.rs index 76b25127a9c..f8ec4afc031 100644 --- a/tests/rustdoc/reexport/doc_auto_cfg-reexport-foreign-113982.rs +++ b/tests/rustdoc/reexport/doc_auto_cfg-reexport-foreign-113982.rs @@ -1,7 +1,7 @@ //@ aux-build: issue-113982-doc_auto_cfg-reexport-foreign.rs // https://github.com/rust-lang/rust/issues/113982 -#![feature(no_core, doc_auto_cfg)] +#![feature(no_core, doc_cfg)] #![no_core] #![crate_name = "foo"] diff --git a/tests/rustdoc/reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs b/tests/rustdoc/reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs index d0a2165ec8a..0aed2b0c462 100644 --- a/tests/rustdoc/reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs +++ b/tests/rustdoc/reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs @@ -2,7 +2,7 @@ // the reexported item whereas glob reexports do with the `doc_auto_cfg` feature. #![crate_name = "foo"] -#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] //@ has 'foo/index.html' // There are two items. diff --git a/tests/ui/feature-gates/feature-gate-doc_cfg.rs b/tests/ui/feature-gates/feature-gate-doc_cfg.rs index 83053e8c8bf..213a5a8c5a9 100644 --- a/tests/ui/feature-gates/feature-gate-doc_cfg.rs +++ b/tests/ui/feature-gates/feature-gate-doc_cfg.rs @@ -1,6 +1,2 @@ -//@ build-pass - -// FIXME: Remove this test once `doc_cfg` feature is completely removed. - -#[doc(cfg(unix))] +#[doc(cfg(unix))] //~ ERROR fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-doc_cfg.stderr b/tests/ui/feature-gates/feature-gate-doc_cfg.stderr new file mode 100644 index 00000000000..5315aaeeb3e --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-doc_cfg.stderr @@ -0,0 +1,13 @@ +error[E0658]: `#[doc(cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:1:1 + | +LL | #[doc(cfg(unix))] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. -- cgit 1.4.1-3-g733a5 From 1561efe41afe40c1afaf3d11316ef754d9a8f9a9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 27 May 2025 17:14:46 +0200 Subject: Add code documentation, improve code and improve error message --- compiler/rustc_passes/messages.ftl | 2 +- compiler/rustc_passes/src/check_attr.rs | 9 +++------ src/librustdoc/clean/types.rs | 13 +++++++++++++ src/librustdoc/passes/propagate_doc_cfg.rs | 14 +++++++++----- tests/rustdoc-ui/lints/doc_cfg_hide.stderr | 4 ++-- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 8326ddaf194..a9a0f6d8b51 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -147,7 +147,7 @@ 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 + `#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items passes_doc_auto_cfg_hide_show_unexpected_item = `#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/values items diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 38b67e2f9dc..c521467b556 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -10,7 +10,7 @@ use std::collections::hash_map::Entry; use std::slice; use rustc_abi::{Align, ExternAbi, Size}; -use rustc_ast::{AttrStyle, LitKind, MetaItemInner, MetaItemKind, ast}; +use rustc_ast::{AttrStyle, LitKind, MetaItem, MetaItemInner, MetaItemKind, ast}; use rustc_attr_parsing::{AttributeParser, Late}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey}; @@ -1161,10 +1161,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } /// 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!(); - }; + fn check_doc_auto_cfg(&self, meta: &MetaItem, hir_id: HirId) { match &meta.kind { MetaItemKind::Word => {} MetaItemKind::NameValue(lit) => { @@ -1286,7 +1283,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } Some(sym::auto_cfg) => { - self.check_doc_auto_cfg(meta, hir_id); + self.check_doc_auto_cfg(i_meta, hir_id); } Some(sym::inline | sym::no_inline) => { diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 2ec97a57087..545f1ef6da4 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -922,11 +922,19 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator>( .flatten() } +/// This type keeps track of (doc) cfg information as we go down the item tree. #[derive(Clone, Debug)] pub(crate) struct CfgInfo { + /// List of `doc(auto_cfg(hide(...)))` cfgs. hidden_cfg: FxHashSet, + /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while + /// taking into account the `hidden_cfg` information. current_cfg: Cfg, + /// Whether the `doc(auto_cfg())` feature is enabled or not at this point. auto_cfg_active: bool, + /// If the parent item used `doc(cfg(...))`, then we don't want to overwrite `current_cfg`, + /// instead we will concatenate with it. However, if it's not the case, we need to overwrite + /// `current_cfg`. parent_is_doc_cfg: bool, } @@ -962,6 +970,11 @@ fn show_hide_show_conflict_error( diag.emit(); } +/// This function checks if a same `cfg` is present in both `auto_cfg(hide(...))` and +/// `auto_cfg(show(...))` on the same item. If so, it emits an error. +/// +/// Because we go through a list of `cfg`s, we keep track of the `cfg`s we saw in `new_show_attrs` +/// and in `new_hide_attrs` arguments. fn handle_auto_cfg_hide_show( tcx: TyCtxt<'_>, cfg_info: &mut CfgInfo, diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 802f2fbe569..ea77065ac07 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -30,7 +30,8 @@ struct CfgPropagator<'a, 'tcx> { cfg_info: CfgInfo, } -fn should_retain(token: &TokenTree) -> bool { +/// 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, @@ -47,7 +48,9 @@ fn should_retain(token: &TokenTree) -> bool { ) } -fn filter_tokens_from_list(args_tokens: &TokenStream) -> Vec { +/// 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() { @@ -58,7 +61,7 @@ fn filter_tokens_from_list(args_tokens: &TokenStream) -> Vec { } skip_next_delimited = false; } - token if should_retain(token) => { + token if is_cfg_token(token) => { skip_next_delimited = false; tokens.push(token.clone()); } @@ -70,7 +73,8 @@ fn filter_tokens_from_list(args_tokens: &TokenStream) -> Vec { tokens } -// We only care about `#[cfg()]` and `#[doc(cfg())]`, we discard everything else. +/// 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() { @@ -84,7 +88,7 @@ fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) if ident == sym::doc && let AttrArgs::Delimited(args) = &mut normal.args { - let tokens = filter_tokens_from_list(&args.tokens); + let tokens = filter_non_cfg_tokens_from_list(&args.tokens); args.tokens = TokenStream::new(tokens); attrs.push(attr); } else if ident == sym::cfg_trace { diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr index 0e9db5a30d8..9e820a77b5e 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr @@ -1,4 +1,4 @@ -error: `#![doc(auto_cfg(hide(...)))]` only expects a list of items +error: `#![doc(auto_cfg(hide(...)))]` expects a list of items --> $DIR/doc_cfg_hide.rs:2:8 | LL | #![doc(auto_cfg(hide = "test"))] @@ -6,7 +6,7 @@ LL | #![doc(auto_cfg(hide = "test"))] | = note: `#[deny(invalid_doc_attributes)]` on by default -error: `#![doc(auto_cfg(hide(...)))]` only expects a list of items +error: `#![doc(auto_cfg(hide(...)))]` expects a list of items --> $DIR/doc_cfg_hide.rs:3:8 | LL | #![doc(auto_cfg(hide))] -- cgit 1.4.1-3-g733a5 From 553308b11503eafac6341b82f345bd62b09ba317 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 29 May 2025 18:39:08 +0200 Subject: Improve code and better check `doc(cfg(...))` attributes --- compiler/rustc_ast_passes/src/feature_gate.rs | 1 + compiler/rustc_passes/messages.ftl | 2 +- compiler/rustc_passes/src/check_attr.rs | 23 +++++- compiler/rustc_span/src/symbol.rs | 1 - src/librustdoc/clean/inline.rs | 4 + src/librustdoc/clean/types.rs | 89 ++++++++++++++--------- tests/rustdoc-ui/doc-cfg.rs | 9 +++ tests/rustdoc-ui/doc-cfg.stderr | 66 ++++++++++++----- tests/rustdoc-ui/feature-gate-doc_cfg.rs | 6 ++ tests/rustdoc-ui/feature-gate-doc_cfg.stderr | 63 ++++++++++++++++ tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs | 8 -- tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr | 10 --- tests/rustdoc-ui/lints/doc_cfg_hide.stderr | 2 +- 13 files changed, 209 insertions(+), 75 deletions(-) create mode 100644 tests/rustdoc-ui/feature-gate-doc_cfg.rs create mode 100644 tests/rustdoc-ui/feature-gate-doc_cfg.stderr delete mode 100644 tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs delete mode 100644 tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 35531378784..608ccfefeb6 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -183,6 +183,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { gate_doc!( "experimental" { cfg => doc_cfg + auto_cfg => doc_cfg masked => doc_masked notable_trait => doc_notable_trait } diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index a9a0f6d8b51..a5ff169e638 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -150,7 +150,7 @@ passes_doc_auto_cfg_hide_show_expects_list = `#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items passes_doc_auto_cfg_hide_show_unexpected_item = - `#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/values items + `#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items passes_doc_auto_cfg_wrong_literal = `expected boolean for #[doc(auto_cfg = ...)]` diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index c521467b556..88b49c781e7 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1176,7 +1176,15 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } MetaItemKind::List(list) => { for item in list { - let Some(attr_name) = item.name() else { continue }; + let Some(attr_name) = item.name() else { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span, + errors::DocAutoCfgExpectsHideOrShow, + ); + continue; + }; if attr_name != sym::hide && attr_name != sym::show { self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, @@ -1195,6 +1203,19 @@ impl<'tcx> CheckAttrVisitor<'tcx> { attr_name: attr_name.as_str(), }, ); + } else if match item { + MetaItemInner::Lit(_) => true, + // We already checked above that it's not a list. + MetaItemInner::MetaItem(meta) => meta.path.segments.len() != 1, + } { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + item.span(), + errors::DocAutoCfgHideShowUnexpectedItem { + attr_name: attr_name.as_str(), + }, + ); } } } else { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e80a98994db..18c3faed932 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1150,7 +1150,6 @@ symbols! { hashset_iter_ty, hexagon_target_feature, hidden, - hidden_cfg, hide, hint, homogeneous_aggregate, diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 8ffa6033c9b..8beea0580de 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -605,6 +605,10 @@ pub(crate) fn build_impl( }); } + // In here, we pass an empty `CfgInfo` because the computation of `cfg` happens later, so it + // doesn't matter at this point. + // + // We need to pass this empty `CfgInfo` because `merge_attrs` is used when computing the `cfg`. let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs, &mut CfgInfo::default()); trace!("merged_attrs={merged_attrs:?}"); diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 545f1ef6da4..605b77109a0 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -970,8 +970,10 @@ fn show_hide_show_conflict_error( diag.emit(); } -/// This function checks if a same `cfg` is present in both `auto_cfg(hide(...))` and -/// `auto_cfg(show(...))` on the same item. If so, it emits an error. +/// This functions updates the `hidden_cfg` field of the provided `cfg_info` argument. +/// +/// It also checks if a same `cfg` is present in both `auto_cfg(hide(...))` and +/// `auto_cfg(show(...))` on the same item and emits an error if it's the case. /// /// Because we go through a list of `cfg`s, we keep track of the `cfg`s we saw in `new_show_attrs` /// and in `new_hide_attrs` arguments. @@ -1023,6 +1025,31 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator Some(item) } + fn check_changed_auto_active_status( + changed_auto_active_status: &mut Option, + attr: &ast::MetaItem, + cfg_info: &mut CfgInfo, + tcx: TyCtxt<'_>, + new_value: bool, + ) -> bool { + if let Some(first_change) = changed_auto_active_status { + if cfg_info.auto_cfg_active != new_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 true; + } + } else { + *changed_auto_active_status = Some(attr.span); + } + cfg_info.auto_cfg_active = new_value; + false + } + let mut new_show_attrs = FxHashMap::default(); let mut new_hide_attrs = FxHashMap::default(); @@ -1070,49 +1097,39 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator }; match &attr.kind { MetaItemKind::Word => { - if let Some(first_change) = changed_auto_active_status { - if !cfg_info.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); + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + true, + ) { + return None; } - cfg_info.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.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); + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + value, + ) { + return None; } - cfg_info.auto_cfg_active = value; } } MetaItemKind::List(sub_attrs) => { - if let Some(first_change) = changed_auto_active_status { - if !cfg_info.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); + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + true, + ) { + return None; } - // Whatever happens next, the feature is enabled again. - cfg_info.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) diff --git a/tests/rustdoc-ui/doc-cfg.rs b/tests/rustdoc-ui/doc-cfg.rs index 14943bbc341..9840c305290 100644 --- a/tests/rustdoc-ui/doc-cfg.rs +++ b/tests/rustdoc-ui/doc-cfg.rs @@ -8,4 +8,13 @@ //~^^ WARN unexpected `cfg` condition name: `bar` #[doc(cfg())] //~ ERROR #[doc(cfg(foo, bar))] //~ ERROR +#[doc(auto_cfg(42))] //~ ERROR +#[doc(auto_cfg(hide(true)))] //~ ERROR +#[doc(auto_cfg(hide(42)))] //~ ERROR +#[doc(auto_cfg(hide("a")))] //~ ERROR +#[doc(auto_cfg(hide(foo::bar)))] //~ ERROR +// Shouldn't lint +#[doc(auto_cfg(hide(windows)))] +#[doc(auto_cfg(hide(feature = "windows")))] +#[doc(auto_cfg(hide(foo)))] pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index 1233ee010de..36ca18eed8f 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -1,26 +1,34 @@ -error: `cfg` predicate is not specified - --> $DIR/doc-cfg.rs:3:7 +error: `only "hide" or "show" are allowed in "#[doc(auto_cfg(...))]"` + --> $DIR/doc-cfg.rs:11:7 | -LL | #[doc(cfg(), cfg(foo, bar))] - | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` +LL | #[doc(auto_cfg(42))] + | ^^^^^^^^^^^^ + | + = note: `#[deny(invalid_doc_attributes)]` on by default -error: multiple `cfg` predicates are specified - --> $DIR/doc-cfg.rs:3:23 +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg.rs:12:21 | -LL | #[doc(cfg(), cfg(foo, bar))] - | ^^^ +LL | #[doc(auto_cfg(hide(true)))] + | ^^^^ -error: `cfg` predicate is not specified - --> $DIR/doc-cfg.rs:9:7 +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg.rs:13:21 | -LL | #[doc(cfg())] - | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` +LL | #[doc(auto_cfg(hide(42)))] + | ^^ -error: multiple `cfg` predicates are specified - --> $DIR/doc-cfg.rs:10:16 +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg.rs:14:21 | -LL | #[doc(cfg(foo, bar))] - | ^^^ +LL | #[doc(auto_cfg(hide("a")))] + | ^^^ + +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg.rs:15:21 + | +LL | #[doc(auto_cfg(hide(foo::bar)))] + | ^^^^^^^^ warning: unexpected `cfg` condition name: `foo` --> $DIR/doc-cfg.rs:6:11 @@ -42,5 +50,29 @@ LL | #[doc(cfg(foo), cfg(bar))] = help: to expect this configuration use `--check-cfg=cfg(bar)` = note: see for more information about checking conditional configuration -error: aborting due to 4 previous errors; 2 warnings emitted +error: `cfg` predicate is not specified + --> $DIR/doc-cfg.rs:3:7 + | +LL | #[doc(cfg(), cfg(foo, bar))] + | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` + +error: multiple `cfg` predicates are specified + --> $DIR/doc-cfg.rs:3:23 + | +LL | #[doc(cfg(), cfg(foo, bar))] + | ^^^ + +error: `cfg` predicate is not specified + --> $DIR/doc-cfg.rs:9:7 + | +LL | #[doc(cfg())] + | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` + +error: multiple `cfg` predicates are specified + --> $DIR/doc-cfg.rs:10:16 + | +LL | #[doc(cfg(foo, bar))] + | ^^^ + +error: aborting due to 9 previous errors; 2 warnings emitted diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg.rs b/tests/rustdoc-ui/feature-gate-doc_cfg.rs new file mode 100644 index 00000000000..b474a1524bc --- /dev/null +++ b/tests/rustdoc-ui/feature-gate-doc_cfg.rs @@ -0,0 +1,6 @@ +#![doc(auto_cfg)] //~ ERROR +#![doc(auto_cfg(false))] //~ ERROR +#![doc(auto_cfg(true))] //~ ERROR +#![doc(auto_cfg(hide(feature = "solecism")))] //~ ERROR +#![doc(auto_cfg(show(feature = "bla")))] //~ ERROR +#![doc(cfg(feature = "solecism"))] //~ ERROR diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg.stderr b/tests/rustdoc-ui/feature-gate-doc_cfg.stderr new file mode 100644 index 00000000000..68a86c1abb7 --- /dev/null +++ b/tests/rustdoc-ui/feature-gate-doc_cfg.stderr @@ -0,0 +1,63 @@ +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:1:1 + | +LL | #![doc(auto_cfg)] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:2:1 + | +LL | #![doc(auto_cfg(false))] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:3:1 + | +LL | #![doc(auto_cfg(true))] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:4:1 + | +LL | #![doc(auto_cfg(hide(feature = "solecism")))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:5:1 + | +LL | #![doc(auto_cfg(show(feature = "bla")))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:6:1 + | +LL | #![doc(cfg(feature = "solecism"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs b/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs deleted file mode 100644 index e49285a01b8..00000000000 --- a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs +++ /dev/null @@ -1,8 +0,0 @@ -// FIXME: Remove this file once feature is removed - -#![doc(cfg_hide(test))] //~ ERROR - -#[cfg(not(test))] -pub fn public_fn() {} -#[cfg(test)] -pub fn internal_use_only() {} diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr b/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr deleted file mode 100644 index b3eee2af7f7..00000000000 --- a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: unknown `doc` attribute `cfg_hide` - --> $DIR/feature-gate-doc_cfg_hide.rs:3:8 - | -LL | #![doc(cfg_hide(test))] - | ^^^^^^^^^^^^^^ - | - = note: `#[deny(invalid_doc_attributes)]` on by default - -error: aborting due to 1 previous error - diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr index 9e820a77b5e..c63c8d607fa 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr @@ -12,7 +12,7 @@ error: `#![doc(auto_cfg(hide(...)))]` expects a list of items LL | #![doc(auto_cfg(hide))] | ^^^^^^^^^^^^^^ -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/values items +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items --> $DIR/doc_cfg_hide.rs:4:22 | LL | #![doc(auto_cfg(hide(not(windows))))] -- cgit 1.4.1-3-g733a5 From fccba2c341b65b62f513c0b7ee8842f134f3cbcd Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 29 May 2025 21:54:16 +0200 Subject: Remove `doc_cfg_hide` feature --- compiler/rustc_feature/src/unstable.rs | 2 -- compiler/rustc_span/src/symbol.rs | 1 - library/alloc/src/lib.rs | 2 +- library/core/src/lib.rs | 2 +- library/std/src/lib.rs | 3 ++- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 6ef0df72365..4dc961cb927 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -476,8 +476,6 @@ declare_features! ( (unstable, doc_auto_cfg, "1.58.0", Some(43781)), /// Allows `#[doc(cfg(...))]`. (unstable, doc_cfg, "1.21.0", Some(43781)), - /// Allows `#[doc(cfg_hide(...))]`. - (unstable, doc_cfg_hide, "1.57.0", Some(43781)), /// Allows `#[doc(masked)]`. (unstable, doc_masked, "1.21.0", Some(44027)), /// Allows features to allow target_feature to better interact with traits. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 18c3faed932..79f00e2e94d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -878,7 +878,6 @@ symbols! { doc_alias, doc_auto_cfg, doc_cfg, - doc_cfg_hide, doc_keyword, doc_masked, doc_notable_trait, diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index dc5d243e882..24d7e6216a0 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -207,7 +207,7 @@ // // Rustdoc features: #![feature(doc_cfg)] -#![feature(doc_cfg_hide)] +#![cfg_attr(bootstrap, feature(doc_cfg_hide))] // Technically, this is a bug in rustdoc: rustdoc sees the documentation on `#[lang = slice_alloc]` // blocks is for `&[T]`, which also has documentation using this feature in `core`, and gets mad // that the feature-gate isn't enabled. Ideally, it wouldn't check for the feature gate for docs diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 32a3d6c7042..b1e559065fe 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -161,6 +161,7 @@ // // Language features: // tidy-alphabetical-start +#![cfg_attr(bootstrap, feature(doc_cfg_hide))] #![feature(abi_unadjusted)] #![feature(adt_const_params)] #![feature(allow_internal_unsafe)] @@ -176,7 +177,6 @@ #![feature(deprecated_suggestion)] #![feature(derive_const)] #![feature(doc_cfg)] -#![feature(doc_cfg_hide)] #![feature(doc_notable_trait)] #![feature(extern_types)] #![feature(f16)] diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ecd354f599e..ff997ff7603 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -278,6 +278,8 @@ // tidy-alphabetical-start // stabilization was reverted after it hit beta +#![cfg_attr(bootstrap, feature(doc_cfg_hide))] +#![cfg_attr(not(bootstrap), feature(autodiff))] #![feature(alloc_error_handler)] #![feature(allocator_internals)] #![feature(allow_internal_unsafe)] @@ -293,7 +295,6 @@ #![feature(decl_macro)] #![feature(deprecated_suggestion)] #![feature(doc_cfg)] -#![feature(doc_cfg_hide)] #![feature(doc_masked)] #![feature(doc_notable_trait)] #![feature(dropck_eyepatch)] -- cgit 1.4.1-3-g733a5 From a7ed9bf6c7473827b7c876679a2a633facec09b8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 2 Jun 2025 22:19:33 +0200 Subject: fmt --- library/alloc/src/lib.rs | 7 +------ library/std/src/lib.rs | 6 +----- src/librustdoc/doctest/rust.rs | 8 +++----- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 24d7e6216a0..7499db8bf2c 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -77,12 +77,7 @@ )] #![cfg_attr( not(bootstrap), - doc(auto_cfg(hide( - no_global_oom_handling, - no_rc, - no_sync, - target_has_atomic = "ptr" - ))) + doc(auto_cfg(hide(no_global_oom_handling, no_rc, no_sync, target_has_atomic = "ptr"))) )] #![doc(rust_logo)] #![feature(rustdoc_internals)] diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ff997ff7603..c8fe5c55e97 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -237,11 +237,7 @@ #![doc(rust_logo)] #![cfg_attr( bootstrap, - doc(cfg_hide( - not(test), - no_global_oom_handling, - not(no_global_oom_handling) - )) + doc(cfg_hide(not(test), no_global_oom_handling, not(no_global_oom_handling))) )] #![cfg_attr(not(bootstrap), doc(auto_cfg(hide(no_global_oom_handling))))] // Don't link to std. We are std. diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index b9367b88005..4d3f976c2a6 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -119,11 +119,9 @@ 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, - &mut CfgInfo::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; } -- cgit 1.4.1-3-g733a5 From ec00723ba153c72124f8271b6dc15540f23e6832 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 4 Jun 2025 13:34:41 +0200 Subject: Fix autodiff feature activation --- library/std/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index c8fe5c55e97..501c22cfb49 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -275,7 +275,6 @@ // stabilization was reverted after it hit beta #![cfg_attr(bootstrap, feature(doc_cfg_hide))] -#![cfg_attr(not(bootstrap), feature(autodiff))] #![feature(alloc_error_handler)] #![feature(allocator_internals)] #![feature(allow_internal_unsafe)] -- cgit 1.4.1-3-g733a5 From 254a2139f6c4b251d9210843d2e7d7952ef9f40d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 10 Jun 2025 17:44:14 +0200 Subject: Remove `cfg(bootstrap)` for `doc_cfg` feature following #141925 --- library/alloc/src/lib.rs | 17 +----------- library/core/src/lib.rs | 70 +++++++++++++++--------------------------------- library/std/src/lib.rs | 7 +---- 3 files changed, 23 insertions(+), 71 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 7499db8bf2c..fc3266b7479 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -64,21 +64,7 @@ issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/", test(no_crate_inject, attr(allow(unused_variables), deny(warnings))) )] -#![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(no_global_oom_handling, no_rc, no_sync, target_has_atomic = "ptr"))) -)] +#![doc(auto_cfg(hide(no_global_oom_handling, no_rc, no_sync, target_has_atomic = "ptr")))] #![doc(rust_logo)] #![feature(rustdoc_internals)] #![no_std] @@ -202,7 +188,6 @@ // // Rustdoc features: #![feature(doc_cfg)] -#![cfg_attr(bootstrap, feature(doc_cfg_hide))] // Technically, this is a bug in rustdoc: rustdoc sees the documentation on `#[lang = slice_alloc]` // blocks is for `&[T]`, which also has documentation using this feature in `core`, and gets mad // that the feature-gate isn't enabled. Ideally, it wouldn't check for the feature gate for docs diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index b1e559065fe..54adf97f100 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -51,54 +51,27 @@ test(attr(allow(dead_code, deprecated, unused_variables, unused_mut))) )] #![doc(rust_logo)] -#![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", - ))) -)] +#![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] @@ -161,7 +134,6 @@ // // Language features: // tidy-alphabetical-start -#![cfg_attr(bootstrap, feature(doc_cfg_hide))] #![feature(abi_unadjusted)] #![feature(adt_const_params)] #![feature(allow_internal_unsafe)] diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 501c22cfb49..61b7d170b8b 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -235,11 +235,7 @@ test(attr(allow(dead_code, deprecated, unused_variables, unused_mut))) )] #![doc(rust_logo)] -#![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(no_global_oom_handling))))] +#![doc(auto_cfg(hide(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 @@ -274,7 +270,6 @@ // tidy-alphabetical-start // stabilization was reverted after it hit beta -#![cfg_attr(bootstrap, feature(doc_cfg_hide))] #![feature(alloc_error_handler)] #![feature(allocator_internals)] #![feature(allow_internal_unsafe)] -- cgit 1.4.1-3-g733a5 From 6fecff45d915b31eee3170f32fedb6db045611f1 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 1 Jul 2025 17:00:21 +0200 Subject: Fix `tests/rustdoc/target-feature.rs` test by adding missing `#![feature(doc_cfg)]` --- tests/rustdoc/target-feature.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/rustdoc/target-feature.rs b/tests/rustdoc/target-feature.rs index 59a08a0ca94..f2686f81fbf 100644 --- a/tests/rustdoc/target-feature.rs +++ b/tests/rustdoc/target-feature.rs @@ -1,3 +1,5 @@ +#![feature(doc_cfg)] + #![crate_name = "foo"] //@ has 'foo/index.html' -- cgit 1.4.1-3-g733a5 From ef8b2a26caccba46584e4753c3091218d4a2e17d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 3 Jul 2025 20:05:02 +0200 Subject: Correctly handle target_feature in rustdoc cfg --- src/librustdoc/clean/types.rs | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 605b77109a0..c8e9f3dc479 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1153,35 +1153,20 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator // 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) - { - cfg_info.current_cfg &= new_cfg; - } - } + if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr { // 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; - } - } - } + for (feature, _) in features { + cfg_info.current_cfg &= Cfg::Cfg(sym::target_feature, Some(*feature)); } - _ => {} + continue; + } else if !cfg_info.parent_is_doc_cfg + && let Some(ident) = attr.ident() + && matches!(ident.name, sym::cfg | sym::cfg_trace) + && let Some(attr) = single(attr.meta_item_list()?) + && let Ok(new_cfg) = Cfg::parse(&attr) + { + cfg_info.current_cfg &= new_cfg; } } -- cgit 1.4.1-3-g733a5 From 77885fef2cd9a7f9daa4f95336f0cfdeb62b3016 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 26 Aug 2025 16:03:12 +0200 Subject: Improve code comments and extend tests for `doc_cfg` feature --- src/librustdoc/clean/types.rs | 6 ++++-- tests/rustdoc-ui/doc-cfg.rs | 2 ++ tests/rustdoc-ui/doc-cfg.stderr | 14 +++++++++++++- tests/rustdoc/doc_auto_cfg_reexports.rs | 13 +++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index c8e9f3dc479..8b8cf19d8e4 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -925,7 +925,8 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator>( /// This type keeps track of (doc) cfg information as we go down the item tree. #[derive(Clone, Debug)] pub(crate) struct CfgInfo { - /// List of `doc(auto_cfg(hide(...)))` cfgs. + /// List of currently active `doc(auto_cfg(hide(...)))` cfgs,minus currently active + /// `doc(auto_cfg(show(...)))` cfgs. hidden_cfg: FxHashSet, /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while /// taking into account the `hidden_cfg` information. @@ -1181,7 +1182,8 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator Some(Arc::new(cfg_info.current_cfg.clone())) } } else { - // Since we always want to collect all `cfg` items, we remove the hidden ones afterward. + // If `doc(auto_cfg)` feature is enabled, we 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)), diff --git a/tests/rustdoc-ui/doc-cfg.rs b/tests/rustdoc-ui/doc-cfg.rs index 9840c305290..d72643e2355 100644 --- a/tests/rustdoc-ui/doc-cfg.rs +++ b/tests/rustdoc-ui/doc-cfg.rs @@ -13,6 +13,8 @@ #[doc(auto_cfg(hide(42)))] //~ ERROR #[doc(auto_cfg(hide("a")))] //~ ERROR #[doc(auto_cfg(hide(foo::bar)))] //~ ERROR +#[doc(auto_cfg = 42)] //~ ERROR +#[doc(auto_cfg = "a")] //~ ERROR // Shouldn't lint #[doc(auto_cfg(hide(windows)))] #[doc(auto_cfg(hide(feature = "windows")))] diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index 36ca18eed8f..13f62e50d90 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -30,6 +30,18 @@ error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value item LL | #[doc(auto_cfg(hide(foo::bar)))] | ^^^^^^^^ +error: `expected boolean for #[doc(auto_cfg = ...)]` + --> $DIR/doc-cfg.rs:16:7 + | +LL | #[doc(auto_cfg = 42)] + | ^^^^^^^^^^^^^ + +error: `expected boolean for #[doc(auto_cfg = ...)]` + --> $DIR/doc-cfg.rs:17:7 + | +LL | #[doc(auto_cfg = "a")] + | ^^^^^^^^^^^^^^ + warning: unexpected `cfg` condition name: `foo` --> $DIR/doc-cfg.rs:6:11 | @@ -74,5 +86,5 @@ error: multiple `cfg` predicates are specified LL | #[doc(cfg(foo, bar))] | ^^^ -error: aborting due to 9 previous errors; 2 warnings emitted +error: aborting due to 11 previous errors; 2 warnings emitted diff --git a/tests/rustdoc/doc_auto_cfg_reexports.rs b/tests/rustdoc/doc_auto_cfg_reexports.rs index f6315e9d49d..ecfe9aabcfe 100644 --- a/tests/rustdoc/doc_auto_cfg_reexports.rs +++ b/tests/rustdoc/doc_auto_cfg_reexports.rs @@ -20,3 +20,16 @@ mod x { // 'Available on non-crate feature pistache only.' #[cfg(not(feature = "pistache"))] pub use crate::x::B; + +// Now checking that `cfg`s are not applied on non-inlined reexports. +pub mod pub_sub_mod { + //@ has 'foo/pub_sub_mod/index.html' + // There should be only only item with `cfg` note. + //@ count - '//*[@class="stab portability"]' 1 + // And obviously the item should be "blabla". + //@ has - '//dt' 'blablaNon-pistache' + #[cfg(not(feature = "pistache"))] + pub fn blabla() {} + + pub use self::blabla as another; +} -- cgit 1.4.1-3-g733a5 From 653e1036ed5a343a7be7b2a73096c138efc51523 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 22 Sep 2025 13:58:02 +0200 Subject: Apply first review round suggestions --- compiler/rustc_feature/src/removed.rs | 2 ++ compiler/rustc_passes/messages.ftl | 4 ++-- compiler/rustc_span/src/symbol.rs | 1 + src/librustdoc/passes/propagate_doc_cfg.rs | 14 +------------- src/librustdoc/visit_ast.rs | 2 +- tests/rustdoc-ui/doc-cfg.stderr | 6 +++--- 6 files changed, 10 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 32115535e99..71365da316c 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -101,6 +101,8 @@ declare_features! ( Some("never properly implemented; requires significant design work"), 127655), /// Allows deriving traits as per `SmartPointer` specification (removed, derive_smart_pointer, "1.84.0", Some(123430), Some("replaced by `CoercePointee`"), 131284), + /// Allows `#[doc(cfg_hide(...))]`. + (removed, doc_cfg_hide, "1.57.0", Some(43781), Some("merged into `doc_cfg`"), 138907), /// Allows using `#[doc(keyword = "...")]`. (removed, doc_keyword, "1.58.0", Some(51315), Some("merged into `#![feature(rustdoc_internals)]`"), 90420), diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index a5ff169e638..21fc1124c2f 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -144,7 +144,7 @@ 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(...))]"` + only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]` passes_doc_auto_cfg_hide_show_expects_list = `#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items @@ -153,7 +153,7 @@ passes_doc_auto_cfg_hide_show_unexpected_item = `#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items passes_doc_auto_cfg_wrong_literal = - `expected boolean for #[doc(auto_cfg = ...)]` + 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_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 79f00e2e94d..18c3faed932 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -878,6 +878,7 @@ symbols! { doc_alias, doc_auto_cfg, doc_cfg, + doc_cfg_hide, doc_keyword, doc_masked, doc_notable_trait, diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index ea77065ac07..4bcb60a9a40 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -33,19 +33,7 @@ struct CfgPropagator<'a, 'tcx> { /// 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( - ident, - _, - ), - .. - }, - _, - ) if *ident == sym::cfg, - ) + 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 diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index ac67871b0cb..dc9889cec21 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -9,7 +9,7 @@ use rustc_hir::attrs::AttributeKind; use rustc_hir::def::{DefKind, MacroKinds, Res}; use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet}; use rustc_hir::intravisit::{Visitor, walk_body, walk_item}; -use rustc_hir::{CRATE_HIR_ID, Node, find_attr}; +use rustc_hir::{Node, find_attr}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::Span; diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index 13f62e50d90..49e8c324fac 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -1,4 +1,4 @@ -error: `only "hide" or "show" are allowed in "#[doc(auto_cfg(...))]"` +error: only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]` --> $DIR/doc-cfg.rs:11:7 | LL | #[doc(auto_cfg(42))] @@ -30,13 +30,13 @@ error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value item LL | #[doc(auto_cfg(hide(foo::bar)))] | ^^^^^^^^ -error: `expected boolean for #[doc(auto_cfg = ...)]` +error: expected boolean for `#[doc(auto_cfg = ...)]` --> $DIR/doc-cfg.rs:16:7 | LL | #[doc(auto_cfg = 42)] | ^^^^^^^^^^^^^ -error: `expected boolean for #[doc(auto_cfg = ...)]` +error: expected boolean for `#[doc(auto_cfg = ...)]` --> $DIR/doc-cfg.rs:17:7 | LL | #[doc(auto_cfg = "a")] -- cgit 1.4.1-3-g733a5 From 9362ab549f87e535eeba12c5d42f584ff55ff886 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 25 Sep 2025 11:33:58 +0200 Subject: Improve code and fix typo --- compiler/rustc_passes/messages.ftl | 3 --- compiler/rustc_passes/src/check_attr.rs | 41 +++++++++-------------------- compiler/rustc_passes/src/errors.rs | 8 +++--- compiler/rustc_span/src/symbol.rs | 1 - src/doc/rustdoc/src/unstable-features.md | 42 +++++++++++++++--------------- src/librustdoc/clean/types.rs | 10 +++---- src/librustdoc/passes/propagate_doc_cfg.rs | 3 +-- 7 files changed, 43 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 21fc1124c2f..df4016dfa1b 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -140,9 +140,6 @@ passes_doc_attribute_not_attribute = nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]` .help = only existing builtin attributes are allowed in core/std -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(...))]` diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 88b49c781e7..4ea237cfa03 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1160,7 +1160,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - /// Check that the `#![doc(auto_cfg(..))]` attribute has expected input. + /// Check that the `#![doc(auto_cfg)]` attribute has the expected input. fn check_doc_auto_cfg(&self, meta: &MetaItem, hir_id: HirId) { match &meta.kind { MetaItemKind::Word => {} @@ -1176,7 +1176,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } MetaItemKind::List(list) => { for item in list { - let Some(attr_name) = item.name() else { + let Some(attr_name @ (sym::hide | sym::show)) = item.name() else { self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, hir_id, @@ -1185,36 +1185,21 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ); 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 let Some(list) = item.meta_item_list() { + if let Some(list) = item.meta_item_list() { for item in list { - if item.meta_item_list().is_some() { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - item.span(), - errors::DocAutoCfgHideShowUnexpectedItem { - attr_name: attr_name.as_str(), - }, - ); - } else if match item { - MetaItemInner::Lit(_) => true, - // We already checked above that it's not a list. - MetaItemInner::MetaItem(meta) => meta.path.segments.len() != 1, - } { + let valid = item.meta_item().is_some_and(|meta| { + meta.path.segments.len() == 1 + && matches!( + &meta.kind, + MetaItemKind::Word | MetaItemKind::NameValue(_) + ) + }); + if !valid { self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, hir_id, item.span(), - errors::DocAutoCfgHideShowUnexpectedItem { - attr_name: attr_name.as_str(), - }, + errors::DocAutoCfgHideShowUnexpectedItem { attr_name }, ); } } @@ -1223,7 +1208,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { INVALID_DOC_ATTRIBUTES, hir_id, meta.span, - errors::DocAutoCfgHideShowExpectsList { attr_name: attr_name.as_str() }, + errors::DocAutoCfgHideShowExpectsList { attr_name }, ); } } diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 1d2428c4f9a..f0726014e0a 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -318,14 +318,14 @@ 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, +pub(crate) struct DocAutoCfgHideShowExpectsList { + pub attr_name: Symbol, } #[derive(LintDiagnostic)] #[diag(passes_doc_auto_cfg_hide_show_unexpected_item)] -pub(crate) struct DocAutoCfgHideShowUnexpectedItem<'a> { - pub attr_name: &'a str, +pub(crate) struct DocAutoCfgHideShowUnexpectedItem { + pub attr_name: Symbol, } #[derive(LintDiagnostic)] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 18c3faed932..cd422da0c1c 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -629,7 +629,6 @@ symbols! { cfg_emscripten_wasm_eh, cfg_eval, cfg_fmt_debug, - cfg_hide, cfg_overflow_checks, cfg_panic, cfg_relocation_model, diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index cfa1936f3d7..04d3c0cd630 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -720,9 +720,9 @@ pass `--doctest-build-arg ARG` for each argument `ARG`. This flag enables the generation of toggles to expand macros in the HTML source code pages. -## `#[doc(cfg)]` +## `#[doc(cfg)]` and `#[doc(auto_cfg)]` -This feature aims at providing rustdoc users the possibility to add visual markers to the rendered documentation to know under which conditions an item is available (currently possible through the following unstable features: `doc_cfg`, `doc_auto_cfg` and `doc_cfg_hide`). +This feature aims at providing rustdoc users the possibility to add visual markers to the rendered documentation to know under which conditions an item is available (currently possible through the following unstable feature: `doc_cfg`). It does not aim to allow having a same item with different `cfg`s to appear more than once in the generated documentation. @@ -736,25 +736,6 @@ This features adds the following attributes: All of these attributes can be added to a module or to the crate root, and they will be inherited by the child items unless another attribute overrides it. This is why "opposite" attributes like `auto_cfg(hide(...))` and `auto_cfg(show(...))` are provided: they allow a child item to override its parent. -### `#[doc(auto_cfg)`/`#[doc(auto_cfg = true)]`/`#[doc(auto_cfg = false)]` - -By default, `#[doc(auto_cfg)]` is enabled at the crate-level. When it's enabled, Rustdoc will automatically display `cfg(...)` compatibility information as-if the same `#[doc(cfg(...))]` had been specified. - -This attribute impacts the item on which it is used and its descendants. - -So if we take back the previous example: - -```rust -#[cfg(feature = "futures-io")] -pub mod futures {} -``` - -There's no need to "duplicate" the `cfg` into a `doc(cfg())` to make Rustdoc display it. - -In some situations, the detailed conditional compilation rules used to implement the feature might not serve as good documentation (for example, the list of supported platforms might be very long, and it might be better to document them in one place). To turn it off, add the `#[doc(auto_cfg = false)]` attribute on the item. - -If no argument is specified (ie `#[doc(auto_cfg)]`), it's the same as writing `#[doc(auto_cfg = true)]`. - ### `#[doc(cfg(...))]` This attribute provides a standardized format to override `#[cfg()]` attributes to document conditionally available items. Example: @@ -927,6 +908,25 @@ Using this attribute will re-enable `auto_cfg` if it was disabled at this locati pub fn foo() {} ``` +### `#[doc(auto_cfg)`/`#[doc(auto_cfg = true)]`/`#[doc(auto_cfg = false)]` + +By default, `#[doc(auto_cfg)]` is enabled at the crate-level. When it's enabled, Rustdoc will automatically display `cfg(...)` compatibility information as-if the same `#[doc(cfg(...))]` had been specified. + +This attribute impacts the item on which it is used and its descendants. + +So if we take back the previous example: + +```rust +#[cfg(feature = "futures-io")] +pub mod futures {} +``` + +There's no need to "duplicate" the `cfg` into a `doc(cfg())` to make Rustdoc display it. + +In some situations, the detailed conditional compilation rules used to implement the feature might not serve as good documentation (for example, the list of supported platforms might be very long, and it might be better to document them in one place). To turn it off, add the `#[doc(auto_cfg = false)]` attribute on the item. + +If no argument is specified (ie `#[doc(auto_cfg)]`), it's the same as writing `#[doc(auto_cfg = true)]`. + ## Inheritance Rustdoc merges `cfg` attributes from parent modules to its children. For example, in this case, the module `non_unix` will describe the entire compatibility matrix for the module, and not just its directly attached information: diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 8b8cf19d8e4..c2cf39c4be0 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -925,7 +925,7 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator>( /// This type keeps track of (doc) cfg information as we go down the item tree. #[derive(Clone, Debug)] pub(crate) struct CfgInfo { - /// List of currently active `doc(auto_cfg(hide(...)))` cfgs,minus currently active + /// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active /// `doc(auto_cfg(show(...)))` cfgs. hidden_cfg: FxHashSet, /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while @@ -942,13 +942,11 @@ pub(crate) struct CfgInfo { impl Default for CfgInfo { fn default() -> Self { Self { - hidden_cfg: [ + hidden_cfg: FxHashSet::from_iter([ Cfg::Cfg(sym::test, None), Cfg::Cfg(sym::doc, None), Cfg::Cfg(sym::doctest, None), - ] - .into_iter() - .collect(), + ]), current_cfg: Cfg::True, auto_cfg_active: true, parent_is_doc_cfg: false, @@ -990,7 +988,7 @@ fn handle_auto_cfg_hide_show( && let MetaItemKind::List(items) = &item.kind { for item in items { - // Cfg parsing errors should already have been reported in `rustc_passes::check_attr`. + // FIXME: Report in case `Cfg::parse` reports an error? if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) { if is_show { if let Some(span) = new_hide_attrs.get(&(key, value)) { diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 4bcb60a9a40..d5b20f2b941 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -97,9 +97,8 @@ impl CfgPropagator<'_, '_> { // // 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 Some(mut next_def_id) = item.item_id.as_local_def_id() { - 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); -- cgit 1.4.1-3-g733a5 From 6cccea8731c5f8540d65c2458a36655a4b8793f3 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 26 Sep 2025 14:46:04 +0200 Subject: Remove `doc_auto_cfg` feature as well --- compiler/rustc_feature/src/removed.rs | 2 ++ compiler/rustc_feature/src/unstable.rs | 2 -- tests/rustdoc-gui/src/lib2/lib.rs | 1 - tests/rustdoc/doc-cfg/doc-cfg-implicit.rs | 2 +- tests/rustdoc/reexport/reexport-cfg.rs | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 71365da316c..3c51f91331a 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -101,6 +101,8 @@ declare_features! ( Some("never properly implemented; requires significant design work"), 127655), /// Allows deriving traits as per `SmartPointer` specification (removed, derive_smart_pointer, "1.84.0", Some(123430), Some("replaced by `CoercePointee`"), 131284), + /// Tells rustdoc to automatically generate `#[doc(cfg(...))]`. + (removed, doc_auto_cfg, "1.58.0", Some(43781), Some("merged into `doc_cfg`"), 138907), /// Allows `#[doc(cfg_hide(...))]`. (removed, doc_cfg_hide, "1.57.0", Some(43781), Some("merged into `doc_cfg`"), 138907), /// Allows using `#[doc(keyword = "...")]`. diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 4dc961cb927..b286ae3eb23 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -472,8 +472,6 @@ declare_features! ( (incomplete, deref_patterns, "1.79.0", Some(87121)), /// Allows deriving the From trait on single-field structs. (unstable, derive_from, "1.91.0", Some(144889)), - /// Tells rustdoc to automatically generate `#[doc(cfg(...))]`. - (unstable, doc_auto_cfg, "1.58.0", Some(43781)), /// Allows `#[doc(cfg(...))]`. (unstable, doc_cfg, "1.21.0", Some(43781)), /// Allows `#[doc(masked)]`. diff --git a/tests/rustdoc-gui/src/lib2/lib.rs b/tests/rustdoc-gui/src/lib2/lib.rs index 8db754f91ce..400488cbe85 100644 --- a/tests/rustdoc-gui/src/lib2/lib.rs +++ b/tests/rustdoc-gui/src/lib2/lib.rs @@ -1,7 +1,6 @@ // ignore-tidy-linelength #![feature(doc_cfg)] -#![feature(doc_auto_cfg)] pub mod another_folder; pub mod another_mod; diff --git a/tests/rustdoc/doc-cfg/doc-cfg-implicit.rs b/tests/rustdoc/doc-cfg/doc-cfg-implicit.rs index 69b10867ee3..c092675ee5c 100644 --- a/tests/rustdoc/doc-cfg/doc-cfg-implicit.rs +++ b/tests/rustdoc/doc-cfg/doc-cfg-implicit.rs @@ -1,5 +1,5 @@ #![crate_name = "funambulism"] -#![feature(doc_auto_cfg, doc_cfg)] +#![feature(doc_cfg)] //@ has 'funambulism/struct.Disorbed.html' //@ count - '//*[@class="stab portability"]' 1 diff --git a/tests/rustdoc/reexport/reexport-cfg.rs b/tests/rustdoc/reexport/reexport-cfg.rs index 73b66824316..b624e5acf50 100644 --- a/tests/rustdoc/reexport/reexport-cfg.rs +++ b/tests/rustdoc/reexport/reexport-cfg.rs @@ -2,7 +2,7 @@ // include `cfg`s from the previous chained items. #![crate_name = "foo"] -#![feature(doc_auto_cfg, doc_cfg)] +#![feature(doc_cfg)] mod foo { #[cfg(not(feature = "foo"))] -- cgit 1.4.1-3-g733a5