Skip to content

Fix dangling ID when pub useing item which is Doc(hidden) or inherits it in rustdoc JSON output #117810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use rustc_target::spec::abi::Abi;
use crate::clean::cfg::Cfg;
use crate::clean::external_path;
use crate::clean::inline::{self, print_inlined_const};
use crate::clean::utils::{is_literal_expr, print_evaluated_const};
use crate::clean::utils::{inherits_doc_hidden, is_literal_expr, print_evaluated_const};
use crate::core::DocContext;
use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType;
Expand Down Expand Up @@ -2489,6 +2489,15 @@ impl Import {
pub(crate) fn imported_item_is_doc_hidden(&self, tcx: TyCtxt<'_>) -> bool {
self.source.did.is_some_and(|did| tcx.is_doc_hidden(did))
}

pub(crate) fn inherits_doc_hidden(&self, tcx: TyCtxt<'_>) -> bool {
self.imported_item_is_doc_hidden(tcx)
|| self
.source
.did
.and_then(|did| did.as_local())
.map_or(false, |did| inherits_doc_hidden(tcx, did, None))
}
}

#[derive(Clone, Debug)]
Expand All @@ -2499,6 +2508,12 @@ pub(crate) enum ImportKind {
Glob,
}

impl ImportKind {
pub fn is_glob(&self) -> bool {
matches!(self, Self::Glob)
}
}

#[derive(Clone, Debug)]
pub(crate) struct ImportSource {
pub(crate) path: Path,
Expand Down
24 changes: 13 additions & 11 deletions src/librustdoc/json/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use rustc_target::spec::abi::Abi as RustcAbi;

use rustdoc_json_types::*;

use crate::clean::{self, ItemId};
use crate::clean::{self, ImportKind, ItemId};
use crate::formats::item_type::ItemType;
use crate::formats::FormatRenderer;
use crate::json::JsonRenderer;
Expand Down Expand Up @@ -761,20 +761,22 @@ impl FromWithTcx<clean::Discriminant> for Discriminant {

impl FromWithTcx<clean::Import> for Import {
fn from_tcx(import: clean::Import, tcx: TyCtxt<'_>) -> Self {
use clean::ImportKind::*;
let (name, glob) = match import.kind {
Simple(s) => (s.to_string(), false),
Glob => (
let (name, glob, id) = match import.kind {
ImportKind::Simple(s) => {
let id = if import.inherits_doc_hidden(tcx) {
None
} else {
import.source.did.map(ItemId::from).map(|i| id_from_item_default(i, tcx))
};
(s.to_string(), false, id)
}
ImportKind::Glob => (
import.source.path.last_opt().unwrap_or_else(|| Symbol::intern("*")).to_string(),
true,
import.source.did.map(ItemId::from).map(|i| id_from_item_default(i, tcx)),
),
};
Import {
source: import.source.path.whole_name(),
name,
id: import.source.did.map(ItemId::from).map(|i| id_from_item_default(i, tcx)),
glob,
}
Import { source: import.source.path.whole_name(), name, id, glob }
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/passes/stripper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ pub(crate) struct ImportStripper<'tcx> {

impl<'tcx> ImportStripper<'tcx> {
fn import_should_be_hidden(&self, i: &Item, imp: &clean::Import) -> bool {
if self.is_json_output {
if self.is_json_output && imp.kind.is_glob() {
// FIXME: This should be handled the same way as for HTML output.
imp.imported_item_is_doc_hidden(self.tcx)
} else {
Expand Down
19 changes: 17 additions & 2 deletions src/rustdoc-json-types/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,10 +669,25 @@ pub struct Import {
/// May be different from the last segment of `source` when renaming imports:
/// `use source as name;`
pub name: String,
/// The ID of the item being imported. Will be `None` in case of re-exports of primitives:
/// ```rust
/// The ID of the item being imported. Will be `None` in case of re-exports of primitives or
/// if the reexported item is `#[doc(hidden)]` or inherits it from one of its parents.
///
/// Example of reexported primitive type:
///
/// ```
/// pub use i32 as my_i32;
/// ```
///
/// Example of reexported item which inherits `doc(hidden)`:
///
/// ```
/// #[doc(hidden)]
/// pub mod foo {
/// pub struct Bar;
/// }
///
/// pub use foo::Bar;
/// ```
pub id: Option<Id>,
/// Whether this import uses a glob: `use source::*;`
pub glob: bool,
Expand Down
19 changes: 19 additions & 0 deletions tests/rustdoc-json/doc_hidden_reexports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Regression test for <https://github.com/rust-lang/rust/issues/117718>.
// Ensures that the various "doc(hidden)" reexport cases are correctly handled.

pub mod submodule {
#[doc(hidden)]
pub struct Hidden {}
}

#[doc(hidden)]
mod x {
pub struct Y {}
}

// @has "$.index[*].inner[?(@.import.name=='UsedHidden')].import.source" '"submodule::Hidden"'
// @has "$.index[*].inner[?(@.import.name=='UsedHidden')].import.id" "null"
pub use submodule::Hidden as UsedHidden;
// @has "$.index[*].inner[?(@.import.name=='Z')].import.source" '"x::Y"'
// @has "$.index[*].inner[?(@.import.name=='Z')].import.id" 'null'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the same logic as for reexport of item inside private module. We discussed that after this PR, we want to inline such items and I think in the meantime, this is the right course of action.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the same logic as for reexport of item inside private module.

When reexporting an item defined in a private module, the import will have the ID of the underlying item. We should do the same thing here.

We discussed that after this PR, we want to inline such items

Please no. Inlining was a massive source of bugs for rustdoc-json, and removing it was a great improvement. I'm strongly opposed to bringing it back.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having an ID pointing to an item that is not available means that the current bug fixed by this PR will remain.

pub use x::Y as Z;
3 changes: 2 additions & 1 deletion tests/rustdoc-json/reexport/pub_use_doc_hidden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ mod repeat_n {
pub struct RepeatN {}
}

/// not here
/// is here
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right behavior. If the underlying item isn't part of the public API than I don't think the import of it should be either.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import is publicly accessible so I don't see why not?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Ideologically: RepeatN has asked to not be part of the public API. We don't show it in the HTML docs.
  2. Practicly: Theirs very little use to consumers telling them "You import something here, but you don't get to know what it it". If it's a part of the API, we should tell them the full story. If it isn't then we shouldn't acknowledge the import at all. What purpose does this weird in-between serve? (If theirs a compelling use case for just knowing this, that'd easily change my mind)

pub use repeat_n::RepeatN;

// @count "$.index[*][?(@.name=='pub_use_doc_hidden')].inner.items[*]" 0
// @has "$.index[*][?(@.docs == 'is here')]"
// @!has "$.index[*][?(@.docs == 'not here')]"