]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches/needless_match.rs
76131d307d777e10e262cba3971b4b890a6c8c49
[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, UnOp};
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 /// Strip `return` keyword if the expression type is `ExprKind::Ret`.
111 fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
112     if let ExprKind::Ret(Some(ret)) = expr.kind {
113         ret
114     } else {
115         expr
116     }
117 }
118
119 fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
120     let expr = strip_return(expr);
121     match (&pat.kind, &expr.kind) {
122         // Example: `Some(val) => Some(val)`
123         (
124             PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
125             ExprKind::Call(call_expr, [first_param, ..]),
126         ) => {
127             if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
128                 if has_identical_segments(path.segments, call_path.segments)
129                     && has_same_non_ref_symbol(first_pat, first_param)
130                 {
131                     return true;
132                 }
133             }
134         },
135         // Example: `val => val`, or `ref val => *val`
136         (PatKind::Binding(annot, _, pat_ident, _), _) => {
137             let new_expr = if let (
138                 BindingAnnotation::Ref | BindingAnnotation::RefMut,
139                 ExprKind::Unary(UnOp::Deref, operand_expr),
140             ) = (annot, &expr.kind)
141             {
142                 operand_expr
143             } else {
144                 expr
145             };
146
147             if let ExprKind::Path(QPath::Resolved(
148                 _,
149                 Path {
150                     segments: [first_seg, ..],
151                     ..
152                 },
153             )) = new_expr.kind
154             {
155                 return pat_ident.name == first_seg.ident.name;
156             }
157         },
158         // Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
159         (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
160             return has_identical_segments(p_path.segments, e_path.segments);
161         },
162         // Example: `5 => 5`
163         (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
164             if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
165                 return pat_spanned.node == expr_spanned.node;
166             }
167         },
168         _ => {},
169     }
170
171     false
172 }
173
174 fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
175     if left_segs.len() != right_segs.len() {
176         return false;
177     }
178     for i in 0..left_segs.len() {
179         if left_segs[i].ident.name != right_segs[i].ident.name {
180             return false;
181         }
182     }
183     true
184 }
185
186 fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
187     if_chain! {
188         if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
189         if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
190         if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
191         then {
192             return pat_ident.name == first_seg.ident.name;
193         }
194     }
195
196     false
197 }