diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 988df2b200f86..23eef6334ccee 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 { + DocCfgHideShow::Any(any_span) => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + DocAutoCfgHideShowValuesMix { value_span: *span }, + *any_span, + ); + } + DocCfgHideShow::List(symbols) => { + if values_set.insert(symbol) { + symbols.push(DocCfgHideShowValue::new(*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 DocCfgHideShow::List(values) = &cfg_values + && let Some(value) = values.first() + { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + DocAutoCfgHideShowValuesMix { value_span: value.span }, + sub_item.span(), + ); + } else { + cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span())); + } + } else { + cfg_values.push_none(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..acae158acb2b4 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -515,19 +515,66 @@ pub enum HideOrShow { Show, } -#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] -pub struct CfgInfo { - pub name: Symbol, - pub name_span: Span, - pub value: Option<(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, +} + +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 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::List(ThinVec::new()) + } + + pub fn new_with_only_key(span: Span) -> Self { + let mut values = ThinVec::with_capacity(1); + values.push(DocCfgHideShowValue { span, value: None }); + Self::List(values) + } + + pub fn push_none(&mut self, span: Span) { + if let Self::List(values) = self + && !values.iter().any(|v| v.value.is_none()) + { + values.push(DocCfgHideShowValue { span, value: None }); + } + } + + pub fn merge_with(&mut self, other: &Self) { + match (self, other) { + (Self::Any(_), Self::Any(_) | Self::List(_)) => { + // Nothing to do. + } + (s, Self::Any(span)) => { + // We "upgrade" the list values to "all". + *s = Self::Any(*span); + } + (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 in other_values { + if !values.iter().any(|value| value.value == other.value) { + values.push(*other); + } + } + } } } } @@ -535,7 +582,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/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] diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index f16f375a5a84b..cc6cf2f8a648c 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -850,12 +850,63 @@ 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: -The attribute accepts only a list of identifiers or key/value items. So you can write: +```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 a key and all its values, you can use `any()`: + +```rust,ignore (nightly) +#[doc(auto_cfg( + hide(feature, values(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("something", 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(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 and `values()`. 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 +916,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 @@ -879,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) @@ -904,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(...)))]` @@ -919,6 +954,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 +973,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/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 170f1439ecc9c..2950e3b563c1c 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -8,11 +8,13 @@ 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, HideOrShow}; +use rustc_hir::attrs::{ + AttributeKind, CfgEntry, CfgHideShow, DocCfgHideShow, DocCfgHideShowValue, HideOrShow, +}; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::{Symbol, sym}; use rustc_span::{DUMMY_SP, Span}; @@ -30,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 { @@ -53,14 +136,14 @@ 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 { + CfgEntry::NameValue { name, value, .. } => { + if hidden.get(name).is_some_and(|values| values.contains(*value)) { None + } else { + Some(cfg.clone()) } } CfgEntry::Not(cfg, _) => { @@ -653,39 +736,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: 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 +756,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, DocCfgHide::new()), + (sym::doc, DocCfgHide::new()), + (sym::doctest, DocCfgHide::new()), ]), current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)), auto_cfg_active: true, @@ -712,51 +768,24 @@ impl Default for CfgInfo { } } -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(); -} - /// 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<(Symbol, Option), rustc_span::Span>, - new_hide_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, -) { - for value in &attr.values { - let simple = NameValueCfg::from(value); +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 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()); - } - cfg_info.hidden_cfg.remove(&simple); + cfg_info + .hidden_cfg + .entry(*cfg_name) + .and_modify(|entry| entry.remove(value)) + .or_insert_with(|| value.into()); } 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()); - } - cfg_info.hidden_cfg.insert(simple); + cfg_info + .hidden_cfg + .entry(*cfg_name) + .and_modify(|entry| entry.merge_with(value)) + .or_insert_with(|| value.into()); } } } @@ -791,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 { @@ -844,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 4de9c46900a76..882220a0cae03 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, DocCfgHideShow, DocInline, HideOrShow, +}; use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def_id::DefId; use rustc_hir::{HeaderSafety, Safety, find_attr}; @@ -1122,24 +1124,41 @@ 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(")); + match cfgs { + DocCfgHideShow::Any(_) => { + out.push_str("any()"); + } + 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()")); + } + } + } + } + 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/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-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_auto_cfg.rs b/tests/rustdoc-html/doc-cfg/doc-auto-cfg-2.rs similarity index 90% rename from tests/rustdoc-html/doc_auto_cfg.rs rename to tests/rustdoc-html/doc-cfg/doc-auto-cfg-2.rs index a1903e1a0ca3d..a33c5830157fb 100644 --- a/tests/rustdoc-html/doc_auto_cfg.rs +++ b/tests/rustdoc-html/doc-cfg/doc-auto-cfg-2.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-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-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_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 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..3f0e2ffca2136 --- /dev/null +++ b/tests/rustdoc-html/doc-cfg/hide-inheritance.rs @@ -0,0 +1,179 @@ +// 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() {} +} + +// 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-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-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() {} 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 8e98b95c85bb9..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 = "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 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..ba9e22e755203 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..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 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-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..75d0e30f76af9 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-4.rs @@ -0,0 +1,7 @@ +// Checks that you cannot have any item inside `any()` and `none()`. + +#![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 + 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