Skip to content

Commit 506495a

Browse files
authored
Rollup merge of #108870 - GuillaumeGomez:rustdoc-reexport-of-reexport-of-private, r=notriddle
Fix invalid inlining of reexport of reexport of private item Fixes #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`
2 parents bec7011 + c0c93be commit 506495a

File tree

3 files changed

+114
-43
lines changed

3 files changed

+114
-43
lines changed

src/librustdoc/clean/mod.rs

+66-42
Original file line numberDiff line numberDiff line change
@@ -2065,23 +2065,81 @@ fn clean_bare_fn_ty<'tcx>(
20652065
BareFunctionDecl { unsafety: bare_fn.unsafety, abi: bare_fn.abi, decl, generic_params }
20662066
}
20672067

2068-
/// This visitor is used to go through only the "top level" of a item and not enter any sub
2069-
/// item while looking for a given `Ident` which is stored into `item` if found.
2070-
struct OneLevelVisitor<'hir> {
2068+
/// Get DefId of of an item's user-visible parent.
2069+
///
2070+
/// "User-visible" should account for re-exporting and inlining, which is why this function isn't
2071+
/// just `tcx.parent(def_id)`. If the provided `path` has more than one path element, the `DefId`
2072+
/// of the second-to-last will be given.
2073+
///
2074+
/// ```text
2075+
/// use crate::foo::Bar;
2076+
/// ^^^ DefId of this item will be returned
2077+
/// ```
2078+
///
2079+
/// If the provided path has only one item, `tcx.parent(def_id)` will be returned instead.
2080+
fn get_path_parent_def_id(
2081+
tcx: TyCtxt<'_>,
2082+
def_id: DefId,
2083+
path: &hir::UsePath<'_>,
2084+
) -> Option<DefId> {
2085+
if let [.., parent_segment, _] = &path.segments {
2086+
match parent_segment.res {
2087+
hir::def::Res::Def(_, parent_def_id) => Some(parent_def_id),
2088+
_ if parent_segment.ident.name == kw::Crate => {
2089+
// In case the "parent" is the crate, it'll give `Res::Err` so we need to
2090+
// circumvent it this way.
2091+
Some(tcx.parent(def_id))
2092+
}
2093+
_ => None,
2094+
}
2095+
} else {
2096+
// If the path doesn't have a parent, then the parent is the current module.
2097+
Some(tcx.parent(def_id))
2098+
}
2099+
}
2100+
2101+
/// This visitor is used to find an HIR Item based on its `use` path. This doesn't use the ordinary
2102+
/// name resolver because it does not walk all the way through a chain of re-exports.
2103+
pub(crate) struct OneLevelVisitor<'hir> {
20712104
map: rustc_middle::hir::map::Map<'hir>,
2072-
item: Option<&'hir hir::Item<'hir>>,
2105+
pub(crate) item: Option<&'hir hir::Item<'hir>>,
20732106
looking_for: Ident,
20742107
target_def_id: LocalDefId,
20752108
}
20762109

