]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/pattern_type_mismatch.rs
Added restriction lint: pattern-type-mismatch
[rust.git] / clippy_lints / src / pattern_type_mismatch.rs
1 use crate::utils::{last_path_segment, span_help_and_lint};
2 use rustc::lint::in_external_macro;
3 use rustc::ty::subst::SubstsRef;
4 use rustc::ty::{AdtDef, FieldDef, Ty, TyKind, VariantDef};
5 use rustc_hir::{
6     intravisit, Body, Expr, ExprKind, FieldPat, FnDecl, HirId, LocalSource, MatchSource, Mutability, Pat, PatKind,
7     QPath, Stmt, StmtKind,
8 };
9 use rustc_lint::{LateContext, LateLintPass, LintContext};
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
11 use rustc_span::source_map::Span;
12
13 declare_clippy_lint! {
14     /// **What it does:** Checks for patterns that aren't exact representations of the types
15     /// they are applied to.
16     ///
17     /// **Why is this bad?** It isn't bad in general. But in some contexts it can be desirable
18     /// because it increases ownership hints in the code, and will guard against some changes
19     /// in ownership.
20     ///
21     /// **Known problems:** None.
22     ///
23     /// **Example:**
24     ///
25     /// ```rust,ignore
26     /// // Bad
27     /// let value = &Some(Box::new(23));
28     /// match value {
29     ///     Some(inner) => println!("{}", inner),
30     ///     None => println!("none"),
31     /// }
32     ///
33     /// // Good
34     /// let value = &Some(Box::new(23));
35     /// match *value {
36     ///     Some(ref inner) => println!("{}", inner),
37     ///     None => println!("none"),
38     /// }
39     /// ```
40     pub PATTERN_TYPE_MISMATCH,
41     restriction,
42     "type of pattern does not match the expression type"
43 }
44
45 declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]);
46
47 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PatternTypeMismatch {
48     fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt<'_>) {
49         if let StmtKind::Local(ref local) = stmt.kind {
50             if let Some(init) = &local.init {
51                 if let Some(init_ty) = cx.tables.node_type_opt(init.hir_id) {
52                     let pat = &local.pat;
53                     if in_external_macro(cx.sess(), pat.span) {
54                         return;
55                     }
56                     let deref_possible = match local.source {
57                         LocalSource::Normal => DerefPossible::Possible,
58                         _ => DerefPossible::Impossible,
59                     };
60                     apply_lint(cx, pat, init_ty, deref_possible);
61                 }
62             }
63         }
64     }
65
66     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
67         if let ExprKind::Match(ref expr, arms, source) = expr.kind {
68             match source {
69                 MatchSource::Normal | MatchSource::IfLetDesugar { .. } | MatchSource::WhileLetDesugar => {
70                     if let Some(expr_ty) = cx.tables.node_type_opt(expr.hir_id) {
71                         'pattern_checks: for arm in arms {
72                             let pat = &arm.pat;
73                             if in_external_macro(cx.sess(), pat.span) {
74                                 continue 'pattern_checks;
75                             }
76                             if apply_lint(cx, pat, expr_ty, DerefPossible::Possible) {
77                                 break 'pattern_checks;
78                             }
79                         }
80                     }
81                 },
82                 _ => (),
83             }
84         }
85     }
86
87     fn check_fn(
88         &mut self,
89         cx: &LateContext<'a, 'tcx>,
90         _: intravisit::FnKind<'tcx>,
91         _: &'tcx FnDecl<'_>,
92         body: &'tcx Body<'_>,
93         _: Span,
94         hir_id: HirId,
95     ) {
96         if let Some(fn_sig) = cx.tables.liberated_fn_sigs().get(hir_id) {
97             for (param, ty) in body.params.iter().zip(fn_sig.inputs().iter()) {
98                 apply_lint(cx, &param.pat, ty, DerefPossible::Impossible);
99             }
100         }
101     }
102 }
103
104 #[derive(Debug, Clone, Copy)]
105 enum DerefPossible {
106     Possible,
107     Impossible,
108 }
109
110 fn apply_lint<'a, 'tcx>(
111     cx: &LateContext<'a, 'tcx>,
112     pat: &Pat<'_>,
113     expr_ty: Ty<'tcx>,
114     deref_possible: DerefPossible,
115 ) -> bool {
116     let maybe_mismatch = find_first_mismatch(cx, pat, expr_ty, Level::Top);
117     if let Some((span, mutability, level)) = maybe_mismatch {
118         span_help_and_lint(
119             cx,
120             PATTERN_TYPE_MISMATCH,
121             span,
122             "type of pattern does not match the expression type",
123             &format!(
124                 "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
125                 match (deref_possible, level) {
126                     (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
127                     _ => "",
128                 },
129                 match mutability {
130                     Mutability::Mut => "&mut _",
131                     Mutability::Not => "&_",
132                 },
133             ),
134         );
135         true
136     } else {
137         false
138     }
139 }
140
141 #[derive(Debug, Copy, Clone)]
142 enum Level {
143     Top,
144     Lower,
145 }
146
147 #[allow(rustc::usage_of_ty_tykind)]
148 fn find_first_mismatch<'a, 'tcx>(
149     cx: &LateContext<'a, 'tcx>,
150     pat: &Pat<'_>,
151     ty: Ty<'tcx>,
152     level: Level,
153 ) -> Option<(Span, Mutability, Level)> {
154     if let PatKind::Ref(ref sub_pat, _) = pat.kind {
155         if let TyKind::Ref(_, sub_ty, _) = ty.kind {
156             return find_first_mismatch(cx, sub_pat, sub_ty, Level::Lower);
157         }
158     }
159
160     if let TyKind::Ref(_, _, mutability) = ty.kind {
161         if is_non_ref_pattern(&pat.kind) {
162             return Some((pat.span, mutability, level));
163         }
164     }
165
166     if let PatKind::Struct(ref qpath, ref field_pats, _) = pat.kind {
167         if let TyKind::Adt(ref adt_def, ref substs_ref) = ty.kind {
168             if let Some(variant) = get_variant(adt_def, qpath) {
169                 let field_defs = &variant.fields;
170                 return find_first_mismatch_in_struct(cx, field_pats, field_defs, substs_ref);
171             }
172         }
173     }
174
175     if let PatKind::TupleStruct(ref qpath, ref pats, _) = pat.kind {
176         if let TyKind::Adt(ref adt_def, ref substs_ref) = ty.kind {
177             if let Some(variant) = get_variant(adt_def, qpath) {
178                 let field_defs = &variant.fields;
179                 let ty_iter = field_defs.iter().map(|field_def| field_def.ty(cx.tcx, substs_ref));
180                 return find_first_mismatch_in_tuple(cx, pats, ty_iter);
181             }
182         }
183     }
184
185     if let PatKind::Tuple(ref pats, _) = pat.kind {
186         if let TyKind::Tuple(..) = ty.kind {
187             return find_first_mismatch_in_tuple(cx, pats, ty.tuple_fields());
188         }
189     }
190
191     if let PatKind::Or(sub_pats) = pat.kind {
192         for pat in sub_pats {
193             let maybe_mismatch = find_first_mismatch(cx, pat, ty, level);
194             if let Some(mismatch) = maybe_mismatch {
195                 return Some(mismatch);
196             }
197         }
198     }
199
200     None
201 }
202
203 fn get_variant<'a>(adt_def: &'a AdtDef, qpath: &QPath<'_>) -> Option<&'a VariantDef> {
204     if adt_def.is_struct() {
205         if let Some(variant) = adt_def.variants.iter().next() {
206             return Some(variant);
207         }
208     }
209
210     if adt_def.is_enum() {
211         let pat_ident = last_path_segment(qpath).ident;
212         for variant in &adt_def.variants {
213             if variant.ident == pat_ident {
214                 return Some(variant);
215             }
216         }
217     }
218
219     None
220 }
221
222 fn find_first_mismatch_in_tuple<'a, 'tcx, I>(
223     cx: &LateContext<'a, 'tcx>,
224     pats: &[&Pat<'_>],
225     ty_iter_src: I,
226 ) -> Option<(Span, Mutability, Level)>
227 where
228     I: IntoIterator<Item = Ty<'tcx>>,
229 {
230     let mut field_tys = ty_iter_src.into_iter();
231     'fields: for pat in pats {
232         let field_ty = if let Some(ty) = field_tys.next() {
233             ty
234         } else {
235             break 'fields;
236         };
237
238         let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
239         if let Some(mismatch) = maybe_mismatch {
240             return Some(mismatch);
241         }
242     }
243
244     None
245 }
246
247 fn find_first_mismatch_in_struct<'a, 'tcx>(
248     cx: &LateContext<'a, 'tcx>,
249     field_pats: &[FieldPat<'_>],
250     field_defs: &[FieldDef],
251     substs_ref: SubstsRef<'tcx>,
252 ) -> Option<(Span, Mutability, Level)> {
253     for field_pat in field_pats {
254         'definitions: for field_def in field_defs {
255             if field_pat.ident == field_def.ident {
256                 let field_ty = field_def.ty(cx.tcx, substs_ref);
257                 let pat = &field_pat.pat;
258                 let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
259                 if let Some(mismatch) = maybe_mismatch {
260                     return Some(mismatch);
261                 }
262                 break 'definitions;
263             }
264         }
265     }
266
267     None
268 }
269
270 fn is_non_ref_pattern(pat_kind: &PatKind<'_>) -> bool {
271     match pat_kind {
272         PatKind::Struct(..) | PatKind::Tuple(..) | PatKind::TupleStruct(..) | PatKind::Path(..) => true,
273         PatKind::Or(sub_pats) => sub_pats.iter().any(|pat| is_non_ref_pattern(&pat.kind)),
274         _ => false,
275     }
276 }