]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/inherent_impl.rs
Improve `multiple_inherent_impl` lint
[rust.git] / clippy_lints / src / inherent_impl.rs
1 //! lint on inherent implementations
2
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 };
10 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_session::{declare_lint_pass, declare_tool_lint};
12 use rustc_span::Span;
13 use std::collections::hash_map::Entry;
14
15 declare_clippy_lint! {
16     /// **What it does:** Checks for multiple inherent implementations of a struct
17     ///
18     /// **Why is this bad?** Splitting the implementation of a type makes the code harder to navigate.
19     ///
20     /// **Known problems:** None.
21     ///
22     /// **Example:**
23     /// ```rust
24     /// struct X;
25     /// impl X {
26     ///     fn one() {}
27     /// }
28     /// impl X {
29     ///     fn other() {}
30     /// }
31     /// ```
32     ///
33     /// Could be written:
34     ///
35     /// ```rust
36     /// struct X;
37     /// impl X {
38     ///     fn one() {}
39     ///     fn other() {}
40     /// }
41     /// ```
42     pub MULTIPLE_INHERENT_IMPL,
43     restriction,
44     "Multiple inherent impl that could be grouped"
45 }
46
47 declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]);
48
49 impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
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             })
71         {
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                 }
99             }
100
101             // Switching to the next type definition, no need to keep the current entries around.
102             type_map.clear();
103         }
104
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             );
116         }
117     }
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 }