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