| use clippy_config::Conf; |
| use clippy_config::types::InherentImplLintScope; |
| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::fulfill_or_allowed; |
| use rustc_data_structures::fx::FxHashMap; |
| use rustc_hir::def_id::{LocalDefId, LocalModDefId}; |
| use rustc_hir::{Item, ItemKind, Node}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_session::impl_lint_pass; |
| use rustc_span::{FileName, Span}; |
| use std::collections::hash_map::Entry; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for multiple inherent implementations of a struct |
| /// |
| /// The config option controls the scope in which multiple inherent `impl` blocks for the same |
| /// struct are linted, allowing values of `module` (only within the same module), `file` |
| /// (within the same file), or `crate` (anywhere in the crate, default). |
| /// |
| /// ### 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" |
| } |
| |
| impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); |
| |
| pub struct MultipleInherentImpl { |
| scope: InherentImplLintScope, |
| } |
| |
| impl MultipleInherentImpl { |
| pub fn new(conf: &'static Conf) -> Self { |
| Self { |
| scope: conf.inherent_impl_lint_scope, |
| } |
| } |
| } |
| |
| #[derive(Hash, Eq, PartialEq, Clone)] |
| enum Criterion { |
| Module(LocalModDefId), |
| File(FileName), |
| Crate, |
| } |
| |
| 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 (impls, _) = cx.tcx.crate_inherent_impls(()); |
| |
| for (&id, impl_ids) in &impls.inherent_impls { |
| if impl_ids.len() < 2 |
| // Check for `#[expect]` or `#[allow]` on the type definition |
| || fulfill_or_allowed( |
| cx, |
| MULTIPLE_INHERENT_IMPL, |
| [cx.tcx.local_def_id_to_hir_id(id)], |
| ) { |
| continue; |
| } |
| |
| for impl_id in impl_ids.iter().map(|id| id.expect_local()) { |
| let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity(); |
| let hir_id = cx.tcx.local_def_id_to_hir_id(impl_id); |
| let criterion = match self.scope { |
| InherentImplLintScope::Module => Criterion::Module(cx.tcx.parent_module(hir_id)), |
| InherentImplLintScope::File => { |
| let span = cx.tcx.hir_span(hir_id); |
| Criterion::File(cx.tcx.sess.source_map().lookup_source_file(span.lo()).name.clone()) |
| }, |
| InherentImplLintScope::Crate => Criterion::Crate, |
| }; |
| match type_map.entry((impl_ty, criterion)) { |
| 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_then( |
| cx, |
| MULTIPLE_INHERENT_IMPL, |
| span, |
| "multiple implementations of this structure", |
| |diag| { |
| diag.span_note(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() |
| && !fulfill_or_allowed(cx, MULTIPLE_INHERENT_IMPL, [id])) |
| .then_some(span) |
| } else { |
| None |
| } |
| } |
| |
| enum IdOrSpan { |
| Id(LocalDefId), |
| Span(Span), |
| } |