Skip to content

Commit 05c68f3

Browse files
authored
Rollup merge of #143590 - GuillaumeGomez:reexport-shadowing, r=lolbinary
Fix weird rustdoc output when single and glob reexport conflict on a name Fixes #143107. The problem was that the second reexport would overwrite the first, leading to having unexpected results. To fix it, I now group items by their original `DefId` and their name and keep tracks of all imports for this item (should very rarely be more than one though, and even less often more than 2). cc `@lolbinarycat`
2 parents b11e9e3 + 086b13d commit 05c68f3

File tree

4 files changed

+125
-40
lines changed

4 files changed

+125
-40
lines changed

src/librustdoc/clean/inline.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,14 @@ pub(crate) fn try_inline(
152152
};
153153

154154
cx.inlined.insert(did.into());
155-
let mut item =
156-
crate::clean::generate_item_with_correct_attrs(cx, kind, did, name, import_def_id, None);
155+
let mut item = crate::clean::generate_item_with_correct_attrs(
156+
cx,
157+
kind,
158+
did,
159+
name,
160+
import_def_id.as_slice(),
161+
None,
162+
);
157163
// The visibility needs to reflect the one from the reexport and not from the "source" DefId.
158164
item.inner.inline_stmt_id = import_def_id;
159165
ret.push(item);

src/librustdoc/clean/mod.rs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,12 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
9494
// This covers the case where somebody does an import which should pull in an item,
9595
// but there's already an item with the same namespace and same name. Rust gives
9696
// priority to the not-imported one, so we should, too.
97-
items.extend(doc.items.values().flat_map(|(item, renamed, import_id)| {
97+
items.extend(doc.items.values().flat_map(|(item, renamed, import_ids)| {
9898
// First, lower everything other than glob imports.
9999
if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
100100
return Vec::new();
101101
}
102-
let v = clean_maybe_renamed_item(cx, item, *renamed, *import_id);
102+
let v = clean_maybe_renamed_item(cx, item, *renamed, import_ids);
103103
for item in &v {
104104
if let Some(name) = item.name
105105
&& (cx.render_options.document_hidden || !item.is_doc_hidden())
@@ -162,7 +162,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
162162
kind,
163163
doc.def_id.to_def_id(),
164164
doc.name,
165-
doc.import_id,
165+
doc.import_id.as_slice(),
166166
doc.renamed,
167167
)
168168
}
@@ -182,22 +182,29 @@ fn generate_item_with_correct_attrs(
182182
kind: ItemKind,
183183
def_id: DefId,
184184
name: Symbol,
185-
import_id: Option<LocalDefId>,
185+
import_ids: &[LocalDefId],
186186
renamed: Option<Symbol>,
187187
) -> Item {
188188
let target_attrs = inline::load_attrs(cx, def_id);
189-
let attrs = if let Some(import_id) = import_id {
190-
// glob reexports are treated the same as `#[doc(inline)]` items.
191-
//
192-
// For glob re-exports the item may or may not exist to be re-exported (potentially the cfgs
193-
// on the path up until the glob can be removed, and only cfgs on the globbed item itself
194-
// matter), for non-inlined re-exports see #85043.
195-
let is_inline = hir_attr_lists(inline::load_attrs(cx, import_id.to_def_id()), sym::doc)
196-
.get_word_attr(sym::inline)
197-
.is_some()
198-
|| (is_glob_import(cx.tcx, import_id)
199-
&& (cx.render_options.document_hidden || !cx.tcx.is_doc_hidden(def_id)));
200-
let mut attrs = get_all_import_attributes(cx, import_id, def_id, is_inline);
189+
let attrs = if !import_ids.is_empty() {
190+
let mut attrs = Vec::with_capacity(import_ids.len());
191+
let mut is_inline = false;
192+
193+
for import_id in import_ids.iter().copied() {
194+
// glob reexports are treated the same as `#[doc(inline)]` items.
195+
//
196+
// For glob re-exports the item may or may not exist to be re-exported (potentially the
197+
// cfgs on the path up until the glob can be removed, and only cfgs on the globbed item
198+
// itself matter), for non-inlined re-exports see #85043.
199+
let import_is_inline =
200+
hir_attr_lists(inline::load_attrs(cx, import_id.to_def_id()), sym::doc)
201+
.get_word_attr(sym::inline)
202+
.is_some()
203+
|| (is_glob_import(cx.tcx, import_id)
204+
&& (cx.render_options.document_hidden || !cx.tcx.is_doc_hidden(def_id)));
205+
attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline));
206+
is_inline = is_inline || import_is_inline;
207+
}
201208
add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None);
202209
attrs
203210
} else {
@@ -216,7 +223,8 @@ fn generate_item_with_correct_attrs(
216223

217224
let name = renamed.or(Some(name));
218225
let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, cfg);
219-
item.inner.inline_stmt_id = import_id;
226+
// FIXME (GuillaumeGomez): Should we also make `inline_stmt_id` a `Vec` instead of an `Option`?
227+
item.inner.inline_stmt_id = import_ids.first().copied();
220228
item
221229
}
222230

@@ -2754,7 +2762,7 @@ fn clean_maybe_renamed_item<'tcx>(
27542762
cx: &mut DocContext<'tcx>,
27552763
item: &hir::Item<'tcx>,
27562764
renamed: Option<Symbol>,
2757-
import_id: Option<LocalDefId>,
2765+
import_ids: &[LocalDefId],
27582766
) -> Vec<Item> {
27592767
use hir::ItemKind;
27602768
fn get_name(
@@ -2825,7 +2833,7 @@ fn clean_maybe_renamed_item<'tcx>(
28252833
})),
28262834
item.owner_id.def_id.to_def_id(),
28272835
name,
2828-
import_id,
2836+
import_ids,
28292837
renamed,
28302838
));
28312839
return ret;
@@ -2880,7 +2888,7 @@ fn clean_maybe_renamed_item<'tcx>(
28802888
kind,
28812889
item.owner_id.def_id.to_def_id(),
28822890
name,
2883-
import_id,
2891+
import_ids,
28842892
renamed,
28852893
)]
28862894
})
@@ -3151,7 +3159,7 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
31513159
kind,
31523160
item.owner_id.def_id.to_def_id(),
31533161
item.ident.name,
3154-
import_id,
3162+
import_id.as_slice(),
31553163
renamed,
31563164
)
31573165
})

