summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs
blob: 97b5a4ce36413f4820b209a68f45c3beeb26db54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::{
    intravisit, Body, Expr, ExprKind, FnDecl, HirId, Let, LocalSource, Mutability, Pat, PatKind, Stmt, StmtKind,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for patterns that aren't exact representations of the types
    /// they are applied to.
    ///
    /// To satisfy this lint, you will have to adjust either the expression that is matched
    /// against or the pattern itself, as well as the bindings that are introduced by the
    /// adjusted patterns. For matching you will have to either dereference the expression
    /// with the `*` operator, or amend the patterns to explicitly match against `&<pattern>`
    /// or `&mut <pattern>` depending on the reference mutability. For the bindings you need
    /// to use the inverse. You can leave them as plain bindings if you wish for the value
    /// to be copied, but you must use `ref mut <variable>` or `ref <variable>` to construct
    /// a reference into the matched structure.
    ///
    /// If you are looking for a way to learn about ownership semantics in more detail, it
    /// is recommended to look at IDE options available to you to highlight types, lifetimes
    /// and reference semantics in your code. The available tooling would expose these things
    /// in a general way even outside of the various pattern matching mechanics. Of course
    /// this lint can still be used to highlight areas of interest and ensure a good understanding
    /// of ownership semantics.
    ///
    /// ### Why is this bad?
    /// It isn't bad in general. But in some contexts it can be desirable
    /// because it increases ownership hints in the code, and will guard against some changes
    /// in ownership.
    ///
    /// ### Example
    /// This example shows the basic adjustments necessary to satisfy the lint. Note how
    /// the matched expression is explicitly dereferenced with `*` and the `inner` variable
    /// is bound to a shared borrow via `ref inner`.
    ///
    /// ```rust,ignore
    /// // Bad
    /// let value = &Some(Box::new(23));
    /// match value {
    ///     Some(inner) => println!("{}", inner),
    ///     None => println!("none"),
    /// }
    ///
    /// // Good
    /// let value = &Some(Box::new(23));
    /// match *value {
    ///     Some(ref inner) => println!("{}", inner),
    ///     None => println!("none"),
    /// }
    /// ```
    ///
    /// The following example demonstrates one of the advantages of the more verbose style.
    /// Note how the second version uses `ref mut a` to explicitly declare `a` a shared mutable
    /// borrow, while `b` is simply taken by value. This ensures that the loop body cannot
    /// accidentally modify the wrong part of the structure.
    ///
    /// ```rust,ignore
    /// // Bad
    /// let mut values = vec![(2, 3), (3, 4)];
    /// for (a, b) in &mut values {
    ///     *a += *b;
    /// }
    ///
    /// // Good
    /// let mut values = vec![(2, 3), (3, 4)];
    /// for &mut (ref mut a, b) in &mut values {
    ///     *a += b;
    /// }
    /// ```
    #[clippy::version = "1.47.0"]
    pub PATTERN_TYPE_MISMATCH,
    restriction,
    "type of pattern does not match the expression type"
}

declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]);

impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
    fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
        if let StmtKind::Local(local) = stmt.kind {
            if in_external_macro(cx.sess(), local.pat.span) {
                return;
            }
            let deref_possible = match local.source {
                LocalSource::Normal => DerefPossible::Possible,
                _ => DerefPossible::Impossible,
            };
            apply_lint(cx, local.pat, deref_possible);
        }
    }

    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
        if let ExprKind::Match(_, arms, _) = expr.kind {
            for arm in arms {
                let pat = &arm.pat;
                if apply_lint(cx, pat, DerefPossible::Possible) {
                    break;
                }
            }
        }
        if let ExprKind::Let(Let { pat, .. }) = expr.kind {
            apply_lint(cx, pat, DerefPossible::Possible);
        }
    }

    fn check_fn(
        &mut self,
        cx: &LateContext<'tcx>,
        _: intravisit::FnKind<'tcx>,
        _: &'tcx FnDecl<'_>,
        body: &'tcx Body<'_>,
        _: Span,
        _: HirId,
    ) {
        for param in body.params {
            apply_lint(cx, param.pat, DerefPossible::Impossible);
        }
    }
}

#[derive(Debug, Clone, Copy)]
enum DerefPossible {
    Possible,
    Impossible,
}

fn apply_lint(cx: &LateContext<'_>, pat: &Pat<'_>, deref_possible: DerefPossible) -> bool {
    let maybe_mismatch = find_first_mismatch(cx, pat);
    if let Some((span, mutability, level)) = maybe_mismatch {
        span_lint_and_help(
            cx,
            PATTERN_TYPE_MISMATCH,
            span,
            "type of pattern does not match the expression type",
            None,
            &format!(
                "{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
                match (deref_possible, level) {
                    (DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
                    _ => "",
                },
                match mutability {
                    Mutability::Mut => "&mut _",
                    Mutability::Not => "&_",
                },
            ),
        );
        true
    } else {
        false
    }
}

#[derive(Debug, Copy, Clone)]
enum Level {
    Top,
    Lower,
}

fn find_first_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(Span, Mutability, Level)> {
    let mut result = None;
    pat.walk(|p| {
        if result.is_some() {
            return false;
        }
        if in_external_macro(cx.sess(), p.span) {
            return true;
        }
        let adjust_pat = match p.kind {
            PatKind::Or([p, ..]) => p,
            _ => p,
        };
        if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
            if let [first, ..] = **adjustments {
                if let ty::Ref(.., mutability) = *first.kind() {
                    let level = if p.hir_id == pat.hir_id {
                        Level::Top
                    } else {
                        Level::Lower
                    };
                    result = Some((p.span, mutability, level));
                }
            }
        }
        result.is_none()
    });
    result
}