Skip to content

Commit 2011ced

Browse files
authored
Rollup merge of #108349 - GuillaumeGomez:fix-duplicated-imports2, r=notriddle
rustdoc: Prevent duplicated imports Fixes #108163. Interestingly enough, the AST is providing us an import for each corresponding item, even though the `Res` links to multiple ones each time, which leaded to the same import being duplicated. So in this PR, I decided to prevent the add of the import before the clean pass. However, I originally took a different path by instead filtering after cleaning the path. You can see it [here](https://github.com/rust-lang/rust/compare/master...GuillaumeGomez:rust:fix-duplicated-imports?expand=1). Only the second commit differs. I think this approach is better though, but at least we can compare both if we want. The first commit adds the check for duplicated items in the rustdoc-json output as asked in #108163. cc `@aDotInTheVoid` r? `@notriddle`
2 parents c4a4bce + 20dd1bd commit 2011ced

File tree

4 files changed

+64
-14
lines changed

4 files changed

+64
-14
lines changed

src/librustdoc/clean/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
7777
// This covers the case where somebody does an import which should pull in an item,
7878
// but there's already an item with the same namespace and same name. Rust gives
7979
// priority to the not-imported one, so we should, too.
80-
items.extend(doc.items.iter().flat_map(|(item, renamed, import_id)| {
80+
items.extend(doc.items.values().flat_map(|(item, renamed, import_id)| {
8181
// First, lower everything other than imports.
8282
if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
8383
return Vec::new();
@@ -90,7 +90,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
9090
}
9191
v
9292
}));
93-
items.extend(doc.items.iter().flat_map(|(item, renamed, _)| {
93+
items.extend(doc.items.values().flat_map(|(item, renamed, _)| {
9494
// Now we actually lower the imports, skipping everything else.
9595
if let hir::ItemKind::Use(path, hir::UseKind::Glob) = item.kind {
9696
let name = renamed.unwrap_or_else(|| cx.tcx.hir().name(item.hir_id()));

src/librustdoc/visit_ast.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! The Rust AST Visitor. Extracts useful information and massages it into a form
22
//! usable for `clean`.
33
4-
use rustc_data_structures::fx::FxHashSet;
4+
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
55
use rustc_hir as hir;
66
use rustc_hir::def::{DefKind, Res};
77
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
@@ -26,8 +26,12 @@ pub(crate) struct Module<'hir> {
2626
pub(crate) where_inner: Span,
2727
pub(crate) mods: Vec<Module<'hir>>,
2828
pub(crate) def_id: LocalDefId,
29-
// (item, renamed, import_id)
30-
pub(crate) items: Vec<(&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>)>,
29+
/// The key is the item `ItemId` and the value is: (item, renamed, import_id).
30+
/// We use `FxIndexMap` to keep the insert order.
31+
pub(crate) items: FxIndexMap<
32+
(LocalDefId, Option<Symbol>),
33+
(&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>),
34+
>,
3135
pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>)>,
3236
}
3337

@@ -38,7 +42,7 @@ impl Module<'_> {
3842
def_id,
3943
where_inner,
4044
mods: Vec::new(),
41-
items: Vec::new(),
45+
items: FxIndexMap::default(),
4246
foreigns: Vec::new(),
4347
}
4448
}
@@ -136,7 +140,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
136140
inserted.insert(def_id)
137141
{
138142
let item = self.cx.tcx.hir().expect_item(local_def_id);
139-
top_level_module.items.push((item, None, None));
143+
top_level_module.items.insert((local_def_id, Some(item.ident.name)), (item, None, None));
140144
}
141145
}
142146

@@ -294,7 +298,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
294298
renamed: Option<Symbol>,
295299
parent_id: Option<LocalDefId>,
296300
) {
297-
self.modules.last_mut().unwrap().items.push((item, renamed, parent_id))
301+
self.modules
302+
.last_mut()
303+
.unwrap()
304+
.items
305+
.insert((item.owner_id.def_id, renamed), (item, renamed, parent_id));
298306
}
299307

300308
fn visit_item_inner(

src/tools/jsondoclint/src/validator.rs

+22-6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,19 @@ impl<'a> Validator<'a> {
7171
}
7272
}
7373

74+
fn check_items(&mut self, id: &Id, items: &[Id]) {
75+
let mut visited_ids = HashSet::with_capacity(items.len());
76+
77+
for item in items {
78+
if !visited_ids.insert(item) {
79+
self.fail(
80+
id,
81+
ErrorKind::Custom(format!("Duplicated entry in `items` field: `{item:?}`")),
82+
);
83+
}
84+
}
85+
}
86+
7487
fn check_item(&mut self, id: &'a Id) {
7588
if let Some(item) = &self.krate.index.get(id) {
7689
item.links.values().for_each(|id| self.add_any_id(id));
@@ -83,9 +96,9 @@ impl<'a> Validator<'a> {
8396
ItemEnum::Enum(x) => self.check_enum(x),
8497
ItemEnum::Variant(x) => self.check_variant(x, id),
8598
ItemEnum::Function(x) => self.check_function(x),
86-
ItemEnum::Trait(x) => self.check_trait(x),
99+
ItemEnum::Trait(x) => self.check_trait(x, id),
87100
ItemEnum::TraitAlias(x) => self.check_trait_alias(x),
88-
ItemEnum::Impl(x) => self.check_impl(x),
101+
ItemEnum::Impl(x) => self.check_impl(x, id),
89102
ItemEnum::Typedef(x) => self.check_typedef(x),
90103
ItemEnum::OpaqueTy(x) => self.check_opaque_ty(x),
91104
ItemEnum::Constant(x) => self.check_constant(x),
@@ -94,7 +107,7 @@ impl<'a> Validator<'a> {
94107
ItemEnum::Macro(x) => self.check_macro(x),
95108
ItemEnum::ProcMacro(x) => self.check_proc_macro(x),
96109
ItemEnum::Primitive(x) => self.check_primitive_type(x),
97-
ItemEnum::Module(x) => self.check_module(x),
110+
ItemEnum::Module(x) => self.check_module(x, id),
98111
// FIXME: Why don't these have their own structs?
99112
ItemEnum::ExternCrate { .. } => {}
100113
ItemEnum::AssocConst { type_, default: _ } => self.check_type(type_),
@@ -112,7 +125,8 @@ impl<'a> Validator<'a> {
112125
}
113126

114127
// Core checkers
115-
fn check_module(&mut self, module: &'a Module) {
128+
fn check_module(&mut self, module: &'a Module, id: &Id) {
129+
self.check_items(id, &module.items);
116130
module.items.iter().for_each(|i| self.add_mod_item_id(i));
117131
}
118132

@@ -181,7 +195,8 @@ impl<'a> Validator<'a> {
181195
self.check_fn_decl(&x.decl);
182196
}
183197

184-
fn check_trait(&mut self, x: &'a Trait) {
198+
fn check_trait(&mut self, x: &'a Trait, id: &Id) {
199+
self.check_items(id, &x.items);
185200
self.check_generics(&x.generics);
186201
x.items.iter().for_each(|i| self.add_trait_item_id(i));
187202
x.bounds.iter().for_each(|i| self.check_generic_bound(i));
@@ -193,7 +208,8 @@ impl<'a> Validator<'a> {
193208
x.params.iter().for_each(|i| self.check_generic_bound(i));
194209
}
195210

196-
fn check_impl(&mut self, x: &'a Impl) {
211+
fn check_impl(&mut self, x: &'a Impl, id: &Id) {
212+
self.check_items(id, &x.items);
197213
self.check_generics(&x.generics);
198214
if let Some(path) = &x.trait_ {
199215
self.check_path(path, PathKind::Trait);
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// This test ensures that there are 4 imports as expected:
2+
// * 2 for `Foo`
3+
// * 2 for `Bar`
4+
5+
#![crate_name = "foo"]
6+
7+
// @has 'foo/index.html'
8+
9+
pub mod nested {
10+
/// Foo the struct
11+
pub struct Foo {}
12+
13+
#[allow(non_snake_case)]
14+
/// Foo the function
15+
pub fn Foo() {}
16+
}
17+
18+
// @count - '//*[@id="main-content"]//code' 'pub use nested::Foo;' 2
19+
// @has - '//*[@id="reexport.Foo"]//a[@href="nested/struct.Foo.html"]' 'Foo'
20+
// @has - '//*[@id="reexport.Foo-1"]//a[@href="nested/fn.Foo.html"]' 'Foo'
21+
pub use nested::Foo;
22+
23+
// @count - '//*[@id="main-content"]//code' 'pub use Foo as Bar;' 2
24+
// @has - '//*[@id="reexport.Bar"]//a[@href="nested/struct.Foo.html"]' 'Foo'
25+
// @has - '//*[@id="reexport.Bar-1"]//a[@href="nested/fn.Foo.html"]' 'Foo'
26+
pub use Foo as Bar;

0 commit comments

Comments
 (0)