]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs
Rollup merge of #97021 - Volker-Weissmann:patch-1, r=Dylan-DPC
[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, Let, 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     #[clippy::version = "1.47.0"]
77     pub PATTERN_TYPE_MISMATCH,
78     restriction,
79     "type of pattern does not match the expression type"
80 }
81
82 declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]);
83
84 impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
85     fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
86         if let StmtKind::Local(local) = stmt.kind {
87             if in_external_macro(cx.sess(), local.pat.span) {
88                 return;
89             }
90             let deref_possible = match local.source {
91                 LocalSource::Normal => DerefPossible::Possible,
92                 _ => DerefPossible::Impossible,
93             };
94             apply_lint(cx, local.pat, deref_possible);
95         }
96     }
97
98     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
99         if let ExprKind::Match(_, arms, _) = expr.kind {
100             for arm in arms {
101                 let pat = &arm.pat;
102                 if apply_lint(cx, pat, DerefPossible::Possible) {
103                     break;
104                 }
105             }
106         }
107         if let ExprKind::Let(Let { pat, .. }) = expr.kind {
108             apply_lint(cx, pat, DerefPossible::Possible);
109         }
110     }
111
112     fn check_fn(
113         &mut self,
114         cx: &LateContext<'tcx>,
115         _: intravisit::FnKind<'tcx>,
116         _: &'tcx FnDecl<'_>,
117         body: &'tcx Body<'_>,
118         _: Span,
119         _: HirId,
120     ) {
121         for param in body.params {
122             apply_lint(cx, param.pat, DerefPossible::Impossible);
123         }
124     }
125 }
126
127 #[derive(Debug, Clone, Copy)]
128 enum DerefPossible {
129     Possible,
130     Impossible,
131 }
132
133 fn apply_lint<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, deref_possible: DerefPossible) -> bool {
134     let maybe_mismatch = find_first_mismatch(cx, pat);
135     if let Some((span, mutability, level)) = maybe_mismatch {
136         span_lint_and_help(
137             cx,
138             PATTERN_TYPE_MISMATCH,
139             span,
140             "type of pattern does not match the expression type",
141             None,
142             &format!(
143                 "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
144                 match (deref_possible, level) {
145                     (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
146                     _ => "",
147                 },
148                 match mutability {
149                     Mutability::Mut => "&mut _",
150                     Mutability::Not => "&_",
151                 },
152             ),
153         );
154         true
155     } else {
156         false
157     }
158 }
159
160 #[derive(Debug, Copy, Clone)]
161 enum Level {
162     Top,
163     Lower,
164 }
165
166 #[allow(rustc::usage_of_ty_tykind)]
167 fn find_first_mismatch<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> Option<(Span, Mutability, Level)> {
168     let mut result = None;
169     pat.walk(|p| {
170         if result.is_some() {
171             return false;
172         }
173         if in_external_macro(cx.sess(), p.span) {
174             return true;
175         }
176         let adjust_pat = match p.kind {
177             PatKind::Or([p, ..]) => p,
178             _ => p,
179         };
180         if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
181             if let [first, ..] = **adjustments {
182                 if let ty::Ref(.., mutability) = *first.kind() {
183                     let level = if p.hir_id == pat.hir_id {
184                         Level::Top
185                     } else {
186                         Level::Lower
187                     };
188                     result = Some((p.span, mutability, level));
189                 }
190             }
191         }
192         result.is_none()
193     });
194     result
195 }