summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/equatable_if_let.rs
blob: fdfb821ac7895becb9fa97f56463dad42f0b7632 (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
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::implements_trait;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
    /// ### What it does
    /// Checks for pattern matchings that can be expressed using equality.
    ///
    /// ### Why is this bad?
    ///
    /// * It reads better and has less cognitive load because equality won't cause binding.
    /// * It is a [Yoda condition](https://en.wikipedia.org/wiki/Yoda_conditions). Yoda conditions are widely
    /// criticized for increasing the cognitive load of reading the code.
    /// * Equality is a simple bool expression and can be merged with `&&` and `||` and
    /// reuse if blocks
    ///
    /// ### Example
    /// ```rust,ignore
    /// if let Some(2) = x {
    ///     do_thing();
    /// }
    /// ```
    /// Use instead:
    /// ```rust,ignore
    /// if x == Some(2) {
    ///     do_thing();
    /// }
    /// ```
    #[clippy::version = "1.57.0"]
    pub EQUATABLE_IF_LET,
    nursery,
    "using pattern matching instead of equality"
}

declare_lint_pass!(PatternEquality => [EQUATABLE_IF_LET]);

/// detects if pattern matches just one thing
fn unary_pattern(pat: &Pat<'_>) -> bool {
    fn array_rec(pats: &[Pat<'_>]) -> bool {
        pats.iter().all(unary_pattern)
    }
    match &pat.kind {
        PatKind::Slice(_, _, _) | PatKind::Range(_, _, _) | PatKind::Binding(..) | PatKind::Wild | PatKind::Or(_) => {
            false
        },
        PatKind::Struct(_, a, etc) => !etc && a.iter().all(|x| unary_pattern(x.pat)),
        PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => !etc.is_some() && array_rec(a),
        PatKind::Ref(x, _) | PatKind::Box(x) => unary_pattern(x),
        PatKind::Path(_) | PatKind::Lit(_) => true,
    }
}

fn is_structural_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> bool {
    if let Some(def_id) = cx.tcx.lang_items().eq_trait() {
        implements_trait(cx, ty, def_id, &[other.into()])
    } else {
        false
    }
}

impl<'tcx> LateLintPass<'tcx> for PatternEquality {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
        if_chain! {
            if !in_external_macro(cx.sess(), expr.span);
            if let ExprKind::Let(let_expr) = expr.kind;
            if unary_pattern(let_expr.pat);
            let exp_ty = cx.typeck_results().expr_ty(let_expr.init);
            let pat_ty = cx.typeck_results().pat_ty(let_expr.pat);
            if is_structural_partial_eq(cx, exp_ty, pat_ty);
            then {

                let mut applicability = Applicability::MachineApplicable;
                let pat_str = match let_expr.pat.kind {
                    PatKind::Struct(..) => format!(
                        "({})",
                        snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0,
                    ),
                    _ => snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0.to_string(),
                };
                span_lint_and_sugg(
                    cx,
                    EQUATABLE_IF_LET,
                    expr.span,
                    "this pattern matching can be expressed using equality",
                    "try",
                    format!(
                        "{} == {}",
                        snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0,
                        pat_str,
                    ),
                    applicability,
                );
            }
        }
    }
}