]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/matches/needless_match.rs
New upstream version 1.61.0+dfsg1
[rustc.git] / src / tools / clippy / 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 }