src/librustdoc/visit_ast.rs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,29 @@ pub(crate) struct Module<'hir> {
3232
pub(crate) def_id: LocalDefId,
3333
pub(crate) renamed: Option<Symbol>,
3434
pub(crate) import_id: Option<LocalDefId>,
35-
/// The key is the item `ItemId` and the value is: (item, renamed, import_id).
35+
/// The key is the item `ItemId` and the value is: (item, renamed, Vec<import_id>).
3636
/// We use `FxIndexMap` to keep the insert order.
37+
///
38+
/// `import_id` needs to be a `Vec` because we live in a dark world where you can have code
39+
/// like:
40+
///
41+
/// ```
42+
/// mod raw {
43+
/// pub fn foo() {}
44+
/// }
45+
///
46+
/// /// Foobar
47+
/// pub use raw::foo;
48+
///
49+
/// pub use raw::*;
50+
/// ```
51+
///
52+
/// So in this case, we don't want to have two items but just one with attributes from all
53+
/// non-glob imports to be merged. Glob imports attributes are always ignored, whether they're
54+
/// shadowed or not.
3755
pub(crate) items: FxIndexMap<
3856
(LocalDefId, Option<Symbol>),
39-
(&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>),
57+
(&'hir hir::Item<'hir>, Option<Symbol>, Vec<LocalDefId>),
4058
>,
4159

4260
/// (def_id, renamed) -> (res, local_import_id)
@@ -154,7 +172,9 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
154172
{
155173
let item = self.cx.tcx.hir_expect_item(local_def_id);
156174
let (ident, _, _) = item.expect_macro();
157-
top_level_module.items.insert((local_def_id, Some(ident.name)), (item, None, None));
175+
top_level_module
176+
.items
177+
.insert((local_def_id, Some(ident.name)), (item, None, Vec::new()));
158178
}
159179
}
160180

