From 9af31eabbe34e6ff4a8e005e246ea25e8638874d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 Mar 2023 21:57:23 +0200 Subject: [PATCH 1/7] Correctly handle re-exports of `#[doc(hidden)]` items --- src/librustdoc/clean/mod.rs | 14 ++---- src/librustdoc/visit_ast.rs | 96 ++++++++++++++++++++++++------------- 2 files changed, 66 insertions(+), 44 deletions(-) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 59a3e63172406..2d87f44a6b323 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())); @@ -2236,16 +2236,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)); 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, From ed49e5c97a2ca546465227ce38e2ec71735171b9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 28 Mar 2023 14:23:46 +0200 Subject: [PATCH 2/7] Remove unneeded `is_inline` argument --- src/librustdoc/clean/mod.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 2d87f44a6b323..f3aca032db48e 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -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 @@ -2584,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 From 5387d8b2840c423645dea31cfd46ea46ab112c59 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 Mar 2023 21:58:01 +0200 Subject: [PATCH 3/7] Update rustdoc tests and add test for `#[doc(hidden)]` re-exports. --- tests/rustdoc/doc-hidden-reexports.rs | 114 ++++++++++++++++++++++++++ tests/rustdoc/reexport-attr-merge.rs | 15 ++-- tests/rustdoc/reexport-doc-hidden.rs | 26 ------ 3 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 tests/rustdoc/doc-hidden-reexports.rs delete mode 100644 tests/rustdoc/reexport-doc-hidden.rs 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; From 64a495910ea0a935b48d6d395b9472212b4aed04 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 28 Mar 2023 17:58:05 +0200 Subject: [PATCH 4/7] Doc hide SourceIter and InPlaceIterable --- library/core/src/iter/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/iter/mod.rs b/library/core/src/iter/mod.rs index de638552fa378..3bd6df4f3471f 100644 --- a/library/core/src/iter/mod.rs +++ b/library/core/src/iter/mod.rs @@ -410,6 +410,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,6 +436,7 @@ 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; From cdd55453d1f3e6f4e557350b75f1017d56229e89 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 21 Mar 2023 20:25:29 +0100 Subject: [PATCH 5/7] Add chapter for re-exports in the rustdoc book --- src/doc/rustdoc/src/SUMMARY.md | 1 + .../src/write-documentation/re-exports.md | 128 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/doc/rustdoc/src/write-documentation/re-exports.md 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..d5572f9682958 --- /dev/null +++ b/src/doc/rustdoc/src/write-documentation/re-exports.md @@ -0,0 +1,128 @@ +# 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. + +## Attributes + +When an item is inlined, its doc comments and most of its attributes will be inlined along with it: + +| Attribute | Inlined? | 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. 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. From 918e45519349563fcaf8282ca38b8fb26b4cd9e5 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 11 May 2023 23:23:03 +0200 Subject: [PATCH 6/7] Improve documentation for re-exports --- .../src/write-documentation/re-exports.md | 21 +++++++++++++++++-- .../write-documentation/the-doc-attribute.md | 6 ++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/doc/rustdoc/src/write-documentation/re-exports.md b/src/doc/rustdoc/src/write-documentation/re-exports.md index d5572f9682958..480ebd8585f3b 100644 --- a/src/doc/rustdoc/src/write-documentation/re-exports.md +++ b/src/doc/rustdoc/src/write-documentation/re-exports.md @@ -111,11 +111,28 @@ 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 | Inlined? | Notes +| 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 | @@ -123,6 +140,6 @@ When an item is inlined, its doc comments and most of its attributes will be inl | `#[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. Glob-based imports (such as `use module::*`), hidden items are not inlined. +| `#[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. From e1b3f417ee798fdb63de1f8127d1e38d84a2695e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 15 May 2023 16:34:58 +0200 Subject: [PATCH 7/7] Hide re-exported doc hidden items --- library/core/src/char/mod.rs | 2 ++ library/core/src/iter/adapters/mod.rs | 2 ++ library/core/src/iter/mod.rs | 3 +++ library/core/src/iter/sources.rs | 1 + library/core/src/iter/traits/mod.rs | 1 + library/core/src/ops/mod.rs | 1 + library/core/src/prelude/v1.rs | 1 + library/core/src/ptr/mod.rs | 1 + 8 files changed, 12 insertions(+) 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 3bd6df4f3471f..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}; @@ -441,8 +442,10 @@ 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;