|
1 | | -use clippy_utils::diagnostics::span_lint_and_sugg; |
2 | | -use clippy_utils::source::snippet_with_context; |
3 | | -use clippy_utils::ty::implements_trait; |
4 | | -use rustc_errors::Applicability; |
5 | | -use rustc_hir::{Expr, ExprKind, Pat, PatKind}; |
6 | | -use rustc_lint::{LateContext, LateLintPass, LintContext}; |
7 | | -use rustc_middle::lint::in_external_macro; |
8 | | -use rustc_middle::ty::Ty; |
9 | | -use rustc_session::declare_lint_pass; |
10 | 1 |
|
11 | | -declare_clippy_lint! { |
12 | | - /// ### What it does |
13 | | - /// Checks for pattern matchings that can be expressed using equality. |
14 | | - /// |
15 | | - /// ### Why is this bad? |
16 | | - /// |
17 | | - /// * It reads better and has less cognitive load because equality won't cause binding. |
18 | | - /// * It is a [Yoda condition](https://en.wikipedia.org/wiki/Yoda_conditions). Yoda conditions are widely |
19 | | - /// criticized for increasing the cognitive load of reading the code. |
20 | | - /// * Equality is a simple bool expression and can be merged with `&&` and `||` and |
21 | | - /// reuse if blocks |
22 | | - /// |
23 | | - /// ### Example |
24 | | - /// ```rust,ignore |
25 | | - /// if let Some(2) = x { |
26 | | - /// do_thing(); |
27 | | - /// } |
28 | | - /// ``` |
29 | | - /// Use instead: |
30 | | - /// ```rust,ignore |
31 | | - /// if x == Some(2) { |
32 | | - /// do_thing(); |
33 | | - /// } |
34 | | - /// ``` |
35 | | - #[clippy::version = "1.57.0"] |
36 | | - pub EQUATABLE_IF_LET, |
37 | | - nursery, |
38 | | - "using pattern matching instead of equality" |
39 | | -} |
40 | | - |
41 | | -declare_lint_pass!(PatternEquality => [EQUATABLE_IF_LET]); |
42 | | - |
43 | | -/// detects if pattern matches just one thing |
44 | | -fn unary_pattern(pat: &Pat<'_>) -> bool { |
45 | | - fn array_rec(pats: &[Pat<'_>]) -> bool { |
46 | | - pats.iter().all(unary_pattern) |
47 | | - } |
48 | | - match &pat.kind { |
49 | | - PatKind::Slice(_, _, _) |
50 | | - | PatKind::Range(_, _, _) |
51 | | - | PatKind::Binding(..) |
52 | | - | PatKind::Wild |
53 | | - | PatKind::Never |
54 | | - | PatKind::Or(_) |
55 | | - | PatKind::Err(_) => false, |
56 | | - PatKind::Struct(_, a, etc) => !etc && a.iter().all(|x| unary_pattern(x.pat)), |
57 | | - PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a), |
58 | | - PatKind::Ref(x, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x), |
59 | | - PatKind::Expr(_) => true, |
60 | | - } |
61 | | -} |
62 | | - |
63 | | -fn is_structural_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> bool { |
64 | | - if let Some(def_id) = cx.tcx.lang_items().eq_trait() { |
65 | | - implements_trait(cx, ty, def_id, &[other.into()]) |
66 | | - } else { |
67 | | - false |
68 | | - } |
69 | | -} |
70 | | - |
71 | | -impl<'tcx> LateLintPass<'tcx> for PatternEquality { |
72 | | - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
73 | | - if let ExprKind::Let(let_expr) = expr.kind |
74 | | - && unary_pattern(let_expr.pat) |
75 | | - && !in_external_macro(cx.sess(), expr.span) |
76 | | - { |
77 | | - let exp_ty = cx.typeck_results().expr_ty(let_expr.init); |
78 | | - let pat_ty = cx.typeck_results().pat_ty(let_expr.pat); |
79 | | - let mut applicability = Applicability::MachineApplicable; |
80 | | - |
81 | | - if is_structural_partial_eq(cx, exp_ty, pat_ty) { |
82 | | - let pat_str = match let_expr.pat.kind { |
83 | | - PatKind::Struct(..) => format!( |
84 | | - "({})", |
85 | | - snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0, |
86 | | - ), |
87 | | - _ => snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability) |
88 | | - .0 |
89 | | - .to_string(), |
90 | | - }; |
91 | | - span_lint_and_sugg( |
92 | | - cx, |
93 | | - EQUATABLE_IF_LET, |
94 | | - expr.span, |
95 | | - "this pattern matching can be expressed using equality", |
96 | | - "try", |
97 | | - format!( |
98 | | - "{} == {pat_str}", |
99 | | - snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0, |
100 | | - ), |
101 | | - applicability, |
102 | | - ); |
103 | | - } else { |
104 | | - span_lint_and_sugg( |
105 | | - cx, |
106 | | - EQUATABLE_IF_LET, |
107 | | - expr.span, |
108 | | - "this pattern matching can be expressed using `matches!`", |
109 | | - "try", |
110 | | - format!( |
111 | | - "matches!({}, {})", |
112 | | - snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0, |
113 | | - snippet_with_context(cx, let_expr.pat.span, expr.span.ctxt(), "..", &mut applicability).0, |
114 | | - ), |
115 | | - applicability, |
116 | | - ); |
117 | | - } |
118 | | - } |
119 | | - } |
120 | | -} |
0 commit comments