summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/inherent_impl.rs
blob: e9b2e31a769ad5a216a3375d9be6a52f9e4f2e24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! lint on inherent implementations

use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::is_lint_allowed;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{def_id::LocalDefId, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
use std::collections::hash_map::Entry;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for multiple inherent implementations of a struct
    ///
    /// ### Why is this bad?
    /// Splitting the implementation of a type makes the code harder to navigate.
    ///
    /// ### Example
    /// ```rust
    /// struct X;
    /// impl X {
    ///     fn one() {}
    /// }
    /// impl X {
    ///     fn other() {}
    /// }
    /// ```
    ///
    /// Could be written:
    ///
    /// ```rust
    /// 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"
}

declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]);

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 inherent_impls = cx
            .tcx
            .with_stable_hashing_context(|hcx| cx.tcx.crate_inherent_impls(()).inherent_impls.to_sorted(&hcx, true));

        for (_, impl_ids) in inherent_impls.into_iter().filter(|(&id, impls)| {
            impls.len() > 1
            // Check for `#[allow]` on the type definition
            && !is_lint_allowed(
                cx,
                MULTIPLE_INHERENT_IMPL,
                cx.tcx.hir().local_def_id_to_hir_id(id),
            )
        }) {
            for impl_id in impl_ids.iter().map(|id| id.expect_local()) {
                match type_map.entry(cx.tcx.type_of(impl_id)) {
                    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_note(
                cx,
                MULTIPLE_INHERENT_IMPL,
                span,
                "multiple implementations of this structure",
                Some(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.hir().local_def_id_to_hir_id(id);
    if let Node::Item(&Item {
        kind: ItemKind::Impl(impl_item),
        span,
        ..
    }) = cx.tcx.hir().get(id)
    {
        (!span.from_expansion()
            && impl_item.generics.params.is_empty()
            && !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id))
        .then_some(span)
    } else {
        None
    }
}

enum IdOrSpan {
    Id(LocalDefId),
    Span(Span),
}