mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-05 03:23:25 +00:00
Rollup merge of #108870 - GuillaumeGomez:rustdoc-reexport-of-reexport-of-private, r=notriddle
Fix invalid inlining of reexport of reexport of private item Fixes https://github.com/rust-lang/rust/issues/108679. The problem is that a reexport is always resolving to the end type, so if the end type is private, the reexport inlines. Except that if you reexport a public reexport (which reexports the private item), then it should not be inlined again. r? `@notriddle`
This commit is contained in:
commit
506495a4b3
@ -2065,23 +2065,81 @@ fn clean_bare_fn_ty<'tcx>(
|
|||||||
BareFunctionDecl { unsafety: bare_fn.unsafety, abi: bare_fn.abi, decl, generic_params }
|
BareFunctionDecl { unsafety: bare_fn.unsafety, abi: bare_fn.abi, decl, generic_params }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This visitor is used to go through only the "top level" of a item and not enter any sub
|
/// Get DefId of of an item's user-visible parent.
|
||||||
/// item while looking for a given `Ident` which is stored into `item` if found.
|
///
|
||||||
struct OneLevelVisitor<'hir> {
|
/// "User-visible" should account for re-exporting and inlining, which is why this function isn't
|
||||||
|
/// just `tcx.parent(def_id)`. If the provided `path` has more than one path element, the `DefId`
|
||||||
|
/// of the second-to-last will be given.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// use crate::foo::Bar;
|
||||||
|
/// ^^^ DefId of this item will be returned
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If the provided path has only one item, `tcx.parent(def_id)` will be returned instead.
|
||||||
|
fn get_path_parent_def_id(
|
||||||
|
tcx: TyCtxt<'_>,
|
||||||
|
def_id: DefId,
|
||||||
|
path: &hir::UsePath<'_>,
|
||||||
|
) -> Option<DefId> {
|
||||||
|
if let [.., parent_segment, _] = &path.segments {
|
||||||
|
match parent_segment.res {
|
||||||
|
hir::def::Res::Def(_, parent_def_id) => Some(parent_def_id),
|
||||||
|
_ if parent_segment.ident.name == kw::Crate => {
|
||||||
|
// In case the "parent" is the crate, it'll give `Res::Err` so we need to
|
||||||
|
// circumvent it this way.
|
||||||
|
Some(tcx.parent(def_id))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the path doesn't have a parent, then the parent is the current module.
|
||||||
|
Some(tcx.parent(def_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This visitor is used to find an HIR Item based on its `use` path. This doesn't use the ordinary
|
||||||
|
/// name resolver because it does not walk all the way through a chain of re-exports.
|
||||||
|
pub(crate) struct OneLevelVisitor<'hir> {
|
||||||
map: rustc_middle::hir::map::Map<'hir>,
|
map: rustc_middle::hir::map::Map<'hir>,
|
||||||
item: Option<&'hir hir::Item<'hir>>,
|
pub(crate) item: Option<&'hir hir::Item<'hir>>,
|
||||||
looking_for: Ident,
|
looking_for: Ident,
|
||||||
target_def_id: LocalDefId,
|
target_def_id: LocalDefId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'hir> OneLevelVisitor<'hir> {
|
impl<'hir> OneLevelVisitor<'hir> {
|
||||||
fn new(map: rustc_middle::hir::map::Map<'hir>, target_def_id: LocalDefId) -> Self {
|
pub(crate) fn new(map: rustc_middle::hir::map::Map<'hir>, target_def_id: LocalDefId) -> Self {
|
||||||
Self { map, item: None, looking_for: Ident::empty(), target_def_id }
|
Self { map, item: None, looking_for: Ident::empty(), target_def_id }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self, looking_for: Ident) {
|
pub(crate) fn find_target(
|
||||||
self.looking_for = looking_for;
|
&mut self,
|
||||||
|
tcx: TyCtxt<'_>,
|
||||||
|
def_id: DefId,
|
||||||
|
path: &hir::UsePath<'_>,
|
||||||
|
) -> Option<&'hir hir::Item<'hir>> {
|
||||||
|
let parent_def_id = get_path_parent_def_id(tcx, def_id, path)?;
|
||||||
|
let parent = self.map.get_if_local(parent_def_id)?;
|
||||||
|
|
||||||
|
// We get the `Ident` we will be looking for into `item`.
|
||||||
|
self.looking_for = path.segments[path.segments.len() - 1].ident;
|
||||||
|
// We reset the `item`.
|
||||||
self.item = None;
|
self.item = None;
|
||||||
|
|
||||||
|
match parent {
|
||||||
|
hir::Node::Item(parent_item) => {
|
||||||
|
hir::intravisit::walk_item(self, parent_item);
|
||||||
|
}
|
||||||
|
hir::Node::Crate(m) => {
|
||||||
|
hir::intravisit::walk_mod(
|
||||||
|
self,
|
||||||
|
m,
|
||||||
|
tcx.local_def_id_to_hir_id(parent_def_id.as_local().unwrap()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
self.item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2129,41 +2187,7 @@ fn get_all_import_attributes<'hir>(
|
|||||||
add_without_unwanted_attributes(attributes, hir_map.attrs(item.hir_id()), is_inline);
|
add_without_unwanted_attributes(attributes, hir_map.attrs(item.hir_id()), is_inline);
|
||||||
}
|
}
|
||||||
|
|
||||||
let def_id = if let [.., parent_segment, _] = &path.segments {
|
if let Some(i) = visitor.find_target(tcx, item.owner_id.def_id.to_def_id(), path) {
|
||||||
match parent_segment.res {
|
|
||||||
hir::def::Res::Def(_, def_id) => def_id,
|
|
||||||
_ if parent_segment.ident.name == kw::Crate => {
|
|
||||||
// In case the "parent" is the crate, it'll give `Res::Err` so we need to
|
|
||||||
// circumvent it this way.
|
|
||||||
tcx.parent(item.owner_id.def_id.to_def_id())
|
|
||||||
}
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If the path doesn't have a parent, then the parent is the current module.
|
|
||||||
tcx.parent(item.owner_id.def_id.to_def_id())
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(parent) = hir_map.get_if_local(def_id) else { break };
|
|
||||||
|
|
||||||
// We get the `Ident` we will be looking for into `item`.
|
|
||||||
let looking_for = path.segments[path.segments.len() - 1].ident;
|
|
||||||
visitor.reset(looking_for);
|
|
||||||
|
|
||||||
match parent {
|
|
||||||
hir::Node::Item(parent_item) => {
|
|
||||||
hir::intravisit::walk_item(&mut visitor, parent_item);
|
|
||||||
}
|
|
||||||
hir::Node::Crate(m) => {
|
|
||||||
hir::intravisit::walk_mod(
|
|
||||||
&mut visitor,
|
|
||||||
m,
|
|
||||||
tcx.local_def_id_to_hir_id(def_id.as_local().unwrap()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
if let Some(i) = visitor.item {
|
|
||||||
item = i;
|
item = i;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -15,7 +15,7 @@ use rustc_span::Span;
|
|||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use crate::clean::{cfg::Cfg, AttributesExt, NestedAttributesExt};
|
use crate::clean::{cfg::Cfg, AttributesExt, NestedAttributesExt, OneLevelVisitor};
|
||||||
use crate::core;
|
use crate::core;
|
||||||
|
|
||||||
/// This module is used to store stuff from Rust's AST in a more convenient
|
/// This module is used to store stuff from Rust's AST in a more convenient
|
||||||
@ -220,6 +220,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
|
|||||||
renamed: Option<Symbol>,
|
renamed: Option<Symbol>,
|
||||||
glob: bool,
|
glob: bool,
|
||||||
please_inline: bool,
|
please_inline: bool,
|
||||||
|
path: &hir::UsePath<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
debug!("maybe_inline_local res: {:?}", res);
|
debug!("maybe_inline_local res: {:?}", res);
|
||||||
|
|
||||||
@ -263,6 +264,22 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !please_inline &&
|
||||||
|
let mut visitor = OneLevelVisitor::new(self.cx.tcx.hir(), res_did) &&
|
||||||
|
let Some(item) = visitor.find_target(self.cx.tcx, def_id.to_def_id(), path) &&
|
||||||
|
let item_def_id = item.owner_id.def_id &&
|
||||||
|
item_def_id != def_id &&
|
||||||
|
self
|
||||||
|
.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)
|
||||||
|
{
|
||||||
|
// The imported item is public and not `doc(hidden)` so no need to inline it.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let ret = match tcx.hir().get_by_def_id(res_did) {
|
let ret = match tcx.hir().get_by_def_id(res_did) {
|
||||||
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => {
|
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => {
|
||||||
let prev = mem::replace(&mut self.inlining, true);
|
let prev = mem::replace(&mut self.inlining, true);
|
||||||
@ -361,6 +378,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
|
|||||||
ident,
|
ident,
|
||||||
is_glob,
|
is_glob,
|
||||||
please_inline,
|
please_inline,
|
||||||
|
path,
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
29
tests/rustdoc/issue-108679-reexport-of-reexport.rs
Normal file
29
tests/rustdoc/issue-108679-reexport-of-reexport.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// This test ensures that the `struct.B.html` only exists in `a`:
|
||||||
|
// since `a::B` is public (and inlined too), `self::a::B` doesn't
|
||||||
|
// need to be inlined as well.
|
||||||
|
|
||||||
|
#![crate_name = "foo"]
|
||||||
|
|
||||||
|
pub mod a {
|
||||||
|
// @has 'foo/a/index.html'
|
||||||
|
// Should only contain "Structs".
|
||||||
|
// @count - '//*[@id="main-content"]//*[@class="item-table"]' 1
|
||||||
|
// @has - '//*[@id="structs"]' 'Structs'
|
||||||
|
// @has - '//*[@id="main-content"]//a[@href="struct.A.html"]' 'A'
|
||||||
|
// @has - '//*[@id="main-content"]//a[@href="struct.B.html"]' 'B'
|
||||||
|
mod b {
|
||||||
|
pub struct B;
|
||||||
|
}
|
||||||
|
pub use self::b::B;
|
||||||
|
pub struct A;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @has 'foo/index.html'
|
||||||
|
// @!has - '//*[@id="structs"]' 'Structs'
|
||||||
|
// @has - '//*[@id="reexports"]' 'Re-exports'
|
||||||
|
// @has - '//*[@id="modules"]' 'Modules'
|
||||||
|
// @has - '//*[@id="main-content"]//*[@id="reexport.A"]' 'pub use self::a::A;'
|
||||||
|
// @has - '//*[@id="main-content"]//*[@id="reexport.B"]' 'pub use self::a::B;'
|
||||||
|
// Should only contain "Modules" and "Re-exports".
|
||||||
|
// @count - '//*[@id="main-content"]//*[@class="item-table"]' 2
|
||||||
|
pub use self::a::{A, B};
|
Loading…
Reference in New Issue
Block a user