]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/pattern_type_mismatch.rs
Fix rebase fallout
[rust.git] / clippy_lints / src / pattern_type_mismatch.rs
1 use crate::utils::{last_path_segment, span_lint_and_help};
2 use rustc_hir::{
3     intravisit, Body, Expr, ExprKind, FieldPat, FnDecl, HirId, LocalSource, MatchSource, Mutability, Pat, PatKind,
4     QPath, Stmt, StmtKind,
5 };
6 use rustc_lint::{LateContext, LateLintPass, LintContext};
7 use rustc_middle::lint::in_external_macro;
8 use rustc_middle::ty::subst::SubstsRef;
9 use rustc_middle::ty::{AdtDef, FieldDef, Ty, TyKind, VariantDef};
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_lint_and_help(
119             cx,
120             PATTERN_TYPE_MISMATCH,
121             span,
122             "type of pattern does not match the expression type",
123             None,
124             &format!(
125                 "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
126                 match (deref_possible, level) {
127                     (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
128                     _ => "",
129                 },
130                 match mutability {
131                     Mutability::Mut => "&mut _",
132                     Mutability::Not => "&_",
133                 },
134             ),
135         );
136         true
137     } else {
138         false
139     }
140 }
141
142 #[derive(Debug, Copy, Clone)]
143 enum Level {
144     Top,
145     Lower,
146 }
147
148 #[allow(rustc::usage_of_ty_tykind)]
149 fn find_first_mismatch<'a, 'tcx>(
150     cx: &LateContext<'a, 'tcx>,
151     pat: &Pat<'_>,
152     ty: Ty<'tcx>,
153     level: Level,
154 ) -> Option<(Span, Mutability, Level)> {
155     if let PatKind::Ref(ref sub_pat, _) = pat.kind {
156         if let TyKind::Ref(_, sub_ty, _) = ty.kind {
157             return find_first_mismatch(cx, sub_pat, sub_ty, Level::Lower);
158         }
159     }
160
161     if let TyKind::Ref(_, _, mutability) = ty.kind {
162         if is_non_ref_pattern(&pat.kind) {
163             return Some((pat.span, mutability, level));
164         }
165     }
166
167     if let PatKind::Struct(ref qpath, ref field_pats, _) = pat.kind {
168         if let TyKind::Adt(ref adt_def, ref substs_ref) = ty.kind {
169             if let Some(variant) = get_variant(adt_def, qpath) {
170                 let field_defs = &variant.fields;
171                 return find_first_mismatch_in_struct(cx, field_pats, field_defs, substs_ref);
172             }
173         }
174     }
175
176     if let PatKind::TupleStruct(ref qpath, ref pats, _) = pat.kind {
177         if let TyKind::Adt(ref adt_def, ref substs_ref) = ty.kind {
178             if let Some(variant) = get_variant(adt_def, qpath) {
179                 let field_defs = &variant.fields;
180                 let ty_iter = field_defs.iter().map(|field_def| field_def.ty(cx.tcx, substs_ref));
181                 return find_first_mismatch_in_tuple(cx, pats, ty_iter);
182             }
183         }
184     }
185
186     if let PatKind::Tuple(ref pats, _) = pat.kind {
187         if let TyKind::Tuple(..) = ty.kind {
188             return find_first_mismatch_in_tuple(cx, pats, ty.tuple_fields());
189         }
190     }
191
192     if let PatKind::Or(sub_pats) = pat.kind {
193         for pat in sub_pats {
194             let maybe_mismatch = find_first_mismatch(cx, pat, ty, level);
195             if let Some(mismatch) = maybe_mismatch {
196                 return Some(mismatch);
197             }
198         }
199     }
200
201     None
202 }
203
204 fn get_variant<'a>(adt_def: &'a AdtDef, qpath: &QPath<'_>) -> Option<&'a VariantDef> {
205     if adt_def.is_struct() {
206         if let Some(variant) = adt_def.variants.iter().next() {
207             return Some(variant);
208         }
209     }
210
211     if adt_def.is_enum() {
212         let pat_ident = last_path_segment(qpath).ident;
213         for variant in &adt_def.variants {
214             if variant.ident == pat_ident {
215                 return Some(variant);
216             }
217         }
218     }
219
220     None
221 }
222
223 fn find_first_mismatch_in_tuple<'a, 'tcx, I>(
224     cx: &LateContext<'a, 'tcx>,
225     pats: &[&Pat<'_>],
226     ty_iter_src: I,
227 ) -> Option<(Span, Mutability, Level)>
228 where
229     I: IntoIterator<Item = Ty<'tcx>>,
230 {
231     let mut field_tys = ty_iter_src.into_iter();
232     'fields: for pat in pats {
233         let field_ty = if let Some(ty) = field_tys.next() {
234             ty
235         } else {
236             break 'fields;
237         };
238
239         let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
240         if let Some(mismatch) = maybe_mismatch {
241             return Some(mismatch);
242         }
243     }
244
245     None
246 }
247
248 fn find_first_mismatch_in_struct<'a, 'tcx>(
249     cx: &LateContext<'a, 'tcx>,
250     field_pats: &[FieldPat<'_>],
251     field_defs: &[FieldDef],
252     substs_ref: SubstsRef<'tcx>,
253 ) -> Option<(Span, Mutability, Level)> {
254     for field_pat in field_pats {
255         'definitions: for field_def in field_defs {
256             if field_pat.ident == field_def.ident {
257                 let field_ty = field_def.ty(cx.tcx, substs_ref);
258                 let pat = &field_pat.pat;
259                 let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
260                 if let Some(mismatch) = maybe_mismatch {
261                     return Some(mismatch);
262                 }
263                 break 'definitions;
264             }
265         }
266     }
267
268     None
269 }
270
271 fn is_non_ref_pattern(pat_kind: &PatKind<'_>) -> bool {
272     match pat_kind {
273         PatKind::Struct(..) | PatKind::Tuple(..) | PatKind::TupleStruct(..) | PatKind::Path(..) => true,
274         PatKind::Or(sub_pats) => sub_pats.iter().any(|pat| is_non_ref_pattern(&pat.kind)),
275         _ => false,
276     }
277 }