diff --git a/library/core/src/char/mod.rs b/library/core/src/char/mod.rs index e186db7052cd0..41a72c083a328 100644 --- a/library/core/src/char/mod.rs +++ b/library/core/src/char/mod.rs @@ -34,8 +34,10 @@ pub use self::decode::{DecodeUtf16, DecodeUtf16Error}; // perma-unstable re-exports #[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")] +#[doc(hidden)] pub use self::methods::encode_utf16_raw; #[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")] +#[doc(hidden)] pub use self::methods::encode_utf8_raw; use crate::error::Error; diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index 8cc2b7cec4165..9de25153fe7cc 100644 --- a/library/core/src/iter/adapters/mod.rs +++ b/library/core/src/iter/adapters/mod.rs @@ -58,9 +58,11 @@ pub use self::intersperse::{Intersperse, IntersperseWith}; pub use self::map_while::MapWhile; #[unstable(feature = "trusted_random_access", issue = "none")] +#[doc(hidden)] pub use self::zip::TrustedRandomAccess; #[unstable(feature = "trusted_random_access", issue = "none")] +#[doc(hidden)] pub use self::zip::TrustedRandomAccessNoCoerce; #[stable(feature = "iter_zip", since = "1.59.0")] diff --git a/library/core/src/iter/mod.rs b/library/core/src/iter/mod.rs index de638552fa378..99fbb613cf27c 100644 --- a/library/core/src/iter/mod.rs +++ b/library/core/src/iter/mod.rs @@ -401,6 +401,7 @@ pub use self::sources::{once_with, OnceWith}; #[stable(feature = "rust1", since = "1.0.0")] pub use self::sources::{repeat, Repeat}; #[unstable(feature = "iter_repeat_n", issue = "104434")] +#[doc(hidden)] // waiting on ACP#120 to decide whether to expose publicly pub use self::sources::{repeat_n, RepeatN}; #[stable(feature = "iterator_repeat_with", since = "1.28.0")] pub use self::sources::{repeat_with, RepeatWith}; @@ -410,6 +411,7 @@ pub use self::sources::{successors, Successors}; #[stable(feature = "fused", since = "1.26.0")] pub use self::traits::FusedIterator; #[unstable(issue = "none", feature = "inplace_iteration")] +#[doc(hidden)] pub use self::traits::InPlaceIterable; #[unstable(feature = "trusted_len", issue = "37572")] pub use self::traits::TrustedLen; @@ -435,12 +437,15 @@ pub use self::adapters::Flatten; #[stable(feature = "iter_map_while", since = "1.57.0")] pub use self::adapters::MapWhile; #[unstable(feature = "inplace_iteration", issue = "none")] +#[doc(hidden)] pub use self::adapters::SourceIter; #[stable(feature = "iterator_step_by", since = "1.28.0")] pub use self::adapters::StepBy; #[unstable(feature = "trusted_random_access", issue = "none")] +#[doc(hidden)] pub use self::adapters::TrustedRandomAccess; #[unstable(feature = "trusted_random_access", issue = "none")] +#[doc(hidden)] pub use self::adapters::TrustedRandomAccessNoCoerce; #[stable(feature = "rust1", since = "1.0.0")] pub use self::adapters::{ diff --git a/library/core/src/iter/sources.rs b/library/core/src/iter/sources.rs index 3ec426a3ad9a1..65ac4c5375244 100644 --- a/library/core/src/iter/sources.rs +++ b/library/core/src/iter/sources.rs @@ -18,6 +18,7 @@ pub use self::empty::{empty, Empty}; pub use self::once::{once, Once}; #[unstable(feature = "iter_repeat_n", issue = "104434")] +#[doc(hidden)] pub use self::repeat_n::{repeat_n, RepeatN}; #[stable(feature = "iterator_repeat_with", since = "1.28.0")] diff --git a/library/core/src/iter/traits/mod.rs b/library/core/src/iter/traits/mod.rs index 41ea29e6a84d9..a788d096a725a 100644 --- a/library/core/src/iter/traits/mod.rs +++ b/library/core/src/iter/traits/mod.rs @@ -17,6 +17,7 @@ pub use self::{ }; #[unstable(issue = "none", feature = "inplace_iteration")] +#[doc(hidden)] pub use self::marker::InPlaceIterable; #[unstable(feature = "trusted_step", issue = "85731")] pub use self::marker::TrustedStep; diff --git a/library/core/src/ops/mod.rs b/library/core/src/ops/mod.rs index 97d9b750d92f9..734f50d986c87 100644 --- a/library/core/src/ops/mod.rs +++ b/library/core/src/ops/mod.rs @@ -165,6 +165,7 @@ pub use self::bit::{BitAndAssign, BitOrAssign, BitXorAssign, ShlAssign, ShrAssig pub use self::deref::{Deref, DerefMut}; #[unstable(feature = "receiver_trait", issue = "none")] +#[doc(hidden)] pub use self::deref::Receiver; #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs index 10525a16f3a66..4c96afed23b08 100644 --- a/library/core/src/prelude/v1.rs +++ b/library/core/src/prelude/v1.rs @@ -71,6 +71,7 @@ pub use crate::concat_bytes; // Do not `doc(inline)` these `doc(hidden)` items. #[stable(feature = "builtin_macro_prelude", since = "1.38.0")] #[allow(deprecated)] +#[doc(hidden)] pub use crate::macros::builtin::{RustcDecodable, RustcEncodable}; // Do not `doc(no_inline)` so that they become doc items on their own diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index ecbf4e66fa489..06e25afff3f35 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -404,6 +404,7 @@ pub use non_null::NonNull; mod unique; #[unstable(feature = "ptr_internals", issue = "none")] +#[doc(hidden)] pub use unique::Unique; mod const_ptr; diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md index b512135d92770..12a8b2b8db4b6 100644 --- a/src/doc/rustdoc/src/SUMMARY.md +++ b/src/doc/rustdoc/src/SUMMARY.md @@ -7,6 +7,7 @@ - [How to write documentation](how-to-write-documentation.md) - [What to include (and exclude)](write-documentation/what-to-include.md) - [The `#[doc]` attribute](write-documentation/the-doc-attribute.md) + - [Re-exports](write-documentation/re-exports.md) - [Linking to items by name](write-documentation/linking-to-items-by-name.md) - [Documentation tests](write-documentation/documentation-tests.md) - [Rustdoc-specific lints](lints.md) diff --git a/src/doc/rustdoc/src/write-documentation/re-exports.md b/src/doc/rustdoc/src/write-documentation/re-exports.md new file mode 100644 index 0000000000000..480ebd8585f3b --- /dev/null +++ b/src/doc/rustdoc/src/write-documentation/re-exports.md @@ -0,0 +1,145 @@ +# Re-exports + +Let's start by explaining what are re-exports. To do so, we will use an example where we are +writing a library (named `lib`) with some types dispatched in sub-modules: + +```rust +pub mod sub_module1 { + pub struct Foo; +} +pub mod sub_module2 { + pub struct AnotherFoo; +} +``` + +Users can import them like this: + +```rust,ignore (inline) +use lib::sub_module1::Foo; +use lib::sub_module2::AnotherFoo; +``` + +But what if you want the types to be available directly at the crate root or if we don't want the +modules to be visible for users? That's where re-exports come in: + +```rust,ignore (inline) +// `sub_module1` and `sub_module2` are not visible outside. +mod sub_module1 { + pub struct Foo; +} +mod sub_module2 { + pub struct AnotherFoo; +} + +// We re-export both types: +pub use crate::sub_module1::Foo; +pub use crate::sub_module2::AnotherFoo; +``` + +And now users will be able to do: + +```rust,ignore (inline) +use lib::{Foo, AnotherFoo}; +``` + +And since both `sub_module1` and `sub_module2` are private, users won't be able to import them. + +Now what's interesting is that the generated documentation for this crate will show both `Foo` and +`AnotherFoo` directly at the crate root, meaning they have been inlined. There are a few rules to +know whether or not a re-exported item will be inlined. + +## Inlining rules + +If a public item comes from a private module, it will be inlined: + +```rust,ignore (inline) +mod private_module { + pub struct Public; +} + +pub mod public_mod { + // `Public` will inlined here since `private_module` is private. + pub use super::private_module::Public; +} + +// `Public` will not be inlined here since `public_mod` is public. +pub use self::public_mod::Public; +``` + +Likewise, if an item has `#[doc(hidden)]` or inherits it (from any of its parents), it +will be inlined: + +```rust,ignore (inline) +#[doc(hidden)] +pub mod public_mod { + pub struct Public; +} + +#[doc(hidden)] +pub struct Hidden; + +// `Public` be inlined since its parent (`public_mod`) has `#[doc(hidden)]`. +pub use self::public_mod::Public; +// `Hidden` be inlined since it has `#[doc(hidden)]`. +pub use self::Hidden; +``` + +The inlining rules are a bit different for glob re-exports (`pub use x::*`) for `#[doc(hidden)]` +types. If we take the previous example and then re-export like this: + +```rust,ignore (inline) +pub use self::*; // It will not inline the `Hidden` struct. +pub use self::public_mod::*; // It will inline the `Public` struct. +``` + +It only impacts elements that have the `#[doc(hidden)]` attributes. If it only inherits it, then it +is inlined. + +## Inlining with `#[doc(inline)]` + +You can use the `#[doc(inline)]` attribute if you want to force an item to be inlined: + +```rust,ignore (inline) +pub mod public_mod { + pub struct Public; +} + +#[doc(inline)] +pub use self::public_mod::Public; +``` + +With this code, even though `public_mod::Public` is public and present in the documentation, the +`Public` type will be present both at the crate root and in the `public_mod` module. + +## Preventing inlining with `#[doc(no_inline)]` + +On the opposite of the `#[doc(inline)]` attribute, if you want to prevent an item from being +inlined, you can use `#[doc(no_inline)]`: + +```rust,ignore (inline) +pub mod public_mod { + pub struct Public; +} + +#[doc(no_inline)] +pub use self::public_mod::Public; +``` + +In the generated documentation, you will see a re-export at the crate root and not the type +directly. + +## Attributes + +When an item is inlined, its doc comments and most of its attributes will be inlined along with it: + +| Attribute | Is it inlined alongside its item? | Notes +|--|--|-- +| `#[doc=""]` | Yes | Intra-doc links are resolved relative to where the doc comment is defined (`///` is syntax sugar for doc string attributes). +| `#[doc(cfg(..))]` | Yes | +| `#[deprecated]` | Yes | Intra-doc links are resolved relative to where the description is defined. +| `#[doc(alias="")]` | No | +| `#[doc(inline)]` | No | +| `#[doc(no_inline)]` | No | +| `#[doc(hidden)]` | Glob imports | For name-based imports (such as `use module::Item as ModuleItem`), hiding an item acts the same as making it private. For glob-based imports (such as `use module::*`), hidden items are not inlined. + +All other attributes are inherited when inlined, so that the documentation matches the behavior if the inlined item was directly defined at the spot where it's shown. diff --git a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md index 8ecf05f0e121f..e444dc030bb7c 100644 --- a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md +++ b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md @@ -223,6 +223,9 @@ Now we'll have a `Re-exports` line, and `Bar` will not link to anywhere. One special case: In Rust 2018 and later, if you `pub use` one of your dependencies, `rustdoc` will not eagerly inline it as a module unless you add `#[doc(inline)]`. +If you want to know more about inlining rules, take a look at the +[`re-exports` chapter](./re-exports.md). + ### `hidden` @@ -230,6 +233,9 @@ not eagerly inline it as a module unless you add `#[doc(inline)]`. Any item annotated with `#[doc(hidden)]` will not appear in the documentation, unless the `strip-hidden` pass is removed. +For name-based imports (such as `use module::Item as ModuleItem`), hiding an item acts the same as +making it private. For glob-based imports (such as `use module::*`), hidden items are not inlined. + ### `alias` This attribute adds an alias in the search index. diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 59a3e63172406..f3aca032db48e 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -41,7 +41,7 @@ use thin_vec::ThinVec; use crate::core::{self, DocContext, ImplTraitParam}; use crate::formats::item_type::ItemType; -use crate::visit_ast::Module as DocModule; +use crate::visit_ast::{ItemEntry, Module as DocModule}; use utils::*; @@ -77,7 +77,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< // This covers the case where somebody does an import which should pull in an item, // but there's already an item with the same namespace and same name. Rust gives // priority to the not-imported one, so we should, too. - items.extend(doc.items.values().flat_map(|(item, renamed, import_id)| { + items.extend(doc.items.values().flat_map(|ItemEntry { item, renamed, import_id }| { // First, lower everything other than imports. if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { return Vec::new(); @@ -90,7 +90,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< } v })); - items.extend(doc.items.values().flat_map(|(item, renamed, _)| { + items.extend(doc.items.values().flat_map(|ItemEntry { item, renamed, .. }| { // Now we actually lower the imports, skipping everything else. if let hir::ItemKind::Use(path, hir::UseKind::Glob) = item.kind { let name = renamed.unwrap_or_else(|| cx.tcx.hir().name(item.hir_id())); @@ -133,12 +133,8 @@ fn generate_item_with_correct_attrs( let def_id = local_def_id.to_def_id(); let target_attrs = inline::load_attrs(cx, def_id); let attrs = if let Some(import_id) = import_id { - let is_inline = inline::load_attrs(cx, import_id.to_def_id()) - .lists(sym::doc) - .get_word_attr(sym::inline) - .is_some(); - let mut attrs = get_all_import_attributes(cx, import_id, local_def_id, is_inline); - add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None); + let mut attrs = get_all_import_attributes(cx, import_id, local_def_id); + add_without_unwanted_attributes(&mut attrs, target_attrs, None); attrs } else { // We only keep the item's attributes. @@ -2170,7 +2166,6 @@ fn get_all_import_attributes<'hir>( cx: &mut DocContext<'hir>, import_def_id: LocalDefId, target_def_id: LocalDefId, - is_inline: bool, ) -> Vec<(Cow<'hir, ast::Attribute>, Option)> { let mut attrs = Vec::new(); let mut first = true; @@ -2184,7 +2179,7 @@ fn get_all_import_attributes<'hir>( attrs = import_attrs.iter().map(|attr| (Cow::Borrowed(attr), Some(def_id))).collect(); first = false; } else { - add_without_unwanted_attributes(&mut attrs, import_attrs, is_inline, Some(def_id)); + add_without_unwanted_attributes(&mut attrs, import_attrs, Some(def_id)); } } attrs @@ -2236,16 +2231,8 @@ fn filter_tokens_from_list( fn add_without_unwanted_attributes<'hir>( attrs: &mut Vec<(Cow<'hir, ast::Attribute>, Option)>, new_attrs: &'hir [ast::Attribute], - is_inline: bool, import_parent: Option, ) { - // If it's not `#[doc(inline)]`, we don't want all attributes, otherwise we keep everything. - if !is_inline { - for attr in new_attrs { - attrs.push((Cow::Borrowed(attr), import_parent)); - } - return; - } for attr in new_attrs { if matches!(attr.kind, ast::AttrKind::DocComment(..)) { attrs.push((Cow::Borrowed(attr), import_parent)); @@ -2592,7 +2579,8 @@ fn clean_use_statement_inner<'tcx>( } else { if inline_attr.is_none() && let Res::Def(DefKind::Mod, did) = path.res - && !did.is_local() && did.is_crate_root() + && !did.is_local() + && did.is_crate_root() { // if we're `pub use`ing an extern crate root, don't inline it unless we // were specifically asked for it diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 8f8dc6b709053..46a941646cab0 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -19,6 +19,13 @@ use std::{iter, mem}; use crate::clean::{cfg::Cfg, reexport_chain, AttributesExt, NestedAttributesExt}; use crate::core; +#[derive(Debug)] +pub(crate) struct ItemEntry<'hir> { + pub(crate) item: &'hir hir::Item<'hir>, + pub(crate) renamed: Option, + pub(crate) import_id: Option, +} + /// This module is used to store stuff from Rust's AST in a more convenient /// manner (and with prettier names) before cleaning. #[derive(Debug)] @@ -31,10 +38,7 @@ pub(crate) struct Module<'hir> { pub(crate) import_id: Option, /// The key is the item `ItemId` and the value is: (item, renamed, import_id). /// We use `FxIndexMap` to keep the insert order. - pub(crate) items: FxIndexMap< - (LocalDefId, Option), - (&'hir hir::Item<'hir>, Option, Option), - >, + pub(crate) items: FxIndexMap<(LocalDefId, Option), ItemEntry<'hir>>, pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option)>, } @@ -107,6 +111,7 @@ pub(crate) struct RustdocVisitor<'a, 'tcx> { modules: Vec>, is_importable_from_parent: bool, inside_body: bool, + ignored_items: usize, } impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { @@ -131,6 +136,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { modules: vec![om], is_importable_from_parent: true, inside_body: false, + ignored_items: 0, } } @@ -165,7 +171,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { inserted.insert(def_id) { let item = self.cx.tcx.hir().expect_item(local_def_id); - top_level_module.items.insert((local_def_id, Some(item.ident.name)), (item, None, None)); + top_level_module.items.insert((local_def_id, Some(item.ident.name)), ItemEntry { + item, + renamed: None, + import_id: None, + }); } } @@ -263,9 +273,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { }; let use_attrs = tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(def_id)); - // Don't inline `doc(hidden)` imports so they can be stripped at a later stage. - let is_no_inline = use_attrs.lists(sym::doc).has_word(sym::no_inline) - || use_attrs.lists(sym::doc).has_word(sym::hidden); + let is_no_inline = use_attrs.lists(sym::doc).has_word(sym::no_inline); // For cross-crate impl inlining we need to know whether items are // reachable in documentation -- a previously unreachable item can be @@ -280,12 +288,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { return false; }; - let is_private = - !self.cx.cache.effective_visibilities.is_directly_public(self.cx.tcx, ori_res_did); - let is_hidden = inherits_doc_hidden(self.cx.tcx, res_did, None); + let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did); + let is_hidden = tcx.is_doc_hidden(res_did) || inherits_doc_hidden(tcx, res_did, None); // Only inline if requested or if the item would otherwise be stripped. - if (!please_inline && !is_private && !is_hidden) || is_no_inline { + if is_no_inline || (!please_inline && !is_private && !is_hidden) { return false; } @@ -298,8 +305,9 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { .cx .cache .effective_visibilities - .is_directly_public(self.cx.tcx, item_def_id.to_def_id()) && - !inherits_doc_hidden(self.cx.tcx, item_def_id, None) + .is_directly_public(tcx, item_def_id.to_def_id()) && + !tcx.is_doc_hidden(item_def_id) && + !inherits_doc_hidden(tcx, item_def_id, None) { // The imported item is public and not `doc(hidden)` so no need to inline it. return false; @@ -312,22 +320,25 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let ret = match tcx.hir().get_by_def_id(res_did) { Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => { let prev = mem::replace(&mut self.inlining, true); + let prev_ignored = self.ignored_items; for &i in m.item_ids { - let i = self.cx.tcx.hir().item(i); - self.visit_item_inner(i, None, Some(def_id)); + let i = tcx.hir().item(i); + self.visit_item_inner(i, None, Some(def_id), true); } self.inlining = prev; - true + let inline = self.ignored_items - prev_ignored == 0; + self.ignored_items = prev_ignored; + inline } Node::Item(it) if !glob => { let prev = mem::replace(&mut self.inlining, true); - self.visit_item_inner(it, renamed, Some(def_id)); + self.visit_item_inner(it, renamed, Some(def_id), false); self.inlining = prev; true } Node::ForeignItem(it) if !glob => { let prev = mem::replace(&mut self.inlining, true); - self.visit_foreign_item_inner(it, renamed); + self.visit_foreign_item_inner(it, renamed, false); self.inlining = prev; true } @@ -343,6 +354,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { item: &'tcx hir::Item<'_>, renamed: Option, parent_id: Option, + from_glob_reexport: bool, ) { if self.is_importable_from_parent // If we're inside an item, only impl blocks and `macro_rules!` with the `macro_export` @@ -355,11 +367,14 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { _ => false, } { - self.modules - .last_mut() - .unwrap() - .items - .insert((item.owner_id.def_id, renamed), (item, renamed, parent_id)); + if !from_glob_reexport || !self.cx.tcx.is_doc_hidden(item.owner_id.to_def_id()) { + self.modules.last_mut().unwrap().items.insert( + (item.owner_id.def_id, renamed), + ItemEntry { item, renamed, import_id: parent_id }, + ); + } else { + self.ignored_items += 1; + } } } @@ -368,6 +383,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { item: &'tcx hir::Item<'_>, renamed: Option, import_id: Option, + from_glob_reexport: bool, ) { debug!("visiting item {:?}", item); if self.inside_body { @@ -386,7 +402,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // them up regardless of where they're located. impl_.of_trait.is_none() { - self.add_to_current_mod(item, None, None); + self.add_to_current_mod(item, None, None, false); } return; } @@ -404,7 +420,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { hir::ItemKind::ForeignMod { items, .. } => { for item in items { let item = tcx.hir().foreign_item(item.id); - self.visit_foreign_item_inner(item, None); + self.visit_foreign_item_inner(item, None, from_glob_reexport); } } // If we're inlining, skip private items. @@ -419,6 +435,15 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { continue; } + if from_glob_reexport && + tcx.is_doc_hidden(def_id) && + let Some(res_def_id) = res.opt_def_id() && + tcx.is_doc_hidden(res_def_id) + { + self.ignored_items += 1; + continue; + } + let attrs = tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(item.owner_id.def_id)); @@ -444,7 +469,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { } } - self.add_to_current_mod(item, renamed, import_id); + self.add_to_current_mod(item, renamed, import_id, false); } } hir::ItemKind::Macro(ref macro_def, _) => { @@ -464,7 +489,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let nonexported = !tcx.has_attr(def_id, sym::macro_export); if is_macro_2_0 || nonexported || self.inlining { - self.add_to_current_mod(item, renamed, import_id); + self.add_to_current_mod(item, renamed, import_id, from_glob_reexport); } } hir::ItemKind::Mod(ref m) => { @@ -483,7 +508,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { | hir::ItemKind::Static(..) | hir::ItemKind::Trait(..) | hir::ItemKind::TraitAlias(..) => { - self.add_to_current_mod(item, renamed, import_id); + self.add_to_current_mod(item, renamed, import_id, from_glob_reexport); } hir::ItemKind::OpaqueTy(hir::OpaqueTy { origin: hir::OpaqueTyOrigin::AsyncFn(_) | hir::OpaqueTyOrigin::FnReturn(_), @@ -495,14 +520,14 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // Underscore constants do not correspond to a nameable item and // so are never useful in documentation. if name != kw::Underscore { - self.add_to_current_mod(item, renamed, import_id); + self.add_to_current_mod(item, renamed, import_id, from_glob_reexport); } } hir::ItemKind::Impl(impl_) => { // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick // them up regardless of where they're located. if !self.inlining && impl_.of_trait.is_none() { - self.add_to_current_mod(item, None, None); + self.add_to_current_mod(item, None, None, false); } } } @@ -512,10 +537,15 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { &mut self, item: &'tcx hir::ForeignItem<'_>, renamed: Option, + from_glob_reexport: bool, ) { // If inlining we only want to include public functions. if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() { - self.modules.last_mut().unwrap().foreigns.push((item, renamed)); + if !from_glob_reexport || !self.cx.tcx.is_doc_hidden(item.owner_id.to_def_id()) { + self.modules.last_mut().unwrap().foreigns.push((item, renamed)); + } else { + self.ignored_items += 1; + } } } @@ -549,7 +579,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RustdocVisitor<'a, 'tcx> { } fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) { - self.visit_item_inner(i, None, None); + self.visit_item_inner(i, None, None, false); let new_value = self.is_importable_from_parent && matches!( i.kind, diff --git a/tests/rustdoc/doc-hidden-reexports.rs b/tests/rustdoc/doc-hidden-reexports.rs new file mode 100644 index 0000000000000..6978fee1cfe9a --- /dev/null +++ b/tests/rustdoc/doc-hidden-reexports.rs @@ -0,0 +1,114 @@ +// Test to enforce rules over re-exports inlining from +// . + +#![crate_name = "foo"] + +mod private_module { + #[doc(hidden)] + pub struct Public; + #[doc(hidden)] + pub type Bar = (); +} + +#[doc(hidden)] +mod module { + pub struct Public2; + pub type Bar2 = (); +} + +#[doc(hidden)] +pub type Bar3 = (); +#[doc(hidden)] +pub struct FooFoo; + +// Checking that re-exporting a `#[doc(hidden)]` item will inline it. +pub mod single_reexport { + // @has 'foo/single_reexport/index.html' + + // First we check that we have all 6 items: 3 structs and 3 type aliases. + // @count - '//a[@class="struct"]' 3 + // @count - '//a[@class="type"]' 3 + + // Then we check that we have the correct link for each re-export. + + // @has - '//*[@href="struct.Foo.html"]' 'Foo' + pub use crate::private_module::Public as Foo; + // @has - '//*[@href="type.Foo2.html"]' 'Foo2' + pub use crate::private_module::Bar as Foo2; + // @has - '//*[@href="type.Yo.html"]' 'Yo' + pub use crate::Bar3 as Yo; + // @has - '//*[@href="struct.Yo2.html"]' 'Yo2' + pub use crate::FooFoo as Yo2; + // @has - '//*[@href="struct.Foo3.html"]' 'Foo3' + pub use crate::module::Public2 as Foo3; + // @has - '//*[@href="type.Foo4.html"]' 'Foo4' + pub use crate::module::Bar2 as Foo4; + + // Checking that each file is also created as expected. + // @has 'foo/single_reexport/struct.Foo.html' + // @has 'foo/single_reexport/type.Foo2.html' + // @has 'foo/single_reexport/type.Yo.html' + // @has 'foo/single_reexport/struct.Yo2.html' + // @has 'foo/single_reexport/struct.Foo3.html' + // @has 'foo/single_reexport/type.Foo4.html' +} + +// Checking that re-exporting a `#[doc(hidden)]` item with `#[doc(no_inline)]` will not inline +// it but will still display a re-export in the documentation. +pub mod single_reexport_no_inline { + // First we ensure that we only have re-exports and no inlined items. + // @has 'foo/single_reexport_no_inline/index.html' + // @count - '//*[@id="main-content"]/*[@class="small-section-header"]' 1 + // @has - '//*[@id="main-content"]/*[@class="small-section-header"]' 'Re-exports' + + // Now we check that we don't have links to the items, just `pub use`. + // @has - '//*[@id="main-content"]//*' 'pub use crate::private_module::Public as XFoo;' + // @!has - '//*[@id="main-content"]//a' 'XFoo' + #[doc(no_inline)] + pub use crate::private_module::Public as XFoo; + // @has - '//*[@id="main-content"]//*' 'pub use crate::private_module::Bar as Foo2;' + // @!has - '//*[@id="main-content"]//a' 'Foo2' + #[doc(no_inline)] + pub use crate::private_module::Bar as Foo2; + // @has - '//*[@id="main-content"]//*' 'pub use crate::Bar3 as Yo;' + // @!has - '//*[@id="main-content"]//a' 'Yo' + #[doc(no_inline)] + pub use crate::Bar3 as Yo; + // @has - '//*[@id="main-content"]//*' 'pub use crate::FooFoo as Yo2;' + // @!has - '//*[@id="main-content"]//a' 'Yo2' + #[doc(no_inline)] + pub use crate::FooFoo as Yo2; + // @has - '//*[@id="main-content"]//*' 'pub use crate::module::Public2 as Foo3;' + // @!has - '//*[@id="main-content"]//a' 'Foo3' + #[doc(no_inline)] + pub use crate::module::Public2 as Foo3; + // @has - '//*[@id="main-content"]//*' 'pub use crate::module::Bar2 as Foo4;' + // @!has - '//*[@id="main-content"]//a' 'Foo4' + #[doc(no_inline)] + pub use crate::module::Bar2 as Foo4; +} + +// Checking that glob re-exports don't inline `#[doc(hidden)]` items. +pub mod glob_reexport { + // With glob re-exports, we don't inline `#[doc(hidden)]` items so only `module` items + // should be inlined. + // @has 'foo/glob_reexport/index.html' + // @count - '//*[@id="main-content"]/*[@class="small-section-header"]' 3 + // @has - '//*[@id="main-content"]/*[@class="small-section-header"]' 'Re-exports' + // @has - '//*[@id="main-content"]/*[@class="small-section-header"]' 'Structs' + // @has - '//*[@id="main-content"]/*[@class="small-section-header"]' 'Type Definitions' + + // Now we check we have 2 re-exports and 2 inlined items. + // @has - '//*[@id="main-content"]//*' 'pub use crate::private_module::*;' + pub use crate::private_module::*; + // @has - '//*[@id="main-content"]//*' 'pub use crate::*;' + pub use crate::*; + // This one should be inlined. + // @!has - '//*[@id="main-content"]//*' 'pub use crate::module::*;' + // @has - '//*[@id="main-content"]//a[@href="struct.Public2.html"]' 'Public2' + // @has - '//*[@id="main-content"]//a[@href="type.Bar2.html"]' 'Bar2' + // And we check that the two files were created too. + // @has 'foo/glob_reexport/struct.Public2.html' + // @has 'foo/glob_reexport/type.Bar2.html' + pub use crate::module::*; +} diff --git a/tests/rustdoc/reexport-attr-merge.rs b/tests/rustdoc/reexport-attr-merge.rs index f6c23a1365f46..5300dc4324da6 100644 --- a/tests/rustdoc/reexport-attr-merge.rs +++ b/tests/rustdoc/reexport-attr-merge.rs @@ -4,6 +4,8 @@ #![crate_name = "foo"] #![feature(doc_cfg)] +#![feature(no_core)] +#![no_core] // @has 'foo/index.html' @@ -20,14 +22,13 @@ pub use Foo1 as Foo2; // are inlined. // @count - '//a[@class="struct"]' 2 // Then we check that both `cfg` are displayed. -// @has - '//*[@class="stab portability"]' 'foo' -// @has - '//*[@class="stab portability"]' 'bar' +// @matches - '//*[@class="stab portability"]' '^foo$' // And finally we check that the only element displayed is `Bar`. -// @has - '//a[@class="struct"]' 'Bar' +// @has - '//a[@href="struct.Bar.html"]' 'Bar' #[doc(inline)] -pub use Foo2 as Bar; +pub use Foo as Bar; -// This one should appear but `Bar2` won't be linked because there is no -// `#[doc(inline)]`. -// @has - '//*[@id="reexport.Bar2"]' 'pub use Foo2 as Bar2;' +// Re-exported `#[doc(hidden)]` items are inlined as well. +// @has - '//a[@href="struct.Bar2.html"]' 'Bar2' +// @matches - '//*[@class="stab portability"]' '^bar and foo$' pub use Foo2 as Bar2; diff --git a/tests/rustdoc/reexport-doc-hidden.rs b/tests/rustdoc/reexport-doc-hidden.rs deleted file mode 100644 index 3ea5fde72f711..0000000000000 --- a/tests/rustdoc/reexport-doc-hidden.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Part of . -// This test ensures that reexporting a `doc(hidden)` item will -// still show the reexport. - -#![crate_name = "foo"] - -#[doc(hidden)] -pub type Type = u32; - -// @has 'foo/index.html' -// @has - '//*[@id="reexport.Type2"]/code' 'pub use crate::Type as Type2;' -pub use crate::Type as Type2; - -// @count - '//*[@id="reexport.Type3"]' 0 -#[doc(hidden)] -pub use crate::Type as Type3; - -#[macro_export] -#[doc(hidden)] -macro_rules! foo { - () => {}; -} - -// This is a bug: https://github.com/rust-lang/rust/issues/59368 -// @!has - '//*[@id="reexport.Macro"]/code' 'pub use crate::foo as Macro;' -pub use crate::foo as Macro;