diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 3682d25d34147..af03117475b0d 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -173,7 +173,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { gate_doc!( "experimental" { cfg => doc_cfg - cfg_hide => doc_cfg_hide + auto_cfg => doc_cfg masked => doc_masked notable_trait => doc_notable_trait } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index b46eac6d8a602..9335a3dfaf88b 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -473,8 +473,6 @@ declare_features! ( (unstable, doc_auto_cfg, "1.58.0", Some(43781)), /// Allows `#[doc(cfg(...))]`. (unstable, doc_cfg, "1.21.0", Some(43781)), - /// Allows `#[doc(cfg_hide(...))]`. - (unstable, doc_cfg_hide, "1.57.0", Some(43781)), /// Allows `#[doc(masked)]`. (unstable, doc_masked, "1.21.0", Some(44027)), /// Allows `dyn* Trait` objects. diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 6d815e510ea20..6c0c3ff4c77f9 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -182,8 +182,17 @@ passes_doc_alias_start_end = passes_doc_attr_not_crate_level = `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute -passes_doc_cfg_hide_takes_list = - `#[doc(cfg_hide(...))]` takes a list of attributes +passes_doc_auto_cfg_expects_hide_or_show = + `only "hide" or "show" are allowed in "#[doc(auto_cfg(...))]"` + +passes_doc_auto_cfg_hide_show_expects_list = + `#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items + +passes_doc_auto_cfg_hide_show_unexpected_item = + `#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items + +passes_doc_auto_cfg_wrong_literal = + `expected boolean for #[doc(auto_cfg = ...)]` passes_doc_expect_str = doc {$attr_name} attribute expects a string: #[doc({$attr_name} = "a")] diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 5b7d45bb1526f..0d8c8e11c7943 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -9,7 +9,7 @@ use std::cell::Cell; use std::collections::hash_map::Entry; use rustc_abi::{Align, ExternAbi, Size}; -use rustc_ast::{AttrStyle, LitKind, MetaItemInner, MetaItemKind, MetaItemLit, ast}; +use rustc_ast::{AttrStyle, LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, ast}; use rustc_attr_data_structures::{AttributeKind, ReprAttr, find_attr}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey}; @@ -1303,16 +1303,74 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - /// Check that the `#![doc(cfg_hide(...))]` attribute only contains a list of attributes. - /// - fn check_doc_cfg_hide(&self, meta: &MetaItemInner, hir_id: HirId) { - if meta.meta_item_list().is_none() { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - meta.span(), - errors::DocCfgHideTakesList, - ); + /// Check that the `#![doc(auto_cfg(..))]` attribute has expected input. + fn check_doc_auto_cfg(&self, meta: &MetaItem, hir_id: HirId) { + match &meta.kind { + MetaItemKind::Word => {} + MetaItemKind::NameValue(lit) => { + if !matches!(lit.kind, LitKind::Bool(_)) { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span, + errors::DocAutoCfgWrongLiteral, + ); + } + } + MetaItemKind::List(list) => { + for item in list { + let Some(attr_name) = item.name() else { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span, + errors::DocAutoCfgExpectsHideOrShow, + ); + continue; + }; + if attr_name != sym::hide && attr_name != sym::show { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span, + errors::DocAutoCfgExpectsHideOrShow, + ); + } else if let Some(list) = item.meta_item_list() { + for item in list { + if item.meta_item_list().is_some() { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + item.span(), + errors::DocAutoCfgHideShowUnexpectedItem { + attr_name: attr_name.as_str(), + }, + ); + } else if match item { + MetaItemInner::Lit(_) => true, + // We already checked above that it's not a list. + MetaItemInner::MetaItem(meta) => meta.path.segments.len() != 1, + } { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + item.span(), + errors::DocAutoCfgHideShowUnexpectedItem { + attr_name: attr_name.as_str(), + }, + ); + } + } + } else { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span, + errors::DocAutoCfgHideShowExpectsList { attr_name: attr_name.as_str() }, + ); + } + } + } } } @@ -1375,10 +1433,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.check_attr_crate_level(attr, meta, hir_id); } - Some(sym::cfg_hide) => { - if self.check_attr_crate_level(attr, meta, hir_id) { - self.check_doc_cfg_hide(meta, hir_id); - } + Some(sym::auto_cfg) => { + self.check_doc_auto_cfg(i_meta, hir_id); } Some(sym::inline | sym::no_inline) => { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 00682a9c7a794..842a98f490428 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -333,8 +333,24 @@ pub(crate) struct DocTestLiteral; pub(crate) struct DocTestTakesList; #[derive(LintDiagnostic)] -#[diag(passes_doc_cfg_hide_takes_list)] -pub(crate) struct DocCfgHideTakesList; +#[diag(passes_doc_auto_cfg_wrong_literal)] +pub(crate) struct DocAutoCfgWrongLiteral; + +#[derive(LintDiagnostic)] +#[diag(passes_doc_auto_cfg_expects_hide_or_show)] +pub(crate) struct DocAutoCfgExpectsHideOrShow; + +#[derive(LintDiagnostic)] +#[diag(passes_doc_auto_cfg_hide_show_expects_list)] +pub(crate) struct DocAutoCfgHideShowExpectsList<'a> { + pub attr_name: &'a str, +} + +#[derive(LintDiagnostic)] +#[diag(passes_doc_auto_cfg_hide_show_unexpected_item)] +pub(crate) struct DocAutoCfgHideShowUnexpectedItem<'a> { + pub attr_name: &'a str, +} #[derive(LintDiagnostic)] #[diag(passes_doc_test_unknown_any)] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4e842a8f93a8f..f6e2e4bcd1291 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -524,6 +524,7 @@ symbols! { attributes, audit_that, augmented_assignments, + auto_cfg, auto_traits, autodiff_forward, autodiff_reverse, @@ -840,7 +841,6 @@ symbols! { doc_alias, doc_auto_cfg, doc_cfg, - doc_cfg_hide, doc_keyword, doc_masked, doc_notable_trait, @@ -1108,6 +1108,7 @@ symbols! { hashset_iter_ty, hexagon_target_feature, hidden, + hide, hint, homogeneous_aggregate, host, @@ -1912,6 +1913,7 @@ symbols! { shl_assign, shorter_tail_lifetimes, should_panic, + show, shr, shr_assign, sig_dfl, diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 30540f48aa105..a397012cafd04 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -64,14 +64,21 @@ issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/", test(no_crate_inject, attr(allow(unused_variables), deny(warnings))) )] -#![doc(cfg_hide( - not(test), - no_global_oom_handling, - not(no_global_oom_handling), - not(no_rc), - not(no_sync), - target_has_atomic = "ptr" -))] +#![cfg_attr( + bootstrap, + doc(cfg_hide( + not(test), + no_global_oom_handling, + not(no_global_oom_handling), + not(no_rc), + not(no_sync), + target_has_atomic = "ptr" + )) +)] +#![cfg_attr( + not(bootstrap), + doc(auto_cfg(hide(no_global_oom_handling, no_rc, no_sync, target_has_atomic = "ptr"))) +)] #![doc(rust_logo)] #![feature(rustdoc_internals)] #![no_std] @@ -192,7 +199,7 @@ // // Rustdoc features: #![feature(doc_cfg)] -#![feature(doc_cfg_hide)] +#![cfg_attr(bootstrap, feature(doc_cfg_hide))] // Technically, this is a bug in rustdoc: rustdoc sees the documentation on `#[lang = slice_alloc]` // blocks is for `&[T]`, which also has documentation using this feature in `core`, and gets mad // that the feature-gate isn't enabled. Ideally, it wouldn't check for the feature gate for docs diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index f2a5c40bada0b..cc738c7e45c1d 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -51,27 +51,54 @@ test(attr(allow(dead_code, deprecated, unused_variables, unused_mut))) )] #![doc(rust_logo)] -#![doc(cfg_hide( - no_fp_fmt_parse, - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64", - target_has_atomic = "8", - target_has_atomic = "16", - target_has_atomic = "32", - target_has_atomic = "64", - target_has_atomic = "ptr", - target_has_atomic_equal_alignment = "8", - target_has_atomic_equal_alignment = "16", - target_has_atomic_equal_alignment = "32", - target_has_atomic_equal_alignment = "64", - target_has_atomic_equal_alignment = "ptr", - target_has_atomic_load_store = "8", - target_has_atomic_load_store = "16", - target_has_atomic_load_store = "32", - target_has_atomic_load_store = "64", - target_has_atomic_load_store = "ptr", -))] +#![cfg_attr( + bootstrap, + doc(cfg_hide( + no_fp_fmt_parse, + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64", + target_has_atomic = "8", + target_has_atomic = "16", + target_has_atomic = "32", + target_has_atomic = "64", + target_has_atomic = "ptr", + target_has_atomic_equal_alignment = "8", + target_has_atomic_equal_alignment = "16", + target_has_atomic_equal_alignment = "32", + target_has_atomic_equal_alignment = "64", + target_has_atomic_equal_alignment = "ptr", + target_has_atomic_load_store = "8", + target_has_atomic_load_store = "16", + target_has_atomic_load_store = "32", + target_has_atomic_load_store = "64", + target_has_atomic_load_store = "ptr", + )) +)] +#![cfg_attr( + not(bootstrap), + doc(auto_cfg(hide( + no_fp_fmt_parse, + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64", + target_has_atomic = "8", + target_has_atomic = "16", + target_has_atomic = "32", + target_has_atomic = "64", + target_has_atomic = "ptr", + target_has_atomic_equal_alignment = "8", + target_has_atomic_equal_alignment = "16", + target_has_atomic_equal_alignment = "32", + target_has_atomic_equal_alignment = "64", + target_has_atomic_equal_alignment = "ptr", + target_has_atomic_load_store = "8", + target_has_atomic_load_store = "16", + target_has_atomic_load_store = "32", + target_has_atomic_load_store = "64", + target_has_atomic_load_store = "ptr", + ))) +)] #![no_core] #![rustc_coherence_is_core] #![rustc_preserve_ub_checks] @@ -132,6 +159,7 @@ // // Language features: // tidy-alphabetical-start +#![cfg_attr(bootstrap, feature(doc_cfg_hide))] #![feature(abi_unadjusted)] #![feature(adt_const_params)] #![feature(allow_internal_unsafe)] @@ -146,7 +174,6 @@ #![feature(decl_macro)] #![feature(deprecated_suggestion)] #![feature(doc_cfg)] -#![feature(doc_cfg_hide)] #![feature(doc_notable_trait)] #![feature(extern_types)] #![feature(f128)] diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 7c54e731edc62..75c2899f21ebd 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -235,7 +235,11 @@ test(attr(allow(dead_code, deprecated, unused_variables, unused_mut))) )] #![doc(rust_logo)] -#![doc(cfg_hide(not(test), no_global_oom_handling, not(no_global_oom_handling)))] +#![cfg_attr( + bootstrap, + doc(cfg_hide(not(test), no_global_oom_handling, not(no_global_oom_handling))) +)] +#![cfg_attr(not(bootstrap), doc(auto_cfg(hide(no_global_oom_handling))))] // Don't link to std. We are std. #![no_std] // Tell the compiler to link to either panic_abort or panic_unwind @@ -271,6 +275,7 @@ // tidy-alphabetical-start // stabilization was reverted after it hit beta +#![cfg_attr(bootstrap, feature(doc_cfg_hide))] #![feature(alloc_error_handler)] #![feature(allocator_internals)] #![feature(allow_internal_unsafe)] @@ -286,7 +291,6 @@ #![feature(decl_macro)] #![feature(deprecated_suggestion)] #![feature(doc_cfg)] -#![feature(doc_cfg_hide)] #![feature(doc_masked)] #![feature(doc_notable_trait)] #![feature(dropck_eyepatch)] diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 69e5a5adbec29..e77f232871b9e 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -56,88 +56,6 @@ It is also not emitted for foreign items, aliases, extern crates and imports. These features operate by extending the `#[doc]` attribute, and thus can be caught by the compiler and enabled with a `#![feature(...)]` attribute in your crate. -### `#[doc(cfg)]`: Recording what platforms or features are required for code to be present - - * Tracking issue: [#43781](https://github.com/rust-lang/rust/issues/43781) - -You can use `#[doc(cfg(...))]` to tell Rustdoc exactly which platform items appear on. -This has two effects: - -1. doctests will only run on the appropriate platforms, and -2. When Rustdoc renders documentation for that item, it will be accompanied by a banner explaining - that the item is only available on certain platforms. - -`#[doc(cfg)]` is intended to be used alongside [`#[cfg(doc)]`][cfg-doc]. -For example, `#[cfg(any(windows, doc))]` will preserve the item either on Windows or during the -documentation process. Then, adding a new attribute `#[doc(cfg(windows))]` will tell Rustdoc that -the item is supposed to be used on Windows. For example: - -```rust -#![feature(doc_cfg)] - -/// Token struct that can only be used on Windows. -#[cfg(any(windows, doc))] -#[doc(cfg(windows))] -pub struct WindowsToken; - -/// Token struct that can only be used on Unix. -#[cfg(any(unix, doc))] -#[doc(cfg(unix))] -pub struct UnixToken; - -/// Token struct that is only available with the `serde` feature -#[cfg(feature = "serde")] -#[doc(cfg(feature = "serde"))] -#[derive(serde::Deserialize)] -pub struct SerdeToken; -``` - -In this sample, the tokens will only appear on their respective platforms, but they will both appear -in documentation. - -`#[doc(cfg(...))]` was introduced to be used by the standard library and currently requires the -`#![feature(doc_cfg)]` feature gate. For more information, see [its chapter in the Unstable -Book][unstable-doc-cfg] and [its tracking issue][issue-doc-cfg]. - -### `doc_auto_cfg`: Automatically generate `#[doc(cfg)]` - - * Tracking issue: [#43781](https://github.com/rust-lang/rust/issues/43781) - -`doc_auto_cfg` is an extension to the `#[doc(cfg)]` feature. With it, you don't need to add -`#[doc(cfg(...)]` anymore unless you want to override the default behaviour. So if we take the -previous source code: - -```rust -#![feature(doc_auto_cfg)] - -/// Token struct that can only be used on Windows. -#[cfg(any(windows, doc))] -pub struct WindowsToken; - -/// Token struct that can only be used on Unix. -#[cfg(any(unix, doc))] -pub struct UnixToken; - -/// Token struct that is only available with the `serde` feature -#[cfg(feature = "serde")] -#[derive(serde::Deserialize)] -pub struct SerdeToken; -``` - -It'll render almost the same, the difference being that `doc` will also be displayed. To fix this, -you can use `doc_cfg_hide`: - -```rust -#![feature(doc_cfg_hide)] -#![doc(cfg_hide(doc))] -``` - -And `doc` won't show up anymore! - -[cfg-doc]: ./advanced-features.md -[unstable-doc-cfg]: ../unstable-book/language-features/doc-cfg.html -[issue-doc-cfg]: https://github.com/rust-lang/rust/issues/43781 - ### Adding your trait to the "Notable traits" dialog * Tracking issue: [#45040](https://github.com/rust-lang/rust/issues/45040) @@ -776,3 +694,271 @@ will be split as follows: "you today?", ] ``` + +## `#[doc(cfg)]` + +This feature aims at providing rustdoc users the possibility to add visual markers to the rendered documentation to know under which conditions an item is available (currently possible through the following unstable features: `doc_cfg`, `doc_auto_cfg` and `doc_cfg_hide`). + +It does not aim to allow having a same item with different `cfg`s to appear more than once in the generated documentation. + +It does not aim to document items which are *inactive* under the current configuration (i.e., “`cfg`ed out”). + +This features adds the following attributes: + + * `#[doc(auto_cfg)]`/`#[doc(auto_cfg = true)]`/`#[doc(auto_cfg = false)]` + * `#[doc(cfg(...))]` + * `#![doc(auto_cfg(hide(...)))]` / `#[doc(auto_cfg(show(...)))]` + +All of these attributes can be added to a module or to the crate root, and they will be inherited by the child items unless another attribute overrides it. This is why "opposite" attributes like `auto_cfg(hide(...))` and `auto_cfg(show(...))` are provided: they allow a child item to override its parent. + +### `#[doc(auto_cfg)`/`#[doc(auto_cfg = true)]`/`#[doc(auto_cfg = false)]` + +By default, `#[doc(auto_cfg)]` is enabled at the crate-level. When it's enabled, Rustdoc will automatically display `cfg(...)` compatibility information as-if the same `#[doc(cfg(...))]` had been specified. + +This attribute impacts the item on which it is used and its descendants. + +So if we take back the previous example: + +```rust +#[cfg(feature = "futures-io")] +pub mod futures {} +``` + +There's no need to "duplicate" the `cfg` into a `doc(cfg())` to make Rustdoc display it. + +In some situations, the detailed conditional compilation rules used to implement the feature might not serve as good documentation (for example, the list of supported platforms might be very long, and it might be better to document them in one place). To turn it off, add the `#[doc(auto_cfg = false)]` attribute on the item. + +If no argument is specified (ie `#[doc(auto_cfg)]`), it's the same as writing `#[doc(auto_cfg = true)]`. + +### `#[doc(cfg(...))]` + +This attribute provides a standardized format to override `#[cfg()]` attributes to document conditionally available items. Example: + +```rust,ignore (nightly) +// the "real" cfg condition +#[cfg(feature = "futures-io")] +// the `doc(cfg())` so it's displayed to the readers +#[doc(cfg(feature = "futures-io"))] +pub mod futures {} +``` + +It will display in the documentation for this module: + +```text +This is supported on feature="futures-io" only. +``` + +You can use it to display information in generated documentation, whether or not there is a `#[cfg()]` attribute: + +```rust,ignore (nightly) +#[doc(cfg(feature = "futures-io"))] +pub mod futures {} +``` + +It will be displayed exactly the same as the previous code. + +This attribute has the same syntax as conditional compilation, but it only causes documentation to be added. This means `#[doc(cfg(not(windows)))]` will not cause your docs to be hidden on non-windows targets, even though `#[cfg(not(windows))]` does do that. + +If `doc(auto_cfg)` is enabled on the item, `doc(cfg)` will override it anyway so in the two previous examples, even if the `doc(auto_cfg)` feature was enabled, it would still display the same thing. + +This attribute works on modules and on items. + +### `#[doc(auto_cfg(hide(...)))]` + +This attribute is used to prevent some `cfg` to be generated in the visual markers. It only applies to `#[doc(auto_cfg = true)]`, not to `#[doc(cfg(...))]`. So in the previous example: + +```rust,ignore (nightly) +#[cfg(any(unix, feature = "futures-io"))] +pub mod futures {} +``` + +It currently displays both `unix` and `feature = "futures-io"` into the documentation, which is not great. To prevent the `unix` cfg to ever be displayed, you can use this attribute at the crate root level: + +```rust,ignore (nightly) +#![doc(auto_cfg(hide(unix)))] +``` + +Or directly on a given item/module as it covers any of the item's descendants: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(unix)))] +#[cfg(any(unix, feature = "futures-io"))] +pub mod futures { + // `futures` and all its descendants won't display "unix" in their cfgs. +} +``` + +Then, the `unix` cfg will never be displayed into the documentation. + +Rustdoc currently hides `doc` and `doctest` attributes by default and reserves the right to change the list of "hidden by default" attributes. + +The attribute accepts only a list of identifiers or key/value items. So you can write: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(unix, doctest, feature = "something")))] +#[doc(auto_cfg(hide()))] +``` + +But you cannot write: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(not(unix))))] +``` + +So if we use `doc(auto_cfg(hide(unix)))`, it means it will hide all mentions of `unix`: + +```rust,ignore (nightly) +#[cfg(unix)] // nothing displayed +#[cfg(any(unix))] // nothing displayed +#[cfg(any(unix, windows))] // only `windows` displayed +``` + +However, it only impacts the `unix` cfg, not the feature: + +```rust,ignore (nightly) +#[cfg(feature = "unix")] // `feature = "unix"` is displayed +``` + +If `cfg_auto(show(...))` and `cfg_auto(hide(...))` are used to show/hide a same `cfg` on a same item, it'll emit an error. Example: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(unix)))] +#[doc(auto_cfg(show(unix)))] // Error! +pub fn foo() {} +``` + +Using this attribute will re-enable `auto_cfg` if it was disabled at this location: + +```rust,ignore (nightly) +#[doc(auto_cfg = false)] // Disabling `auto_cfg` +pub fn foo() {} +``` + +And using `doc(auto_cfg)` will re-enable it: + +```rust,ignore (nightly) +#[doc(auto_cfg = false)] // Disabling `auto_cfg` +pub mod module { + #[doc(auto_cfg(hide(unix)))] // `auto_cfg` is re-enabled. + pub fn foo() {} +} +``` + +However, using `doc(auto_cfg = ...)` and `doc(auto_cfg(...))` on the same item will emit an error: + +```rust,ignore (nightly) +#[doc(auto_cfg = false)] +#[doc(auto_cfg(hide(unix)))] // error +pub fn foo() {} +``` + +The reason behind this is that `doc(auto_cfg = ...)` enables or disables the feature, whereas `doc(auto_cfg(...))` enables it unconditionally, making the first attribute to appear useless as it will be overidden by the next `doc(auto_cfg)` attribute. + +### `#[doc(auto_cfg(show(...)))]` + +This attribute does the opposite of `#[doc(auto_cfg(hide(...)))]`: if you used `#[doc(auto_cfg(hide(...)))]` and want to revert its effect on an item and its descendants, you can use `#[doc(auto_cfg(show(...)))]`. +It only applies to `#[doc(auto_cfg = true)]`, not to `#[doc(cfg(...))]`. + +For example: + +```rust,ignore (nightly) +#[doc(auto_cfg(hide(unix)))] +#[cfg(any(unix, feature = "futures-io"))] +pub mod futures { + // `futures` and all its descendants won't display "unix" in their cfgs. + #[doc(auto_cfg(show(unix)))] + pub mod child { + // `child` and all its descendants will display "unix" in their cfgs. + } +} +``` + +The attribute accepts only a list of identifiers or key/value items. So you can write: + +```rust,ignore (nightly) +#[doc(auto_cfg(show(unix, doctest, feature = "something")))] +#[doc(auto_cfg(show()))] +``` + +But you cannot write: + +```rust,ignore (nightly) +#[doc(auto_cfg(show(not(unix))))] +``` + +If `auto_cfg(show(...))` and `auto_cfg(hide(...))` are used to show/hide a same `cfg` on a same item, it'll emit an error. Example: + +```rust,ignore (nightly) +#[doc(auto_cfg(show(unix)))] +#[doc(auto_cfg(hide(unix)))] // Error! +pub fn foo() {} +``` + +Using this attribute will re-enable `auto_cfg` if it was disabled at this location: + +```rust,ignore (nightly) +#[doc(auto_cfg = false)] // Disabling `auto_cfg` +#[doc(auto_cfg(show(unix)))] // `auto_cfg` is re-enabled. +pub fn foo() {} +``` + +## Inheritance + +Rustdoc merges `cfg` attributes from parent modules to its children. For example, in this case, the module `non_unix` will describe the entire compatibility matrix for the module, and not just its directly attached information: + +```rust,ignore (nightly) +#[doc(cfg(any(windows, unix)))] +pub mod desktop { + #[doc(cfg(not(unix)))] + pub mod non_unix { + // ... + } +} +``` + +This code will display: + +```text +Available on (Windows or Unix) and non-Unix only. +``` + +### Re-exports and inlining + +`cfg` attributes of a re-export are never merged with the re-exported item(s) attributes except if the re-export has the `#[doc(inline)]` attribute. In this case, the `cfg` of the re-exported item will be merged with the re-export's. + +When talking about "attributes merge", we mean that if the re-export has `#[cfg(unix)]` and the re-exported item has `#[cfg(feature = "foo")]`, you will only see `cfg(unix)` on the re-export and only `cfg(feature = "foo")` on the re-exported item, unless the re-export has `#[doc(inline)]`, then you will only see the re-exported item with both `cfg(unix)` and `cfg(feature = "foo")`. + +Example: + +```rust,ignore (nightly) +#[doc(cfg(any(windows, unix)))] +pub mod desktop { + #[doc(cfg(not(unix)))] + pub mod non_unix { + // code + } +} + +#[doc(cfg(target_os = "freebsd"))] +pub use desktop::non_unix as non_unix_desktop; +#[doc(cfg(target_os = "macos"))] +#[doc(inline)] +pub use desktop::non_unix as inlined_non_unix_desktop; +``` + +In this example, `non_unix_desktop` will only display `cfg(target_os = "freeebsd")` and not display any `cfg` from `desktop::non_unix`. + +On the contrary, `inlined_non_unix_desktop` will have cfgs from both the re-export and the re-exported item. + +So that also means that if a crate re-exports a foreign item, unless it has `#[doc(inline)]`, the `cfg` and `doc(cfg)` attributes will not be visible: + +```rust,ignore (nightly) +// dep: +#[cfg(feature = "a")] +pub struct S; + +// crate using dep: + +// There will be no mention of `feature = "a"` in the documentation. +pub use dep::S as Y; +``` diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index ebc276b38fbfa..eadca18998d29 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -253,6 +253,36 @@ impl Cfg { fn omit_preposition(&self) -> bool { matches!(self, Cfg::True | Cfg::False) } + + pub(crate) fn strip_hidden(&self, hidden: &FxHashSet) -> Option { + match self { + Self::True | Self::False => Some(self.clone()), + Self::Cfg(..) => { + if !hidden.contains(self) { + Some(self.clone()) + } else { + None + } + } + Self::Not(cfg) => { + if let Some(cfg) = cfg.strip_hidden(hidden) { + Some(Self::Not(Box::new(cfg))) + } else { + None + } + } + Self::Any(cfgs) => { + let cfgs = + cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::>(); + if cfgs.is_empty() { None } else { Some(Self::Any(cfgs)) } + } + Self::All(cfgs) => { + let cfgs = + cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::>(); + if cfgs.is_empty() { None } else { Some(Self::All(cfgs)) } + } + } + } } impl ops::Not for Cfg { diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 55a116a018a8a..f7616e7f37ef9 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -19,10 +19,10 @@ use tracing::{debug, trace}; use super::{Item, extract_cfg_from_attrs}; use crate::clean::{ - self, Attributes, ImplKind, ItemId, Type, clean_bound_vars, clean_generics, clean_impl_item, - clean_middle_assoc_item, clean_middle_field, clean_middle_ty, clean_poly_fn_sig, - clean_trait_ref_with_constraints, clean_ty, clean_ty_alias_inner_type, clean_ty_generics, - clean_variant_def, utils, + self, Attributes, CfgInfo, ImplKind, ItemId, Type, clean_bound_vars, clean_generics, + clean_impl_item, clean_middle_assoc_item, clean_middle_field, clean_middle_ty, + clean_poly_fn_sig, clean_trait_ref_with_constraints, clean_ty, clean_ty_alias_inner_type, + clean_ty_generics, clean_variant_def, utils, }; use crate::core::DocContext; use crate::formats::item_type::ItemType; @@ -395,6 +395,7 @@ pub(crate) fn merge_attrs( cx: &mut DocContext<'_>, old_attrs: &[hir::Attribute], new_attrs: Option<(&[hir::Attribute], Option)>, + cfg_info: &mut CfgInfo, ) -> (clean::Attributes, Option>) { // NOTE: If we have additional attributes (from a re-export), // always insert them first. This ensure that re-export @@ -409,12 +410,12 @@ pub(crate) fn merge_attrs( } else { Attributes::from_hir(&both) }, - extract_cfg_from_attrs(both.iter(), cx.tcx, &cx.cache.hidden_cfg), + extract_cfg_from_attrs(both.iter(), cx.tcx, cfg_info), ) } else { ( Attributes::from_hir(old_attrs), - extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg), + extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, cfg_info), ) } } @@ -593,7 +594,11 @@ pub(crate) fn build_impl( }); } - let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs); + // In here, we pass an empty `CfgInfo` because the computation of `cfg` happens later, so it + // doesn't matter at this point. + // + // We need to pass this empty `CfgInfo` because `merge_attrs` is used when computing the `cfg`. + let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs, &mut CfgInfo::default()); trace!("merged_attrs={merged_attrs:?}"); trace!( diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index e8cf25b190621..8864769cc8957 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -204,18 +204,10 @@ fn generate_item_with_correct_attrs( // We only keep the item's attributes. target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect() }; - let cfg = extract_cfg_from_attrs( - attrs.iter().map(move |(attr, _)| match attr { - Cow::Borrowed(attr) => *attr, - Cow::Owned(attr) => attr, - }), - cx.tcx, - &cx.cache.hidden_cfg, - ); let attrs = Attributes::from_hir_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false); let name = renamed.or(Some(name)); - let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, cfg); + let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, None); item.inner.inline_stmt_id = import_id; item } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 9e46d0b47e91b..cd36011b6fc01 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -5,10 +5,11 @@ use std::{fmt, iter}; use arrayvec::ArrayVec; use rustc_abi::{ExternAbi, VariantIdx}; +use rustc_ast::ast::{LitKind, MetaItemInner, MetaItemKind}; use rustc_attr_data_structures::{ AttributeKind, ConstStability, Deprecation, Stability, StableSince, }; -use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::lang_items::LangItem; @@ -477,7 +478,7 @@ impl Item { name, kind, Attributes::from_hir(hir_attrs), - extract_cfg_from_attrs(hir_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg), + None, ) } @@ -987,30 +988,6 @@ impl ItemKind { | KeywordItem => [].iter(), } } - - /// Returns `true` if this item does not appear inside an impl block. - pub(crate) fn is_non_assoc(&self) -> bool { - matches!( - self, - StructItem(_) - | UnionItem(_) - | EnumItem(_) - | TraitItem(_) - | ModuleItem(_) - | ExternCrateItem { .. } - | FunctionItem(_) - | TypeAliasItem(_) - | StaticItem(_) - | ConstantItem(_) - | TraitAliasItem(_) - | ForeignFunctionItem(_, _) - | ForeignStaticItem(_, _) - | ForeignTypeItem - | MacroItem(_) - | ProcMacroItem(_) - | PrimitiveItem(_) - ) - } } #[derive(Clone, Debug)] @@ -1030,14 +1007,100 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator>( .flatten() } +/// This type keeps track of (doc) cfg information as we go down the item tree. +#[derive(Clone, Debug)] +pub(crate) struct CfgInfo { + /// List of `doc(auto_cfg(hide(...)))` cfgs. + hidden_cfg: FxHashSet, + /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while + /// taking into account the `hidden_cfg` information. + current_cfg: Cfg, + /// Whether the `doc(auto_cfg())` feature is enabled or not at this point. + auto_cfg_active: bool, + /// If the parent item used `doc(cfg(...))`, then we don't want to overwrite `current_cfg`, + /// instead we will concatenate with it. However, if it's not the case, we need to overwrite + /// `current_cfg`. + parent_is_doc_cfg: bool, +} + +impl Default for CfgInfo { + fn default() -> Self { + Self { + hidden_cfg: [ + Cfg::Cfg(sym::test, None), + Cfg::Cfg(sym::doc, None), + Cfg::Cfg(sym::doctest, None), + ] + .into_iter() + .collect(), + current_cfg: Cfg::True, + auto_cfg_active: true, + parent_is_doc_cfg: false, + } + } +} + +fn show_hide_show_conflict_error( + tcx: TyCtxt<'_>, + item_span: rustc_span::Span, + previous: rustc_span::Span, +) { + let mut diag = tcx.sess.dcx().struct_span_err( + item_span, + format!( + "same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item" + ), + ); + diag.span_note(previous, "first change was here"); + diag.emit(); +} + +/// 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, + sub_attr: &MetaItemInner, + is_show: bool, + new_show_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, + new_hide_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, +) { + if let MetaItemInner::MetaItem(item) = sub_attr + && let MetaItemKind::List(items) = &item.kind + { + for item in items { + // Cfg parsing errors should already have been reported in `rustc_passes::check_attr`. + if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) { + if is_show { + if let Some(span) = new_hide_attrs.get(&(key, value)) { + show_hide_show_conflict_error(tcx, item.span(), *span); + } else { + new_show_attrs.insert((key, value), item.span()); + } + cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value)); + } else { + if let Some(span) = new_show_attrs.get(&(key, value)) { + show_hide_show_conflict_error(tcx, item.span(), *span); + } else { + new_hide_attrs.insert((key, value), item.span()); + } + cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value)); + } + } + } + } +} + pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator + Clone>( attrs: I, tcx: TyCtxt<'_>, - hidden_cfg: &FxHashSet, + cfg_info: &mut CfgInfo, ) -> Option> { - let doc_cfg_active = tcx.features().doc_cfg(); - let doc_auto_cfg_active = tcx.features().doc_auto_cfg(); - fn single(it: T) -> Option { let mut iter = it.into_iter(); let item = iter.next()?; @@ -1047,61 +1110,184 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator Some(item) } - let mut cfg = if doc_cfg_active || doc_auto_cfg_active { - let mut doc_cfg = attrs - .clone() - .filter(|attr| attr.has_name(sym::doc)) - .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) - .filter(|attr| attr.has_name(sym::cfg)) - .peekable(); - if doc_cfg.peek().is_some() && doc_cfg_active { - let sess = tcx.sess; - - doc_cfg.fold(Cfg::True, |mut cfg, item| { - if let Some(cfg_mi) = - item.meta_item().and_then(|item| rustc_expand::config::parse_cfg(item, sess)) + fn check_changed_auto_active_status( + changed_auto_active_status: &mut Option, + attr: &ast::MetaItem, + cfg_info: &mut CfgInfo, + tcx: TyCtxt<'_>, + new_value: bool, + ) -> bool { + if let Some(first_change) = changed_auto_active_status { + if cfg_info.auto_cfg_active != new_value { + tcx.sess + .dcx() + .struct_span_err( + vec![*first_change, attr.span], + "`auto_cfg` was disabled and enabled more than once on the same item", + ) + .emit(); + return true; + } + } else { + *changed_auto_active_status = Some(attr.span); + } + cfg_info.auto_cfg_active = new_value; + false + } + + let mut new_show_attrs = FxHashMap::default(); + let mut new_hide_attrs = FxHashMap::default(); + + let mut doc_cfg = attrs + .clone() + .filter(|attr| attr.has_name(sym::doc)) + .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) + .filter(|attr| attr.has_name(sym::cfg)) + .peekable(); + // If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes. + if doc_cfg.peek().is_some() { + let sess = tcx.sess; + // We overwrite existing `cfg`. + if !cfg_info.parent_is_doc_cfg { + cfg_info.current_cfg = Cfg::True; + cfg_info.parent_is_doc_cfg = true; + } + for attr in doc_cfg { + if let Some(cfg_mi) = + attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg(attr, sess)) + { + match Cfg::parse(cfg_mi) { + Ok(new_cfg) => cfg_info.current_cfg &= new_cfg, + Err(e) => { + sess.dcx().span_err(e.span, e.msg); + } + } + } + } + } else { + cfg_info.parent_is_doc_cfg = false; + } + + let mut changed_auto_active_status = None; + + // First we get all `doc(auto_cfg)` attributes. + for attr in attrs.clone() { + if let Some(ident) = attr.ident() + && ident.name == sym::doc + && let Some(attrs) = attr.meta_item_list() + { + for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) { + let MetaItemInner::MetaItem(attr) = attr else { + continue; + }; + match &attr.kind { + MetaItemKind::Word => { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + true, + ) { + return None; + } + } + MetaItemKind::NameValue(lit) => { + if let LitKind::Bool(value) = lit.kind { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + value, + ) { + return None; + } + } + } + MetaItemKind::List(sub_attrs) => { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + true, + ) { + return None; + } + for sub_attr in sub_attrs.iter() { + if let Some(ident) = sub_attr.ident() + && (ident.name == sym::show || ident.name == sym::hide) + { + handle_auto_cfg_hide_show( + tcx, + cfg_info, + &sub_attr, + ident.name == sym::show, + &mut new_show_attrs, + &mut new_hide_attrs, + ); + } + } + } + } + } + } + } + + // If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because + // `doc(cfg())` overrides `cfg()`). + for attr in attrs { + let Some(ident) = attr.ident() else { continue }; + match ident.name { + sym::cfg | sym::cfg_trace if !cfg_info.parent_is_doc_cfg => { + if let Some(attr) = single(attr.meta_item_list()?) + && let Ok(new_cfg) = Cfg::parse(&attr) { - match Cfg::parse(cfg_mi) { - Ok(new_cfg) => cfg &= new_cfg, - Err(e) => { - sess.dcx().span_err(e.span, e.msg); + cfg_info.current_cfg &= new_cfg; + } + } + // treat #[target_feature(enable = "feat")] attributes as if they were + // #[doc(cfg(target_feature = "feat"))] attributes as well + sym::target_feature + if !cfg_info.parent_is_doc_cfg + && let Some(attrs) = attr.meta_item_list() => + { + for attr in attrs { + if attr.has_name(sym::enable) && attr.value_str().is_some() { + // Clone `enable = "feat"`, change to `target_feature = "feat"`. + // Unwrap is safe because `value_str` succeeded above. + let mut meta = attr.meta_item().unwrap().clone(); + meta.path = + ast::Path::from_ident(Ident::with_dummy_span(sym::target_feature)); + + if let Ok(feat_cfg) = Cfg::parse(&ast::MetaItemInner::MetaItem(meta)) { + cfg_info.current_cfg &= feat_cfg; } } } - cfg - }) - } else if doc_auto_cfg_active { - // If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because - // `doc(cfg())` overrides `cfg()`). - attrs - .clone() - .filter(|attr| attr.has_name(sym::cfg_trace)) - .filter_map(|attr| single(attr.meta_item_list()?)) - .filter_map(|attr| Cfg::parse_without(attr.meta_item()?, hidden_cfg).ok().flatten()) - .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg) + } + _ => {} + } + } + + // If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing + // to be done here. + if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg { + None + } else if cfg_info.parent_is_doc_cfg { + if cfg_info.current_cfg == Cfg::True { + None } else { - Cfg::True + Some(Arc::new(cfg_info.current_cfg.clone())) } } else { - Cfg::True - }; - - // treat #[target_feature(enable = "feat")] attributes as if they were - // #[doc(cfg(target_feature = "feat"))] attributes as well - for attr in hir_attr_lists(attrs, sym::target_feature) { - if attr.has_name(sym::enable) && attr.value_str().is_some() { - // Clone `enable = "feat"`, change to `target_feature = "feat"`. - // Unwrap is safe because `value_str` succeeded above. - let mut meta = attr.meta_item().unwrap().clone(); - meta.path = ast::Path::from_ident(Ident::with_dummy_span(sym::target_feature)); - - if let Ok(feat_cfg) = Cfg::parse(&ast::MetaItemInner::MetaItem(meta)) { - cfg &= feat_cfg; - } + // Since we always want to collect all `cfg` items, we remove the hidden ones afterward. + match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) { + None | Some(Cfg::True) => None, + Some(cfg) => Some(Arc::new(cfg)), } } - - if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) } } pub(crate) trait NestedAttributesExt { diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index f9d2aa3d3b4bd..254bd596b5f52 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -4,7 +4,6 @@ use std::cell::Cell; use std::env; use std::sync::Arc; -use rustc_data_structures::fx::FxHashSet; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit}; use rustc_middle::hir::nested_filter; @@ -14,7 +13,7 @@ use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span}; use super::{DocTestVisitor, ScrapedDocTest}; -use crate::clean::{Attributes, extract_cfg_from_attrs}; +use crate::clean::{Attributes, CfgInfo, extract_cfg_from_attrs}; use crate::html::markdown::{self, ErrorCodes, LangString, MdRelLine}; struct RustCollector { @@ -117,7 +116,7 @@ impl HirCollector<'_> { ) { let ast_attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id)); if let Some(ref cfg) = - extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &FxHashSet::default()) + extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &mut CfgInfo::default()) && !cfg.matches(&self.tcx.sess.psess) { return; diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 4989bd718c9f9..b10f45ed770ab 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -125,8 +125,6 @@ pub(crate) struct Cache { /// /// Links are indexed by the DefId of the item they document. pub(crate) intra_doc_links: FxHashMap>, - /// Cfg that have been hidden via #![doc(cfg_hide(...))] - pub(crate) hidden_cfg: FxHashSet, /// Contains the list of `DefId`s which have been inlined. It is used when generating files /// to check if a stripped item should get its file generated or not: if it's inside a diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index eddafa9ba8e49..ea77065ac0799 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -1,12 +1,12 @@ //! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items. -use std::sync::Arc; +use rustc_ast::token::{Token, TokenKind}; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_hir::{AttrArgs, Attribute}; +use rustc_span::symbol::sym; -use rustc_hir::def_id::LocalDefId; - -use crate::clean::cfg::Cfg; use crate::clean::inline::{load_attrs, merge_attrs}; -use crate::clean::{Crate, Item, ItemKind}; +use crate::clean::{CfgInfo, Crate, Item, ItemKind}; use crate::core::DocContext; use crate::fold::DocFolder; use crate::passes::Pass; @@ -18,80 +18,125 @@ pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass { }; pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate { - CfgPropagator { parent_cfg: None, parent: None, cx }.fold_crate(cr) + if cx.tcx.features().doc_cfg() { + CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr) + } else { + cr + } } struct CfgPropagator<'a, 'tcx> { - parent_cfg: Option>, - parent: Option, cx: &'a mut DocContext<'tcx>, + cfg_info: CfgInfo, } -impl CfgPropagator<'_, '_> { - // Some items need to merge their attributes with their parents' otherwise a few of them - // (mostly `cfg` ones) will be missing. - fn merge_with_parent_attributes(&mut self, item: &mut Item) { - let check_parent = match &item.kind { - // impl blocks can be in different modules with different cfg and we need to get them - // as well. - ItemKind::ImplItem(_) => false, - kind if kind.is_non_assoc() => true, - _ => return, - }; +/// Returns true if the provided `token` is a `cfg` ident. +fn is_cfg_token(token: &TokenTree) -> bool { + // We only keep `doc(cfg)` items. + matches!( + token, + TokenTree::Token( + Token { + kind: TokenKind::Ident( + ident, + _, + ), + .. + }, + _, + ) if *ident == sym::cfg, + ) +} - let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else { - return; - }; +/// We only want to keep `#[cfg()]` and `#[doc(cfg())]` attributes so we rebuild a vec of +/// `TokenTree` with only the tokens we're interested into. +fn filter_non_cfg_tokens_from_list(args_tokens: &TokenStream) -> Vec { + let mut tokens = Vec::with_capacity(args_tokens.len()); + let mut skip_next_delimited = false; + for token in args_tokens.iter() { + match token { + TokenTree::Delimited(..) => { + if !skip_next_delimited { + tokens.push(token.clone()); + } + skip_next_delimited = false; + } + token if is_cfg_token(token) => { + skip_next_delimited = false; + tokens.push(token.clone()); + } + _ => { + skip_next_delimited = true; + } + } + } + tokens +} - if check_parent { - let expected_parent = self.cx.tcx.opt_local_parent(def_id); - // If parents are different, it means that `item` is a reexport and we need - // to compute the actual `cfg` by iterating through its "real" parents. - if self.parent.is_some() && self.parent == expected_parent { - return; +/// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from +/// it and put them into `attrs`. +fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) { + for attr in new_attrs { + if attr.is_doc_comment() { + continue; + } + let mut attr = attr.clone(); + if let Attribute::Unparsed(ref mut normal) = attr + && let [ident] = &*normal.path.segments + { + let ident = ident.name; + if ident == sym::doc + && let AttrArgs::Delimited(args) = &mut normal.args + { + let tokens = filter_non_cfg_tokens_from_list(&args.tokens); + args.tokens = TokenStream::new(tokens); + attrs.push(attr); + } else if ident == sym::cfg_trace { + // If it's a `cfg()` attribute, we keep it. + attrs.push(attr); } } + } +} +impl CfgPropagator<'_, '_> { + // Some items need to merge their attributes with their parents' otherwise a few of them + // (mostly `cfg` ones) will be missing. + fn merge_with_parent_attributes(&mut self, item: &mut Item) { let mut attrs = Vec::new(); - let mut next_def_id = def_id; - while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) { - attrs.extend_from_slice(load_attrs(self.cx, parent_def_id.to_def_id())); - next_def_id = parent_def_id; + // We only need to merge an item attributes with its parent's in case it's an impl as an + // impl might not be defined in the same module as the item it implements. + // + // Otherwise, `cfg_info` already tracks everything we need so nothing else to do! + if matches!(item.kind, ItemKind::ImplItem(_)) + && let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) + { + let mut next_def_id = def_id; + while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) { + let x = load_attrs(self.cx, parent_def_id.to_def_id()); + add_only_cfg_attributes(&mut attrs, x); + next_def_id = parent_def_id; + } } - let (_, cfg) = - merge_attrs(self.cx, item.attrs.other_attrs.as_slice(), Some((&attrs, None))); + let (_, cfg) = merge_attrs( + self.cx, + item.attrs.other_attrs.as_slice(), + Some((&attrs, None)), + &mut self.cfg_info, + ); item.inner.cfg = cfg; } } impl DocFolder for CfgPropagator<'_, '_> { fn fold_item(&mut self, mut item: Item) -> Option { - let old_parent_cfg = self.parent_cfg.clone(); + let old_cfg_info = self.cfg_info.clone(); self.merge_with_parent_attributes(&mut item); - let new_cfg = match (self.parent_cfg.take(), item.inner.cfg.take()) { - (None, None) => None, - (Some(rc), None) | (None, Some(rc)) => Some(rc), - (Some(mut a), Some(b)) => { - let b = Arc::try_unwrap(b).unwrap_or_else(|rc| Cfg::clone(&rc)); - *Arc::make_mut(&mut a) &= b; - Some(a) - } - }; - self.parent_cfg = new_cfg.clone(); - item.inner.cfg = new_cfg; - - let old_parent = - if let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) { - self.parent.replace(def_id) - } else { - self.parent.take() - }; let result = self.fold_item_recur(item); - self.parent_cfg = old_parent_cfg; - self.parent = old_parent; + self.cfg_info = old_cfg_info; Some(result) } diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 5b52e785b8f59..ec71cba224ea3 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -5,10 +5,10 @@ use std::mem; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir as hir; +use rustc_hir::Node; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet}; use rustc_hir::intravisit::{Visitor, walk_body, walk_item}; -use rustc_hir::{CRATE_HIR_ID, Node}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::Span; @@ -17,7 +17,6 @@ use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{Symbol, kw, sym}; use tracing::debug; -use crate::clean::cfg::Cfg; use crate::clean::utils::{inherits_doc_hidden, should_ignore_res}; use crate::clean::{NestedAttributesExt, hir_attr_lists, reexport_chain}; use crate::core; @@ -149,32 +148,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { } } - self.cx.cache.hidden_cfg = self - .cx - .tcx - .hir_attrs(CRATE_HIR_ID) - .iter() - .filter(|attr| attr.has_name(sym::doc)) - .flat_map(|attr| attr.meta_item_list().into_iter().flatten()) - .filter(|attr| attr.has_name(sym::cfg_hide)) - .flat_map(|attr| { - attr.meta_item_list() - .unwrap_or(&[]) - .iter() - .filter_map(|attr| { - Cfg::parse(attr) - .map_err(|e| self.cx.sess().dcx().span_err(e.span, e.msg)) - .ok() - }) - .collect::>() - }) - .chain([ - Cfg::Cfg(sym::test, None), - Cfg::Cfg(sym::doc, None), - Cfg::Cfg(sym::doctest, None), - ]) - .collect(); - self.cx.cache.exact_paths = self.exact_paths; top_level_module } diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.rs b/tests/rustdoc-ui/cfg-hide-show-conflict.rs new file mode 100644 index 0000000000000..8e98b95c85bb9 --- /dev/null +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.rs @@ -0,0 +1,3 @@ +#![feature(doc_cfg)] +#![doc(auto_cfg(hide(target_os = "linux")))] +#![doc(auto_cfg(show(windows, target_os = "linux")))] //~ ERROR diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr new file mode 100644 index 0000000000000..22231e82cd7bf --- /dev/null +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr @@ -0,0 +1,14 @@ +error: same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item + --> $DIR/cfg-hide-show-conflict.rs:3:31 + | +LL | #![doc(auto_cfg(show(windows, target_os = "linux")))] + | ^^^^^^^^^^^^^^^^^^^ + | +note: first change was here + --> $DIR/cfg-hide-show-conflict.rs:2:22 + | +LL | #![doc(auto_cfg(hide(target_os = "linux")))] + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc-ui/doc-cfg.rs b/tests/rustdoc-ui/doc-cfg.rs index 14943bbc3418e..9840c305290ae 100644 --- a/tests/rustdoc-ui/doc-cfg.rs +++ b/tests/rustdoc-ui/doc-cfg.rs @@ -8,4 +8,13 @@ //~^^ WARN unexpected `cfg` condition name: `bar` #[doc(cfg())] //~ ERROR #[doc(cfg(foo, bar))] //~ ERROR +#[doc(auto_cfg(42))] //~ ERROR +#[doc(auto_cfg(hide(true)))] //~ ERROR +#[doc(auto_cfg(hide(42)))] //~ ERROR +#[doc(auto_cfg(hide("a")))] //~ ERROR +#[doc(auto_cfg(hide(foo::bar)))] //~ ERROR +// Shouldn't lint +#[doc(auto_cfg(hide(windows)))] +#[doc(auto_cfg(hide(feature = "windows")))] +#[doc(auto_cfg(hide(foo)))] pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index 1233ee010de21..36ca18eed8fc8 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -1,26 +1,34 @@ -error: `cfg` predicate is not specified - --> $DIR/doc-cfg.rs:3:7 +error: `only "hide" or "show" are allowed in "#[doc(auto_cfg(...))]"` + --> $DIR/doc-cfg.rs:11:7 | -LL | #[doc(cfg(), cfg(foo, bar))] - | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` +LL | #[doc(auto_cfg(42))] + | ^^^^^^^^^^^^ + | + = note: `#[deny(invalid_doc_attributes)]` on by default -error: multiple `cfg` predicates are specified - --> $DIR/doc-cfg.rs:3:23 +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg.rs:12:21 | -LL | #[doc(cfg(), cfg(foo, bar))] - | ^^^ +LL | #[doc(auto_cfg(hide(true)))] + | ^^^^ -error: `cfg` predicate is not specified - --> $DIR/doc-cfg.rs:9:7 +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg.rs:13:21 | -LL | #[doc(cfg())] - | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` +LL | #[doc(auto_cfg(hide(42)))] + | ^^ -error: multiple `cfg` predicates are specified - --> $DIR/doc-cfg.rs:10:16 +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg.rs:14:21 | -LL | #[doc(cfg(foo, bar))] - | ^^^ +LL | #[doc(auto_cfg(hide("a")))] + | ^^^ + +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg.rs:15:21 + | +LL | #[doc(auto_cfg(hide(foo::bar)))] + | ^^^^^^^^ warning: unexpected `cfg` condition name: `foo` --> $DIR/doc-cfg.rs:6:11 @@ -42,5 +50,29 @@ LL | #[doc(cfg(foo), cfg(bar))] = help: to expect this configuration use `--check-cfg=cfg(bar)` = note: see for more information about checking conditional configuration -error: aborting due to 4 previous errors; 2 warnings emitted +error: `cfg` predicate is not specified + --> $DIR/doc-cfg.rs:3:7 + | +LL | #[doc(cfg(), cfg(foo, bar))] + | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` + +error: multiple `cfg` predicates are specified + --> $DIR/doc-cfg.rs:3:23 + | +LL | #[doc(cfg(), cfg(foo, bar))] + | ^^^ + +error: `cfg` predicate is not specified + --> $DIR/doc-cfg.rs:9:7 + | +LL | #[doc(cfg())] + | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` + +error: multiple `cfg` predicates are specified + --> $DIR/doc-cfg.rs:10:16 + | +LL | #[doc(cfg(foo, bar))] + | ^^^ + +error: aborting due to 9 previous errors; 2 warnings emitted diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg.rs b/tests/rustdoc-ui/feature-gate-doc_cfg.rs new file mode 100644 index 0000000000000..b474a1524bc19 --- /dev/null +++ b/tests/rustdoc-ui/feature-gate-doc_cfg.rs @@ -0,0 +1,6 @@ +#![doc(auto_cfg)] //~ ERROR +#![doc(auto_cfg(false))] //~ ERROR +#![doc(auto_cfg(true))] //~ ERROR +#![doc(auto_cfg(hide(feature = "solecism")))] //~ ERROR +#![doc(auto_cfg(show(feature = "bla")))] //~ ERROR +#![doc(cfg(feature = "solecism"))] //~ ERROR diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg.stderr b/tests/rustdoc-ui/feature-gate-doc_cfg.stderr new file mode 100644 index 0000000000000..68a86c1abb777 --- /dev/null +++ b/tests/rustdoc-ui/feature-gate-doc_cfg.stderr @@ -0,0 +1,63 @@ +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:1:1 + | +LL | #![doc(auto_cfg)] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:2:1 + | +LL | #![doc(auto_cfg(false))] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:3:1 + | +LL | #![doc(auto_cfg(true))] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:4:1 + | +LL | #![doc(auto_cfg(hide(feature = "solecism")))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(auto_cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:5:1 + | +LL | #![doc(auto_cfg(show(feature = "bla")))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `#[doc(cfg)]` is experimental + --> $DIR/feature-gate-doc_cfg.rs:6:1 + | +LL | #![doc(cfg(feature = "solecism"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #43781 for more information + = help: add `#![feature(doc_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs b/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs deleted file mode 100644 index 17812018b9b7a..0000000000000 --- a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![doc(cfg_hide(test))] -//~^ ERROR `#[doc(cfg_hide)]` is experimental - -#[cfg(not(test))] -pub fn public_fn() {} -#[cfg(test)] -pub fn internal_use_only() {} diff --git a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr b/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr deleted file mode 100644 index 55135986ffe76..0000000000000 --- a/tests/rustdoc-ui/feature-gate-doc_cfg_hide.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0658]: `#[doc(cfg_hide)]` is experimental - --> $DIR/feature-gate-doc_cfg_hide.rs:1:1 - | -LL | #![doc(cfg_hide(test))] - | ^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #43781 for more information - = help: add `#![feature(doc_cfg_hide)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0658`. diff --git a/tests/rustdoc-ui/invalid-cfg.rs b/tests/rustdoc-ui/invalid-cfg.rs index d237b8605c068..aff36286c535c 100644 --- a/tests/rustdoc-ui/invalid-cfg.rs +++ b/tests/rustdoc-ui/invalid-cfg.rs @@ -1,4 +1,4 @@ #![feature(doc_cfg)] #[doc(cfg = "x")] //~ ERROR not followed by parentheses #[doc(cfg(x, y))] //~ ERROR multiple `cfg` predicates -struct S {} +pub struct S {} diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.rs b/tests/rustdoc-ui/lints/doc_cfg_hide.rs index 9a8bce2a92aa5..397b21393e5c7 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.rs +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.rs @@ -1,7 +1,4 @@ -#![feature(doc_cfg_hide)] - -#![doc(cfg_hide = "test")] //~ ERROR -#![doc(cfg_hide)] //~ ERROR - -#[doc(cfg_hide(doc))] //~ ERROR -pub fn foo() {} +#![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 0c9d0879b0ac7..c63c8d607fa02 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr @@ -1,27 +1,22 @@ -error: this attribute can only be applied at the crate level - --> $DIR/doc_cfg_hide.rs:6:7 +error: `#![doc(auto_cfg(hide(...)))]` expects a list of items + --> $DIR/doc_cfg_hide.rs:2:8 | -LL | #[doc(cfg_hide(doc))] - | ^^^^^^^^^^^^^ +LL | #![doc(auto_cfg(hide = "test"))] + | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: read for more information = note: `#[deny(invalid_doc_attributes)]` on by default -help: to apply to the crate, use an inner attribute - | -LL | #![doc(cfg_hide(doc))] - | + -error: `#[doc(cfg_hide(...))]` takes a list of attributes +error: `#![doc(auto_cfg(hide(...)))]` expects a list of items --> $DIR/doc_cfg_hide.rs:3:8 | -LL | #![doc(cfg_hide = "test")] - | ^^^^^^^^^^^^^^^^^ +LL | #![doc(auto_cfg(hide))] + | ^^^^^^^^^^^^^^ -error: `#[doc(cfg_hide(...))]` takes a list of attributes - --> $DIR/doc_cfg_hide.rs:4:8 +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc_cfg_hide.rs:4:22 | -LL | #![doc(cfg_hide)] - | ^^^^^^^^ +LL | #![doc(auto_cfg(hide(not(windows))))] + | ^^^^^^^^^^^^ error: aborting due to 3 previous errors diff --git a/tests/rustdoc/doc-auto-cfg.rs b/tests/rustdoc/doc-auto-cfg.rs index b3fe8922fd788..e56cf18d08a0c 100644 --- a/tests/rustdoc/doc-auto-cfg.rs +++ b/tests/rustdoc/doc-auto-cfg.rs @@ -1,4 +1,4 @@ -#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] #![crate_name = "foo"] //@ has foo/fn.foo.html diff --git a/tests/rustdoc/doc-cfg/doc-cfg-hide.rs b/tests/rustdoc/doc-cfg/doc-cfg-hide.rs index ceb1f99fae0d1..e919206d3a478 100644 --- a/tests/rustdoc/doc-cfg/doc-cfg-hide.rs +++ b/tests/rustdoc/doc-cfg/doc-cfg-hide.rs @@ -1,7 +1,7 @@ #![crate_name = "oud"] -#![feature(doc_auto_cfg, doc_cfg, doc_cfg_hide)] +#![feature(doc_cfg)] -#![doc(cfg_hide(feature = "solecism"))] +#![doc(auto_cfg(hide(feature = "solecism")))] //@ has 'oud/struct.Solecism.html' //@ count - '//*[@class="stab portability"]' 0 @@ -18,7 +18,7 @@ pub struct Scribacious; //@ has 'oud/struct.Hyperdulia.html' //@ count - '//*[@class="stab portability"]' 1 -//@ matches - '//*[@class="stab portability"]' 'crate feature hyperdulia' +//@ matches - '//*[@class="stab portability"]' 'crate features hyperdulia only' //@ compile-flags:--cfg feature="hyperdulia" #[cfg(feature = "solecism")] #[cfg(feature = "hyperdulia")] @@ -26,7 +26,7 @@ pub struct Hyperdulia; //@ has 'oud/struct.Oystercatcher.html' //@ count - '//*[@class="stab portability"]' 1 -//@ matches - '//*[@class="stab portability"]' 'crate feature oystercatcher only' +//@ matches - '//*[@class="stab portability"]' 'crate features oystercatcher only' //@ compile-flags:--cfg feature="oystercatcher" #[cfg(all(feature = "solecism", feature = "oystercatcher"))] pub struct Oystercatcher; diff --git a/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs b/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs index b5b8d0f427bf7..9ae8b8fca4f7a 100644 --- a/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs +++ b/tests/rustdoc/doc-cfg/doc-cfg-implicit-gate.rs @@ -1,7 +1,8 @@ //@ compile-flags:--cfg feature="worricow" +#![feature(doc_cfg)] #![crate_name = "xenogenous"] //@ has 'xenogenous/struct.Worricow.html' -//@ count - '//*[@class="stab portability"]' 0 +//@ count - '//*[@class="stab portability"]' 1 #[cfg(feature = "worricow")] pub struct Worricow; diff --git a/tests/rustdoc/doc_auto_cfg.rs b/tests/rustdoc/doc_auto_cfg.rs new file mode 100644 index 0000000000000..19ef174c1778d --- /dev/null +++ b/tests/rustdoc/doc_auto_cfg.rs @@ -0,0 +1,77 @@ +// Test covering RFC 3631 features. + +#![crate_name = "foo"] +#![feature(doc_cfg)] +#![doc(auto_cfg(hide(feature = "hidden")))] + +//@ has 'foo/index.html' +//@ !has - '//*[@class="stab portability"]' 'Non-moustache' +//@ has - '//*[@class="stab portability"]' 'Non-pistache' +//@ count - '//*[@class="stab portability"]' 1 + +//@ has 'foo/m/index.html' +//@ count - '//*[@title="Available on non-crate feature `hidden` only"]' 2 +#[cfg(not(feature = "hidden"))] +pub mod m { + //@ count 'foo/m/struct.A.html' '//*[@class="stab portability"]' 0 + pub struct A; + + //@ has 'foo/m/inner/index.html' '//*[@class="stab portability"]' 'Available on non-crate feature hidden only.' + #[doc(auto_cfg(show(feature = "hidden")))] + pub mod inner { + //@ has 'foo/m/inner/struct.B.html' '//*[@class="stab portability"]' 'Available on non-crate feature hidden only.' + pub struct B; + + //@ count 'foo/m/inner/struct.A.html' '//*[@class="stab portability"]' 0 + #[doc(auto_cfg(hide(feature = "hidden")))] + pub struct A; + } + + //@ has 'foo/m/struct.B.html' '//*[@class="stab portability"]' 'Available on non-crate feature hidden only.' + #[doc(auto_cfg(show(feature = "hidden")))] + pub struct B; +} + +//@ count 'foo/n/index.html' '//*[@title="Available on non-crate feature `moustache` only"]' 3 +//@ count - '//dl/dt' 4 +#[cfg(not(feature = "moustache"))] +#[doc(auto_cfg = false)] +pub mod n { + // Should not have `moustache` listed. + //@ count 'foo/n/struct.X.html' '//*[@class="stab portability"]' 0 + pub struct X; + + // Should re-enable `auto_cfg` and make `moustache` listed. + //@ has 'foo/n/struct.Y.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature moustache only.' + #[doc(auto_cfg)] + pub struct Y; + + // Should re-enable `auto_cfg` and make `moustache` listed for itself + // and for `Y`. + //@ has 'foo/n/inner/index.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature moustache only.' + #[doc(auto_cfg = true)] + pub mod inner { + //@ has 'foo/n/inner/struct.Y.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature moustache only.' + pub struct Y; + } + + // Should re-enable `auto_cfg` and make `moustache` listed. + //@ has 'foo/n/struct.Z.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature moustache only.' + #[doc(auto_cfg(hide(feature = "hidden")))] + pub struct Z; +} + +// Checking inheritance. +//@ has 'foo/o/index.html' '//*[@class="stab portability"]' \ +// 'Available on non-crate feature pistache only.' +#[doc(cfg(not(feature = "pistache")))] +pub mod o { + //@ has 'foo/o/struct.A.html' '//*[@class="stab portability"]' \ + // 'Available on non-crate feature pistache and non-crate feature tarte only.' + #[doc(cfg(not(feature = "tarte")))] + pub struct A; +} diff --git a/tests/rustdoc/doc_auto_cfg_reexports.rs b/tests/rustdoc/doc_auto_cfg_reexports.rs new file mode 100644 index 0000000000000..f6315e9d49dde --- /dev/null +++ b/tests/rustdoc/doc_auto_cfg_reexports.rs @@ -0,0 +1,22 @@ +// Checks that `cfg` are correctly applied on inlined reexports. + +#![crate_name = "foo"] +#![feature(doc_cfg)] + +// Check with `std` item. +//@ has 'foo/index.html' '//*[@class="stab portability"]' 'Non-moustache' +//@ has 'foo/struct.C.html' '//*[@class="stab portability"]' \ +// 'Available on non-crate feature moustache only.' +#[cfg(not(feature = "moustache"))] +pub use std::cell::RefCell as C; + +// Check with local item. +mod x { + pub struct B; +} + +//@ has 'foo/index.html' '//*[@class="stab portability"]' 'Non-pistache' +//@ has 'foo/struct.B.html' '//*[@class="stab portability"]' \ +// 'Available on non-crate feature pistache only.' +#[cfg(not(feature = "pistache"))] +pub use crate::x::B; diff --git a/tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs b/tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs index f85d7b236372d..f24ebcd52acb6 100644 --- a/tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs +++ b/tests/rustdoc/impl/doc_auto_cfg_nested_impl.rs @@ -1,6 +1,6 @@ // Regression test for . -#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] #![crate_type = "lib"] #![crate_name = "foo"] diff --git a/tests/rustdoc/reexport/doc_auto_cfg-reexport-foreign-113982.rs b/tests/rustdoc/reexport/doc_auto_cfg-reexport-foreign-113982.rs index 76b25127a9c64..f8ec4afc0313e 100644 --- a/tests/rustdoc/reexport/doc_auto_cfg-reexport-foreign-113982.rs +++ b/tests/rustdoc/reexport/doc_auto_cfg-reexport-foreign-113982.rs @@ -1,7 +1,7 @@ //@ aux-build: issue-113982-doc_auto_cfg-reexport-foreign.rs // https://github.com/rust-lang/rust/issues/113982 -#![feature(no_core, doc_auto_cfg)] +#![feature(no_core, doc_cfg)] #![no_core] #![crate_name = "foo"] diff --git a/tests/rustdoc/reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs b/tests/rustdoc/reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs index d0a2165ec8abb..0aed2b0c46208 100644 --- a/tests/rustdoc/reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs +++ b/tests/rustdoc/reexport/glob-reexport-attribute-merge-doc-auto-cfg.rs @@ -2,7 +2,7 @@ // the reexported item whereas glob reexports do with the `doc_auto_cfg` feature. #![crate_name = "foo"] -#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] //@ has 'foo/index.html' // There are two items. diff --git a/tests/ui/feature-gates/feature-gate-doc_cfg.rs b/tests/ui/feature-gates/feature-gate-doc_cfg.rs index b12b8a1057182..213a5a8c5a988 100644 --- a/tests/ui/feature-gates/feature-gate-doc_cfg.rs +++ b/tests/ui/feature-gates/feature-gate-doc_cfg.rs @@ -1,2 +1,2 @@ -#[doc(cfg(unix))] //~ ERROR: `#[doc(cfg)]` is experimental +#[doc(cfg(unix))] //~ ERROR fn main() {}