Skip to content

Commit 4b12862

Browse files
committed
fix #8551, add test cases, and some code improvement
1 parent 2909b33 commit 4b12862

File tree

5 files changed

+185
-71
lines changed

5 files changed

+185
-71
lines changed

clippy_lints/src/matches/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
667667
overlapping_arms::check(cx, ex, arms);
668668
match_wild_enum::check(cx, ex, arms);
669669
match_as_ref::check(cx, ex, arms, expr);
670-
needless_match::check_match(cx, ex, arms);
670+
needless_match::check_match(cx, ex, arms, expr);
671671

672672
if self.infallible_destructuring_match_linted {
673673
self.infallible_destructuring_match_linted = false;

clippy_lints/src/matches/needless_match.rs

+73-35
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,25 @@
11
use super::NEEDLESS_MATCH;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
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};
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,
7+
peel_blocks_with_stmt,
8+
};
69
use rustc_errors::Applicability;
710
use rustc_hir::LangItem::OptionNone;
8-
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath};
11+
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, PathSegment, QPath};
912
use rustc_lint::LateContext;
1013
use rustc_span::sym;
14+
use rustc_typeck::hir_ty_to_ty;
1115

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, peel_blocks_with_stmt(arm.body)) {
25-
return;
26-
}
27-
}
28-
29-
if let Some(match_expr) = get_parent_expr(cx, ex) {
16+
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
17+
if arms.len() > 1 && !is_coercion_casting(cx, ex, expr) && check_all_arms(cx, ex, arms) {
3018
let mut applicability = Applicability::MachineApplicable;
3119
span_lint_and_sugg(
3220
cx,
3321
NEEDLESS_MATCH,
34-
match_expr.span,
22+
expr.span,
3523
"this match expression is unnecessary",
3624
"replace it with",
3725
snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
@@ -60,11 +48,8 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
6048
/// }
6149
/// ```
6250
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 {
51+
if let Some(ref if_let) = higher::IfLet::hir(cx, ex) {
52+
if !is_else_clause(cx.tcx, ex) && !is_coercion_casting(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) {
6853
let mut applicability = Applicability::MachineApplicable;
6954
span_lint_and_sugg(
7055
cx,
@@ -79,6 +64,19 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
7964
}
8065
}
8166

67+
fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
68+
for arm in arms {
69+
let arm_expr = peel_blocks_with_stmt(arm.body);
70+
if let PatKind::Wild = arm.pat.kind {
71+
return eq_expr_value(cx, match_expr, strip_return(arm_expr));
72+
} else if !pat_same_as_expr(arm.pat, arm_expr) {
73+
return false;
74+
}
75+
}
76+
77+
true
78+
}
79+
8280
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
8381
if let Some(if_else) = if_let.if_else {
8482
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
@@ -101,12 +99,12 @@ fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
10199
if let ExprKind::Path(ref qpath) = ret.kind {
102100
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
103101
}
104-
} else {
105-
return eq_expr_value(cx, if_let.let_expr, ret);
102+
return true;
106103
}
107-
return true;
104+
return eq_expr_value(cx, if_let.let_expr, ret);
108105
}
109106
}
107+
110108
false
111109
}
112110

@@ -119,14 +117,52 @@ fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
119117
}
120118
}
121119

120+
/// Manually check for coercion casting by checking if the type of the match operand or let expr
121+
/// differs with the assigned local variable or the funtion return type.
122+
fn is_coercion_casting(cx: &LateContext<'_>, match_expr: &Expr<'_>, expr: &Expr<'_>) -> bool {
123+
if let Some(p_node) = get_parent_node(cx.tcx, expr.hir_id) {
124+
match p_node {
125+
// Compare match_expr ty with local in `let local = match match_expr {..}`
126+
Node::Local(local) => {
127+
let results = cx.typeck_results();
128+
return !same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(match_expr));
129+
},
130+
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
131+
Node::Item(..) => {
132+
if let Some(fn_decl) = p_node.fn_decl() {
133+
if let FnRetTy::Return(ret_ty) = fn_decl.output {
134+
return !same_type_and_consts(
135+
hir_ty_to_ty(cx.tcx, ret_ty),
136+
cx.typeck_results().expr_ty(match_expr),
137+
);
138+
}
139+
}
140+
},
141+
// check the parent expr for this whole block `{ match match_expr {..} }`
142+
Node::Block(block) => {
143+
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
144+
return is_coercion_casting(cx, match_expr, block_parent_expr);
145+
}
146+
},
147+
// recursively call on `if xxx {..}` etc.
148+
Node::Expr(p_expr) => {
149+
return is_coercion_casting(cx, match_expr, p_expr);
150+
},
151+
_ => {},
152+
}
153+
}
154+
155+
false
156+
}
157+
122158
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
123159
let expr = strip_return(expr);
124160
match (&pat.kind, &expr.kind) {
125161
// Example: `Some(val) => Some(val)`
126162
(PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
127163
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
128-
return has_identical_segments(path.segments, call_path.segments)
129-
&& has_same_non_ref_symbols(tuple_params, call_params);
164+
return same_segments(path.segments, call_path.segments)
165+
&& same_non_ref_symbols(tuple_params, call_params);
130166
}
131167
},
132168
// Example: `val => val`
@@ -145,7 +181,7 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
145181
},
146182
// Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
147183
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
148-
return has_identical_segments(p_path.segments, e_path.segments);
184+
return same_segments(p_path.segments, e_path.segments);
149185
},
150186
// Example: `5 => 5`
151187
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
@@ -159,19 +195,21 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
159195
false
160196
}
161197

