|
1 | 1 | //! lint on inherent implementations
|
2 | 2 |
|
3 |
| -use clippy_utils::diagnostics::span_lint_and_then; |
4 |
| -use clippy_utils::in_macro; |
5 |
| -use rustc_hir::def_id::DefIdMap; |
6 |
| -use rustc_hir::{def_id, Crate, Impl, Item, ItemKind}; |
| 3 | +use clippy_utils::diagnostics::span_lint_and_note; |
| 4 | +use clippy_utils::{in_macro, is_allowed}; |
| 5 | +use rustc_data_structures::fx::FxHashMap; |
| 6 | +use rustc_hir::{ |
| 7 | + def_id::{LocalDefId, LOCAL_CRATE}, |
| 8 | + Crate, Item, ItemKind, Node, |
| 9 | +}; |
7 | 10 | use rustc_lint::{LateContext, LateLintPass};
|
8 |
| -use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 11 | +use rustc_session::{declare_lint_pass, declare_tool_lint}; |
9 | 12 | use rustc_span::Span;
|
| 13 | +use std::collections::hash_map::Entry; |
10 | 14 |
|
11 | 15 | declare_clippy_lint! {
|
12 | 16 | /// **What it does:** Checks for multiple inherent implementations of a struct
|
@@ -40,51 +44,96 @@ declare_clippy_lint! {
|
40 | 44 | "Multiple inherent impl that could be grouped"
|
41 | 45 | }
|
42 | 46 |
|
43 |
| -#[allow(clippy::module_name_repetitions)] |
44 |
| -#[derive(Default)] |
45 |
| -pub struct MultipleInherentImpl { |
46 |
| - impls: DefIdMap<Span>, |
47 |
| -} |
48 |
| - |
49 |
| -impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); |
| 47 | +declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); |
50 | 48 |
|
51 | 49 | impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
|
52 |
| - fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'_>) { |
53 |
| - if let ItemKind::Impl(Impl { |
54 |
| - ref generics, |
55 |
| - of_trait: None, |
56 |
| - .. |
57 |
| - }) = item.kind |
| 50 | + fn check_crate_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Crate<'_>) { |
| 51 | + // Map from a type to it's first impl block. Needed to distinguish generic arguments. |
| 52 | + // e.g. `Foo<Bar>` and `Foo<Baz>` |
| 53 | + let mut type_map = FxHashMap::default(); |
| 54 | + // List of spans to lint. (lint_span, first_span) |
| 55 | + let mut lint_spans = Vec::new(); |
| 56 | + |
| 57 | + for (_, impl_ids) in cx |
| 58 | + .tcx |
| 59 | + .crate_inherent_impls(LOCAL_CRATE) |
| 60 | + .inherent_impls |
| 61 | + .iter() |
| 62 | + .filter(|(id, impls)| { |
| 63 | + impls.len() > 1 |
| 64 | + // Check for `#[allow]` on the type definition |
| 65 | + && !is_allowed( |
| 66 | + cx, |
| 67 | + MULTIPLE_INHERENT_IMPL, |
| 68 | + cx.tcx.hir().local_def_id_to_hir_id(id.expect_local()), |
| 69 | + ) |
| 70 | + }) |
58 | 71 | {
|
59 |
| - // Remember for each inherent implementation encountered its span and generics |
60 |
| - // but filter out implementations that have generic params (type or lifetime) |
61 |
| - // or are derived from a macro |
62 |
| - if !in_macro(item.span) && generics.params.is_empty() { |
63 |
| - self.impls.insert(item.def_id.to_def_id(), item.span); |
| 72 | + for impl_id in impl_ids.iter().map(|id| id.expect_local()) { |
| 73 | + match type_map.entry(cx.tcx.type_of(impl_id)) { |
| 74 | + Entry::Vacant(e) => { |
| 75 | + // Store the id for the first impl block of this type. The span is retrieved lazily. |
| 76 | + e.insert(IdOrSpan::Id(impl_id)); |
| 77 | + }, |
| 78 | + Entry::Occupied(mut e) => { |
| 79 | + if let Some(span) = get_impl_span(cx, impl_id) { |
| 80 | + let first_span = match *e.get() { |
| 81 | + IdOrSpan::Span(s) => s, |
| 82 | + IdOrSpan::Id(id) => { |
| 83 | + if let Some(s) = get_impl_span(cx, id) { |
| 84 | + // Remember the span of the first block. |
| 85 | + *e.get_mut() = IdOrSpan::Span(s); |
| 86 | + s |
| 87 | + } else { |
| 88 | + // The first impl block isn't considered by the lint. Replace it with the |
| 89 | + // current one. |
| 90 | + *e.get_mut() = IdOrSpan::Span(span); |
| 91 | + continue; |
| 92 | + } |
| 93 | + }, |
| 94 | + }; |
| 95 | + lint_spans.push((span, first_span)); |
| 96 | + } |
| 97 | + }, |
| 98 | + } |
64 | 99 | }
|
| 100 | + |
| 101 | + // Switching to the next type definition, no need to keep the current entries around. |
| 102 | + type_map.clear(); |
65 | 103 | }
|
66 |
| - } |
67 | 104 |
|
68 |
| - fn check_crate_post(&mut self, cx: &LateContext<'tcx>, krate: &'tcx Crate<'_>) { |
69 |
| - if !krate.items.is_empty() { |
70 |
| - // Retrieve all inherent implementations from the crate, grouped by type |
71 |
| - for impls in cx.tcx.crate_inherent_impls(def_id::LOCAL_CRATE).inherent_impls.values() { |
72 |
| - // Filter out implementations that have generic params (type or lifetime) |
73 |
| - let mut impl_spans = impls.iter().filter_map(|impl_def| self.impls.get(impl_def)); |
74 |
| - if let Some(initial_span) = impl_spans.next() { |
75 |
| - impl_spans.for_each(|additional_span| { |
76 |
| - span_lint_and_then( |
77 |
| - cx, |
78 |
| - MULTIPLE_INHERENT_IMPL, |
79 |
| - *additional_span, |
80 |
| - "multiple implementations of this structure", |
81 |
| - |diag| { |
82 |
| - diag.span_note(*initial_span, "first implementation here"); |
83 |
| - }, |
84 |
| - ) |
85 |
| - }) |
86 |
| - } |
87 |
| - } |
| 105 | + // `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first. |
| 106 | + lint_spans.sort_by_key(|x| x.0.lo()); |
| 107 | + for (span, first_span) in lint_spans { |
| 108 | + span_lint_and_note( |
| 109 | + cx, |
| 110 | + MULTIPLE_INHERENT_IMPL, |
| 111 | + span, |
| 112 | + "multiple implementations of this structure", |
| 113 | + Some(first_span), |
| 114 | + "first implementation here", |
| 115 | + ); |
88 | 116 | }
|
89 | 117 | }
|
90 | 118 | }
|
| 119 | + |
| 120 | +/// Gets the span for the given impl block unless it's not being considered by the lint. |
| 121 | +fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option<Span> { |
| 122 | + let id = cx.tcx.hir().local_def_id_to_hir_id(id); |
| 123 | + if let Node::Item(&Item { |
| 124 | + kind: ItemKind::Impl(ref impl_item), |
| 125 | + span, |
| 126 | + .. |
| 127 | + }) = cx.tcx.hir().get(id) |
| 128 | + { |
| 129 | + (!in_macro(span) && impl_item.generics.params.is_empty() && !is_allowed(cx, MULTIPLE_INHERENT_IMPL, id)) |
| 130 | + .then(|| span) |
| 131 | + } else { |
| 132 | + None |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +enum IdOrSpan { |
| 137 | + Id(LocalDefId), |
| 138 | + Span(Span), |
| 139 | +} |
0 commit comments