| use clippy_config::Conf; | |
| use clippy_config::types::InherentImplLintScope; | |
| use clippy_utils::diagnostics::span_lint_and_then; | |
| use clippy_utils::{fulfill_or_allowed, is_cfg_test, is_in_cfg_test}; | |
| 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, | |
| }; | |
| let is_test = is_cfg_test(cx.tcx, hir_id) || is_in_cfg_test(cx.tcx, hir_id); | |
| let predicates = { | |
| // Gets the predicates (bounds) for the given impl block, | |
| // sorted for consistent comparison to allow distinguishing between impl blocks | |
| // with different generic bounds. | |
| let mut predicates = cx | |
| .tcx | |
| .predicates_of(impl_id) | |
| .predicates | |
| .iter() | |
| .map(|(clause, _)| *clause) | |
| .collect::<Vec<_>>(); | |
| predicates.sort_by_key(|c| format!("{c:?}")); | |
| predicates | |
| }; | |
| match type_map.entry((impl_ty, predicates, criterion, is_test)) { | |
| 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(_), | |
| span, | |
| .. | |
| }) = cx.tcx.hir_node(id) | |
| { | |
| (!span.from_expansion() && !fulfill_or_allowed(cx, MULTIPLE_INHERENT_IMPL, [id])).then_some(span) | |
| } else { | |
| None | |
| } | |
| } | |
| enum IdOrSpan { | |
| Id(LocalDefId), | |
| Span(Span), | |
| } |