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