The `restriction` group contains many lints which are not about necessarily “bad” things, but style choices — perhaps even style choices which contradict conventional Rust style — or are otherwise very situational. This results in silly wording like “Why is this bad? It isn't, but ...”, which I’ve seen confuse a newcomer at least once. To improve this situation, this commit replaces the “Why is this bad?” section heading with “Why restrict this?”, for most, but not all, restriction lints. I left alone the ones whose placement in the restriction group is more incidental. In order to make this make sense, I had to remove the “It isn't, but” texts from the contents of the sections. Sometimes further changes were needed, or there were obvious fixes to make, and I went ahead and made those changes without attempting to split them into another commit, even though many of them are not strictly necessary for the “Why restrict this?” project.
142 lines
4.9 KiB
Rust
142 lines
4.9 KiB
Rust
//! lint on inherent implementations
|
|
|
|
use clippy_utils::diagnostics::span_lint_and_note;
|
|
use clippy_utils::is_lint_allowed;
|
|
use rustc_data_structures::fx::FxHashMap;
|
|
use rustc_hir::def_id::LocalDefId;
|
|
use rustc_hir::{Item, ItemKind, Node};
|
|
use rustc_lint::{LateContext, LateLintPass};
|
|
use rustc_session::declare_lint_pass;
|
|
use rustc_span::Span;
|
|
use std::collections::hash_map::Entry;
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for multiple inherent implementations of a struct
|
|
///
|
|
/// ### Why restrict this?
|
|
/// Splitting the implementation of a type makes the code harder to navigate.
|
|
///
|
|
/// ### Example
|
|
/// ```no_run
|
|
/// struct X;
|
|
/// impl X {
|
|
/// fn one() {}
|
|
/// }
|
|
/// impl X {
|
|
/// fn other() {}
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Could be written:
|
|
///
|
|
/// ```no_run
|
|
/// struct X;
|
|
/// impl X {
|
|
/// fn one() {}
|
|
/// fn other() {}
|
|
/// }
|
|
/// ```
|
|
#[clippy::version = "pre 1.29.0"]
|
|
pub MULTIPLE_INHERENT_IMPL,
|
|
restriction,
|
|
"Multiple inherent impl that could be grouped"
|
|
}
|
|
|
|
declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]);
|
|
|
|
impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
|
|
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
|
|
// Map from a type to it's first impl block. Needed to distinguish generic arguments.
|
|
// e.g. `Foo<Bar>` and `Foo<Baz>`
|
|
let mut type_map = FxHashMap::default();
|
|
// List of spans to lint. (lint_span, first_span)
|
|
let mut lint_spans = Vec::new();
|
|
|
|
let Ok(impls) = cx.tcx.crate_inherent_impls(()) else {
|
|
return;
|
|
};
|
|
let inherent_impls = cx
|
|
.tcx
|
|
.with_stable_hashing_context(|hcx| impls.inherent_impls.to_sorted(&hcx, true));
|
|
|
|
for (_, impl_ids) in inherent_impls.into_iter().filter(|(&id, impls)| {
|
|
impls.len() > 1
|
|
// Check for `#[allow]` on the type definition
|
|
&& !is_lint_allowed(
|
|
cx,
|
|
MULTIPLE_INHERENT_IMPL,
|
|
cx.tcx.local_def_id_to_hir_id(id),
|
|
)
|
|
}) {
|
|
for impl_id in impl_ids.iter().map(|id| id.expect_local()) {
|
|
let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity();
|
|
match type_map.entry(impl_ty) {
|
|
Entry::Vacant(e) => {
|
|
// Store the id for the first impl block of this type. The span is retrieved lazily.
|
|
e.insert(IdOrSpan::Id(impl_id));
|
|
},
|
|
Entry::Occupied(mut e) => {
|
|
if let Some(span) = get_impl_span(cx, impl_id) {
|
|
let first_span = match *e.get() {
|
|
IdOrSpan::Span(s) => s,
|
|
IdOrSpan::Id(id) => {
|
|
if let Some(s) = get_impl_span(cx, id) {
|
|
// Remember the span of the first block.
|
|
*e.get_mut() = IdOrSpan::Span(s);
|
|
s
|
|
} else {
|
|
// The first impl block isn't considered by the lint. Replace it with the
|
|
// current one.
|
|
*e.get_mut() = IdOrSpan::Span(span);
|
|
continue;
|
|
}
|
|
},
|
|
};
|
|
lint_spans.push((span, first_span));
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
// Switching to the next type definition, no need to keep the current entries around.
|
|
type_map.clear();
|
|
}
|
|
|
|
// `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first.
|
|
lint_spans.sort_by_key(|x| x.0.lo());
|
|
for (span, first_span) in lint_spans {
|
|
span_lint_and_note(
|
|
cx,
|
|
MULTIPLE_INHERENT_IMPL,
|
|
span,
|
|
"multiple implementations of this structure",
|
|
Some(first_span),
|
|
"first implementation here",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Gets the span for the given impl block unless it's not being considered by the lint.
|
|
fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option<Span> {
|
|
let id = cx.tcx.local_def_id_to_hir_id(id);
|
|
if let Node::Item(&Item {
|
|
kind: ItemKind::Impl(impl_item),
|
|
span,
|
|
..
|
|
}) = cx.tcx.hir_node(id)
|
|
{
|
|
(!span.from_expansion()
|
|
&& impl_item.generics.params.is_empty()
|
|
&& !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id))
|
|
.then_some(span)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
enum IdOrSpan {
|
|
Id(LocalDefId),
|
|
Span(Span),
|
|
}
|