]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches/needless_match.rs
Auto merge of #9446 - mikerite:fix-9431-2, r=giraffate
[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, same_type_and_consts};
5 use clippy_utils::{
6     eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_lang_ctor, over,
7     peel_blocks_with_stmt,
8 };
9 use rustc_errors::Applicability;
10 use rustc_hir::LangItem::OptionNone;
11 use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, FnRetTy, Guard, Node, Pat, PatKind, Path, QPath};
12 use rustc_lint::LateContext;
13 use rustc_span::sym;
14 use rustc_typeck::hir_ty_to_ty;
15
16 pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
17     if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, ex, arms) {
18         let mut applicability = Applicability::MachineApplicable;
19         span_lint_and_sugg(
20             cx,
21             NEEDLESS_MATCH,
22             expr.span,
23             "this match expression is unnecessary",
24             "replace it with",
25             snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
26             applicability,
27         );
28     }
29 }
30
31 /// Check for nop `if let` expression that assembled as unnecessary match
32 ///
33 /// ```rust,ignore
34 /// if let Some(a) = option {
35 ///     Some(a)
36 /// } else {
37 ///     None
38 /// }
39 /// ```
40 /// OR
41 /// ```rust,ignore
42 /// if let SomeEnum::A = some_enum {
43 ///     SomeEnum::A
44 /// } else if let SomeEnum::B = some_enum {
45 ///     SomeEnum::B
46 /// } else {
47 ///     some_enum
48 /// }
49 /// ```
50 pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let: &higher::IfLet<'tcx>) {
51     if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let_inner(cx, if_let) {
52         let mut applicability = Applicability::MachineApplicable;
53         span_lint_and_sugg(
54             cx,
55             NEEDLESS_MATCH,
56             ex.span,
57             "this if-let expression is unnecessary",
58             "replace it with",
59             snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
60             applicability,
61         );
62     }
63 }
64
65 fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
66     for arm in arms {
67         let arm_expr = peel_blocks_with_stmt(arm.body);
68
69         if let Some(guard_expr) = &arm.guard {
70             match guard_expr {
71                 // gives up if `pat if expr` can have side effects
72                 Guard::If(if_cond) => {
73                     if if_cond.can_have_side_effects() {
74                         return false;
75                     }
76                 },
77                 // gives up `pat if let ...` arm
78                 Guard::IfLet(_) => {
79                     return false;
80                 },
81             };
82         }
83
84         if let PatKind::Wild = arm.pat.kind {
85             if !eq_expr_value(cx, match_expr, strip_return(arm_expr)) {
86                 return false;
87             }
88         } else if !pat_same_as_expr(arm.pat, arm_expr) {
89             return false;
90         }
91     }
92
93     true
94 }
95
96 fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
97     if let Some(if_else) = if_let.if_else {
98         if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
99             return false;
100         }
101
102         // Recursively check for each `else if let` phrase,
103         if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
104             return check_if_let_inner(cx, nested_if_let);
105         }
106
107         if matches!(if_else.kind, ExprKind::Block(..)) {
108             let else_expr = peel_blocks_with_stmt(if_else);
109             if matches!(else_expr.kind, ExprKind::Block(..)) {
110                 return false;
111             }
112             let ret = strip_return(else_expr);
113             let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
114             if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
115                 if let ExprKind::Path(ref qpath) = ret.kind {
116                     return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
117                 }
118                 return false;
119             }
120             return eq_expr_value(cx, if_let.let_expr, ret);
121         }
122     }
123
124     false
125 }
126
127 /// Strip `return` keyword if the expression type is `ExprKind::Ret`.
128 fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
129     if let ExprKind::Ret(Some(ret)) = expr.kind {
130         ret
131     } else {
132         expr
133     }
134 }
135
136 /// Manually check for coercion casting by checking if the type of the match operand or let expr
137 /// differs with the assigned local variable or the function return type.
138 fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
139     if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) {
140         match p_node {
141             // Compare match_expr ty with local in `let local = match match_expr {..}`
142             Node::Local(local) => {
143                 let results = cx.typeck_results();
144                 return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
145             },
146             // compare match_expr ty with RetTy in `fn foo() -> RetTy`
147             Node::Item(..) => {
148                 if let Some(fn_decl) = p_node.fn_decl() {
149                     if let FnRetTy::Return(ret_ty) = fn_decl.output {
150                         return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr));
151                     }
152                 }
153             },
154             // check the parent expr for this whole block `{ match match_expr {..} }`
155             Node::Block(block) => {
156                 if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
157                     return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
158                 }
159             },
160             // recursively call on `if xxx {..}` etc.
161             Node::Expr(p_expr) => {
162                 return expr_ty_matches_p_ty(cx, expr, p_expr);
163             },
164             _ => {},
165         }
166     }
167     false
168 }
169
170 fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
171     let expr = strip_return(expr);
172     match (&pat.kind, &expr.kind) {
173         // Example: `Some(val) => Some(val)`
174         (PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
175             if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
176                 return over(path.segments, call_path.segments, |pat_seg, call_seg| {
177                     pat_seg.ident.name == call_seg.ident.name
178                 }) && same_non_ref_symbols(tuple_params, call_params);
179             }
180         },
181         // Example: `val => val`
182         (
183             PatKind::Binding(annot, _, pat_ident, _),
184             ExprKind::Path(QPath::Resolved(
185                 _,
186                 Path {
187                     segments: [first_seg, ..],
188                     ..
189                 },
190             )),
191         ) => {
192             return !matches!(annot, BindingAnnotation(ByRef::Yes, _)) && pat_ident.name == first_seg.ident.name;
193         },
194         // Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
195         (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
196             return over(p_path.segments, e_path.segments, |p_seg, e_seg| {
197                 p_seg.ident.name == e_seg.ident.name
198             });
199         },
200         // Example: `5 => 5`
201         (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
202             if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
203                 return pat_spanned.node == expr_spanned.node;
204             }
205         },
206         _ => {},
207     }
208
209     false
210 }
211
212 fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
213     if pats.len() != exprs.len() {
214         return false;
215     }
216
217     for i in 0..pats.len() {
218         if !pat_same_as_expr(&pats[i], &exprs[i]) {
219             return false;
220         }
221     }
222
223     true
224 }