Skip to content

rustdoc: Prevent duplicated imports #108349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
// This covers the case where somebody does an import which should pull in an item,
// but there's already an item with the same namespace and same name. Rust gives
// priority to the not-imported one, so we should, too.
items.extend(doc.items.iter().flat_map(|(item, renamed, import_id)| {
items.extend(doc.items.values().flat_map(|(item, renamed, import_id)| {
// First, lower everything other than imports.
if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
return Vec::new();
Expand All @@ -90,7 +90,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
}
v
}));
items.extend(doc.items.iter().flat_map(|(item, renamed, _)| {
items.extend(doc.items.values().flat_map(|(item, renamed, _)| {
// Now we actually lower the imports, skipping everything else.
if let hir::ItemKind::Use(path, hir::UseKind::Glob) = item.kind {
let name = renamed.unwrap_or_else(|| cx.tcx.hir().name(item.hir_id()));
Expand Down
20 changes: 14 additions & 6 deletions src/librustdoc/visit_ast.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! The Rust AST Visitor. Extracts useful information and massages it into a form
//! usable for `clean`.

use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
Expand All @@ -26,8 +26,12 @@ pub(crate) struct Module<'hir> {
pub(crate) where_inner: Span,
pub(crate) mods: Vec<Module<'hir>>,
pub(crate) def_id: LocalDefId,
// (item, renamed, import_id)
pub(crate) items: Vec<(&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>)>,
/// The key is the item `ItemId` and the value is: (item, renamed, import_id).
/// We use `FxIndexMap` to keep the insert order.
pub(crate) items: FxIndexMap<
(LocalDefId, Option<Symbol>),
(&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>),
>,
pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>)>,
}

Expand All @@ -38,7 +42,7 @@ impl Module<'_> {
def_id,
where_inner,
mods: Vec::new(),
items: Vec::new(),
items: FxIndexMap::default(),
foreigns: Vec::new(),
}
}
Expand Down Expand Up @@ -136,7 +140,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
inserted.insert(def_id)
{
let item = self.cx.tcx.hir().expect_item(local_def_id);
top_level_module.items.push((item, None, None));
top_level_module.items.insert((local_def_id, Some(item.ident.name)), (item, None, None));
}
}

Expand Down Expand Up @@ -294,7 +298,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
renamed: Option<Symbol>,
parent_id: Option<LocalDefId>,
) {
self.modules.last_mut().unwrap().items.push((item, renamed, parent_id))
self.modules
.last_mut()
.unwrap()
.items
.insert((item.owner_id.def_id, renamed), (item, renamed, parent_id));
}

fn visit_item_inner(
Expand Down
28 changes: 22 additions & 6 deletions src/tools/jsondoclint/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ impl<'a> Validator<'a> {
}
}

fn check_items(&mut self, id: &Id, items: &[Id]) {
let mut visited_ids = HashSet::with_capacity(items.len());

for item in items {
if !visited_ids.insert(item) {
self.fail(
id,
ErrorKind::Custom(format!("Duplicated entry in `items` field: `{item:?}`")),
);
}
}
}

fn check_item(&mut self, id: &'a Id) {
if let Some(item) = &self.krate.index.get(id) {
item.links.values().for_each(|id| self.add_any_id(id));
Expand All @@ -83,9 +96,9 @@ impl<'a> Validator<'a> {
ItemEnum::Enum(x) => self.check_enum(x),
ItemEnum::Variant(x) => self.check_variant(x, id),
ItemEnum::Function(x) => self.check_function(x),
ItemEnum::Trait(x) => self.check_trait(x),
ItemEnum::Trait(x) => self.check_trait(x, id),
ItemEnum::TraitAlias(x) => self.check_trait_alias(x),
ItemEnum::Impl(x) => self.check_impl(x),
ItemEnum::Impl(x) => self.check_impl(x, id),
ItemEnum::Typedef(x) => self.check_typedef(x),
ItemEnum::OpaqueTy(x) => self.check_opaque_ty(x),
ItemEnum::Constant(x) => self.check_constant(x),
Expand All @@ -94,7 +107,7 @@ impl<'a> Validator<'a> {
ItemEnum::Macro(x) => self.check_macro(x),
ItemEnum::ProcMacro(x) => self.check_proc_macro(x),
ItemEnum::Primitive(x) => self.check_primitive_type(x),
ItemEnum::Module(x) => self.check_module(x),
ItemEnum::Module(x) => self.check_module(x, id),
// FIXME: Why don't these have their own structs?
ItemEnum::ExternCrate { .. } => {}
ItemEnum::AssocConst { type_, default: _ } => self.check_type(type_),
Expand All @@ -112,7 +125,8 @@ impl<'a> Validator<'a> {
}

// Core checkers
fn check_module(&mut self, module: &'a Module) {
fn check_module(&mut self, module: &'a Module, id: &Id) {
self.check_items(id, &module.items);
module.items.iter().for_each(|i| self.add_mod_item_id(i));
}

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

fn check_trait(&mut self, x: &'a Trait) {
fn check_trait(&mut self, x: &'a Trait, id: &Id) {
self.check_items(id, &x.items);
self.check_generics(&x.generics);
x.items.iter().for_each(|i| self.add_trait_item_id(i));
x.bounds.iter().for_each(|i| self.check_generic_bound(i));
Expand All @@ -193,7 +208,8 @@ impl<'a> Validator<'a> {
x.params.iter().for_each(|i| self.check_generic_bound(i));
}

fn check_impl(&mut self, x: &'a Impl) {
fn check_impl(&mut self, x: &'a Impl, id: &Id) {
self.check_items(id, &x.items);
self.check_generics(&x.generics);
if let Some(path) = &x.trait_ {
self.check_path(path, PathKind::Trait);
Expand Down
26 changes: 26 additions & 0 deletions tests/rustdoc/reexports-of-same-name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// This test ensures that there are 4 imports as expected:
// * 2 for `Foo`
// * 2 for `Bar`

#![crate_name = "foo"]

// @has 'foo/index.html'

pub mod nested {
/// Foo the struct
pub struct Foo {}

#[allow(non_snake_case)]
/// Foo the function
pub fn Foo() {}
}

// @count - '//*[@id="main-content"]//code' 'pub use nested::Foo;' 2
// @has - '//*[@id="reexport.Foo"]//a[@href="nested/struct.Foo.html"]' 'Foo'
// @has - '//*[@id="reexport.Foo-1"]//a[@href="nested/fn.Foo.html"]' 'Foo'
pub use nested::Foo;

// @count - '//*[@id="main-content"]//code' 'pub use Foo as Bar;' 2
// @has - '//*[@id="reexport.Bar"]//a[@href="nested/struct.Foo.html"]' 'Foo'
// @has - '//*[@id="reexport.Bar-1"]//a[@href="nested/fn.Foo.html"]' 'Foo'
pub use Foo as Bar;