Skip to content

Commit

Permalink
Rollup merge of rust-lang#112304 - GuillaumeGomez:re-exports, r=notri…
Browse files Browse the repository at this point in the history
…ddle

Add chapter in rustdoc book for re-exports and add a regression test for `#[doc(hidden)]` behaviour

Fixes rust-lang#109449.
Fixes rust-lang#53417.

After the discussion in rust-lang#109697, I made a few PRs to fix a few corner cases:
 * rust-lang#112178
 * rust-lang#112108
 * rust-lang#111997

With this I think I covered all cases. Only thing missing at this point was a chapter covering re-exports in the rustdoc book.

r? `@notriddle`
  • Loading branch information
matthiaskrgr authored Jun 15, 2023
2 parents f530016 + 44b1365 commit 9bc95a4
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/doc/rustdoc/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
172 changes: 172 additions & 0 deletions src/doc/rustdoc/src/write-documentation/re-exports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# 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 inherits `#[doc(hidden)]` from any of its ancestors, it will be inlined:

```rust,ignore (inline)
#[doc(hidden)]
pub mod public_mod {
pub struct Public;
}
// `Public` be inlined since its parent (`public_mod`) has `#[doc(hidden)]`.
pub use self::public_mod::Public;
```

If an item has `#[doc(hidden)]`, it won't be inlined (nor visible in the generated documentation):

```rust,ignore (inline)
// This struct won't be visible.
#[doc(hidden)]
pub struct Hidden;
// This re-export won't be visible.
pub use self::Hidden as InlinedHidden;
```

The same applies on re-exports themselves: if you have multiple re-exports and some of them have
`#[doc(hidden)]`, then these ones (and only these) own't appear in the documentation:

```rust,ignore (inline)
mod private_mod {
/// First
pub struct InPrivate;
}
/// Second
#[doc(hidden)]
pub use self::private_mod::InPrivate as Hidden;
/// Third
pub use self::Hidden as Visible;
```

In this case, `InPrivate` will be inlined as `Visible`. However, its documentation will be
`First Third` and not `First Second Third` because the re-export with `Second` as documentation has
`#[doc(hidden)]`, therefore, all its attributes are ignored.

## 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)
mod private_mod {
pub struct Public;
}
#[doc(no_inline)]
pub use self::private_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:

```rust,ignore (inline)
mod private_mod {
/// First
#[cfg(a)]
pub struct InPrivate;
/// Second
#[cfg(b)]
pub use self::InPrivate as Second;
}
/// Third
#[doc(inline)]
#[cfg(c)]
pub use self::private_mod::Second as Visible;
```

In this case, `Visible` will have as documentation `First Second Third` and will also have as `cfg`:
`#[cfg(a, b, c)]`.

[Intra-doc links](./linking-to-items-by-name.md) are resolved relative to where the doc comment is
defined.

There are a few attributes which are not inlined though:
* `#[doc(alias="")]`
* `#[doc(inline)]`
* `#[doc(no_inline)]`
* `#[doc(hidden)]` (because the re-export itself and its attributes are ignored).

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.
8 changes: 7 additions & 1 deletion src/doc/rustdoc/src/write-documentation/the-doc-attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,18 @@ 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`

<span id="dochidden"></span>

Any item annotated with `#[doc(hidden)]` will not appear in the documentation, unless
the `strip-hidden` pass is removed.
the `strip-hidden` pass is removed. Re-exported items where one of its ancestors has
`#[doc(hidden)]` will be considered the same as private.

You can find more information in the [`re-exports` chapter](./re-exports.md).

### `alias`

Expand Down
143 changes: 143 additions & 0 deletions tests/rustdoc/issue-109449-doc-hidden-reexports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Test to enforce rules over re-exports inlining from
// <https://github.com/rust-lang/rust/issues/109449>.

#![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 NOT inline it.
pub mod single_reexport {
// @has 'foo/single_reexport/index.html'

// First we check that we have 4 type aliases.
// @count - '//*[@id="main-content"]/*[@class="item-table"]//code' 4

// Then we check that we have the correct link for each re-export.

// @!has - '//*[@href="struct.Foo.html"]' 'Foo'
// @has - '//*[@id="reexport.Foo"]/code' 'pub use crate::private_module::Public as Foo;'
pub use crate::private_module::Public as Foo;
// @!has - '//*[@href="type.Foo2.html"]' 'Foo2'
// @has - '//*[@id="reexport.Foo2"]/code' 'pub use crate::private_module::Bar as Foo2;'
pub use crate::private_module::Bar as Foo2;
// @!has - '//*[@href="type.Yo.html"]' 'Yo'
// @has - '//*[@id="reexport.Yo"]/code' 'pub use crate::Bar3 as Yo;'
pub use crate::Bar3 as Yo;
// @!has - '//*[@href="struct.Yo2.html"]' 'Yo2'
// @has - '//*[@id="reexport.Yo2"]/code' 'pub use crate::FooFoo as Yo2;'
pub use crate::FooFoo as Yo2;

// 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'
}

// However, re-exporting an item inheriting `#[doc(hidden)]` will inline it.
pub mod single_reexport_inherit_hidden {
// @has 'foo/single_reexport_inherit_hidden/index.html'

// @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;

// @has 'foo/single_reexport_inherit_hidden/struct.Foo3.html'
// @has 'foo/single_reexport_inherit_hidden/type.Foo4.html'
}

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 1 re-export and 2 inlined items.
// If not item from a glob re-export is visible, we don't show the re-export.
// @!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::*;
}

mod private {
/// Original.
pub struct Bar3;
}

// Checking that `#[doc(hidden)]` re-exports documentation isn't generated.
pub mod doc_hidden_reexport {
// @has 'foo/doc_hidden_reexport/index.html'
// Ensure there is only one item in this page and that it's a struct.
// @count - '//*[@class="item-name"]' 1
// @has - '//a[@class="struct"]' 'Reexport'
// Check that the `#[doc(hidden)]` re-export's attributes are not taken into account.
// @has - '//*[@class="desc docblock-short"]' 'Visible. Original.'
/// Hidden.
#[doc(hidden)]
pub use crate::private::Bar3;
/// Visible.
pub use self::Bar3 as Reexport;
}

0 comments on commit 9bc95a4

Please sign in to comment.