]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! lint on inherent implementations |
2 | ||
17df50a5 | 3 | use clippy_utils::diagnostics::span_lint_and_note; |
a2a8927a | 4 | use clippy_utils::is_lint_allowed; |
17df50a5 | 5 | use rustc_data_structures::fx::FxHashMap; |
add651ee FG |
6 | use rustc_hir::def_id::LocalDefId; |
7 | use rustc_hir::{Item, ItemKind, Node}; | |
f20569fa | 8 | use rustc_lint::{LateContext, LateLintPass}; |
4b012472 | 9 | use rustc_session::declare_lint_pass; |
f20569fa | 10 | use rustc_span::Span; |
17df50a5 | 11 | use std::collections::hash_map::Entry; |
f20569fa XL |
12 | |
13 | declare_clippy_lint! { | |
94222f64 XL |
14 | /// ### What it does |
15 | /// Checks for multiple inherent implementations of a struct | |
f20569fa | 16 | /// |
31ef2f64 | 17 | /// ### Why restrict this? |
94222f64 | 18 | /// Splitting the implementation of a type makes the code harder to navigate. |
f20569fa | 19 | /// |
94222f64 | 20 | /// ### Example |
ed00b5ec | 21 | /// ```no_run |
f20569fa XL |
22 | /// struct X; |
23 | /// impl X { | |
24 | /// fn one() {} | |
25 | /// } | |
26 | /// impl X { | |
27 | /// fn other() {} | |
28 | /// } | |
29 | /// ``` | |
30 | /// | |
31 | /// Could be written: | |
32 | /// | |
ed00b5ec | 33 | /// ```no_run |
f20569fa XL |
34 | /// struct X; |
35 | /// impl X { | |
36 | /// fn one() {} | |
37 | /// fn other() {} | |
38 | /// } | |
39 | /// ``` | |
a2a8927a | 40 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
41 | pub MULTIPLE_INHERENT_IMPL, |
42 | restriction, | |
43 | "Multiple inherent impl that could be grouped" | |
44 | } | |
45 | ||
17df50a5 | 46 | declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); |
f20569fa XL |
47 | |
48 | impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { | |
c295e0f8 | 49 | fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { |
17df50a5 XL |
50 | // Map from a type to it's first impl block. Needed to distinguish generic arguments. |
51 | // e.g. `Foo<Bar>` and `Foo<Baz>` | |
52 | let mut type_map = FxHashMap::default(); | |
53 | // List of spans to lint. (lint_span, first_span) | |
54 | let mut lint_spans = Vec::new(); | |
55 | ||
c0240ec0 FG |
56 | let Ok(impls) = cx.tcx.crate_inherent_impls(()) else { |
57 | return; | |
58 | }; | |
9c376795 | 59 | let inherent_impls = cx |
17df50a5 | 60 | .tcx |
c0240ec0 | 61 | .with_stable_hashing_context(|hcx| impls.inherent_impls.to_sorted(&hcx, true)); |
9c376795 FG |
62 | |
63 | for (_, impl_ids) in inherent_impls.into_iter().filter(|(&id, impls)| { | |
64 | impls.len() > 1 | |
65 | // Check for `#[allow]` on the type definition | |
66 | && !is_lint_allowed( | |
67 | cx, | |
68 | MULTIPLE_INHERENT_IMPL, | |
4b012472 | 69 | cx.tcx.local_def_id_to_hir_id(id), |
9c376795 FG |
70 | ) |
71 | }) { | |
17df50a5 | 72 | for impl_id in impl_ids.iter().map(|id| id.expect_local()) { |
add651ee | 73 | let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity(); |
9ffffee4 | 74 | match type_map.entry(impl_ty) { |
17df50a5 XL |
75 | Entry::Vacant(e) => { |
76 | // Store the id for the first impl block of this type. The span is retrieved lazily. | |
77 | e.insert(IdOrSpan::Id(impl_id)); | |
78 | }, | |
79 | Entry::Occupied(mut e) => { | |
80 | if let Some(span) = get_impl_span(cx, impl_id) { | |
81 | let first_span = match *e.get() { | |
82 | IdOrSpan::Span(s) => s, | |
83 | IdOrSpan::Id(id) => { | |
84 | if let Some(s) = get_impl_span(cx, id) { | |
85 | // Remember the span of the first block. | |
86 | *e.get_mut() = IdOrSpan::Span(s); | |
87 | s | |
88 | } else { | |
89 | // The first impl block isn't considered by the lint. Replace it with the | |
90 | // current one. | |
91 | *e.get_mut() = IdOrSpan::Span(span); | |
92 | continue; | |
93 | } | |
94 | }, | |
95 | }; | |
96 | lint_spans.push((span, first_span)); | |
97 | } | |
98 | }, | |
99 | } | |
f20569fa | 100 | } |
17df50a5 XL |
101 | |
102 | // Switching to the next type definition, no need to keep the current entries around. | |
103 | type_map.clear(); | |
f20569fa | 104 | } |
f20569fa | 105 | |
17df50a5 XL |
106 | // `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first. |
107 | lint_spans.sort_by_key(|x| x.0.lo()); | |
108 | for (span, first_span) in lint_spans { | |
109 | span_lint_and_note( | |
110 | cx, | |
111 | MULTIPLE_INHERENT_IMPL, | |
112 | span, | |
113 | "multiple implementations of this structure", | |
114 | Some(first_span), | |
115 | "first implementation here", | |
116 | ); | |
f20569fa XL |
117 | } |
118 | } | |
119 | } | |
17df50a5 XL |
120 | |
121 | /// Gets the span for the given impl block unless it's not being considered by the lint. | |
122 | fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option<Span> { | |
4b012472 | 123 | let id = cx.tcx.local_def_id_to_hir_id(id); |
17df50a5 | 124 | if let Node::Item(&Item { |
04454e1e | 125 | kind: ItemKind::Impl(impl_item), |
17df50a5 XL |
126 | span, |
127 | .. | |
4b012472 | 128 | }) = cx.tcx.hir_node(id) |
17df50a5 | 129 | { |
a2a8927a XL |
130 | (!span.from_expansion() |
131 | && impl_item.generics.params.is_empty() | |
132 | && !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id)) | |
064997fb | 133 | .then_some(span) |
17df50a5 XL |
134 | } else { |
135 | None | |
136 | } | |
137 | } | |
138 | ||
139 | enum IdOrSpan { | |
140 | Id(LocalDefId), | |
141 | Span(Span), | |
142 | } |