• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use rustc_hir::{intravisit, Body, Expr, ExprKind, FnDecl, Let, LocalSource, Mutability, Pat, PatKind, Stmt, StmtKind};
3 use rustc_lint::{LateContext, LateLintPass, LintContext};
4 use rustc_middle::lint::in_external_macro;
5 use rustc_middle::ty;
6 use rustc_session::{declare_lint_pass, declare_tool_lint};
7 use rustc_span::def_id::LocalDefId;
8 use rustc_span::source_map::Span;
9 
10 declare_clippy_lint! {
11     /// ### What it does
12     /// Checks for patterns that aren't exact representations of the types
13     /// they are applied to.
14     ///
15     /// To satisfy this lint, you will have to adjust either the expression that is matched
16     /// against or the pattern itself, as well as the bindings that are introduced by the
17     /// adjusted patterns. For matching you will have to either dereference the expression
18     /// with the `*` operator, or amend the patterns to explicitly match against `&<pattern>`
19     /// or `&mut <pattern>` depending on the reference mutability. For the bindings you need
20     /// to use the inverse. You can leave them as plain bindings if you wish for the value
21     /// to be copied, but you must use `ref mut <variable>` or `ref <variable>` to construct
22     /// a reference into the matched structure.
23     ///
24     /// If you are looking for a way to learn about ownership semantics in more detail, it
25     /// is recommended to look at IDE options available to you to highlight types, lifetimes
26     /// and reference semantics in your code. The available tooling would expose these things
27     /// in a general way even outside of the various pattern matching mechanics. Of course
28     /// this lint can still be used to highlight areas of interest and ensure a good understanding
29     /// of ownership semantics.
30     ///
31     /// ### Why is this bad?
32     /// It isn't bad in general. But in some contexts it can be desirable
33     /// because it increases ownership hints in the code, and will guard against some changes
34     /// in ownership.
35     ///
36     /// ### Example
37     /// This example shows the basic adjustments necessary to satisfy the lint. Note how
38     /// the matched expression is explicitly dereferenced with `*` and the `inner` variable
39     /// is bound to a shared borrow via `ref inner`.
40     ///
41     /// ```rust,ignore
42     /// // Bad
43     /// let value = &Some(Box::new(23));
44     /// match value {
45     ///     Some(inner) => println!("{}", inner),
46     ///     None => println!("none"),
47     /// }
48     ///
49     /// // Good
50     /// let value = &Some(Box::new(23));
51     /// match *value {
52     ///     Some(ref inner) => println!("{}", inner),
53     ///     None => println!("none"),
54     /// }
55     /// ```
56     ///
57     /// The following example demonstrates one of the advantages of the more verbose style.
58     /// Note how the second version uses `ref mut a` to explicitly declare `a` a shared mutable
59     /// borrow, while `b` is simply taken by value. This ensures that the loop body cannot
60     /// accidentally modify the wrong part of the structure.
61     ///
62     /// ```rust,ignore
63     /// // Bad
64     /// let mut values = vec![(2, 3), (3, 4)];
65     /// for (a, b) in &mut values {
66     ///     *a += *b;
67     /// }
68     ///
69     /// // Good
70     /// let mut values = vec![(2, 3), (3, 4)];
71     /// for &mut (ref mut a, b) in &mut values {
72     ///     *a += b;
73     /// }
74     /// ```
75     #[clippy::version = "1.47.0"]
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 {
check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>)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 
check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>)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, pat, DerefPossible::Possible);
108         }
109     }
110 
check_fn( &mut self, cx: &LateContext<'tcx>, _: intravisit::FnKind<'tcx>, _: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, _: Span, _: LocalDefId, )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         _: LocalDefId,
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 
apply_lint(cx: &LateContext<'_>, pat: &Pat<'_>, deref_possible: DerefPossible) -> bool132 fn apply_lint(cx: &LateContext<'_>, 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 
find_first_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(Span, Mutability, Level)>165 fn find_first_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(Span, Mutability, Level)> {
166     let mut result = None;
167     pat.walk(|p| {
168         if result.is_some() {
169             return false;
170         }
171         if in_external_macro(cx.sess(), p.span) {
172             return true;
173         }
174         let adjust_pat = match p.kind {
175             PatKind::Or([p, ..]) => p,
176             _ => p,
177         };
178         if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
179             if let [first, ..] = **adjustments {
180                 if let ty::Ref(.., mutability) = *first.kind() {
181                     let level = if p.hir_id == pat.hir_id {
182                         Level::Top
183                     } else {
184                         Level::Lower
185                     };
186                     result = Some((p.span, mutability, level));
187                 }
188             }
189         }
190         result.is_none()
191     });
192     result
193 }
194