]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/mismatching_type_param_order.rs
Merge commit '1480cea393d0cee195e59949eabdfbcf1230f7f9' into clippyup
[rust.git] / src / tools / clippy / clippy_lints / src / mismatching_type_param_order.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use rustc_data_structures::fx::FxHashMap;
3 use rustc_hir::def::{DefKind, Res};
4 use rustc_hir::{GenericArg, Item, ItemKind, QPath, Ty, TyKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_middle::ty::GenericParamDefKind;
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8
9 declare_clippy_lint! {
10     /// ### What it does
11     /// Checks for type parameters which are positioned inconsistently between
12     /// a type definition and impl block. Specifically, a parameter in an impl
13     /// block which has the same name as a parameter in the type def, but is in
14     /// a different place.
15     ///
16     /// ### Why is this bad?
17     /// Type parameters are determined by their position rather than name.
18     /// Naming type parameters inconsistently may cause you to refer to the
19     /// wrong type parameter.
20     ///
21     /// ### Limitations
22     /// This lint only applies to impl blocks with simple generic params, e.g.
23     /// `A`. If there is anything more complicated, such as a tuple, it will be
24     /// ignored.
25     ///
26     /// ### Example
27     /// ```rust
28     /// struct Foo<A, B> {
29     ///     x: A,
30     ///     y: B,
31     /// }
32     /// // inside the impl, B refers to Foo::A
33     /// impl<B, A> Foo<B, A> {}
34     /// ```
35     /// Use instead:
36     /// ```rust
37     /// struct Foo<A, B> {
38     ///     x: A,
39     ///     y: B,
40     /// }
41     /// impl<A, B> Foo<A, B> {}
42     /// ```
43     #[clippy::version = "1.63.0"]
44     pub MISMATCHING_TYPE_PARAM_ORDER,
45     pedantic,
46     "type parameter positioned inconsistently between type def and impl block"
47 }
48 declare_lint_pass!(TypeParamMismatch => [MISMATCHING_TYPE_PARAM_ORDER]);
49
50 impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch {
51     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
52         if_chain! {
53             if !item.span.from_expansion();
54             if let ItemKind::Impl(imp) = &item.kind;
55             if let TyKind::Path(QPath::Resolved(_, path)) = &imp.self_ty.kind;
56             if let Some(segment) = path.segments.iter().next();
57             if let Some(generic_args) = segment.args;
58             if !generic_args.args.is_empty();
59             then {
60                 // get the name and span of the generic parameters in the Impl
61                 let mut impl_params = Vec::new();
62                 for p in generic_args.args.iter() {
63                     match p {
64                         GenericArg::Type(Ty {kind: TyKind::Path(QPath::Resolved(_, path)), ..}) =>
65                             impl_params.push((path.segments[0].ident.to_string(), path.span)),
66                         GenericArg::Type(_) => return,
67                         _ => (),
68                     };
69                 }
70
71                 // find the type that the Impl is for
72                 // only lint on struct/enum/union for now
73                 let Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, defid) = path.res else {
74                     return
75                 };
76
77                 // get the names of the generic parameters in the type
78                 let type_params = &cx.tcx.generics_of(defid).params;
79                 let type_param_names: Vec<_> = type_params.iter()
80                 .filter_map(|p|
81                     match p.kind {
82                         GenericParamDefKind::Type {..} => Some(p.name.to_string()),
83                         _ => None,
84                     }
85                 ).collect();
86                 // hashmap of name -> index for mismatch_param_name
87                 let type_param_names_hashmap: FxHashMap<&String, usize> =
88                     type_param_names.iter().enumerate().map(|(i, param)| (param, i)).collect();
89
90                 let type_name = segment.ident;
91                 for (i, (impl_param_name, impl_param_span)) in impl_params.iter().enumerate() {
92                     if mismatch_param_name(i, impl_param_name, &type_param_names_hashmap) {
93                         let msg = format!("`{type_name}` has a similarly named generic type parameter `{impl_param_name}` in its declaration, but in a different order");
94                         let help = format!("try `{}`, or a name that does not conflict with `{type_name}`'s generic params",
95                                            type_param_names[i]);
96                         span_lint_and_help(
97                             cx,
98                             MISMATCHING_TYPE_PARAM_ORDER,
99                             *impl_param_span,
100                             &msg,
101                             None,
102                             &help
103                         );
104                     }
105                 }
106             }
107         }
108     }
109 }
110
111 // Checks if impl_param_name is the same as one of type_param_names,
112 // and is in a different position
113 fn mismatch_param_name(i: usize, impl_param_name: &String, type_param_names: &FxHashMap<&String, usize>) -> bool {
114     if let Some(j) = type_param_names.get(impl_param_name) {
115         if i != *j {
116             return true;
117         }
118     }
119     false
120 }