Skip to content

Commit 754d3e5

Browse files
Fix weird rustdoc output when single and glob reexport conflict on a name
1 parent 25cf7d1 commit 754d3e5

File tree

3 files changed

+88
-39
lines changed

3 files changed

+88
-39
lines changed

src/librustdoc/clean/inline.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,12 @@ pub(crate) fn try_inline(
152152
};
153153

154154
cx.inlined.insert(did.into());
155+
let import_def_ids: &[LocalDefId] = match import_def_id {
156+
Some(import_def_id) => &[import_def_id],
157+
None => &[],
158+
};
155159
let mut item =
156-
crate::clean::generate_item_with_correct_attrs(cx, kind, did, name, import_def_id, None);
160+
crate::clean::generate_item_with_correct_attrs(cx, kind, did, name, import_def_ids, None);
157161
// The visibility needs to reflect the one from the reexport and not from the "source" DefId.
158162
item.inner.inline_stmt_id = import_def_id;
159163
ret.push(item);

src/librustdoc/clean/mod.rs

Lines changed: 37 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())
@@ -157,12 +157,16 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
157157
});
158158

159159
let kind = ModuleItem(Module { items, span });
160+
let import_def_ids: &[LocalDefId] = match doc.import_id {
161+
Some(import_def_id) => &[import_def_id],
162+
None => &[],
163+
};
160164
generate_item_with_correct_attrs(
161165
cx,
162166
kind,
163167
doc.def_id.to_def_id(),
164168
doc.name,
165-
doc.import_id,
169+
import_def_ids,
166170
doc.renamed,
167171
)
168172
}
@@ -182,22 +186,29 @@ fn generate_item_with_correct_attrs(
182186
kind: ItemKind,
183187
def_id: DefId,
184188
name: Symbol,
185-
import_id: Option<LocalDefId>,
189+
import_ids: &[LocalDefId],
186190
renamed: Option<Symbol>,
187191
) -> Item {
188192
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);
193+
let attrs = if !import_ids.is_empty() {
194+
let mut attrs = Vec::with_capacity(import_ids.len());
195+
let mut is_inline = false;
196+
197+
for import_id in import_ids.iter().copied() {
198+
// glob reexports are treated the same as `#[doc(inline)]` items.
199+
//
200+
// For glob re-exports the item may or may not exist to be re-exported (potentially the
201+
// cfgs on the path up until the glob can be removed, and only cfgs on the globbed item
202+
// itself matter), for non-inlined re-exports see #85043.
203+
let import_is_inline =
204+
hir_attr_lists(inline::load_attrs(cx, import_id.to_def_id()), sym::doc)
205+
.get_word_attr(sym::inline)
206+
.is_some()
207+
|| (is_glob_import(cx.tcx, import_id)
208+
&& (cx.render_options.document_hidden || !cx.tcx.is_doc_hidden(def_id)));
209+
attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline));
210+
is_inline = is_inline || import_is_inline;
211+
}
201212
add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None);
202213
attrs
203214
} else {
@@ -216,7 +227,8 @@ fn generate_item_with_correct_attrs(
216227

217228
let name = renamed.or(Some(name));
218229
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;
230+
// FIXME (GuillaumeGomez): Should we also make `inline_stmt_id` a `Vec` instead of an `Option`?
231+
item.inner.inline_stmt_id = import_ids.first().copied();
220232
item
221233
}
222234

@@ -2754,7 +2766,7 @@ fn clean_maybe_renamed_item<'tcx>(
27542766
cx: &mut DocContext<'tcx>,
27552767
item: &hir::Item<'tcx>,
27562768
renamed: Option<Symbol>,
2757-
import_id: Option<LocalDefId>,
2769+
import_ids: &[LocalDefId],
27582770
) -> Vec<Item> {
27592771
use hir::ItemKind;
27602772
fn get_name(
@@ -2825,7 +2837,7 @@ fn clean_maybe_renamed_item<'tcx>(
28252837
})),
28262838
item.owner_id.def_id.to_def_id(),
28272839
name,
2828-
import_id,
2840+
import_ids,
28292841
renamed,
28302842
));
28312843
return ret;
@@ -2880,7 +2892,7 @@ fn clean_maybe_renamed_item<'tcx>(
28802892
kind,
28812893
item.owner_id.def_id.to_def_id(),
28822894
name,
2883-
import_id,
2895+
import_ids,
28842896
renamed,
28852897
)]
28862898
})
@@ -3146,12 +3158,16 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
31463158
hir::ForeignItemKind::Type => ForeignTypeItem,
31473159
};
31483160

3161+
let import_ids: &[LocalDefId] = match import_id {
3162+
Some(import_id) => &[import_id],
3163+
None => &[],
3164+
};
31493165
generate_item_with_correct_attrs(
31503166
cx,
31513167
kind,
31523168
item.owner_id.def_id.to_def_id(),
31533169
item.ident.name,
3154-
import_id,
3170+
import_ids,
31553171
renamed,
31563172
)
31573173
})

