summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/mismatching_type_param_order.rs
blob: 020efeaebf02905dd46bb0b0a7ef0c5fa7f91853 (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
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{GenericArg, Item, ItemKind, QPath, Ty, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::GenericParamDefKind;
use rustc_session::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
    /// ### What it does
    /// Checks for type parameters which are positioned inconsistently between
    /// a type definition and impl block. Specifically, a parameter in an impl
    /// block which has the same name as a parameter in the type def, but is in
    /// a different place.
    ///
    /// ### Why is this bad?
    /// Type parameters are determined by their position rather than name.
    /// Naming type parameters inconsistently may cause you to refer to the
    /// wrong type parameter.
    ///
    /// ### Limitations
    /// This lint only applies to impl blocks with simple generic params, e.g.
    /// `A`. If there is anything more complicated, such as a tuple, it will be
    /// ignored.
    ///
    /// ### Example
    /// ```rust
    /// struct Foo<A, B> {
    ///     x: A,
    ///     y: B,
    /// }
    /// // inside the impl, B refers to Foo::A
    /// impl<B, A> Foo<B, A> {}
    /// ```
    /// Use instead:
    /// ```rust
    /// struct Foo<A, B> {
    ///     x: A,
    ///     y: B,
    /// }
    /// impl<A, B> Foo<A, B> {}
    /// ```
    #[clippy::version = "1.63.0"]
    pub MISMATCHING_TYPE_PARAM_ORDER,
    pedantic,
    "type parameter positioned inconsistently between type def and impl block"
}
declare_lint_pass!(TypeParamMismatch => [MISMATCHING_TYPE_PARAM_ORDER]);

impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
        if_chain! {
            if !item.span.from_expansion();
            if let ItemKind::Impl(imp) = &item.kind;
            if let TyKind::Path(QPath::Resolved(_, path)) = &imp.self_ty.kind;
            if let Some(segment) = path.segments.iter().next();
            if let Some(generic_args) = segment.args;
            if !generic_args.args.is_empty();
            then {
                // get the name and span of the generic parameters in the Impl
                let mut impl_params = Vec::new();
                for p in generic_args.args.iter() {
                    match p {
                        GenericArg::Type(Ty {kind: TyKind::Path(QPath::Resolved(_, path)), ..}) =>
                            impl_params.push((path.segments[0].ident.to_string(), path.span)),
                        GenericArg::Type(_) => return,
                        _ => (),
                    };
                }

                // find the type that the Impl is for
                // only lint on struct/enum/union for now
                let defid = match path.res {
                    Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, defid) => defid,
                    _ => return,
                };

                // get the names of the generic parameters in the type
                let type_params = &cx.tcx.generics_of(defid).params;
                let type_param_names: Vec<_> = type_params.iter()
                .filter_map(|p|
                    match p.kind {
                        GenericParamDefKind::Type {..} => Some(p.name.to_string()),
                        _ => None,
                    }
                ).collect();
                // hashmap of name -> index for mismatch_param_name
                let type_param_names_hashmap: FxHashMap<&String, usize> =
                    type_param_names.iter().enumerate().map(|(i, param)| (param, i)).collect();

                let type_name = segment.ident;
                for (i, (impl_param_name, impl_param_span)) in impl_params.iter().enumerate() {
                    if mismatch_param_name(i, impl_param_name, &type_param_names_hashmap) {
                        let msg = format!("`{}` has a similarly named generic type parameter `{}` in its declaration, but in a different order",
                                          type_name, impl_param_name);
                        let help = format!("try `{}`, or a name that does not conflict with `{}`'s generic params",
                                           type_param_names[i], type_name);
                        span_lint_and_help(
                            cx,
                            MISMATCHING_TYPE_PARAM_ORDER,
                            *impl_param_span,
                            &msg,
                            None,
                            &help
                        );
                    }
                }
            }
        }
    }
}

// Checks if impl_param_name is the same as one of type_param_names,
// and is in a different position
fn mismatch_param_name(i: usize, impl_param_name: &String, type_param_names: &FxHashMap<&String, usize>) -> bool {
    if let Some(j) = type_param_names.get(impl_param_name) {
        if i != *j {
            return true;
        }
    }
    false
}