Skip to content

Commit e4d6307

Browse files
committed
Auto merge of #104292 - GuillaumeGomez:fix-missing-reexports-doc-comments, r=notriddle
Fix missing reexports' doc comments Fixes #81893. The issue was that an import directly "links" to the target without the intermediate imports. Unfortunately, to fix this bug we need to go through them one by one. To do so, I take the import path direct parent (so `b` in `a::b::c`) and then look for `c` into it. r? `@notriddle`
2 parents afd7977 + 0839d39 commit e4d6307

File tree

2 files changed

+121
-7
lines changed

2 files changed

+121
-7
lines changed

Diff for: src/librustdoc/clean/mod.rs

+87-7
Original file line numberDiff line numberDiff line change
@@ -1942,6 +1942,79 @@ fn clean_bare_fn_ty<'tcx>(
19421942
BareFunctionDecl { unsafety: bare_fn.unsafety, abi: bare_fn.abi, decl, generic_params }
19431943
}
19441944

1945+
/// This visitor is used to go through only the "top level" of a item and not enter any sub
1946+
/// item while looking for a given `Ident` which is stored into `item` if found.
1947+
struct OneLevelVisitor<'hir> {
1948+
map: rustc_middle::hir::map::Map<'hir>,
1949+
item: Option<&'hir hir::Item<'hir>>,
1950+
looking_for: Ident,
1951+
target_hir_id: hir::HirId,
1952+
}
1953+
1954+
impl<'hir> OneLevelVisitor<'hir> {
1955+
fn new(map: rustc_middle::hir::map::Map<'hir>, target_hir_id: hir::HirId) -> Self {
1956+
Self { map, item: None, looking_for: Ident::empty(), target_hir_id }
1957+
}
1958+
1959+
fn reset(&mut self, looking_for: Ident) {
1960+
self.looking_for = looking_for;
1961+
self.item = None;
1962+
}
1963+
}
1964+
1965+
impl<'hir> hir::intravisit::Visitor<'hir> for OneLevelVisitor<'hir> {
1966+
type NestedFilter = rustc_middle::hir::nested_filter::All;
1967+
1968+
fn nested_visit_map(&mut self) -> Self::Map {
1969+
self.map
1970+
}
1971+
1972+
fn visit_item(&mut self, item: &'hir hir::Item<'hir>) {
1973+
if self.item.is_none()
1974+
&& item.ident == self.looking_for
1975+
&& matches!(item.kind, hir::ItemKind::Use(_, _))
1976+
|| item.hir_id() == self.target_hir_id
1977+
{
1978+
self.item = Some(item);
1979+
}
1980+
}
1981+
}
1982+
1983+
/// Because a `Use` item directly links to the imported item, we need to manually go through each
1984+
/// import one by one. To do so, we go to the parent item and look for the `Ident` into it. Then,
1985+
/// if we found the "end item" (the imported one), we stop there because we don't need its
1986+
/// documentation. Otherwise, we repeat the same operation until we find the "end item".
1987+
fn get_all_import_attributes<'hir>(
1988+
mut item: &hir::Item<'hir>,
1989+
tcx: TyCtxt<'hir>,
1990+
target_hir_id: hir::HirId,
1991+
attributes: &mut Vec<ast::Attribute>,
1992+
) {
1993+
let hir_map = tcx.hir();
1994+
let mut visitor = OneLevelVisitor::new(hir_map, target_hir_id);
1995+
// If the item is an import and has at least a path with two parts, we go into it.
1996+
while let hir::ItemKind::Use(path, _) = item.kind &&
1997+
path.segments.len() > 1 &&
1998+
let hir::def::Res::Def(_, def_id) = path.segments[path.segments.len() - 2].res
1999+
{
2000+
if let Some(hir::Node::Item(parent_item)) = hir_map.get_if_local(def_id) {
2001+
// We add the attributes from this import into the list.
2002+
attributes.extend_from_slice(hir_map.attrs(item.hir_id()));
2003+
// We get the `Ident` we will be looking for into `item`.
2004+
let looking_for = path.segments[path.segments.len() - 1].ident;
2005+
visitor.reset(looking_for);
2006+
hir::intravisit::walk_item(&mut visitor, parent_item);
2007+
if let Some(i) = visitor.item {
2008+
item = i;
2009+
} else {
2010+
break;
2011+
}
2012+
} else {
2013+
break;
2014+
}
2015+
}
2016+
}
2017+
19452018
fn clean_maybe_renamed_item<'tcx>(
19462019
cx: &mut DocContext<'tcx>,
19472020
item: &hir::Item<'tcx>,
@@ -2023,13 +2096,20 @@ fn clean_maybe_renamed_item<'tcx>(
20232096
}
20242097
_ => unreachable!("not yet converted"),
20252098
};
2026-
if let Some(import_id) = import_id {
2027-
let (attrs, cfg) = inline::merge_attrs(
2028-
cx,
2029-
Some(cx.tcx.parent_module(import_id).to_def_id()),
2030-
inline::load_attrs(cx, def_id),
2031-
Some(inline::load_attrs(cx, cx.tcx.hir().local_def_id(import_id).to_def_id())),
2032-
);
2099+
2100+
let mut extra_attrs = Vec::new();
2101+
if let Some(hir::Node::Item(use_node)) =
2102+
import_id.and_then(|hir_id| cx.tcx.hir().find(hir_id))
2103+
{
2104+
// We get all the various imports' attributes.
2105+
get_all_import_attributes(use_node, cx.tcx, item.hir_id(), &mut extra_attrs);
2106+
}
2107+
2108+
if !extra_attrs.is_empty() {
2109+
extra_attrs.extend_from_slice(inline::load_attrs(cx, def_id));
2110+
let attrs = Attributes::from_ast(&extra_attrs);
2111+
let cfg = extra_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg);
2112+
20332113
vec![Item::from_def_id_and_attrs_and_parts(
20342114
def_id,
20352115
Some(name),

Diff for: src/test/rustdoc/multiple-import-levels.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// The goal of this test is to ensure that the attributes of all imports are taken into
2+
// account.
3+
4+
#![crate_name = "foo"]
5+
6+
mod a {
7+
/// 1
8+
pub struct Type;
9+
}
10+
11+
mod b {
12+
/// 2
13+
pub use crate::a::Type;
14+
}
15+
16+
mod c {
17+
/// 3
18+
pub use crate::b::Type;
19+
/// 4
20+
pub use crate::b::Type as Woof;
21+
}
22+
23+
// @has 'foo/struct.Type.html'
24+
// @has - '//*[@class="rustdoc-toggle top-doc"]/*[@class="docblock"]' 'foo 2 1'
25+
/// foo
26+
pub use b::Type;
27+
// @has 'foo/struct.Whatever.html'
28+
// @has - '//*[@class="rustdoc-toggle top-doc"]/*[@class="docblock"]' 'whatever 3 2 1'
29+
/// whatever
30+
pub use c::Type as Whatever;
31+
// @has 'foo/struct.Woof.html'
32+
// @has - '//*[@class="rustdoc-toggle top-doc"]/*[@class="docblock"]' 'a dog 4 2 1'
33+
/// a dog
34+
pub use c::Woof;

0 commit comments

Comments
 (0)