src/librustdoc/visit_ast.rs

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,28 @@ 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 both
53+
/// imports to be merged.
3754
pub(crate) items: FxIndexMap<
3855
(LocalDefId, Option<Symbol>),
39-
(&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>),
56+
(&'hir hir::Item<'hir>, Option<Symbol>, Vec<LocalDefId>),
4057
>,
4158

4259
/// (def_id, renamed) -> (res, local_import_id)
@@ -154,7 +171,9 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
154171
{
155172
let item = self.cx.tcx.hir_expect_item(local_def_id);
156173
let (ident, _, _) = item.expect_macro();
157-
top_level_module.items.insert((local_def_id, Some(ident.name)), (item, None, None));
174+
top_level_module
175+
.items
176+
.insert((local_def_id, Some(ident.name)), (item, None, Vec::new()));
158177
}
159178
}
160179

@@ -236,7 +255,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
236255
) -> bool {
237256
debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
238257

239-
let glob = renamed.is_none();
240258
if renamed == Some(kw::Underscore) {
241259
// We never inline `_` reexports.
242260
return false;
@@ -261,14 +279,15 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
261279
return false;
262280
}
263281

282+
let is_glob = renamed.is_none();
264283
let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
265284
let Some(res_did) = ori_res_did.as_local() else {
266285
// For cross-crate impl inlining we need to know whether items are
267286
// reachable in documentation -- a previously unreachable item can be
268287
// made reachable by cross-crate inlining which we're checking here.
269288
// (this is done here because we need to know this upfront).
270289
crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
271-
if is_hidden || glob {
290+
if is_hidden || is_glob {
272291
return false;
273292
}
274293
// We store inlined foreign items otherwise, it'd mean that the `use` item would be kept
@@ -316,10 +335,10 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
316335
// Bang macros are handled a bit on their because of how they are handled by the
317336
// compiler. If they have `#[doc(hidden)]` and the re-export doesn't have
318337
// `#[doc(inline)]`, then we don't inline it.
319-
Node::Item(_) if is_bang_macro && !please_inline && renamed.is_some() && is_hidden => {
338+
Node::Item(_) if is_bang_macro && !please_inline && !is_glob && is_hidden => {
320339
return false;
321340
}
322-
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if glob => {
341+
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
323342
let prev = mem::replace(&mut self.inlining, true);
324343
for &i in m.item_ids {
325344
let i = tcx.hir_item(i);
@@ -328,13 +347,13 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
328347
self.inlining = prev;
329348
true
330349
}
331-
Node::Item(it) if !glob => {
350+
Node::Item(it) if !is_glob => {
332351
let prev = mem::replace(&mut self.inlining, true);
333352
self.visit_item_inner(it, renamed, Some(def_id));
334353
self.inlining = prev;
335354
true
336355
}
337-
Node::ForeignItem(it) if !glob => {
356+
Node::ForeignItem(it) if !is_glob => {
338357
let prev = mem::replace(&mut self.inlining, true);
339358
self.visit_foreign_item_inner(it, renamed, Some(def_id));
340359
self.inlining = prev;
@@ -378,8 +397,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
378397
fn add_to_current_mod(
379398
&mut self,
380399
item: &'tcx hir::Item<'_>,
381-
renamed: Option<Symbol>,
382-
parent_id: Option<LocalDefId>,
400+
mut renamed: Option<Symbol>,
401+
import_id: Option<LocalDefId>,
383402
) {
384403
if self.is_importable_from_parent
385404
// If we're inside an item, only impl blocks and `macro_rules!` with the `macro_export`
@@ -392,11 +411,21 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
392411
_ => false,
393412
}
394413
{
395-
self.modules
396-
.last_mut()
397-
.unwrap()
398-
.items
399-
.insert((item.owner_id.def_id, renamed), (item, renamed, parent_id));
414+
if renamed == item.kind.ident().map(|ident| ident.name) {
415+
renamed = None;
416+
}
417+
let key = (item.owner_id.def_id, renamed);
418+
if let Some(import_id) = import_id {
419+
self.modules
420+
.last_mut()
421+
.unwrap()
422+
.items
423+
.entry(key)
424+
.and_modify(|v| v.2.push(import_id))
425+
.or_insert_with(|| (item, renamed, vec![import_id]));
426+
} else {
427+
self.modules.last_mut().unwrap().items.insert(key, (item, renamed, Vec::new()));
428+
}
400429
}
401430
}
402431

@@ -468,7 +497,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
468497
_ => false,
469498
});
470499
let ident = match kind {
471-
hir::UseKind::Single(ident) => Some(renamed.unwrap_or(ident.name)),
500+
hir::UseKind::Single(ident) => Some(ident.name),
472501
hir::UseKind::Glob => None,
473502
hir::UseKind::ListStem => unreachable!(),
474503
};

0 commit comments

Comments
 (0)