]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/pattern_type_mismatch.rs
Fix more β€œa”/β€œan” typos
[rust.git] / clippy_lints / src / pattern_type_mismatch.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use clippy_utils::last_path_segment;
3 use rustc_hir::{
4     intravisit, Body, Expr, ExprKind, FnDecl, HirId, LocalSource, MatchSource, Mutability, Pat, PatField, PatKind,
5     QPath, Stmt, StmtKind,
6 };
7 use rustc_lint::{LateContext, LateLintPass, LintContext};
8 use rustc_middle::lint::in_external_macro;
9 use rustc_middle::ty::subst::SubstsRef;
10 use rustc_middle::ty::{AdtDef, FieldDef, Ty, TyKind, VariantDef};
11 use rustc_session::{declare_lint_pass, declare_tool_lint};
12 use rustc_span::source_map::Span;
13 use std::iter;
14
15 declare_clippy_lint! {
16     /// ### What it does
17     /// Checks for patterns that aren't exact representations of the types
18     /// they are applied to.
19     ///
20     /// To satisfy this lint, you will have to adjust either the expression that is matched
21     /// against or the pattern itself, as well as the bindings that are introduced by the
22     /// adjusted patterns. For matching you will have to either dereference the expression
23     /// with the `*` operator, or amend the patterns to explicitly match against `&<pattern>`
24     /// or `&mut <pattern>` depending on the reference mutability. For the bindings you need
25     /// to use the inverse. You can leave them as plain bindings if you wish for the value
26     /// to be copied, but you must use `ref mut <variable>` or `ref <variable>` to construct
27     /// a reference into the matched structure.
28     ///
29     /// If you are looking for a way to learn about ownership semantics in more detail, it
30     /// is recommended to look at IDE options available to you to highlight types, lifetimes
31     /// and reference semantics in your code. The available tooling would expose these things
32     /// in a general way even outside of the various pattern matching mechanics. Of course
33     /// this lint can still be used to highlight areas of interest and ensure a good understanding
34     /// of ownership semantics.
35     ///
36     /// ### Why is this bad?
37     /// It isn't bad in general. But in some contexts it can be desirable
38     /// because it increases ownership hints in the code, and will guard against some changes
39     /// in ownership.
40     ///
41     /// ### Example
42     /// This example shows the basic adjustments necessary to satisfy the lint. Note how
43     /// the matched expression is explicitly dereferenced with `*` and the `inner` variable
44     /// is bound to a shared borrow via `ref inner`.
45     ///
46     /// ```rust,ignore
47     /// // Bad
48     /// let value = &Some(Box::new(23));
49     /// match value {
50     ///     Some(inner) => println!("{}", inner),
51     ///     None => println!("none"),
52     /// }
53     ///
54     /// // Good
55     /// let value = &Some(Box::new(23));
56     /// match *value {
57     ///     Some(ref inner) => println!("{}", inner),
58     ///     None => println!("none"),
59     /// }
60     /// ```
61     ///
62     /// The following example demonstrates one of the advantages of the more verbose style.
63     /// Note how the second version uses `ref mut a` to explicitly declare `a` a shared mutable
64     /// borrow, while `b` is simply taken by value. This ensures that the loop body cannot
65     /// accidentally modify the wrong part of the structure.
66     ///
67     /// ```rust,ignore
68     /// // Bad
69     /// let mut values = vec![(2, 3), (3, 4)];
70     /// for (a, b) in &mut values {
71     ///     *a += *b;
72     /// }
73     ///
74     /// // Good
75     /// let mut values = vec![(2, 3), (3, 4)];
76     /// for &mut (ref mut a, b) in &mut values {
77     ///     *a += b;
78     /// }
79     /// ```
80     pub PATTERN_TYPE_MISMATCH,
81     restriction,
82     "type of pattern does not match the expression type"
83 }
84
85 declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]);
86
87 impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
88     fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
89         if let StmtKind::Local(local) = stmt.kind {
90             if let Some(init) = &local.init {
91                 if let Some(init_ty) = cx.typeck_results().node_type_opt(init.hir_id) {
92                     let pat = &local.pat;
93                     if in_external_macro(cx.sess(), pat.span) {
94                         return;
95                     }
96                     let deref_possible = match local.source {
97                         LocalSource::Normal => DerefPossible::Possible,
98                         _ => DerefPossible::Impossible,
99                     };
100                     apply_lint(cx, pat, init_ty, deref_possible);
101                 }
102             }
103         }
104     }
105
106     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
107         if let ExprKind::Match(scrutinee, arms, MatchSource::Normal) = expr.kind {
108             if let Some(expr_ty) = cx.typeck_results().node_type_opt(scrutinee.hir_id) {
109                 'pattern_checks: for arm in arms {
110                     let pat = &arm.pat;
111                     if in_external_macro(cx.sess(), pat.span) {
112                         continue 'pattern_checks;
113                     }
114                     if apply_lint(cx, pat, expr_ty, DerefPossible::Possible) {
115                         break 'pattern_checks;
116                     }
117                 }
118             }
119         }
120         if let ExprKind::Let(let_pat, let_expr, _) = expr.kind {
121             if let Some(ref expr_ty) = cx.typeck_results().node_type_opt(let_expr.hir_id) {
122                 if in_external_macro(cx.sess(), let_pat.span) {
123                     return;
124                 }
125                 apply_lint(cx, let_pat, expr_ty, DerefPossible::Possible);
126             }
127         }
128     }
129
130     fn check_fn(
131         &mut self,
132         cx: &LateContext<'tcx>,
133         _: intravisit::FnKind<'tcx>,
134         _: &'tcx FnDecl<'_>,
135         body: &'tcx Body<'_>,
136         _: Span,
137         hir_id: HirId,
138     ) {
139         if let Some(fn_sig) = cx.typeck_results().liberated_fn_sigs().get(hir_id) {
140             for (param, ty) in iter::zip(body.params, fn_sig.inputs()) {
141                 apply_lint(cx, param.pat, ty, DerefPossible::Impossible);
142             }
143         }
144     }
145 }
146
147 #[derive(Debug, Clone, Copy)]
148 enum DerefPossible {
149     Possible,
150     Impossible,
151 }
152
153 fn apply_lint<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, expr_ty: Ty<'tcx>, deref_possible: DerefPossible) -> bool {
154     let maybe_mismatch = find_first_mismatch(cx, pat, expr_ty, Level::Top);
155     if let Some((span, mutability, level)) = maybe_mismatch {
156         span_lint_and_help(
157             cx,
158             PATTERN_TYPE_MISMATCH,
159             span,
160             "type of pattern does not match the expression type",
161             None,
162             &format!(
163                 "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
164                 match (deref_possible, level) {
165                     (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
166                     _ => "",
167                 },
168                 match mutability {
169                     Mutability::Mut => "&mut _",
170                     Mutability::Not => "&_",
171                 },
172             ),
173         );
174         true
175     } else {
176         false
177     }
178 }
179
180 #[derive(Debug, Copy, Clone)]
181 enum Level {
182     Top,
183     Lower,
184 }
185
186 #[allow(rustc::usage_of_ty_tykind)]
187 fn find_first_mismatch<'tcx>(
188     cx: &LateContext<'tcx>,
189     pat: &Pat<'_>,
190     ty: Ty<'tcx>,
191     level: Level,
192 ) -> Option<(Span, Mutability, Level)> {
193     if let PatKind::Ref(sub_pat, _) = pat.kind {
194         if let TyKind::Ref(_, sub_ty, _) = ty.kind() {
195             return find_first_mismatch(cx, sub_pat, sub_ty, Level::Lower);
196         }
197     }
198
199     if let TyKind::Ref(_, _, mutability) = *ty.kind() {
200         if is_non_ref_pattern(&pat.kind) {
201             return Some((pat.span, mutability, level));
202         }
203     }
204
205     if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind {
206         if let TyKind::Adt(adt_def, substs_ref) = ty.kind() {
207             if let Some(variant) = get_variant(adt_def, qpath) {
208                 let field_defs = &variant.fields;
209                 return find_first_mismatch_in_struct(cx, field_pats, field_defs, substs_ref);
210             }
211         }
212     }
213
214     if let PatKind::TupleStruct(ref qpath, pats, _) = pat.kind {
215         if let TyKind::Adt(adt_def, substs_ref) = ty.kind() {
216             if let Some(variant) = get_variant(adt_def, qpath) {
217                 let field_defs = &variant.fields;
218                 let ty_iter = field_defs.iter().map(|field_def| field_def.ty(cx.tcx, substs_ref));
219                 return find_first_mismatch_in_tuple(cx, pats, ty_iter);
220             }
221         }
222     }
223
224     if let PatKind::Tuple(pats, _) = pat.kind {
225         if let TyKind::Tuple(..) = ty.kind() {
226             return find_first_mismatch_in_tuple(cx, pats, ty.tuple_fields());
227         }
228     }
229
230     if let PatKind::Or(sub_pats) = pat.kind {
231         for pat in sub_pats {
232             let maybe_mismatch = find_first_mismatch(cx, pat, ty, level);
233             if let Some(mismatch) = maybe_mismatch {
234                 return Some(mismatch);
235             }
236         }
237     }
238
239     None
240 }
241
242 fn get_variant<'a>(adt_def: &'a AdtDef, qpath: &QPath<'_>) -> Option<&'a VariantDef> {
243     if adt_def.is_struct() {
244         if let Some(variant) = adt_def.variants.iter().next() {
245             return Some(variant);
246         }
247     }
248
249     if adt_def.is_enum() {
250         let pat_ident = last_path_segment(qpath).ident;
251         for variant in &adt_def.variants {
252             if variant.ident == pat_ident {
253                 return Some(variant);
254             }
255         }
256     }
257
258     None
259 }
260
261 fn find_first_mismatch_in_tuple<'tcx, I>(
262     cx: &LateContext<'tcx>,
263     pats: &[Pat<'_>],
264     ty_iter_src: I,
265 ) -> Option<(Span, Mutability, Level)>
266 where
267     I: IntoIterator<Item = Ty<'tcx>>,
268 {
269     let mut field_tys = ty_iter_src.into_iter();
270     'fields: for pat in pats {
271         let field_ty = if let Some(ty) = field_tys.next() {
272             ty
273         } else {
274             break 'fields;
275         };
276
277         let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
278         if let Some(mismatch) = maybe_mismatch {
279             return Some(mismatch);
280         }
281     }
282
283     None
284 }
285
286 fn find_first_mismatch_in_struct<'tcx>(
287     cx: &LateContext<'tcx>,
288     field_pats: &[PatField<'_>],
289     field_defs: &[FieldDef],
290     substs_ref: SubstsRef<'tcx>,
291 ) -> Option<(Span, Mutability, Level)> {
292     for field_pat in field_pats {
293         'definitions: for field_def in field_defs {
294             if field_pat.ident == field_def.ident {
295                 let field_ty = field_def.ty(cx.tcx, substs_ref);
296                 let pat = &field_pat.pat;
297                 let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
298                 if let Some(mismatch) = maybe_mismatch {
299                     return Some(mismatch);
300                 }
301                 break 'definitions;
302             }
303         }
304     }
305
306     None
307 }
308
309 fn is_non_ref_pattern(pat_kind: &PatKind<'_>) -> bool {
310     match pat_kind {
311         PatKind::Struct(..) | PatKind::Tuple(..) | PatKind::TupleStruct(..) | PatKind::Path(..) => true,
312         PatKind::Or(sub_pats) => sub_pats.iter().any(|pat| is_non_ref_pattern(&pat.kind)),
313         _ => false,
314     }
315 }