@@ -236,7 +256,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
236256
) -> bool {
237257
debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
238258

239-
let glob = renamed.is_none();
240259
if renamed == Some(kw::Underscore) {
241260
// We never inline `_` reexports.
242261
return false;
@@ -261,14 +280,15 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
261280
return false;
262281
}
263282

283+
let is_glob = renamed.is_none();
264284
let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
265285
let Some(res_did) = ori_res_did.as_local() else {
266286
// For cross-crate impl inlining we need to know whether items are
267287
// reachable in documentation -- a previously unreachable item can be
268288
// made reachable by cross-crate inlining which we're checking here.
269289
// (this is done here because we need to know this upfront).
270290
crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
271-
if is_hidden || glob {
291+
if is_hidden || is_glob {
272292
return false;
273293
}
274294
// We store inlined foreign items otherwise, it'd mean that the `use` item would be kept
@@ -316,10 +336,10 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
316336
// Bang macros are handled a bit on their because of how they are handled by the
317337
// compiler. If they have `#[doc(hidden)]` and the re-export doesn't have
318338
// `#[doc(inline)]`, then we don't inline it.
319-
Node::Item(_) if is_bang_macro && !please_inline && renamed.is_some() && is_hidden => {
339+
Node::Item(_) if is_bang_macro && !please_inline && !is_glob && is_hidden => {
320340
return false;
321341
}
322-
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if glob => {
342+
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
323343
let prev = mem::replace(&mut self.inlining, true);
324344
for &i in m.item_ids {
325345
let i = tcx.hir_item(i);
@@ -328,13 +348,13 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
328348
self.inlining = prev;
329349
true
330350
}
331-
Node::Item(it) if !glob => {
351+
Node::Item(it) if !is_glob => {
332352
let prev = mem::replace(&mut self.inlining, true);
333353
self.visit_item_inner(it, renamed, Some(def_id));
334354
self.inlining = prev;
335355
true
336356
}
337-
Node::ForeignItem(it) if !glob => {
357+
Node::ForeignItem(it) if !is_glob => {
338358
let prev = mem::replace(&mut self.inlining, true);
339359
self.visit_foreign_item_inner(it, renamed, Some(def_id));
340360
self.inlining = prev;
@@ -378,8 +398,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
378398
fn add_to_current_mod(
379399
&mut self,
380400
item: &'tcx hir::Item<'_>,
381-
renamed: Option<Symbol>,
382-
parent_id: Option<LocalDefId>,
401+
mut renamed: Option<Symbol>,
402+
import_id: Option<LocalDefId>,
383403
) {
384404
if self.is_importable_from_parent
385405
// If we're inside an item, only impl blocks and `macro_rules!` with the `macro_export`
@@ -392,11 +412,21 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
392412
_ => false,
393413
}
394414
{
395-
self.modules
396-
.last_mut()
397-
.unwrap()
398-
.items
399-
.insert((item.owner_id.def_id, renamed), (item, renamed, parent_id));
415+
if renamed == item.kind.ident().map(|ident| ident.name) {
416+
renamed = None;
417+
}
418+
let key = (item.owner_id.def_id, renamed);
419+
if let Some(import_id) = import_id {
420+
self.modules
421+
.last_mut()
422+
.unwrap()
423+
.items
424+
.entry(key)
425+
.and_modify(|v| v.2.push(import_id))
426+
.or_insert_with(|| (item, renamed, vec![import_id]));
427+
} else {
428+
self.modules.last_mut().unwrap().items.insert(key, (item, renamed, Vec::new()));
429+
}
400430
}
401431
}
402432

@@ -468,7 +498,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
468498
_ => false,
469499
});
470500
let ident = match kind {
471-
hir::UseKind::Single(ident) => Some(renamed.unwrap_or(ident.name)),
501+
hir::UseKind::Single(ident) => Some(ident.name),
472502
hir::UseKind::Glob => None,
473503
hir::UseKind::ListStem => unreachable!(),
474504
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// This test ensures that if an item is inlined from two different `use`,
2+
// then it will use attributes from both of them.
3+
// This is a regression test for <https://github.com/rust-lang/rust/issues/143107>.
4+
5+
#![feature(no_core)]
6+
#![no_core]
7+
#![no_std]
8+
#![crate_name = "foo"]
9+
10+
// First we ensure we only have two items.
11+
//@ has 'foo/index.html'
12+
//@ count - '//dl[@class="item-table"]/dt' 2
13+
// We should also only have one section (Structs).
14+
//@ count - '//h2[@class="section-header"]' 1
15+
// We now check the short docs.
16+
//@ has - '//dl[@class="item-table"]/dd' 'Foobar Blob'
17+
//@ has - '//dl[@class="item-table"]/dd' 'Tarte Tatin'
18+
19+
//@ has 'foo/struct.Foo.html'
20+
//@ has - '//*[@class="docblock"]' 'Foobar Blob'
21+
22+
//@ has 'foo/struct.Another.html'
23+
//@ has - '//*[@class="docblock"]' 'Tarte Tatin'
24+
25+
mod raw {
26+
/// Blob
27+
pub struct Foo;
28+
29+
/// Tatin
30+
pub struct Another;
31+
}
32+
33+
/// Foobar
34+
pub use raw::Foo;
35+
36+
// Glob reexport attributes are ignored.
37+
/// Baz
38+
pub use raw::*;
39+
40+
/// Tarte
41+
pub use raw::Another as Another;

0 commit comments

Comments
 (0)