20772110
impl<'hir> OneLevelVisitor<'hir> {
2078-
fn new(map: rustc_middle::hir::map::Map<'hir>, target_def_id: LocalDefId) -> Self {
2111+
pub(crate) fn new(map: rustc_middle::hir::map::Map<'hir>, target_def_id: LocalDefId) -> Self {
20792112
Self { map, item: None, looking_for: Ident::empty(), target_def_id }
20802113
}
20812114

2082-
fn reset(&mut self, looking_for: Ident) {
2083-
self.looking_for = looking_for;
2115+
pub(crate) fn find_target(
2116+
&mut self,
2117+
tcx: TyCtxt<'_>,
2118+
def_id: DefId,
2119+
path: &hir::UsePath<'_>,
2120+
) -> Option<&'hir hir::Item<'hir>> {
2121+
let parent_def_id = get_path_parent_def_id(tcx, def_id, path)?;
2122+
let parent = self.map.get_if_local(parent_def_id)?;
2123+
2124+
// We get the `Ident` we will be looking for into `item`.
2125+
self.looking_for = path.segments[path.segments.len() - 1].ident;
2126+
// We reset the `item`.
20842127
self.item = None;
2128+
2129+
match parent {
2130+
hir::Node::Item(parent_item) => {
2131+
hir::intravisit::walk_item(self, parent_item);
2132+
}
2133+
hir::Node::Crate(m) => {
2134+
hir::intravisit::walk_mod(
2135+
self,
2136+
m,
2137+
tcx.local_def_id_to_hir_id(parent_def_id.as_local().unwrap()),
2138+
);
2139+
}
2140+
_ => return None,
2141+
}
2142+
self.item
20852143
}
20862144
}
20872145

@@ -2129,41 +2187,7 @@ fn get_all_import_attributes<'hir>(
21292187
add_without_unwanted_attributes(attributes, hir_map.attrs(item.hir_id()), is_inline);
21302188
}
21312189

2132-
let def_id = if let [.., parent_segment, _] = &path.segments {
2133-
match parent_segment.res {
2134-
hir::def::Res::Def(_, def_id) => def_id,
2135-
_ if parent_segment.ident.name == kw::Crate => {
2136-
// In case the "parent" is the crate, it'll give `Res::Err` so we need to
2137-
// circumvent it this way.
2138-
tcx.parent(item.owner_id.def_id.to_def_id())
2139-
}
2140-
_ => break,
2141-
}
2142-
} else {
2143-
// If the path doesn't have a parent, then the parent is the current module.
2144-
tcx.parent(item.owner_id.def_id.to_def_id())
2145-
};
2146-
2147-
let Some(parent) = hir_map.get_if_local(def_id) else { break };
2148-
2149-
// We get the `Ident` we will be looking for into `item`.
2150-
let looking_for = path.segments[path.segments.len() - 1].ident;
2151-
visitor.reset(looking_for);
2152-
2153-
match parent {
2154-
hir::Node::Item(parent_item) => {
2155-
hir::intravisit::walk_item(&mut visitor, parent_item);
2156-
}
2157-
hir::Node::Crate(m) => {
2158-
hir::intravisit::walk_mod(
2159-
&mut visitor,
2160-
m,
2161-
tcx.local_def_id_to_hir_id(def_id.as_local().unwrap()),
2162-
);
2163-
}
2164-
_ => break,
2165-
}
2166-
if let Some(i) = visitor.item {
2190+
if let Some(i) = visitor.find_target(tcx, item.owner_id.def_id.to_def_id(), path) {
21672191
item = i;
21682192
} else {
21692193
break;

src/librustdoc/visit_ast.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_span::Span;
1515

1616
use std::mem;
1717

18-
use crate::clean::{cfg::Cfg, AttributesExt, NestedAttributesExt};
18+
use crate::clean::{cfg::Cfg, AttributesExt, NestedAttributesExt, OneLevelVisitor};
1919
use crate::core;
2020

2121
/// 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> {
220220
renamed: Option<Symbol>,
221221
glob: bool,
222222
please_inline: bool,
223+
path: &hir::UsePath<'_>,
223224
) -> bool {
224225
debug!("maybe_inline_local res: {:?}", res);
225226

@@ -263,6 +264,22 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
263264
return false;
264265
}
265266

267+
if !please_inline &&
268+
let mut visitor = OneLevelVisitor::new(self.cx.tcx.hir(), res_did) &&
269+
let Some(item) = visitor.find_target(self.cx.tcx, def_id.to_def_id(), path) &&
270+
let item_def_id = item.owner_id.def_id &&
271+
item_def_id != def_id &&
272+
self
273+
.cx
274+
.cache
275+
.effective_visibilities
276+
.is_directly_public(self.cx.tcx, item_def_id.to_def_id()) &&
277+
!inherits_doc_hidden(self.cx.tcx, item_def_id)
278+
{
279+
// The imported item is public and not `doc(hidden)` so no need to inline it.
280+
return false;
281+
}
282+
266283
let ret = match tcx.hir().get_by_def_id(res_did) {
267284
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => {
268285
let prev = mem::replace(&mut self.inlining, true);
@@ -361,6 +378,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
361378
ident,
362379
is_glob,
363380
please_inline,
381+
path,
364382
) {
365383
continue;
366384
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// This test ensures that the `struct.B.html` only exists in `a`:
2+
// since `a::B` is public (and inlined too), `self::a::B` doesn't
3+
// need to be inlined as well.
4+
5+
#![crate_name = "foo"]
6+
7+
pub mod a {
8+
// @has 'foo/a/index.html'
9+
// Should only contain "Structs".
10+
// @count - '//*[@id="main-content"]//*[@class="item-table"]' 1
11+
// @has - '//*[@id="structs"]' 'Structs'
12+
// @has - '//*[@id="main-content"]//a[@href="struct.A.html"]' 'A'
13+
// @has - '//*[@id="main-content"]//a[@href="struct.B.html"]' 'B'
14+
mod b {
15+
pub struct B;
16+
}
17+
pub use self::b::B;
18+
pub struct A;
19+
}
20+
21+
// @has 'foo/index.html'
22+
// @!has - '//*[@id="structs"]' 'Structs'
23+
// @has - '//*[@id="reexports"]' 'Re-exports'
24+
// @has - '//*[@id="modules"]' 'Modules'
25+
// @has - '//*[@id="main-content"]//*[@id="reexport.A"]' 'pub use self::a::A;'
26+
// @has - '//*[@id="main-content"]//*[@id="reexport.B"]' 'pub use self::a::B;'
27+
// Should only contain "Modules" and "Re-exports".
28+
// @count - '//*[@id="main-content"]//*[@class="item-table"]' 2
29+
pub use self::a::{A, B};

0 commit comments

Comments
 (0)