From 810d2dbad580fb11eb8bdac4c199f6f0f8c88712 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 25 May 2026 22:39:36 +0200 Subject: [PATCH 1/8] Update `doc_cfg` hide/show syntax --- .../rustc_attr_parsing/src/attributes/doc.rs | 170 ++++++++++++++---- .../rustc_attr_parsing/src/diagnostics.rs | 17 +- .../rustc_hir/src/attrs/data_structures.rs | 99 ++++++++-- .../rustc_hir/src/attrs/pretty_printing.rs | 2 +- src/librustdoc/clean/cfg.rs | 14 +- src/librustdoc/json/conversions.rs | 1 + 6 files changed, 247 insertions(+), 56 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 988df2b200f86..2aa9769b46be5 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -1,25 +1,29 @@ use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry}; use rustc_errors::{Applicability, msg}; use rustc_feature::AttributeStability; use rustc_hir::Target; use rustc_hir::attrs::{ - AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow, + AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue, + DocInline, HideOrShow, }; use rustc_session::errors::feature_err; use rustc_span::{Span, Symbol, edition, sym}; -use thin_vec::ThinVec; use super::prelude::{ALL_TARGETS, AllowedTargets}; use super::{AcceptMapping, AttributeParser, template}; use crate::context::{AcceptContext, FinalizeContext}; use crate::diagnostics::{ AttrCrateLevelOnly, DocAliasDuplicated, DocAutoCfgExpectsHideOrShow, - DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowUnexpectedItem, DocAutoCfgWrongLiteral, - DocTestLiteral, DocTestTakesList, DocTestUnknown, DocUnknownAny, DocUnknownInclude, - DocUnknownPasses, DocUnknownPlugins, DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, - IllFormedAttributeInput, MalformedDoc, + DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues, + DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues, + DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocTestLiteral, DocTestTakesList, + DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, + DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, MalformedDoc, +}; +use crate::parser::{ + ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser, }; -use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser}; use crate::session_diagnostics::{ DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttrNotCrateLevel, DocAttributeNotAttribute, DocKeywordNotKeyword, UnusedDuplicate, @@ -304,6 +308,81 @@ impl DocParser { } } + // Parses the `doc(auto_cfg(hide/show(..., values())))` attribute. + fn parse_auto_cfg_values( + &self, + cx: &mut AcceptContext<'_, '_>, + list: &MetaItemListParser, + values: &mut Option, + ) { + let mut cfg_values = DocCfgHideShow::new(); + + let mut values_set = FxHashSet::default(); + for item in list.mixed() { + match item { + // If it's a string literal, all good. + MetaItemOrLitParser::Lit(MetaItemLit { + kind: LitKind::Str(symbol, _), + span, + .. + }) => match &mut cfg_values.values { + DocCfgHideShowValue::Any(any_span) => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + DocAutoCfgHideShowValuesMix { value_span: *span }, + *any_span, + ); + } + DocCfgHideShowValue::List(symbols) => { + if values_set.insert(symbol) { + symbols.push((*symbol, *span)); + } + } + }, + // If it's any other kind of literal, then it's wrong and we emit a lint. + MetaItemOrLitParser::Lit(lit) => cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol }, + lit.span, + ), + // If it's a list, then only `any()` and `none()` are allowed and they must not + // contain any item. + MetaItemOrLitParser::MetaItemParser(sub_item) => { + if let Some(ident) = sub_item.ident() + && [sym::any, sym::none].contains(&ident.name) + && let ArgParser::List(list) = sub_item.args() + && list.mixed().count() == 0 + { + if ident.name == sym::any { + if let DocCfgHideShowValue::List(values) = &cfg_values.values + && let Some((_, span)) = values.first() + { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + DocAutoCfgHideShowValuesMix { value_span: *span }, + sub_item.span(), + ); + } else { + cfg_values.values = DocCfgHideShowValue::Any(sub_item.span()); + } + } else { + cfg_values.only_key = Some(sub_item.span()); + } + } else { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + DocAutoCfgHideShowUnexpectedItem { + attr_name: sub_item.ident().unwrap().name, + }, + sub_item.span(), + ); + } + } + } + } + *values = Some(cfg_values); + } + fn parse_auto_cfg( &mut self, cx: &mut AcceptContext<'_, '_>, @@ -315,7 +394,7 @@ impl DocParser { self.attribute.auto_cfg_change.push((true, path.span())); } ArgParser::List(list) => { - for meta in list.mixed() { + 'main: for meta in list.mixed() { let MetaItemOrLitParser::MetaItemParser(item) = meta else { cx.emit_lint( rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, @@ -324,6 +403,8 @@ impl DocParser { ); continue; }; + // Only `hide` and `show` are allowed in `auto_cfg` if it's a list, and both + // must be a list. let (kind, attr_name) = match item.path().word_sym() { Some(sym::hide) => (HideOrShow::Hide, sym::hide), Some(sym::show) => (HideOrShow::Show, sym::show), @@ -345,8 +426,10 @@ impl DocParser { continue; }; - let mut cfg_hide_show = CfgHideShow { kind, values: ThinVec::new() }; + let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() }; + let mut cfg_names = FxHashSet::default(); + let mut values = None; for item in list.mixed() { let MetaItemOrLitParser::MetaItemParser(sub_item) = item else { cx.emit_lint( @@ -354,47 +437,60 @@ impl DocParser { DocAutoCfgHideShowUnexpectedItem { attr_name }, item.span(), ); - continue; + continue 'main; }; match sub_item.args() { - a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => { + ArgParser::NoArgs if values.is_none() => { let Some(name) = sub_item.path().word_sym() else { - // FIXME: remove this method once merged and uncomment the line - // below instead. - // cx.expected_identifier(sub_item.path().span()); + cx.adcx().expected_identifier(sub_item.path().span()); + continue 'main; + }; + cfg_names.insert(name); + } + // The only accepted list is `values()`. + ArgParser::List(list) if values.is_none() => { + let Some(sym::values) = sub_item.path().word_sym() else { + cx.adcx().expected_identifier(sub_item.path().span()); + continue 'main; + }; + if cfg_names.is_empty() { cx.emit_lint( rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - MalformedDoc, - sub_item.path().span(), - ); - continue; - }; - if let Ok(CfgEntry::NameValue { name, value, .. }) = - super::cfg::parse_name_value( - name, - sub_item.path().span(), - a.as_name_value(), + DocAutoCfgHideShowNoIdentBeforeValues, sub_item.span(), - cx, - ) - { - cfg_hide_show.values.push(CfgInfo { - name, - name_span: sub_item.path().span(), - // If `value` is `Some`, `a.name_value()` will always return - // `Some` as well. - value: value - .map(|v| (v, a.as_name_value().unwrap().value_span)), - }) + ); + continue 'main; } + self.parse_auto_cfg_values(cx, list, &mut values); } - _ => { + // No `name = value` is allowed. + ArgParser::NameValue(_) => { cx.emit_lint( rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, DocAutoCfgHideShowUnexpectedItem { attr_name }, sub_item.span(), ); - continue; + } + // If `values()` was already used, no item should come after it. + _ => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + DocAutoCfgHideShowUnexpectedItemAfterValues, + sub_item.span(), + ); + } + } + } + + let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span())); + #[allow(rustc::potential_query_instability)] + for cfg_name in &cfg_names { + match cfg_hide_show.values.entry(*cfg_name) { + IndexEntry::Vacant(v) => { + v.insert(values.clone()); + } + IndexEntry::Occupied(mut o) => { + o.get_mut().merge_with(&values); } } } diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index d8b7144aa01ba..50667952b814d 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -169,11 +169,22 @@ pub(crate) struct DocAutoCfgExpectsHideOrShow; pub(crate) struct AmbiguousDeriveHelpers; #[derive(Diagnostic)] -#[diag("`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items")] +#[diag("`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or `values(...)`")] pub(crate) struct DocAutoCfgHideShowUnexpectedItem { pub attr_name: Symbol, } +#[derive(Diagnostic)] +#[diag("`any()` was used when other values were provided")] +pub(crate) struct DocAutoCfgHideShowValuesMix { + #[label("value declared here")] + pub value_span: Span, +} + +#[derive(Diagnostic)] +#[diag("unexpected item after `values()`")] +pub(crate) struct DocAutoCfgHideShowUnexpectedItemAfterValues; + #[derive(Diagnostic)] #[diag("`#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items")] pub(crate) struct DocAutoCfgHideShowExpectsList { @@ -239,6 +250,10 @@ pub(crate) struct DocUnknownAny { #[diag("expected boolean for `#[doc(auto_cfg = ...)]`")] pub(crate) struct DocAutoCfgWrongLiteral; +#[derive(Diagnostic)] +#[diag("there must be at least one identifier before `values(...)`")] +pub(crate) struct DocAutoCfgHideShowNoIdentBeforeValues; + #[derive(Diagnostic)] #[diag("`#[doc(test(...)]` takes a list of attributes")] pub(crate) struct DocTestTakesList; diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 7c125c3a8983e..e35a9d68a4d00 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -515,19 +515,96 @@ pub enum HideOrShow { Show, } +#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq)] +pub enum DocCfgHideShowValue { + Any(Span), + List(ThinVec<(Symbol, Span)>), +} + #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] -pub struct CfgInfo { - pub name: Symbol, - pub name_span: Span, - pub value: Option<(Symbol, Span)>, +pub struct DocCfgHideShow { + /// If `Some`, then `cfg` without values (like `cfg(windows)`) will be shown/hidden. + /// The `Span` comes from where this value was set. + pub only_key: Option, + /// The values of this `cfg` to shown/hidden. + pub values: DocCfgHideShowValue, } -impl CfgInfo { - pub fn span_for_name_and_value(&self) -> Span { - if let Some((_, value_span)) = self.value { - self.name_span.with_hi(value_span.hi()) - } else { - self.name_span +impl DocCfgHideShow { + pub fn new() -> Self { + Self { only_key: None, values: DocCfgHideShowValue::List(ThinVec::new()) } + } + + pub fn new_with_only_key(span: Span) -> Self { + Self { only_key: Some(span), values: DocCfgHideShowValue::List(ThinVec::new()) } + } + + pub fn merge_with(&mut self, other: &Self) { + if self.only_key.is_none() + && let Some(span) = other.only_key + { + self.only_key = Some(span); + } + match (&mut self.values, &other.values) { + (DocCfgHideShowValue::Any(_), DocCfgHideShowValue::Any(_)) => { + // Nothing to do. + } + (_, DocCfgHideShowValue::Any(span)) => { + // We "upgrade" the list values to "all". + self.values = DocCfgHideShowValue::Any(*span); + } + (DocCfgHideShowValue::List(values), DocCfgHideShowValue::List(other_values)) => { + // Having duplicates is not an issue, we simply ignore them. Would be more + // convenient to have a `set` type though. T_T + for (other_symbol, other_span) in other_values { + if !values.iter().any(|(symbol, _)| symbol == other_symbol) { + values.push((*other_symbol, *other_span)); + } + } + } + (DocCfgHideShowValue::Any(_), DocCfgHideShowValue::List(_)) => { + // Nothing to do here either, we already accept all values. + } + } + } + + pub fn update_with(&mut self, other: &Self) { + if self.only_key.is_none() + && let Some(span) = other.only_key + { + self.only_key = Some(span); + } + match (&mut self.values, &other.values) { + (DocCfgHideShowValue::Any(_), _) => {} + (DocCfgHideShowValue::List(_), DocCfgHideShowValue::Any(span)) => { + self.values = DocCfgHideShowValue::Any(*span); + } + (DocCfgHideShowValue::List(values), DocCfgHideShowValue::List(other_values)) => { + // Having duplicates is not an issue, we simply ignore them. Would be more + // convenient to have a `set` type though. T_T + for (other_symbol, other_span) in other_values { + if !values.iter().any(|(symbol, _)| symbol == other_symbol) { + values.push((*other_symbol, *other_span)); + } + } + } + } + } + + pub fn remove_overlap(&mut self, other: &Self) { + if other.only_key.is_some() { + self.only_key = None; + } + match (&mut self.values, &other.values) { + (_, DocCfgHideShowValue::Any(_)) => { + self.values = DocCfgHideShowValue::List(ThinVec::new()); + } + (DocCfgHideShowValue::Any(_), DocCfgHideShowValue::List(values)) => { + self.values = DocCfgHideShowValue::List(values.clone()); + } + (DocCfgHideShowValue::List(current), DocCfgHideShowValue::List(values)) => { + current.retain(|(name, _)| !values.iter().any(|(other, _)| other == name)); + } } } } @@ -535,7 +612,7 @@ impl CfgInfo { #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] pub struct CfgHideShow { pub kind: HideOrShow, - pub values: ThinVec, + pub values: FxIndexMap, } #[derive(Clone, Debug, Default, StableHash, Decodable, PrintAttribute)] diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index 9d14f9de3078d..d826aa363f349 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -81,7 +81,7 @@ impl PrintAttribute for ThinVec { p.word("]"); } } -impl PrintAttribute for FxIndexMap { +impl PrintAttribute for FxIndexMap { fn should_render(&self) -> bool { self.is_empty() || self[0].should_render() } diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 170f1439ecc9c..8062db16018c6 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -12,7 +12,9 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; use rustc_hir as hir; use rustc_hir::Attribute; -use rustc_hir::attrs::{self, AttributeKind, CfgEntry, CfgHideShow, HideOrShow}; +use rustc_hir::attrs::{ + self, AttributeKind, CfgEntry, CfgHideShow, DocCfgHideShowValue, HideOrShow, +}; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::{Symbol, sym}; use rustc_span::{DUMMY_SP, Span}; @@ -685,7 +687,7 @@ impl<'a> From<&'a attrs::CfgInfo> for NameValueCfg { pub(crate) struct CfgInfo { /// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active /// `doc(auto_cfg(show(...)))` cfgs. - hidden_cfg: FxHashSet, + hidden_cfg: FxHashMap, /// 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, @@ -700,10 +702,10 @@ pub(crate) struct CfgInfo { impl Default for CfgInfo { fn default() -> Self { Self { - hidden_cfg: FxHashSet::from_iter([ - NameValueCfg::new(sym::test), - NameValueCfg::new(sym::doc), - NameValueCfg::new(sym::doctest), + hidden_cfg: FxHashMap::from_iter([ + (sym::test, DocCfgHideShowValue::All), + (sym::doc, DocCfgHideShowValue::All), + (sym::doctest, DocCfgHideShowValue::All), ]), current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)), auto_cfg_active: true, diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 4de9c46900a76..644d6f2acdb98 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -1138,6 +1138,7 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) // characters surrounding the string. out.push_str(&format!(" = {:?}", value.as_str())); } + out.push(')'); } out.push_str(")))]"); ret.push(Attribute::Other(out)); From dc7a2ce9f07509409ead44baa7680d7582a1f365 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 11 Jun 2026 17:58:14 +0200 Subject: [PATCH 2/8] Update `alloc` and `core` to new `doc_cfg` syntax --- library/alloc/src/lib.rs | 5 ++++- library/core/src/lib.rs | 31 ++++++++++--------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index b0e8039263ffe..f8cab52633fe1 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -65,7 +65,10 @@ issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/", test(no_crate_inject, attr(allow(unused_variables, duplicate_features), deny(warnings))) )] -#![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), + hide(target_has_atomic, values("ptr")), +))] #![doc(rust_logo)] #![feature(rustdoc_internals)] #![no_std] diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index a26304c46ecea..61b6741219f7e 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -51,27 +51,16 @@ test(attr(allow(dead_code, deprecated, unused_variables, unused_mut, duplicate_features))) )] #![doc(rust_logo)] -#![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_primitive_alignment = "8", - target_has_atomic_primitive_alignment = "16", - target_has_atomic_primitive_alignment = "32", - target_has_atomic_primitive_alignment = "64", - target_has_atomic_primitive_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), + hide(target_pointer_width, values("16", "32", "64")), + hide( + target_has_atomic, + target_has_atomic_primitive_alignment, + target_has_atomic_load_store, + values("8", "16", "32", "64", "ptr"), + ), +))] #![no_core] #![rustc_coherence_is_core] #![rustc_preserve_ub_checks] From f22107c1c7efe24af676f9ec53f321cac6a1d3e7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 13 Jun 2026 01:06:37 +0200 Subject: [PATCH 3/8] Update rustdoc to use new `doc_cfg` syntax --- src/librustdoc/clean/cfg.rs | 141 +++++++++++------- src/librustdoc/json/conversions.rs | 53 +++++-- tests/rustdoc-html/doc-cfg/decl-macro.rs | 6 +- tests/rustdoc-html/doc-cfg/doc-cfg-hide.rs | 2 +- .../doc-cfg/trait-impls-manual.rs | 6 +- tests/rustdoc-html/doc-cfg/trait-impls.rs | 6 +- tests/rustdoc-html/doc_auto_cfg.rs | 10 +- tests/rustdoc-ui/cfg-hide-show-conflict.rs | 4 +- .../rustdoc-ui/cfg-hide-show-conflict.stderr | 12 +- tests/rustdoc-ui/doc-cfg-2.rs | 4 +- tests/rustdoc-ui/doc-cfg-2.stderr | 22 +-- tests/rustdoc-ui/doc-cfg.rs | 2 +- tests/rustdoc-ui/doc-cfg.stderr | 13 +- tests/rustdoc-ui/lints/doc_cfg_hide-2.rs | 4 + tests/rustdoc-ui/lints/doc_cfg_hide-2.stderr | 11 ++ tests/rustdoc-ui/lints/doc_cfg_hide.rs | 1 - tests/rustdoc-ui/lints/doc_cfg_hide.stderr | 8 +- 17 files changed, 183 insertions(+), 122 deletions(-) create mode 100644 tests/rustdoc-ui/lints/doc_cfg_hide-2.rs create mode 100644 tests/rustdoc-ui/lints/doc_cfg_hide-2.stderr diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 8062db16018c6..8721523f83f58 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -8,12 +8,12 @@ use std::sync::Arc; use std::{fmt, mem, ops}; use itertools::Either; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; use rustc_hir as hir; use rustc_hir::Attribute; use rustc_hir::attrs::{ - self, AttributeKind, CfgEntry, CfgHideShow, DocCfgHideShowValue, HideOrShow, + AttributeKind, CfgEntry, CfgHideShow, DocCfgHideShow, DocCfgHideShowValue, HideOrShow, }; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::{Symbol, sym}; @@ -55,15 +55,24 @@ fn is_any_cfg(cfg: &CfgEntry) -> bool { } } -fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashSet) -> Option { +fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashMap) -> Option { match cfg { CfgEntry::Bool(..) => Some(cfg.clone()), - CfgEntry::NameValue { .. } => { - if !hidden.contains(&NameValueCfg::from(cfg)) { - Some(cfg.clone()) - } else { - None + CfgEntry::NameValue { name, value, .. } => { + let mut is_stripped = false; + if let Some(values) = hidden.get(name) { + if let Some(value) = value { + match &values.values { + DocCfgHideShowValue::Any(_) => is_stripped = true, + DocCfgHideShowValue::List(values) => { + is_stripped = values.iter().any(|(v, _)| v == value); + } + } + } else { + is_stripped = values.only_key.is_some(); + } } + if !is_stripped { Some(cfg.clone()) } else { None } } CfgEntry::Not(cfg, _) => { if let Some(cfg) = strip_hidden(cfg, hidden) { @@ -655,39 +664,12 @@ fn human_readable_target_env(env: Symbol) -> Option<&'static str> { }) } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -struct NameValueCfg { - name: Symbol, - value: Option, -} - -impl NameValueCfg { - fn new(name: Symbol) -> Self { - Self { name, value: None } - } -} - -impl<'a> From<&'a CfgEntry> for NameValueCfg { - fn from(cfg: &'a CfgEntry) -> Self { - match cfg { - CfgEntry::NameValue { name, value, .. } => NameValueCfg { name: *name, value: *value }, - _ => NameValueCfg { name: sym::empty, value: None }, - } - } -} - -impl<'a> From<&'a attrs::CfgInfo> for NameValueCfg { - fn from(cfg: &'a attrs::CfgInfo) -> Self { - Self { name: cfg.name, value: cfg.value.map(|(value, _)| value) } - } -} - /// 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 /// `doc(auto_cfg(show(...)))` cfgs. - hidden_cfg: FxHashMap, + hidden_cfg: FxHashMap, /// 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, @@ -703,9 +685,9 @@ impl Default for CfgInfo { fn default() -> Self { Self { hidden_cfg: FxHashMap::from_iter([ - (sym::test, DocCfgHideShowValue::All), - (sym::doc, DocCfgHideShowValue::All), - (sym::doctest, DocCfgHideShowValue::All), + (sym::test, DocCfgHideShow::new_with_only_key(DUMMY_SP)), + (sym::doc, DocCfgHideShow::new_with_only_key(DUMMY_SP)), + (sym::doctest, DocCfgHideShow::new_with_only_key(DUMMY_SP)), ]), current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)), auto_cfg_active: true, @@ -718,7 +700,9 @@ fn show_hide_show_conflict_error( tcx: TyCtxt<'_>, item_span: rustc_span::Span, previous: rustc_span::Span, + errors: &mut usize, ) { + *errors += 1; let mut diag = tcx.sess.dcx().struct_span_err( item_span, format!( @@ -729,6 +713,48 @@ fn show_hide_show_conflict_error( diag.emit(); } +fn check_if_no_overlap( + tcx: TyCtxt<'_>, + attrs: &FxHashMap, + cfg_name: Symbol, + info: &DocCfgHideShow, +) -> bool { + let mut errors = 0; + if let Some(other) = attrs.get(&cfg_name) { + match (&other.only_key, &info.only_key) { + (Some(previous_span), Some(span)) => { + show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); + } + _ => {} + } + match (&other.values, &info.values) { + (DocCfgHideShowValue::Any(previous_span), DocCfgHideShowValue::Any(span)) => { + show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); + } + (DocCfgHideShowValue::Any(previous_span), DocCfgHideShowValue::List(items)) => { + // If the list is empty, then it's just the default so no problem there. + if let Some((_, span)) = items.first() { + show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); + } + } + (DocCfgHideShowValue::List(previous_items), DocCfgHideShowValue::Any(span)) => { + // If the list is empty, then it's just the default so no problem there. + if let Some((_, previous_span)) = previous_items.first() { + show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); + } + } + (DocCfgHideShowValue::List(previous_items), DocCfgHideShowValue::List(items)) => { + for (previous_name, previous_span) in previous_items { + if let Some((_, span)) = items.iter().find(|(name, _)| name == previous_name) { + show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); + } + } + } + } + } + errors == 0 +} + /// 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 @@ -740,25 +766,34 @@ fn handle_auto_cfg_hide_show( tcx: TyCtxt<'_>, cfg_info: &mut CfgInfo, attr: &CfgHideShow, - new_show_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, - new_hide_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, + new_show_attrs: &mut FxHashMap, + new_hide_attrs: &mut FxHashMap, ) { - for value in &attr.values { - let simple = NameValueCfg::from(value); + for (cfg_name, value) in &attr.values { if attr.kind == HideOrShow::Show { - if let Some(span) = new_hide_attrs.get(&(simple.name, simple.value)) { - show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span); - } else { - new_show_attrs.insert((simple.name, simple.value), value.span_for_name_and_value()); + if check_if_no_overlap(tcx, new_hide_attrs, *cfg_name, value) { + new_show_attrs + .entry(*cfg_name) + .and_modify(|entry| entry.update_with(value)) + .or_insert_with(|| value.clone()); + cfg_info + .hidden_cfg + .entry(*cfg_name) + .and_modify(|entry| entry.remove_overlap(value)) + .or_insert_with(|| value.clone()); } - cfg_info.hidden_cfg.remove(&simple); } else { - if let Some(span) = new_show_attrs.get(&(simple.name, simple.value)) { - show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span); - } else { - new_hide_attrs.insert((simple.name, simple.value), value.span_for_name_and_value()); + if check_if_no_overlap(tcx, new_show_attrs, *cfg_name, value) { + new_hide_attrs + .entry(*cfg_name) + .and_modify(|entry| entry.update_with(value)) + .or_insert_with(|| value.clone()); + cfg_info + .hidden_cfg + .entry(*cfg_name) + .and_modify(|entry| entry.update_with(value)) + .or_insert_with(|| value.clone()); } - cfg_info.hidden_cfg.insert(simple); } } } diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 644d6f2acdb98..9ec7e4bd49ceb 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -7,7 +7,9 @@ use rustc_ast::ast; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; -use rustc_hir::attrs::{self, DeprecatedSince, DocAttribute, DocInline, HideOrShow}; +use rustc_hir::attrs::{ + self, DeprecatedSince, DocAttribute, DocCfgHideShowValue, DocInline, HideOrShow, +}; use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def_id::DefId; use rustc_hir::{HeaderSafety, Safety, find_attr}; @@ -1122,25 +1124,46 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) for sub_cfg in cfg { ret.push(Attribute::Other(format!("#[doc(cfg({sub_cfg}))]"))); } - for (auto_cfg, _) in auto_cfg { - let kind = match auto_cfg.kind { - HideOrShow::Hide => "hide", - HideOrShow::Show => "show", - }; - let mut out = format!("#[doc(auto_cfg({kind}("); - for (pos, value) in auto_cfg.values.iter().enumerate() { - if pos > 0 { + if !auto_cfg.is_empty() { + let mut out = format!("#[doc(auto_cfg("); + for (index, (auto_cfg, _)) in auto_cfg.iter().enumerate() { + let kind = match auto_cfg.kind { + HideOrShow::Hide => "hide", + HideOrShow::Show => "show", + }; + if index > 0 { out.push_str(", "); } - out.push_str(value.name.as_str()); - if let Some((value, _)) = value.value { - // We use `as_str` and debug display to have characters escaped and `"` - // characters surrounding the string. - out.push_str(&format!(" = {:?}", value.as_str())); + out.push_str(&format!("{kind}(")); + for (name, cfgs) in &auto_cfg.values { + out.push_str(&format!("{name}, values(")); + let mut pos = 0; + if cfgs.only_key.is_some() { + out.push_str("none()"); + pos += 1; + } + match &cfgs.values { + DocCfgHideShowValue::Any(_) => { + out.push_str(&format!("{}any()", if pos > 0 { ", " } else { "" })); + } + DocCfgHideShowValue::List(values) => { + for (value, _) in values { + // We use `as_str` and debug display to have characters escaped + // and `"` characters surrounding the string. + out.push_str(&format!( + "{}{:?}", + if pos > 0 { ", " } else { "" }, + value.as_str() + )); + pos += 1; + } + } + } + out.push_str(")"); } out.push(')'); } - out.push_str(")))]"); + out.push_str("))]"); ret.push(Attribute::Other(out)); } for (change, _) in auto_cfg_change { diff --git a/tests/rustdoc-html/doc-cfg/decl-macro.rs b/tests/rustdoc-html/doc-cfg/decl-macro.rs index e97da8647a6ab..ce4cd36d1367e 100644 --- a/tests/rustdoc-html/doc-cfg/decl-macro.rs +++ b/tests/rustdoc-html/doc-cfg/decl-macro.rs @@ -45,7 +45,7 @@ pub mod auto_cfg_disabled { } #[cfg(feature = "routing")] -#[doc(auto_cfg(hide(feature = "routing")))] +#[doc(auto_cfg(hide(feature, values("routing"))))] pub mod auto_cfg_hidden { //@ count 'foo/macro.hidden_cfg_macro.html' '//*[@class="stab portability"]' 0 #[macro_export] @@ -55,9 +55,9 @@ pub mod auto_cfg_hidden { } #[cfg(feature = "routing")] -#[doc(auto_cfg(hide(feature = "routing")))] +#[doc(auto_cfg(hide(feature, values("routing"))))] pub mod auto_cfg_shown { - #[doc(auto_cfg(show(feature = "routing")))] + #[doc(auto_cfg(show(feature, values("routing"))))] pub mod inner { //@ has 'foo/macro.shown_cfg_macro.html' '//*[@class="stab portability"]' 'Available on crate feature routing only.' #[macro_export] diff --git a/tests/rustdoc-html/doc-cfg/doc-cfg-hide.rs b/tests/rustdoc-html/doc-cfg/doc-cfg-hide.rs index e919206d3a478..72c33d209c7a1 100644 --- a/tests/rustdoc-html/doc-cfg/doc-cfg-hide.rs +++ b/tests/rustdoc-html/doc-cfg/doc-cfg-hide.rs @@ -1,7 +1,7 @@ #![crate_name = "oud"] #![feature(doc_cfg)] -#![doc(auto_cfg(hide(feature = "solecism")))] +#![doc(auto_cfg(hide(feature, values("solecism"))))] //@ has 'oud/struct.Solecism.html' //@ count - '//*[@class="stab portability"]' 0 diff --git a/tests/rustdoc-html/doc-cfg/trait-impls-manual.rs b/tests/rustdoc-html/doc-cfg/trait-impls-manual.rs index fbd96cc46d800..4329d8e06dfc5 100644 --- a/tests/rustdoc-html/doc-cfg/trait-impls-manual.rs +++ b/tests/rustdoc-html/doc-cfg/trait-impls-manual.rs @@ -3,7 +3,7 @@ #![feature(doc_cfg)] #![doc(auto_cfg(hide( - target_pointer_width = "64", + target_pointer_width, values("64"), )))] #![crate_name = "foo"] @@ -36,7 +36,7 @@ pub struct X; //@count - '//*[@id="impl-Trait-for-X"]' 1 //@count - '//*[@id="impl-Trait-for-X"]/*[@class="item-info"]' 0 #[doc(cfg(any(target_pointer_width = "64", target_arch = "wasm32")))] -#[doc(auto_cfg(hide(target_arch = "wasm32")))] +#[doc(auto_cfg(hide(target_arch, values("wasm32"))))] mod imp { impl super::Trait for super::X { fn f(&self) {} } } @@ -64,7 +64,7 @@ pub struct Y; //@count - '//*[@id="implementations-list"]/*[@class="impl-items"]' 1 //@count - '//*[@id="implementations-list"]/*[@class="impl-items"]/*[@class="item-info"]' 0 #[doc(cfg(any(target_pointer_width = "64", target_arch = "wasm32")))] -#[doc(auto_cfg(hide(target_arch = "wasm32")))] +#[doc(auto_cfg(hide(target_arch, values("wasm32"))))] mod imp4 { impl super::Y { pub fn plain_auto() {} } } diff --git a/tests/rustdoc-html/doc-cfg/trait-impls.rs b/tests/rustdoc-html/doc-cfg/trait-impls.rs index 581d171123d00..9ea6490ae8e21 100644 --- a/tests/rustdoc-html/doc-cfg/trait-impls.rs +++ b/tests/rustdoc-html/doc-cfg/trait-impls.rs @@ -3,7 +3,7 @@ #![feature(doc_cfg)] #![doc(auto_cfg(hide( - target_pointer_width = "64", + target_pointer_width, values("64"), )))] #![crate_name = "foo"] @@ -36,7 +36,7 @@ pub struct X; //@count - '//*[@id="impl-Trait-for-X"]' 1 //@count - '//*[@id="impl-Trait-for-X"]/*[@class="item-info"]' 0 #[cfg(any(target_pointer_width = "64", target_arch = "wasm32"))] -#[doc(auto_cfg(hide(target_arch = "wasm32")))] +#[doc(auto_cfg(hide(target_arch, values("wasm32"))))] mod imp { impl super::Trait for super::X { fn f(&self) {} } } @@ -64,7 +64,7 @@ pub struct Y; //@count - '//*[@id="implementations-list"]/*[@class="impl-items"]' 1 //@count - '//*[@id="implementations-list"]/*[@class="impl-items"]/*[@class="item-info"]' 0 #[cfg(any(target_pointer_width = "64", target_arch = "wasm32"))] -#[doc(auto_cfg(hide(target_arch = "wasm32")))] +#[doc(auto_cfg(hide(target_arch, values("wasm32"))))] mod imp4 { impl super::Y { pub fn plain_auto() {} } } diff --git a/tests/rustdoc-html/doc_auto_cfg.rs b/tests/rustdoc-html/doc_auto_cfg.rs index a1903e1a0ca3d..a33c5830157fb 100644 --- a/tests/rustdoc-html/doc_auto_cfg.rs +++ b/tests/rustdoc-html/doc_auto_cfg.rs @@ -2,7 +2,7 @@ #![crate_name = "foo"] #![feature(doc_cfg)] -#![doc(auto_cfg(hide(feature = "hidden")))] +#![doc(auto_cfg(hide(feature, values("hidden"))))] //@ has 'foo/index.html' //@ !has - '//*[@class="stab portability"]' 'Non-moustache' @@ -17,18 +17,18 @@ pub mod m { 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")))] + #[doc(auto_cfg(show(feature, values("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")))] + #[doc(auto_cfg(hide(feature, values("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")))] + #[doc(auto_cfg(show(feature, values("hidden"))))] pub struct B; } @@ -61,7 +61,7 @@ pub mod n { // 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")))] + #[doc(auto_cfg(hide(feature, values("hidden"))))] pub struct Z; } diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.rs b/tests/rustdoc-ui/cfg-hide-show-conflict.rs index 8e98b95c85bb9..b2f7728704c26 100644 --- a/tests/rustdoc-ui/cfg-hide-show-conflict.rs +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.rs @@ -1,3 +1,3 @@ #![feature(doc_cfg)] -#![doc(auto_cfg(hide(target_os = "linux")))] -#![doc(auto_cfg(show(windows, target_os = "linux")))] //~ ERROR +#![doc(auto_cfg(hide(target_os, values("linux"))))] +#![doc(auto_cfg(show(windows), show(target_os, values("linux"))))] //~ ERROR diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr index 22231e82cd7bf..b43b8104cdc12 100644 --- a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr @@ -1,14 +1,14 @@ error: same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item - --> $DIR/cfg-hide-show-conflict.rs:3:31 + --> $DIR/cfg-hide-show-conflict.rs:3:55 | -LL | #![doc(auto_cfg(show(windows, target_os = "linux")))] - | ^^^^^^^^^^^^^^^^^^^ +LL | #![doc(auto_cfg(show(windows), show(target_os, values("linux"))))] + | ^^^^^^^ | note: first change was here - --> $DIR/cfg-hide-show-conflict.rs:2:22 + --> $DIR/cfg-hide-show-conflict.rs:2:40 | -LL | #![doc(auto_cfg(hide(target_os = "linux")))] - | ^^^^^^^^^^^^^^^^^^^ +LL | #![doc(auto_cfg(hide(target_os, values("linux"))))] + | ^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/rustdoc-ui/doc-cfg-2.rs b/tests/rustdoc-ui/doc-cfg-2.rs index f615e96bbc6b5..4661bf5445796 100644 --- a/tests/rustdoc-ui/doc-cfg-2.rs +++ b/tests/rustdoc-ui/doc-cfg-2.rs @@ -13,7 +13,5 @@ // Shouldn't lint #[doc(auto_cfg(hide(windows)))] #[doc(auto_cfg(hide(feature = "windows")))] -//~^ WARN unexpected `cfg` condition name: `feature` -#[doc(auto_cfg(hide(foo)))] -//~^ WARN unexpected `cfg` condition name: `foo` +//~^ ERROR `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg-2.stderr b/tests/rustdoc-ui/doc-cfg-2.stderr index 164e755de8ad7..41e2d853aba54 100644 --- a/tests/rustdoc-ui/doc-cfg-2.stderr +++ b/tests/rustdoc-ui/doc-cfg-2.stderr @@ -30,19 +30,19 @@ note: the lint level is defined here LL | #![deny(invalid_doc_attributes)] | ^^^^^^^^^^^^^^^^^^^^^^ -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` --> $DIR/doc-cfg-2.rs:8:21 | LL | #[doc(auto_cfg(hide(true)))] | ^^^^ -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` --> $DIR/doc-cfg-2.rs:9:21 | LL | #[doc(auto_cfg(hide(42)))] | ^^ -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` --> $DIR/doc-cfg-2.rs:10:21 | LL | #[doc(auto_cfg(hide("a")))] @@ -60,23 +60,11 @@ error: expected boolean for `#[doc(auto_cfg = ...)]` LL | #[doc(auto_cfg = "a")] | ^^^ -warning: unexpected `cfg` condition name: `feature` +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` --> $DIR/doc-cfg-2.rs:15:21 | LL | #[doc(auto_cfg(hide(feature = "windows")))] | ^^^^^^^^^^^^^^^^^^^ - | - = help: to expect this configuration use `--check-cfg=cfg(feature, values("windows"))` - = note: see for more information about checking conditional configuration - -warning: unexpected `cfg` condition name: `foo` - --> $DIR/doc-cfg-2.rs:17:21 - | -LL | #[doc(auto_cfg(hide(foo)))] - | ^^^ - | - = help: to expect this configuration use `--check-cfg=cfg(foo)` - = note: see for more information about checking conditional configuration -error: aborting due to 6 previous errors; 4 warnings emitted +error: aborting due to 7 previous errors; 2 warnings emitted diff --git a/tests/rustdoc-ui/doc-cfg.rs b/tests/rustdoc-ui/doc-cfg.rs index abaea97192808..5f8b5a29a4402 100644 --- a/tests/rustdoc-ui/doc-cfg.rs +++ b/tests/rustdoc-ui/doc-cfg.rs @@ -6,5 +6,5 @@ //~| ERROR #[doc(cfg())] //~ ERROR #[doc(cfg(foo, bar))] //~ ERROR -#[doc(auto_cfg(hide(foo::bar)))] +#[doc(auto_cfg(hide(foo::bar)))] //~ ERROR pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index f3af95528b2d2..35d14f913b725 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -62,6 +62,15 @@ LL - #[doc(cfg(foo, bar))] LL + #[doc(cfg(any(foo, bar)))] | -error: aborting due to 4 previous errors +error[E0565]: malformed `doc` attribute input + --> $DIR/doc-cfg.rs:9:1 + | +LL | #[doc(auto_cfg(hide(foo::bar)))] + | ^^^^^^^^^^^^^^^^^^^^--------^^^^ + | | + | expected a valid identifier here + +error: aborting due to 5 previous errors -For more information about this error, try `rustc --explain E0805`. +Some errors have detailed explanations: E0565, E0805. +For more information about an error, try `rustc --explain E0565`. diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide-2.rs b/tests/rustdoc-ui/lints/doc_cfg_hide-2.rs new file mode 100644 index 0000000000000..f1ee59e64c93c --- /dev/null +++ b/tests/rustdoc-ui/lints/doc_cfg_hide-2.rs @@ -0,0 +1,4 @@ +#![feature(doc_cfg)] + +#![deny(invalid_doc_attributes)] +#![doc(auto_cfg(hide(not(windows))))] //~ ERROR diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide-2.stderr b/tests/rustdoc-ui/lints/doc_cfg_hide-2.stderr new file mode 100644 index 0000000000000..68503ebf132fa --- /dev/null +++ b/tests/rustdoc-ui/lints/doc_cfg_hide-2.stderr @@ -0,0 +1,11 @@ +error[E0565]: malformed `doc` attribute input + --> $DIR/doc_cfg_hide-2.rs:4:1 + | +LL | #![doc(auto_cfg(hide(not(windows))))] + | ^^^^^^^^^^^^^^^^^^^^^---^^^^^^^^^^^^^ + | | + | expected a valid identifier here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0565`. diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.rs b/tests/rustdoc-ui/lints/doc_cfg_hide.rs index 6c190f9befac8..28c0c4a89cebd 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.rs +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.rs @@ -2,4 +2,3 @@ #![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 a5ec8fdf5d34e..4b984feb3d5a1 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr @@ -16,11 +16,5 @@ 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/value items - --> $DIR/doc_cfg_hide.rs:5:22 - | -LL | #![doc(auto_cfg(hide(not(windows))))] - | ^^^^^^^^^^^^ - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors From 3c92a3ce4824637f31931fd135c53aac468b79a7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 20 May 2026 14:12:08 +0200 Subject: [PATCH 4/8] Move `doc-cfg` tests into the right location --- tests/rustdoc-html/{ => doc-cfg}/cfg-bool.rs | 0 tests/rustdoc-html/{doc_auto_cfg.rs => doc-cfg/doc-auto-cfg-2.rs} | 0 .../rustdoc-html/{ => doc-cfg}/doc-auto-cfg-public-in-private.rs | 0 tests/rustdoc-html/{ => doc-cfg}/doc-auto-cfg.rs | 0 tests/rustdoc-html/{ => doc-cfg}/doc_auto_cfg_reexports.rs | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename tests/rustdoc-html/{ => doc-cfg}/cfg-bool.rs (100%) rename tests/rustdoc-html/{doc_auto_cfg.rs => doc-cfg/doc-auto-cfg-2.rs} (100%) rename tests/rustdoc-html/{ => doc-cfg}/doc-auto-cfg-public-in-private.rs (100%) rename tests/rustdoc-html/{ => doc-cfg}/doc-auto-cfg.rs (100%) rename tests/rustdoc-html/{ => doc-cfg}/doc_auto_cfg_reexports.rs (100%) diff --git a/tests/rustdoc-html/cfg-bool.rs b/tests/rustdoc-html/doc-cfg/cfg-bool.rs similarity index 100% rename from tests/rustdoc-html/cfg-bool.rs rename to tests/rustdoc-html/doc-cfg/cfg-bool.rs diff --git a/tests/rustdoc-html/doc_auto_cfg.rs b/tests/rustdoc-html/doc-cfg/doc-auto-cfg-2.rs similarity index 100% rename from tests/rustdoc-html/doc_auto_cfg.rs rename to tests/rustdoc-html/doc-cfg/doc-auto-cfg-2.rs diff --git a/tests/rustdoc-html/doc-auto-cfg-public-in-private.rs b/tests/rustdoc-html/doc-cfg/doc-auto-cfg-public-in-private.rs similarity index 100% rename from tests/rustdoc-html/doc-auto-cfg-public-in-private.rs rename to tests/rustdoc-html/doc-cfg/doc-auto-cfg-public-in-private.rs diff --git a/tests/rustdoc-html/doc-auto-cfg.rs b/tests/rustdoc-html/doc-cfg/doc-auto-cfg.rs similarity index 100% rename from tests/rustdoc-html/doc-auto-cfg.rs rename to tests/rustdoc-html/doc-cfg/doc-auto-cfg.rs diff --git a/tests/rustdoc-html/doc_auto_cfg_reexports.rs b/tests/rustdoc-html/doc-cfg/doc_auto_cfg_reexports.rs similarity index 100% rename from tests/rustdoc-html/doc_auto_cfg_reexports.rs rename to tests/rustdoc-html/doc-cfg/doc_auto_cfg_reexports.rs From c383e5c2db59daac4dedc1064530ba83df82b858 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 13 Jun 2026 20:10:47 +0200 Subject: [PATCH 5/8] Add more regression tests for `doc(auto_cfg())` --- .../doc-cfg/hide-inheritance-key-only.rs | 26 ++++ .../rustdoc-html/doc-cfg/hide-inheritance.rs | 135 ++++++++++++++++++ tests/rustdoc-ui/doc-cfg-2.rs | 2 +- tests/rustdoc-ui/doc-cfg-2.stderr | 8 +- tests/rustdoc-ui/doc-cfg-3.rs | 7 + tests/rustdoc-ui/doc-cfg-3.stderr | 22 +++ tests/rustdoc-ui/doc-cfg-4.rs | 7 + tests/rustdoc-ui/doc-cfg-4.stderr | 20 +++ 8 files changed, 222 insertions(+), 5 deletions(-) create mode 100644 tests/rustdoc-html/doc-cfg/hide-inheritance-key-only.rs create mode 100644 tests/rustdoc-html/doc-cfg/hide-inheritance.rs create mode 100644 tests/rustdoc-ui/doc-cfg-3.rs create mode 100644 tests/rustdoc-ui/doc-cfg-3.stderr create mode 100644 tests/rustdoc-ui/doc-cfg-4.rs create mode 100644 tests/rustdoc-ui/doc-cfg-4.stderr diff --git a/tests/rustdoc-html/doc-cfg/hide-inheritance-key-only.rs b/tests/rustdoc-html/doc-cfg/hide-inheritance-key-only.rs new file mode 100644 index 0000000000000..d3842b4b9591c --- /dev/null +++ b/tests/rustdoc-html/doc-cfg/hide-inheritance-key-only.rs @@ -0,0 +1,26 @@ +// This test ensures that `auto_cfg(hide(key))` does not hide `key = value` and that +// `auto_cfg(hide(key, values(none())))` does the same. + +#![feature(doc_cfg)] +#![crate_name = "foo"] + +#![doc(auto_cfg(hide(meow)))] +#![doc(auto_cfg(hide(another_meow, values(none()))))] + +//@ has foo/fn.foo.html +//@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob' +//@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow' +//@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-another_meow' +#[cfg(not(meow))] +#[cfg(not(another_meow))] +#[cfg(not(blob))] +pub fn foo() {} + +//@ has foo/fn.bar.html +//@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lol' +//@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lol' +//@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-another_meow=lol' +#[cfg(not(meow = "lol"))] +#[cfg(not(another_meow = "lol"))] +#[cfg(not(blob = "lol"))] +pub fn bar() {} diff --git a/tests/rustdoc-html/doc-cfg/hide-inheritance.rs b/tests/rustdoc-html/doc-cfg/hide-inheritance.rs new file mode 100644 index 0000000000000..20cf6e3b7114f --- /dev/null +++ b/tests/rustdoc-html/doc-cfg/hide-inheritance.rs @@ -0,0 +1,135 @@ +// This test ensures that using `auto_cfg(show(key))` works correctly. + +#![feature(doc_cfg)] +#![crate_name = "foo"] + +#![doc(auto_cfg(hide(meow)))] +#![doc(auto_cfg(hide(meow, values("lol"))))] + +//@ has foo/fn.foo.html +//@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob' +//@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow' +#[cfg(not(meow))] +#[cfg(not(blob))] +pub fn foo() {} + +//@ has foo/fn.bar.html +//@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lol' +//@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lol' +#[cfg(not(meow = "lol"))] +#[cfg(not(blob = "lol"))] +pub fn bar() {} + +//@ has foo/fn.babar.html +//@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lola' +//@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lola' +#[cfg(not(meow = "lola"))] +#[cfg(not(blob = "lola"))] +pub fn babar() {} + +pub mod sub { + // We show again `meow`, however `meow="lol"` should still be hidden. + #![doc(auto_cfg(show(meow)))] + + //@ has foo/sub/fn.foo.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow' + #[cfg(not(meow))] + #[cfg(not(blob))] + pub fn foo() {} + + //@ has foo/sub/fn.bar.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lol' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lol' + #[cfg(not(meow = "lol"))] + #[cfg(not(blob = "lol"))] + pub fn bar() {} + + //@ has foo/sub/fn.babar.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lola' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lola' + #[cfg(not(meow = "lola"))] + #[cfg(not(blob = "lola"))] + pub fn babar() {} +} + +pub mod sub2 { + // We show again `meow = "lol`, however `meow` should still be hidden. + #![doc(auto_cfg(show(meow, values("lol"))))] + + //@ has foo/sub2/fn.foo.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow' + #[cfg(not(meow))] + #[cfg(not(blob))] + pub fn foo() {} + + //@ has foo/sub2/fn.bar.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lol' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lol' + #[cfg(not(meow = "lol"))] + #[cfg(not(blob = "lol"))] + pub fn bar() {} + + //@ has foo/sub2/fn.babar.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lola' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lola' + #[cfg(not(meow = "lola"))] + #[cfg(not(blob = "lola"))] + pub fn babar() {} +} + +pub mod sub3 { + // We show again `meow = "lol`, but by using `any()` this time. + #![doc(auto_cfg(show(meow, values(any()))))] + + //@ has foo/sub3/fn.foo.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow' + #[cfg(not(meow))] + #[cfg(not(blob))] + pub fn foo() {} + + //@ has foo/sub3/fn.bar.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lol' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lol' + #[cfg(not(meow = "lol"))] + #[cfg(not(blob = "lol"))] + pub fn bar() {} + + //@ has foo/sub3/fn.babar.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob=lola' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow=lola' + #[cfg(not(meow = "lola"))] + #[cfg(not(blob = "lola"))] + pub fn babar() {} +} + +// This test the mix of values and `none()`. +#[doc(auto_cfg( + hide(bla, values(none(), "tic")), + hide(alb, values(none())), +))] +pub mod sub4 { + //@ has foo/sub4/fn.foo.html + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla=tic' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-alb' + #[cfg(not(bla))] + #[cfg(not(bla = "tic"))] + #[cfg(not(alb))] + pub fn foo() {} + + //@ has foo/sub4/fn.foo2.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla=tic' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-alb' + #[doc(auto_cfg( + show(bla, values(none(), "tic")), + show(alb, values(none())), + ))] + #[cfg(not(bla))] + #[cfg(not(bla = "tic"))] + #[cfg(not(alb))] + pub fn foo2() {} +} diff --git a/tests/rustdoc-ui/doc-cfg-2.rs b/tests/rustdoc-ui/doc-cfg-2.rs index 4661bf5445796..ba9e22e755203 100644 --- a/tests/rustdoc-ui/doc-cfg-2.rs +++ b/tests/rustdoc-ui/doc-cfg-2.rs @@ -13,5 +13,5 @@ // Shouldn't lint #[doc(auto_cfg(hide(windows)))] #[doc(auto_cfg(hide(feature = "windows")))] -//~^ ERROR `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` +//~^ ERROR `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values(...)` pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg-2.stderr b/tests/rustdoc-ui/doc-cfg-2.stderr index 41e2d853aba54..aeb842c338c6b 100644 --- a/tests/rustdoc-ui/doc-cfg-2.stderr +++ b/tests/rustdoc-ui/doc-cfg-2.stderr @@ -30,19 +30,19 @@ note: the lint level is defined here LL | #![deny(invalid_doc_attributes)] | ^^^^^^^^^^^^^^^^^^^^^^ -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values(...)` --> $DIR/doc-cfg-2.rs:8:21 | LL | #[doc(auto_cfg(hide(true)))] | ^^^^ -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values(...)` --> $DIR/doc-cfg-2.rs:9:21 | LL | #[doc(auto_cfg(hide(42)))] | ^^ -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values(...)` --> $DIR/doc-cfg-2.rs:10:21 | LL | #[doc(auto_cfg(hide("a")))] @@ -60,7 +60,7 @@ error: expected boolean for `#[doc(auto_cfg = ...)]` LL | #[doc(auto_cfg = "a")] | ^^^ -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values()` +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or `values(...)` --> $DIR/doc-cfg-2.rs:15:21 | LL | #[doc(auto_cfg(hide(feature = "windows")))] diff --git a/tests/rustdoc-ui/doc-cfg-3.rs b/tests/rustdoc-ui/doc-cfg-3.rs new file mode 100644 index 0000000000000..39ae86086c882 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-3.rs @@ -0,0 +1,7 @@ +// Checks that you cannot have `any()` and values at the same time. + +#![deny(invalid_doc_attributes)] +#![feature(doc_cfg)] + +#![doc(auto_cfg(hide(target_os, values(any(), "linux"))))] //~ ERROR +#![doc(auto_cfg(hide(target_os, values("linux", any()))))] //~ ERROR diff --git a/tests/rustdoc-ui/doc-cfg-3.stderr b/tests/rustdoc-ui/doc-cfg-3.stderr new file mode 100644 index 0000000000000..b9cd74388ecd9 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-3.stderr @@ -0,0 +1,22 @@ +error: `any()` was used when other values were provided + --> $DIR/doc-cfg-3.rs:6:40 + | +LL | #![doc(auto_cfg(hide(target_os, values(any(), "linux"))))] + | ^^^^^ ------- value declared here + | +note: the lint level is defined here + --> $DIR/doc-cfg-3.rs:3:9 + | +LL | #![deny(invalid_doc_attributes)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: `any()` was used when other values were provided + --> $DIR/doc-cfg-3.rs:7:49 + | +LL | #![doc(auto_cfg(hide(target_os, values("linux", any()))))] + | ------- ^^^^^ + | | + | value declared here + +error: aborting due to 2 previous errors + diff --git a/tests/rustdoc-ui/doc-cfg-4.rs b/tests/rustdoc-ui/doc-cfg-4.rs new file mode 100644 index 0000000000000..eddbe7d84ff52 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-4.rs @@ -0,0 +1,7 @@ +// Checks that you cannot have any item inside `any()` and + +#![deny(invalid_doc_attributes)] +#![feature(doc_cfg)] + +#![doc(auto_cfg(hide(target_os, values(any("linux")))))] //~ ERROR +#![doc(auto_cfg(hide(target_os, values(none("linux")))))] //~ ERROR diff --git a/tests/rustdoc-ui/doc-cfg-4.stderr b/tests/rustdoc-ui/doc-cfg-4.stderr new file mode 100644 index 0000000000000..5de5e3b806594 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-4.stderr @@ -0,0 +1,20 @@ +error: `#![doc(auto_cfg(any(...)))]` only accepts identifiers or `values(...)` + --> $DIR/doc-cfg-4.rs:6:40 + | +LL | #![doc(auto_cfg(hide(target_os, values(any("linux")))))] + | ^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/doc-cfg-4.rs:3:9 + | +LL | #![deny(invalid_doc_attributes)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: `#![doc(auto_cfg(none(...)))]` only accepts identifiers or `values(...)` + --> $DIR/doc-cfg-4.rs:7:40 + | +LL | #![doc(auto_cfg(hide(target_os, values(none("linux")))))] + | ^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + From a3ad0686c4b81a09b7627910d963b112f94dfb6f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 13 Jun 2026 20:25:02 +0200 Subject: [PATCH 6/8] Update syntax information for `doc(auto_cfg(hide/show()))` in the rustdoc book --- src/doc/rustdoc/src/unstable-features.md | 50 ++++++++++++++++++++++-- tests/rustdoc-ui/doc-cfg-4.rs | 2 +- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index f16f375a5a84b..a6fb56e85ef59 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -850,12 +850,49 @@ pub mod futures { 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 syntax of `hide` is as follows: you can list as many `cfg` name as you want: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(feature, target_os)))] +``` + +With the above example, it means that `#[cfg(feature)]` and `#[cfg(target_os)]` won't be displayed in the docs. However, `#[cfg(target_os = "linux)]` or `#[cfg(feature = "something")]` will be displayed because only the key without values was marked as hidden. if you want to hide some values, you can do: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(feature, target_os, values("something", "linux"))))] +``` + +In this case, `#[cfg(feature = "linux")]`, `#[cfg(feature = "something")]`, `#[cfg(target_os = "something")]` and `#[cfg(target_os = "linux")]` will be hidden. All listed keys will be impacted by `values(...)`. You can split them by having two `hide`: + +```rust,ignore (nightly) +#[doc(auto_cfg( + hide(feature, values("something")), + hide(target_os, values("linux"))))] +``` + +Now, only `#[cfg(feature = "something")]` and `#[cfg(target_os = "linux")]` will be hidden. If you want to hide all values of a key, you can use `any()`: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(feature, values(any()))))] +``` + +If you want to hide when there is no value you can use `none()`: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(feature, values("something", none()))))] +``` + +If the previous example, both `#[cfg(feature)]` and `#[cfg(feature = "something")]` will be hidden. + +Rustdoc currently hides `test`, `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(unix, doctest), + hide(feature, values("something")), +))] #[doc(auto_cfg(hide()))] ``` @@ -865,7 +902,7 @@ But you cannot write: #[doc(auto_cfg(hide(not(unix))))] ``` -So if we use `doc(auto_cfg(hide(unix)))`, it means it will hide all mentions of `unix`: +So if we use `doc(auto_cfg(hide(unix)))`, it means it will hide all mentions of `unix` without a value: ```rust,ignore (nightly) #[cfg(unix)] // nothing displayed @@ -919,6 +956,8 @@ The reason behind this is that `doc(auto_cfg = ...)` enables or disables the fea 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(...))]`. +It follows the same syntax rules as for `#[doc(auto_cfg(hide(...)))]`. + For example: ```rust,ignore (nightly) @@ -936,7 +975,10 @@ pub mod futures { 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(unix, doctest), + show(feature, values("something")), +))] #[doc(auto_cfg(show()))] ``` diff --git a/tests/rustdoc-ui/doc-cfg-4.rs b/tests/rustdoc-ui/doc-cfg-4.rs index eddbe7d84ff52..75d0e30f76af9 100644 --- a/tests/rustdoc-ui/doc-cfg-4.rs +++ b/tests/rustdoc-ui/doc-cfg-4.rs @@ -1,4 +1,4 @@ -// Checks that you cannot have any item inside `any()` and +// Checks that you cannot have any item inside `any()` and `none()`. #![deny(invalid_doc_attributes)] #![feature(doc_cfg)] From 9653fd0ff54d35ae754c3cb5fb1ed0f734e60c52 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 17 Jun 2026 17:06:13 +0200 Subject: [PATCH 7/8] Add rustdoc-json test for `doc_cfg` --- tests/rustdoc-json/doc_cfg.rs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/rustdoc-json/doc_cfg.rs diff --git a/tests/rustdoc-json/doc_cfg.rs b/tests/rustdoc-json/doc_cfg.rs new file mode 100644 index 0000000000000..0033968305bd0 --- /dev/null +++ b/tests/rustdoc-json/doc_cfg.rs @@ -0,0 +1,5 @@ +#![feature(doc_cfg)] + +//@ is "$.index[?(@.name=='f')].attrs" '[{"other": "#[doc(auto_cfg(hide(bar, values(none())), hide(blob, values(\"a\", \"14\"))))]"}]' +#[doc(auto_cfg(hide(bar, values(none())), hide(blob, values("a", "14")),))] +pub fn f() {} From 702445c767598123165fbfaaeae13b18528768d2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 24 Jun 2026 18:30:22 +0200 Subject: [PATCH 8/8] Remove doc_cfg show/hide conflict error and correctly handle `values(any())` --- .../rustc_attr_parsing/src/attributes/doc.rs | 18 +- .../rustc_hir/src/attrs/data_structures.rs | 106 +++------ src/doc/rustdoc/src/unstable-features.md | 42 ++-- src/librustdoc/clean/cfg.rs | 221 ++++++++---------- src/librustdoc/json/conversions.rs | 33 ++- .../rustdoc-html/doc-cfg/hide-inheritance.rs | 46 +++- tests/rustdoc-ui/cfg-hide-show-conflict.rs | 3 - 7 files changed, 228 insertions(+), 241 deletions(-) delete mode 100644 tests/rustdoc-ui/cfg-hide-show-conflict.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 2aa9769b46be5..23eef6334ccee 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -325,17 +325,17 @@ impl DocParser { kind: LitKind::Str(symbol, _), span, .. - }) => match &mut cfg_values.values { - DocCfgHideShowValue::Any(any_span) => { + }) => match &mut cfg_values { + DocCfgHideShow::Any(any_span) => { cx.emit_lint( rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, DocAutoCfgHideShowValuesMix { value_span: *span }, *any_span, ); } - DocCfgHideShowValue::List(symbols) => { + DocCfgHideShow::List(symbols) => { if values_set.insert(symbol) { - symbols.push((*symbol, *span)); + symbols.push(DocCfgHideShowValue::new(*symbol, *span)); } } }, @@ -354,19 +354,19 @@ impl DocParser { && list.mixed().count() == 0 { if ident.name == sym::any { - if let DocCfgHideShowValue::List(values) = &cfg_values.values - && let Some((_, span)) = values.first() + if let DocCfgHideShow::List(values) = &cfg_values + && let Some(value) = values.first() { cx.emit_lint( rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - DocAutoCfgHideShowValuesMix { value_span: *span }, + DocAutoCfgHideShowValuesMix { value_span: value.span }, sub_item.span(), ); } else { - cfg_values.values = DocCfgHideShowValue::Any(sub_item.span()); + cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span())); } } else { - cfg_values.only_key = Some(sub_item.span()); + cfg_values.push_none(sub_item.span()); } } else { cx.emit_lint( diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index e35a9d68a4d00..acae158acb2b4 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -515,98 +515,68 @@ pub enum HideOrShow { Show, } -#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq)] -pub enum DocCfgHideShowValue { - Any(Span), - List(ThinVec<(Symbol, Span)>), +#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq)] +pub struct DocCfgHideShowValue { + pub span: Span, + /// If `value` is `None`, then it's a `none()` value. + pub value: Option, } -#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] -pub struct DocCfgHideShow { - /// If `Some`, then `cfg` without values (like `cfg(windows)`) will be shown/hidden. - /// The `Span` comes from where this value was set. - pub only_key: Option, - /// The values of this `cfg` to shown/hidden. - pub values: DocCfgHideShowValue, +impl DocCfgHideShowValue { + pub fn new(value: Symbol, span: Span) -> Self { + Self { span, value: Some(value) } + } + + pub fn new_none(span: Span) -> Self { + Self { span, value: None } + } +} + +#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq)] +pub enum DocCfgHideShow { + Any(Span), + List(ThinVec), } impl DocCfgHideShow { pub fn new() -> Self { - Self { only_key: None, values: DocCfgHideShowValue::List(ThinVec::new()) } + Self::List(ThinVec::new()) } pub fn new_with_only_key(span: Span) -> Self { - Self { only_key: Some(span), values: DocCfgHideShowValue::List(ThinVec::new()) } + let mut values = ThinVec::with_capacity(1); + values.push(DocCfgHideShowValue { span, value: None }); + Self::List(values) } - pub fn merge_with(&mut self, other: &Self) { - if self.only_key.is_none() - && let Some(span) = other.only_key + pub fn push_none(&mut self, span: Span) { + if let Self::List(values) = self + && !values.iter().any(|v| v.value.is_none()) { - self.only_key = Some(span); + values.push(DocCfgHideShowValue { span, value: None }); } - match (&mut self.values, &other.values) { - (DocCfgHideShowValue::Any(_), DocCfgHideShowValue::Any(_)) => { + } + + pub fn merge_with(&mut self, other: &Self) { + match (self, other) { + (Self::Any(_), Self::Any(_) | Self::List(_)) => { // Nothing to do. } - (_, DocCfgHideShowValue::Any(span)) => { + (s, Self::Any(span)) => { // We "upgrade" the list values to "all". - self.values = DocCfgHideShowValue::Any(*span); - } - (DocCfgHideShowValue::List(values), DocCfgHideShowValue::List(other_values)) => { - // Having duplicates is not an issue, we simply ignore them. Would be more - // convenient to have a `set` type though. T_T - for (other_symbol, other_span) in other_values { - if !values.iter().any(|(symbol, _)| symbol == other_symbol) { - values.push((*other_symbol, *other_span)); - } - } + *s = Self::Any(*span); } - (DocCfgHideShowValue::Any(_), DocCfgHideShowValue::List(_)) => { - // Nothing to do here either, we already accept all values. - } - } - } - - pub fn update_with(&mut self, other: &Self) { - if self.only_key.is_none() - && let Some(span) = other.only_key - { - self.only_key = Some(span); - } - match (&mut self.values, &other.values) { - (DocCfgHideShowValue::Any(_), _) => {} - (DocCfgHideShowValue::List(_), DocCfgHideShowValue::Any(span)) => { - self.values = DocCfgHideShowValue::Any(*span); - } - (DocCfgHideShowValue::List(values), DocCfgHideShowValue::List(other_values)) => { + (Self::List(values), Self::List(other_values)) => { // Having duplicates is not an issue, we simply ignore them. Would be more // convenient to have a `set` type though. T_T - for (other_symbol, other_span) in other_values { - if !values.iter().any(|(symbol, _)| symbol == other_symbol) { - values.push((*other_symbol, *other_span)); + for other in other_values { + if !values.iter().any(|value| value.value == other.value) { + values.push(*other); } } } } } - - pub fn remove_overlap(&mut self, other: &Self) { - if other.only_key.is_some() { - self.only_key = None; - } - match (&mut self.values, &other.values) { - (_, DocCfgHideShowValue::Any(_)) => { - self.values = DocCfgHideShowValue::List(ThinVec::new()); - } - (DocCfgHideShowValue::Any(_), DocCfgHideShowValue::List(values)) => { - self.values = DocCfgHideShowValue::List(values.clone()); - } - (DocCfgHideShowValue::List(current), DocCfgHideShowValue::List(values)) => { - current.retain(|(name, _)| !values.iter().any(|(other, _)| other == name)); - } - } - } } #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index a6fb56e85ef59..cc6cf2f8a648c 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -867,26 +867,40 @@ In this case, `#[cfg(feature = "linux")]`, `#[cfg(feature = "something")]`, `#[c ```rust,ignore (nightly) #[doc(auto_cfg( hide(feature, values("something")), - hide(target_os, values("linux"))))] + hide(target_os, values("linux")), +))] +``` + +Now, only `#[cfg(feature = "something")]` and `#[cfg(target_os = "linux")]` will be hidden. If you want to hide a key and all its values, you can use `any()`: + +```rust,ignore (nightly) +#[doc(auto_cfg( + hide(feature, values(any())), +))] ``` -Now, only `#[cfg(feature = "something")]` and `#[cfg(target_os = "linux")]` will be hidden. If you want to hide all values of a key, you can use `any()`: +If you want to hide only when there is no value you can use `none()`: ```rust,ignore (nightly) -#[doc(auto_cfg(hide(feature, values(any()))))] +#[doc(auto_cfg( + hide(feature, values("something", none())), +))] ``` -If you want to hide when there is no value you can use `none()`: +So now, if you want to forbid all values for a key, but allow the key itself, you can do: ```rust,ignore (nightly) -#[doc(auto_cfg(hide(feature, values("something", none()))))] +#[doc(auto_cfg( + hide(feature, values(any())), // We completely hide "feature". + show(feature), // We show again "feature" (but not any value). +))] ``` If the previous example, both `#[cfg(feature)]` and `#[cfg(feature = "something")]` will be hidden. Rustdoc currently hides `test`, `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: +The attribute accepts only a list of identifiers and `values()`. So you can write: ```rust,ignore (nightly) #[doc(auto_cfg( @@ -916,14 +930,6 @@ However, it only impacts the `unix` cfg, not the feature: #[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) @@ -941,14 +947,6 @@ pub mod module { } ``` -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(...)))]` diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 8721523f83f58..2950e3b563c1c 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -32,6 +32,87 @@ mod tests; #[cfg_attr(test, derive(PartialEq))] pub(crate) struct Cfg(CfgEntry); +// Similar to `hir::DocCfgHideShow` but allows to handle both `show` and `hide` as with the `except` +// field in `Any` variant. +#[derive(Clone, Debug)] +enum DocCfgHide { + Any { except: ThinVec }, + List(ThinVec), +} + +impl DocCfgHide { + fn new() -> Self { + Self::List([DocCfgHideShowValue::new_none(DUMMY_SP)].into()) + } + + fn contains(&self, value: Option) -> bool { + match self { + // Contains any values except the ones listed in `except`. + Self::Any { except } => !except.iter().any(|e| e.value == value), + Self::List(values) => values.iter().any(|v| v.value == value), + } + } + + fn merge_with(&mut self, other: &DocCfgHideShow) { + match (self, other) { + (Self::Any { except }, DocCfgHideShow::Any(_)) => { + except.clear(); + } + (s, DocCfgHideShow::Any(_)) => { + // We "upgrade" the list values to "all". + *s = Self::Any { except: ThinVec::new() }; + } + (Self::Any { except }, DocCfgHideShow::List(values)) => { + for other in values { + if let Some(index) = except.iter().position(|value| value.value == other.value) + { + except.remove(index); + } + } + } + (Self::List(values), DocCfgHideShow::List(other_values)) => { + for other in other_values { + if !values.iter().any(|value| value.value == other.value) { + values.push(*other); + } + } + } + } + } + + fn remove(&mut self, other: &DocCfgHideShow) { + match (self, other) { + (s, DocCfgHideShow::Any(_)) => { + *s = Self::List(ThinVec::new()); + } + (Self::Any { except }, DocCfgHideShow::List(other_values)) => { + for other in other_values { + if !except.iter().any(|value| value.value == other.value) { + except.push(*other); + } + } + } + (Self::List(values), DocCfgHideShow::List(other_values)) => { + for other in other_values { + if let Some(index) = values.iter().position(|value| value.value == other.value) + { + values.remove(index); + } + } + } + } + } +} + +impl From<&DocCfgHideShow> for DocCfgHide { + fn from(from: &DocCfgHideShow) -> Self { + match from { + DocCfgHideShow::Any(_) => Self::Any { except: ThinVec::new() }, + DocCfgHideShow::List(values) => Self::List(values.clone()), + } + } +} + /// Whether the configuration consists of just `Cfg` or `Not`. fn is_simple_cfg(cfg: &CfgEntry) -> bool { match cfg { @@ -55,24 +136,15 @@ fn is_any_cfg(cfg: &CfgEntry) -> bool { } } -fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashMap) -> Option { +fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashMap) -> Option { match cfg { CfgEntry::Bool(..) => Some(cfg.clone()), CfgEntry::NameValue { name, value, .. } => { - let mut is_stripped = false; - if let Some(values) = hidden.get(name) { - if let Some(value) = value { - match &values.values { - DocCfgHideShowValue::Any(_) => is_stripped = true, - DocCfgHideShowValue::List(values) => { - is_stripped = values.iter().any(|(v, _)| v == value); - } - } - } else { - is_stripped = values.only_key.is_some(); - } + if hidden.get(name).is_some_and(|values| values.contains(*value)) { + None + } else { + Some(cfg.clone()) } - if !is_stripped { Some(cfg.clone()) } else { None } } CfgEntry::Not(cfg, _) => { if let Some(cfg) = strip_hidden(cfg, hidden) { @@ -669,7 +741,7 @@ fn human_readable_target_env(env: Symbol) -> Option<&'static str> { pub(crate) struct CfgInfo { /// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active /// `doc(auto_cfg(show(...)))` cfgs. - hidden_cfg: FxHashMap, + hidden_cfg: FxHashMap, /// 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, @@ -685,9 +757,9 @@ impl Default for CfgInfo { fn default() -> Self { Self { hidden_cfg: FxHashMap::from_iter([ - (sym::test, DocCfgHideShow::new_with_only_key(DUMMY_SP)), - (sym::doc, DocCfgHideShow::new_with_only_key(DUMMY_SP)), - (sym::doctest, DocCfgHideShow::new_with_only_key(DUMMY_SP)), + (sym::test, DocCfgHide::new()), + (sym::doc, DocCfgHide::new()), + (sym::doctest, DocCfgHide::new()), ]), current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)), auto_cfg_active: true, @@ -696,104 +768,24 @@ impl Default for CfgInfo { } } -fn show_hide_show_conflict_error( - tcx: TyCtxt<'_>, - item_span: rustc_span::Span, - previous: rustc_span::Span, - errors: &mut usize, -) { - *errors += 1; - 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 check_if_no_overlap( - tcx: TyCtxt<'_>, - attrs: &FxHashMap, - cfg_name: Symbol, - info: &DocCfgHideShow, -) -> bool { - let mut errors = 0; - if let Some(other) = attrs.get(&cfg_name) { - match (&other.only_key, &info.only_key) { - (Some(previous_span), Some(span)) => { - show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); - } - _ => {} - } - match (&other.values, &info.values) { - (DocCfgHideShowValue::Any(previous_span), DocCfgHideShowValue::Any(span)) => { - show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); - } - (DocCfgHideShowValue::Any(previous_span), DocCfgHideShowValue::List(items)) => { - // If the list is empty, then it's just the default so no problem there. - if let Some((_, span)) = items.first() { - show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); - } - } - (DocCfgHideShowValue::List(previous_items), DocCfgHideShowValue::Any(span)) => { - // If the list is empty, then it's just the default so no problem there. - if let Some((_, previous_span)) = previous_items.first() { - show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); - } - } - (DocCfgHideShowValue::List(previous_items), DocCfgHideShowValue::List(items)) => { - for (previous_name, previous_span) in previous_items { - if let Some((_, span)) = items.iter().find(|(name, _)| name == previous_name) { - show_hide_show_conflict_error(tcx, *span, *previous_span, &mut errors); - } - } - } - } - } - errors == 0 -} - /// 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. -fn handle_auto_cfg_hide_show( - tcx: TyCtxt<'_>, - cfg_info: &mut CfgInfo, - attr: &CfgHideShow, - new_show_attrs: &mut FxHashMap, - new_hide_attrs: &mut FxHashMap, -) { +fn handle_auto_cfg_hide_show(cfg_info: &mut CfgInfo, attr: &CfgHideShow) { for (cfg_name, value) in &attr.values { if attr.kind == HideOrShow::Show { - if check_if_no_overlap(tcx, new_hide_attrs, *cfg_name, value) { - new_show_attrs - .entry(*cfg_name) - .and_modify(|entry| entry.update_with(value)) - .or_insert_with(|| value.clone()); - cfg_info - .hidden_cfg - .entry(*cfg_name) - .and_modify(|entry| entry.remove_overlap(value)) - .or_insert_with(|| value.clone()); - } + cfg_info + .hidden_cfg + .entry(*cfg_name) + .and_modify(|entry| entry.remove(value)) + .or_insert_with(|| value.into()); } else { - if check_if_no_overlap(tcx, new_show_attrs, *cfg_name, value) { - new_hide_attrs - .entry(*cfg_name) - .and_modify(|entry| entry.update_with(value)) - .or_insert_with(|| value.clone()); - cfg_info - .hidden_cfg - .entry(*cfg_name) - .and_modify(|entry| entry.update_with(value)) - .or_insert_with(|| value.clone()); - } + cfg_info + .hidden_cfg + .entry(*cfg_name) + .and_modify(|entry| entry.merge_with(value)) + .or_insert_with(|| value.into()); } } } @@ -828,9 +820,6 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator false } - let mut new_show_attrs = FxHashMap::default(); - let mut new_hide_attrs = FxHashMap::default(); - let mut doc_cfg = attrs .clone() .filter_map(|attr| match attr { @@ -881,13 +870,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator return None; } for (value, _) in &d.auto_cfg { - handle_auto_cfg_hide_show( - tcx, - cfg_info, - value, - &mut new_show_attrs, - &mut new_hide_attrs, - ); + handle_auto_cfg_hide_show(cfg_info, value); } } } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr { diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 9ec7e4bd49ceb..882220a0cae03 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -8,7 +8,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; use rustc_hir::attrs::{ - self, DeprecatedSince, DocAttribute, DocCfgHideShowValue, DocInline, HideOrShow, + self, DeprecatedSince, DocAttribute, DocCfgHideShow, DocInline, HideOrShow, }; use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def_id::DefId; @@ -1137,25 +1137,20 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) out.push_str(&format!("{kind}(")); for (name, cfgs) in &auto_cfg.values { out.push_str(&format!("{name}, values(")); - let mut pos = 0; - if cfgs.only_key.is_some() { - out.push_str("none()"); - pos += 1; - } - match &cfgs.values { - DocCfgHideShowValue::Any(_) => { - out.push_str(&format!("{}any()", if pos > 0 { ", " } else { "" })); + match cfgs { + DocCfgHideShow::Any(_) => { + out.push_str("any()"); } - DocCfgHideShowValue::List(values) => { - for (value, _) in values { - // We use `as_str` and debug display to have characters escaped - // and `"` characters surrounding the string. - out.push_str(&format!( - "{}{:?}", - if pos > 0 { ", " } else { "" }, - value.as_str() - )); - pos += 1; + DocCfgHideShow::List(values) => { + for (pos, value) in values.iter().enumerate() { + let separator = if pos > 0 { ", " } else { "" }; + if let Some(value) = &value.value { + // We use `as_str` and debug display to have characters escaped + // and `"` characters surrounding the string. + out.push_str(&format!("{separator}{:?}", value.as_str())); + } else { + out.push_str(&format!("{separator}none()")); + } } } } diff --git a/tests/rustdoc-html/doc-cfg/hide-inheritance.rs b/tests/rustdoc-html/doc-cfg/hide-inheritance.rs index 20cf6e3b7114f..3f0e2ffca2136 100644 --- a/tests/rustdoc-html/doc-cfg/hide-inheritance.rs +++ b/tests/rustdoc-html/doc-cfg/hide-inheritance.rs @@ -85,7 +85,7 @@ pub mod sub3 { //@ has foo/sub3/fn.foo.html //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-blob' - //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-meow' #[cfg(not(meow))] #[cfg(not(blob))] pub fn foo() {} @@ -133,3 +133,47 @@ pub mod sub4 { #[cfg(not(alb))] pub fn foo2() {} } + +// This test the mix of `any()` and values. +#[doc(auto_cfg( + hide(alb, values(any())), + hide(bla, values(any())), + show(bla, values("top")), +))] +pub mod sub5 { + //@ has foo/sub5/fn.foo.html + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-alb' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla=top' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla=a' + #[cfg(not(alb))] + #[cfg(not(bla = "top"))] + #[cfg(not(bla = "a"))] + pub fn foo() {} + + //@ has foo/sub5/fn.foo2.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-alb' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla=top' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla=a' + #[doc(auto_cfg( + show(alb, values(none())), + hide(bla, values("top")), + ))] + #[cfg(not(alb))] + #[cfg(not(bla = "top"))] + #[cfg(not(bla = "a"))] + pub fn foo2() {} + + //@ has foo/sub5/fn.foo3.html + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-alb' + //@ !has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla=top' + //@ has - '//*[@class="item-info"]/*[@class="stab portability"]' 'non-bla=a' + #[doc(auto_cfg( + show(alb, values(any())), + show(bla, values(any())), + hide(bla, values(none(), "top")), + ))] + #[cfg(not(alb))] + #[cfg(not(bla = "top"))] + #[cfg(not(bla = "a"))] + pub fn foo3() {} +} diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.rs b/tests/rustdoc-ui/cfg-hide-show-conflict.rs deleted file mode 100644 index b2f7728704c26..0000000000000 --- a/tests/rustdoc-ui/cfg-hide-show-conflict.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![feature(doc_cfg)] -#![doc(auto_cfg(hide(target_os, values("linux"))))] -#![doc(auto_cfg(show(windows), show(target_os, values("linux"))))] //~ ERROR