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