]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs
Auto merge of #85020 - lrh2000:named-upvars, r=tmandry
[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
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(expr, arms, source) = expr.kind {
108             match source {
109                 MatchSource::Normal | MatchSource::IfLetDesugar { .. } | MatchSource::WhileLetDesugar => {
110                     if let Some(expr_ty) = cx.typeck_results().node_type_opt(expr.hir_id) {
111                         'pattern_checks: for arm in arms {
112                             let pat = &arm.pat;
113                             if in_external_macro(cx.sess(), pat.span) {
114                                 continue 'pattern_checks;
115                             }
116                             if apply_lint(cx, pat, expr_ty, DerefPossible::Possible) {
117                                 break 'pattern_checks;
118                             }
119                         }
120                     }
121                 },
122                 _ => (),
123             }
124         }
125     }
126
127     fn check_fn(
128         &mut self,
129         cx: &LateContext<'tcx>,
130         _: intravisit::FnKind<'tcx>,
131         _: &'tcx FnDecl<'_>,
132         body: &'tcx Body<'_>,
133         _: Span,
134         hir_id: HirId,
135     ) {
136         if let Some(fn_sig) = cx.typeck_results().liberated_fn_sigs().get(hir_id) {
137             for (param, ty) in iter::zip(body.params, fn_sig.inputs()) {
138                 apply_lint(cx, param.pat, ty, DerefPossible::Impossible);
139             }
140         }
141     }
142 }
143
144 #[derive(Debug, Clone, Copy)]
145 enum DerefPossible {
146     Possible,
147     Impossible,
148 }
149
150 fn apply_lint<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, expr_ty: Ty<'tcx>, deref_possible: DerefPossible) -> bool {
151     let maybe_mismatch = find_first_mismatch(cx, pat, expr_ty, Level::Top);
152     if let Some((span, mutability, level)) = maybe_mismatch {
153         span_lint_and_help(
154             cx,
155             PATTERN_TYPE_MISMATCH,
156             span,
157             "type of pattern does not match the expression type",
158             None,
159             &format!(
160                 "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
161                 match (deref_possible, level) {
162                     (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
163                     _ => "",
164                 },
165                 match mutability {
166                     Mutability::Mut => "&mut _",
167                     Mutability::Not => "&_",
168                 },
169             ),
170         );
171         true
172     } else {
173         false
174     }
175 }
176
177 #[derive(Debug, Copy, Clone)]
178 enum Level {
179     Top,
180     Lower,
181 }
182
183 #[allow(rustc::usage_of_ty_tykind)]
184 fn find_first_mismatch<'tcx>(
185     cx: &LateContext<'tcx>,
186     pat: &Pat<'_>,
187     ty: Ty<'tcx>,
188     level: Level,
189 ) -> Option<(Span, Mutability, Level)> {
190     if let PatKind::Ref(sub_pat, _) = pat.kind {
191         if let TyKind::Ref(_, sub_ty, _) = ty.kind() {
192             return find_first_mismatch(cx, sub_pat, sub_ty, Level::Lower);
193         }
194     }
195
196     if let TyKind::Ref(_, _, mutability) = *ty.kind() {
197         if is_non_ref_pattern(&pat.kind) {
198             return Some((pat.span, mutability, level));
199         }
200     }
201
202     if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind {
203         if let TyKind::Adt(adt_def, substs_ref) = ty.kind() {
204             if let Some(variant) = get_variant(adt_def, qpath) {
205                 let field_defs = &variant.fields;
206                 return find_first_mismatch_in_struct(cx, field_pats, field_defs, substs_ref);
207             }
208         }
209     }
210
211     if let PatKind::TupleStruct(ref qpath, pats, _) = pat.kind {
212         if let TyKind::Adt(adt_def, substs_ref) = ty.kind() {
213             if let Some(variant) = get_variant(adt_def, qpath) {
214                 let field_defs = &variant.fields;
215                 let ty_iter = field_defs.iter().map(|field_def| field_def.ty(cx.tcx, substs_ref));
216                 return find_first_mismatch_in_tuple(cx, pats, ty_iter);
217             }
218         }
219     }
220
221     if let PatKind::Tuple(pats, _) = pat.kind {
222         if let TyKind::Tuple(..) = ty.kind() {
223             return find_first_mismatch_in_tuple(cx, pats, ty.tuple_fields());
224         }
225     }
226
227     if let PatKind::Or(sub_pats) = pat.kind {
228         for pat in sub_pats {
229             let maybe_mismatch = find_first_mismatch(cx, pat, ty, level);
230             if let Some(mismatch) = maybe_mismatch {
231                 return Some(mismatch);
232             }
233         }
234     }
235
236     None
237 }
238
239 fn get_variant<'a>(adt_def: &'a AdtDef, qpath: &QPath<'_>) -> Option<&'a VariantDef> {
240     if adt_def.is_struct() {
241         if let Some(variant) = adt_def.variants.iter().next() {
242             return Some(variant);
243         }
244     }
245
246     if adt_def.is_enum() {
247         let pat_ident = last_path_segment(qpath).ident;
248         for variant in &adt_def.variants {
249             if variant.ident == pat_ident {
250                 return Some(variant);
251             }
252         }
253     }
254
255     None
256 }
257
258 fn find_first_mismatch_in_tuple<'tcx, I>(
259     cx: &LateContext<'tcx>,
260     pats: &[Pat<'_>],
261     ty_iter_src: I,
262 ) -> Option<(Span, Mutability, Level)>
263 where
264     I: IntoIterator<Item = Ty<'tcx>>,
265 {
266     let mut field_tys = ty_iter_src.into_iter();
267     'fields: for pat in pats {
268         let field_ty = if let Some(ty) = field_tys.next() {
269             ty
270         } else {
271             break 'fields;
272         };
273
274         let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
275         if let Some(mismatch) = maybe_mismatch {
276             return Some(mismatch);
277         }
278     }
279
280     None
281 }
282
283 fn find_first_mismatch_in_struct<'tcx>(
284     cx: &LateContext<'tcx>,
285     field_pats: &[PatField<'_>],
286     field_defs: &[FieldDef],
287     substs_ref: SubstsRef<'tcx>,
288 ) -> Option<(Span, Mutability, Level)> {
289     for field_pat in field_pats {
290         'definitions: for field_def in field_defs {
291             if field_pat.ident == field_def.ident {
292                 let field_ty = field_def.ty(cx.tcx, substs_ref);
293                 let pat = &field_pat.pat;
294                 let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
295                 if let Some(mismatch) = maybe_mismatch {
296                     return Some(mismatch);
297                 }
298                 break 'definitions;
299             }
300         }
301     }
302
303     None
304 }
305
306 fn is_non_ref_pattern(pat_kind: &PatKind<'_>) -> bool {
307     match pat_kind {
308         PatKind::Struct(..) | PatKind::Tuple(..) | PatKind::TupleStruct(..) | PatKind::Path(..) => true,
309         PatKind::Or(sub_pats) => sub_pats.iter().any(|pat| is_non_ref_pattern(&pat.kind)),
310         _ => false,
311     }
312 }