`ast::Item` has an `ident` field. - It's always non-empty for these item kinds: `ExternCrate`, `Static`, `Const`, `Fn`, `Mod`, `TyAlias`, `Enum`, `Struct`, `Union`, `Trait`, `TraitAlias`, `MacroDef`, `Delegation`. - It's always empty for these item kinds: `Use`, `ForeignMod`, `GlobalAsm`, `Impl`, `MacCall`, `DelegationMac`. There is a similar story for `AssocItemKind` and `ForeignItemKind`. Some sites that handle items check for an empty ident, some don't. This is a very C-like way of doing things, but this is Rust, we have sum types, we can do this properly and never forget to check for the exceptional case and never YOLO possibly empty identifiers (or possibly dummy spans) around and hope that things will work out. The commit is large but it's mostly obvious plumbing work. Some notable things. - `ast::Item` got 8 bytes bigger. This could be avoided by boxing the fields within some of the `ast::ItemKind` variants (specifically: `Struct`, `Union`, `Enum`). I might do that in a follow-up; this commit is big enough already. - For the visitors: `FnKind` no longer needs an `ident` field because the `Fn` within how has one. - In the parser, the `ItemInfo` typedef is no longer needed. It was used in various places to return an `Ident` alongside an `ItemKind`, but now the `Ident` (if present) is within the `ItemKind`. - In a few places I renamed identifier variables called `name` (or `foo_name`) as `ident` (or `foo_ident`), to better match the type, and because `name` is normally used for `Symbol`s. It's confusing to see something like `foo_name.name`.
128 lines
3.8 KiB
Rust
128 lines
3.8 KiB
Rust
use clippy_utils::diagnostics::span_lint_and_help;
|
|
use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind};
|
|
use rustc_errors::MultiSpan;
|
|
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
|
|
use rustc_session::impl_lint_pass;
|
|
use rustc_span::{FileName, Span};
|
|
use std::collections::BTreeMap;
|
|
use std::path::PathBuf;
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for files that are included as modules multiple times.
|
|
///
|
|
/// ### Why is this bad?
|
|
/// Loading a file as a module more than once causes it to be compiled
|
|
/// multiple times, taking longer and putting duplicate content into the
|
|
/// module tree.
|
|
///
|
|
/// ### Example
|
|
/// ```rust,ignore
|
|
/// // lib.rs
|
|
/// mod a;
|
|
/// mod b;
|
|
/// ```
|
|
/// ```rust,ignore
|
|
/// // a.rs
|
|
/// #[path = "./b.rs"]
|
|
/// mod b;
|
|
/// ```
|
|
///
|
|
/// Use instead:
|
|
///
|
|
/// ```rust,ignore
|
|
/// // lib.rs
|
|
/// mod a;
|
|
/// mod b;
|
|
/// ```
|
|
/// ```rust,ignore
|
|
/// // a.rs
|
|
/// use crate::b;
|
|
/// ```
|
|
#[clippy::version = "1.63.0"]
|
|
pub DUPLICATE_MOD,
|
|
suspicious,
|
|
"file loaded as module multiple times"
|
|
}
|
|
|
|
#[derive(PartialOrd, Ord, PartialEq, Eq)]
|
|
struct Modules {
|
|
local_path: PathBuf,
|
|
spans: Vec<Span>,
|
|
lint_levels: Vec<Level>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct DuplicateMod {
|
|
/// map from the canonicalized path to `Modules`, `BTreeMap` to make the
|
|
/// order deterministic for tests
|
|
modules: BTreeMap<PathBuf, Modules>,
|
|
}
|
|
|
|
impl_lint_pass!(DuplicateMod => [DUPLICATE_MOD]);
|
|
|
|
impl EarlyLintPass for DuplicateMod {
|
|
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
|
if let ItemKind::Mod(_, _, ModKind::Loaded(_, Inline::No, mod_spans, _)) = &item.kind
|
|
&& let FileName::Real(real) = cx.sess().source_map().span_to_filename(mod_spans.inner_span)
|
|
&& let Some(local_path) = real.into_local_path()
|
|
&& let Ok(absolute_path) = local_path.canonicalize()
|
|
{
|
|
let modules = self.modules.entry(absolute_path).or_insert(Modules {
|
|
local_path,
|
|
spans: Vec::new(),
|
|
lint_levels: Vec::new(),
|
|
});
|
|
modules.spans.push(item.span_with_attributes());
|
|
modules.lint_levels.push(cx.get_lint_level(DUPLICATE_MOD));
|
|
}
|
|
}
|
|
|
|
fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
|
|
for Modules {
|
|
local_path,
|
|
spans,
|
|
lint_levels,
|
|
} in self.modules.values()
|
|
{
|
|
if spans.len() < 2 {
|
|
continue;
|
|
}
|
|
|
|
// At this point the lint would be emitted
|
|
assert_eq!(spans.len(), lint_levels.len());
|
|
let spans: Vec<_> = spans
|
|
.iter()
|
|
.zip(lint_levels)
|
|
.filter_map(|(span, lvl)| {
|
|
if let Some(id) = lvl.get_expectation_id() {
|
|
cx.fulfill_expectation(id);
|
|
}
|
|
|
|
(!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span)
|
|
})
|
|
.collect();
|
|
|
|
if spans.len() < 2 {
|
|
continue;
|
|
}
|
|
|
|
let mut multi_span = MultiSpan::from_spans(spans.clone());
|
|
let (&first, duplicates) = spans.split_first().unwrap();
|
|
|
|
multi_span.push_span_label(first, "first loaded here");
|
|
for &duplicate in duplicates {
|
|
multi_span.push_span_label(duplicate, "loaded again here");
|
|
}
|
|
|
|
span_lint_and_help(
|
|
cx,
|
|
DUPLICATE_MOD,
|
|
multi_span,
|
|
format!("file is loaded as a module multiple times: `{}`", local_path.display()),
|
|
None,
|
|
"replace all but one `mod` item with `use` items",
|
|
);
|
|
}
|
|
}
|
|
}
|