162-
fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
198+
fn same_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
163199
if left_segs.len() != right_segs.len() {
164200
return false;
165201
}
202+
166203
for i in 0..left_segs.len() {
167204
if left_segs[i].ident.name != right_segs[i].ident.name {
168205
return false;
169206
}
170207
}
208+
171209
true
172210
}
173211

174-
fn has_same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
212+
fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
175213
if pats.len() != exprs.len() {
176214
return false;
177215
}

tests/ui/needless_match.fixed

+52-14
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,26 @@ fn result_match() {
6060
};
6161
}
6262

63-
fn if_let_option() -> Option<i32> {
64-
Some(1)
63+
fn if_let_option() {
64+
let _ = Some(1);
65+
66+
fn do_something() {}
67+
68+
// Don't trigger
69+
let _ = if let Some(a) = Some(1) {
70+
Some(a)
71+
} else {
72+
do_something();
73+
None
74+
};
75+
76+
// Don't trigger
77+
let _ = if let Some(a) = Some(1) {
78+
do_something();
79+
Some(a)
80+
} else {
81+
None
82+
};
6583
}
6684

6785
fn if_let_result() {
@@ -122,25 +140,45 @@ mod issue8542 {
122140
_ => ce,
123141
};
124142
}
143+
}
125144

126-
fn if_let_test() {
127-
fn do_something() {}
145+
/// Lint triggered when type coercions happen.
146+
/// Do NOT trigger on any of these.
147+
mod issue8551 {
148+
trait Trait {}
149+
struct Struct;
150+
impl Trait for Struct {}
151+
152+
fn optmap(s: Option<&Struct>) -> Option<&dyn Trait> {
153+
match s {
154+
Some(s) => Some(s),
155+
None => None,
156+
}
157+
}
128158

129-
// Don't trigger
130-
let _ = if let Some(a) = Some(1) {
131-
Some(a)
132-
} else {
133-
do_something();
134-
None
159+
fn lint_tests() {
160+
let option: Option<&Struct> = None;
161+
let _: Option<&dyn Trait> = match option {
162+
Some(s) => Some(s),
163+
None => None,
135164
};
136165

137-
// Don't trigger
138-
let _ = if let Some(a) = Some(1) {
139-
do_something();
140-
Some(a)
166+
let _: Option<&dyn Trait> = if true {
167+
match option {
168+
Some(s) => Some(s),
169+
None => None,
170+
}
141171
} else {
142172
None
143173
};
174+
175+
let result: Result<&Struct, i32> = Err(0);
176+
let _: Result<&dyn Trait, i32> = match result {
177+
Ok(s) => Ok(s),
178+
Err(e) => Err(e),
179+
};
180+
181+
let _: Option<&dyn Trait> = if let Some(s) = option { Some(s) } else { None };
144182
}
145183
}
146184

tests/ui/needless_match.rs

+52-14
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,26 @@ fn result_match() {
8383
};
8484
}
8585

86-
fn if_let_option() -> Option<i32> {
87-
if let Some(a) = Some(1) { Some(a) } else { None }
86+
fn if_let_option() {
87+
let _ = if let Some(a) = Some(1) { Some(a) } else { None };
88+
89+
fn do_something() {}
90+
91+
// Don't trigger
92+
let _ = if let Some(a) = Some(1) {
93+
Some(a)
94+
} else {
95+
do_something();
96+
None
97+
};
98+
99+
// Don't trigger
100+
let _ = if let Some(a) = Some(1) {
101+
do_something();
102+
Some(a)
103+
} else {
104+
None
105+
};
88106
}
89107

90108
fn if_let_result() {
@@ -159,25 +177,45 @@ mod issue8542 {
159177
_ => ce,
160178
};
161179
}
180+
}
162181

163-
fn if_let_test() {
164-
fn do_something() {}
182+
/// Lint triggered when type coercions happen.
183+
/// Do NOT trigger on any of these.
184+
mod issue8551 {
185+
trait Trait {}
186+
struct Struct;
187+
impl Trait for Struct {}
188+
189+
fn optmap(s: Option<&Struct>) -> Option<&dyn Trait> {
190+
match s {
191+
Some(s) => Some(s),
192+
None => None,
193+
}
194+
}
165195

166-
// Don't trigger
167-
let _ = if let Some(a) = Some(1) {
168-
Some(a)
169-
} else {
170-
do_something();
171-
None
196+
fn lint_tests() {
197+
let option: Option<&Struct> = None;
198+
let _: Option<&dyn Trait> = match option {
199+
Some(s) => Some(s),
200+
None => None,
172201
};
173202

174-
// Don't trigger
175-
let _ = if let Some(a) = Some(1) {
176-
do_something();
177-
Some(a)
203+
let _: Option<&dyn Trait> = if true {
204+
match option {
205+
Some(s) => Some(s),
206+
None => None,
207+
}
178208
} else {
179209
None
180210
};
211+
212+
let result: Result<&Struct, i32> = Err(0);
213+
let _: Result<&dyn Trait, i32> = match result {
214+
Ok(s) => Ok(s),
215+
Err(e) => Err(e),
216+
};
217+
218+
let _: Option<&dyn Trait> = if let Some(s) = option { Some(s) } else { None };
181219
}
182220
}
183221

0 commit comments

Comments
 (0)