]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches/needless_match.rs
rename lint to `needless_match`
[rust.git] / clippy_lints / src / matches / needless_match.rs
1 use super::NEEDLESS_MATCH;
2 use clippy_utils::diagnostics::span_lint_and_sugg;
3 use clippy_utils::source::snippet_with_applicability;
4 use clippy_utils::ty::is_type_diagnostic_item;
5 use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt};
6 use rustc_errors::Applicability;
7 use rustc_hir::LangItem::OptionNone;
8 use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath};
9 use rustc_lint::LateContext;
10 use rustc_span::sym;
11
12 pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
13     // This is for avoiding collision with `match_single_binding`.
14     if arms.len() < 2 {
15         return;
16     }
17
18     for arm in arms {
19         if let PatKind::Wild = arm.pat.kind {
20             let ret_expr = strip_return(arm.body);
21             if !eq_expr_value(cx, ex, ret_expr) {
22                 return;
23             }
24         } else if !pat_same_as_expr(arm.pat, arm.body) {
25             return;
26         }
27     }
28
29     if let Some(match_expr) = get_parent_expr(cx, ex) {
30         let mut applicability = Applicability::MachineApplicable;
31         span_lint_and_sugg(
32             cx,
33             NEEDLESS_MATCH,
34             match_expr.span,
35             "this match expression is unnecessary",
36             "replace it with",
37             snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
38             applicability,
39         );
40     }
41 }
42
43 /// Check for nop `if let` expression that assembled as unnecessary match
44 ///
45 /// ```rust,ignore
46 /// if let Some(a) = option {
47 ///     Some(a)
48 /// } else {
49 ///     None
50 /// }
51 /// ```
52 /// OR
53 /// ```rust,ignore
54 /// if let SomeEnum::A = some_enum {
55 ///     SomeEnum::A
56 /// } else if let SomeEnum::B = some_enum {
57 ///     SomeEnum::B
58 /// } else {
59 ///     some_enum
60 /// }
61 /// ```
62 pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
63     if_chain! {
64         if let Some(ref if_let) = higher::IfLet::hir(cx, ex);
65         if !is_else_clause(cx.tcx, ex);
66         if check_if_let(cx, if_let);
67         then {
68             let mut applicability = Applicability::MachineApplicable;
69             span_lint_and_sugg(
70                 cx,
71                 NEEDLESS_MATCH,
72                 ex.span,
73                 "this if-let expression is unnecessary",
74                 "replace it with",
75                 snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
76                 applicability,
77             );
78         }
79     }
80 }
81
82 fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
83     if let Some(if_else) = if_let.if_else {
84         if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
85             return false;
86         }
87
88         // Recurrsively check for each `else if let` phrase,
89         if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
90             return check_if_let(cx, nested_if_let);
91         }
92
93         if matches!(if_else.kind, ExprKind::Block(..)) {
94             let else_expr = peel_blocks_with_stmt(if_else);
95             let ret = strip_return(else_expr);
96             let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
97             if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
98                 if let ExprKind::Path(ref qpath) = ret.kind {
99                     return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
100                 }
101             } else {
102                 return eq_expr_value(cx, if_let.let_expr, ret);
103             }
104             return true;
105         }
106     }
107     false
108 }
109
110 fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
111     if let ExprKind::Ret(Some(ret)) = expr.kind {
112         ret
113     } else {
114         expr
115     }
116 }
117
118 fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
119     let expr = strip_return(expr);
120     match (&pat.kind, &expr.kind) {
121         (
122             PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
123             ExprKind::Call(call_expr, [first_param, ..]),
124         ) => {
125             if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
126                 if has_identical_segments(path.segments, call_path.segments)
127                     && has_same_non_ref_symbol(first_pat, first_param)
128                 {
129                     return true;
130                 }
131             }
132         },
133         (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
134             return has_identical_segments(p_path.segments, e_path.segments);
135         },
136         (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
137             if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
138                 return pat_spanned.node == expr_spanned.node;
139             }
140         },
141         _ => {},
142     }
143
144     false
145 }
146
147 fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
148     if left_segs.len() != right_segs.len() {
149         return false;
150     }
151     for i in 0..left_segs.len() {
152         if left_segs[i].ident.name != right_segs[i].ident.name {
153             return false;
154         }
155     }
156     true
157 }
158
159 fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
160     if_chain! {
161         if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
162         if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
163         if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
164         then {
165             return pat_ident.name == first_seg.ident.name;
166         }
167     }
168
169     false
170 }