]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs
Auto merge of #91565 - dtolnay:printhelpers, r=jackh726
[rust.git] / src / tools / clippy / clippy_lints / src / pattern_type_mismatch.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use rustc_hir::{
3     intravisit, Body, Expr, ExprKind, FnDecl, HirId, LocalSource, Mutability, Pat, PatKind, Stmt, StmtKind,
4 };
5 use rustc_lint::{LateContext, LateLintPass, LintContext};
6 use rustc_middle::lint::in_external_macro;
7 use rustc_middle::ty;
8 use rustc_session::{declare_lint_pass, declare_tool_lint};
9 use rustc_span::source_map::Span;
10
11 declare_clippy_lint! {
12     /// ### What it does
13     /// Checks for patterns that aren't exact representations of the types
14     /// they are applied to.
15     ///
16     /// To satisfy this lint, you will have to adjust either the expression that is matched
17     /// against or the pattern itself, as well as the bindings that are introduced by the
18     /// adjusted patterns. For matching you will have to either dereference the expression
19     /// with the `*` operator, or amend the patterns to explicitly match against `&<pattern>`
20     /// or `&mut <pattern>` depending on the reference mutability. For the bindings you need
21     /// to use the inverse. You can leave them as plain bindings if you wish for the value
22     /// to be copied, but you must use `ref mut <variable>` or `ref <variable>` to construct
23     /// a reference into the matched structure.
24     ///
25     /// If you are looking for a way to learn about ownership semantics in more detail, it
26     /// is recommended to look at IDE options available to you to highlight types, lifetimes
27     /// and reference semantics in your code. The available tooling would expose these things
28     /// in a general way even outside of the various pattern matching mechanics. Of course
29     /// this lint can still be used to highlight areas of interest and ensure a good understanding
30     /// of ownership semantics.
31     ///
32     /// ### Why is this bad?
33     /// It isn't bad in general. But in some contexts it can be desirable
34     /// because it increases ownership hints in the code, and will guard against some changes
35     /// in ownership.
36     ///
37     /// ### Example
38     /// This example shows the basic adjustments necessary to satisfy the lint. Note how
39     /// the matched expression is explicitly dereferenced with `*` and the `inner` variable
40     /// is bound to a shared borrow via `ref inner`.
41     ///
42     /// ```rust,ignore
43     /// // Bad
44     /// let value = &Some(Box::new(23));
45     /// match value {
46     ///     Some(inner) => println!("{}", inner),
47     ///     None => println!("none"),
48     /// }
49     ///
50     /// // Good
51     /// let value = &Some(Box::new(23));
52     /// match *value {
53     ///     Some(ref inner) => println!("{}", inner),
54     ///     None => println!("none"),
55     /// }
56     /// ```
57     ///
58     /// The following example demonstrates one of the advantages of the more verbose style.
59     /// Note how the second version uses `ref mut a` to explicitly declare `a` a shared mutable
60     /// borrow, while `b` is simply taken by value. This ensures that the loop body cannot
61     /// accidentally modify the wrong part of the structure.
62     ///
63     /// ```rust,ignore
64     /// // Bad
65     /// let mut values = vec![(2, 3), (3, 4)];
66     /// for (a, b) in &mut values {
67     ///     *a += *b;
68     /// }
69     ///
70     /// // Good
71     /// let mut values = vec![(2, 3), (3, 4)];
72     /// for &mut (ref mut a, b) in &mut values {
73     ///     *a += b;
74     /// }
75     /// ```
76     pub PATTERN_TYPE_MISMATCH,
77     restriction,
78     "type of pattern does not match the expression type"
79 }
80
81 declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]);
82
83 impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
84     fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
85         if let StmtKind::Local(local) = stmt.kind {
86             if in_external_macro(cx.sess(), local.pat.span) {
87                 return;
88             }
89             let deref_possible = match local.source {
90                 LocalSource::Normal => DerefPossible::Possible,
91                 _ => DerefPossible::Impossible,
92             };
93             apply_lint(cx, local.pat, deref_possible);
94         }
95     }
96
97     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
98         if let ExprKind::Match(_, arms, _) = expr.kind {
99             for arm in arms {
100                 let pat = &arm.pat;
101                 if apply_lint(cx, pat, DerefPossible::Possible) {
102                     break;
103                 }
104             }
105         }
106         if let ExprKind::Let(let_pat, ..) = expr.kind {
107             apply_lint(cx, let_pat, DerefPossible::Possible);
108         }
109     }
110
111     fn check_fn(
112         &mut self,
113         cx: &LateContext<'tcx>,
114         _: intravisit::FnKind<'tcx>,
115         _: &'tcx FnDecl<'_>,
116         body: &'tcx Body<'_>,
117         _: Span,
118         _: HirId,
119     ) {
120         for param in body.params {
121             apply_lint(cx, param.pat, DerefPossible::Impossible);
122         }
123     }
124 }
125
126 #[derive(Debug, Clone, Copy)]
127 enum DerefPossible {
128     Possible,
129     Impossible,
130 }
131
132 fn apply_lint<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, deref_possible: DerefPossible) -> bool {
133     let maybe_mismatch = find_first_mismatch(cx, pat);
134     if let Some((span, mutability, level)) = maybe_mismatch {
135         span_lint_and_help(
136             cx,
137             PATTERN_TYPE_MISMATCH,
138             span,
139             "type of pattern does not match the expression type",
140             None,
141             &format!(
142                 "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
143                 match (deref_possible, level) {
144                     (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
145                     _ => "",
146                 },
147                 match mutability {
148                     Mutability::Mut => "&mut _",
149                     Mutability::Not => "&_",
150                 },
151             ),
152         );
153         true
154     } else {
155         false
156     }
157 }
158
159 #[derive(Debug, Copy, Clone)]
160 enum Level {
161     Top,
162     Lower,
163 }
164
165 #[allow(rustc::usage_of_ty_tykind)]
166 fn find_first_mismatch<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> Option<(Span, Mutability, Level)> {
167     let mut result = None;
168     pat.walk(|p| {
169         if result.is_some() {
170             return false;
171         }
172         if in_external_macro(cx.sess(), p.span) {
173             return true;
174         }
175         let adjust_pat = match p.kind {
176             PatKind::Or([p, ..]) => p,
177             _ => p,
178         };
179         if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
180             if let [first, ..] = **adjustments {
181                 if let ty::Ref(.., mutability) = *first.kind() {
182                     let level = if p.hir_id == pat.hir_id {
183                         Level::Top
184                     } else {
185                         Level::Lower
186                     };
187                     result = Some((p.span, mutability, level));
188                 }
189             }
190         }
191         result.is_none()
192     });
193     result
194 }