rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.91k stars 12.52k forks source link

Are publicly re-exported items from a `doc(hidden)` module considered public API? #117845

Open obi1kenobi opened 10 months ago

obi1kenobi commented 10 months ago

Let's look at this example from the doc comment in #117810:

#[doc(hidden)]
pub mod foo {
    pub struct Bar;
}

pub use foo::Bar;

To me, this seems like a public re-export of an intended-to-be-public item. In other words, when I see this code, I expect its doc(hidden) semantics to be that it is the module that is hidden, but non-hidden ways to access its contents are public API and "fair game." In my interpretation, if the programmer intended that to not be the case, they could have hidden the re-export (or pub struct Bar itself).

If pub struct Bar is always considered hidden regardless of re-exports (whether themselves hidden or not), I think that has strange implications for what is and is not public API. In that interpretation, the re-export is public API but the item it points to is not public API ... which is just confusing. It seems like it says "I make it part of the public API that this non-public-API type reserves a name in the public API" and nothing else. That's just strange! What public API operations are allowed over that re-export item? It feels to me like the answer is that the item is "neither here nor there" with respect to being public API or not.

It also seems to risk opening a terrible can of worms with further edge cases:

mod defn {
    pub struct Example;
}

#[doc(hidden)]
pub mod hide {
    pub use crate::Example as Example;
}

// Here, `Example` is public API.
pub use defn::Example;

// Replace it with the below code and `Example` is no longer public API.
// Worse, we can't distinguish between these two cases via rustdoc JSON,
// so this interpretation would permanently break cargo-semver-checks.
// pub use hide::Example;

For these reasons, I think a better interpretation of whether an item is doc(hidden) is one based on reachability: if you can reach the item without going through any doc(hidden) items (the item itself + any modules and re-exports), it is not hidden. If all paths require going through at least one hidden item, the item is hidden. This is also how visibility works as well — the pub-in-priv trick wouldn't work otherwise — so I think it makes a lot of sense for doc(hidden) to work that way too.

Originally posted by @obi1kenobi in https://github.com/rust-lang/rust/issues/117810#issuecomment-1806850905

obi1kenobi commented 10 months ago

@rustbot +T-rustdoc +A-rustdoc-json

obi1kenobi commented 10 months ago

Zulip thread here: https://rust-lang.zulipchat.com/#narrow/stream/266220-t-rustdoc/topic/pub.20re-export.20of.20item.20from.20.60doc.28hidden.29.60.20module/near/401627083

GuillaumeGomez commented 10 months ago

@rustbot +T-rustdoc +A-rustdoc-json

GuillaumeGomez commented 10 months ago

@rustbot label +T-rustdoc +A-rustdoc-json

artem-vitae commented 4 months ago

Hey there!

I think that the example in this issue should be considered as public API and inlined into rustdoc JSON. In general, it might be expected that same inlining rules are applied to HTML and JSON, which is not the case for now. I experienced a lot of confusion with this while trying to implement a small semver checker based on https://github.com/Enselic/cargo-public-api. E.g., Hidden doc is inlined even in this case:

mod sealed {
   pub struct Hidden;
}

#[doc(hidden)]
pub mod export_without_doc {
    #[doc(no_inline)]
   pub use crate::sealed::Hidden;
}

I suppose it might be a bug because both hidden and no_inline annotations are ignored.

GuillaumeGomez commented 4 months ago

cc @aDotInTheVoid