Skip to content

Commit 6bfc112

Browse files
committed
add nop if-let expression check.
re-design test cases as some of them are not worth the effort to check.
1 parent db3fcf8 commit 6bfc112

File tree

4 files changed

+224
-171
lines changed

4 files changed

+224
-171
lines changed

clippy_lints/src/matches/nop_match.rs

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,13 @@
1-
#![allow(unused_variables)]
21
use super::NOP_MATCH;
32
use clippy_utils::diagnostics::span_lint_and_sugg;
43
use clippy_utils::source::snippet_with_applicability;
5-
use clippy_utils::{eq_expr_value, get_parent_expr};
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};
66
use rustc_errors::Applicability;
7-
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, PathSegment, QPath};
7+
use rustc_hir::LangItem::OptionNone;
8+
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath};
89
use rustc_lint::LateContext;
9-
10-
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
11-
if false {
12-
span_lint_and_sugg(
13-
cx,
14-
NOP_MATCH,
15-
ex.span,
16-
"this if-let expression is unnecessary",
17-
"replace it with",
18-
"".to_string(),
19-
Applicability::MachineApplicable,
20-
);
21-
}
22-
}
10+
use rustc_span::sym;
2311

2412
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
2513
// This is for avoiding collision with `match_single_binding`.
@@ -52,6 +40,70 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
5240
}
5341
}
5442

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+
NOP_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(else_block) = 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+
let else_expr = peel_blocks_with_stmt(else_block);
89+
// Recurrsively check for each `else if let` phrase,
90+
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, else_expr) {
91+
return check_if_let(cx, nested_if_let);
92+
}
93+
let ret = strip_return(else_expr);
94+
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
95+
if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
96+
if let ExprKind::Path(ref qpath) = ret.kind {
97+
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
98+
}
99+
} else {
100+
return eq_expr_value(cx, if_let.let_expr, ret);
101+
}
102+
return true;
103+
}
104+
false
105+
}
106+
55107
fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
56108
if let ExprKind::Ret(Some(ret)) = expr.kind {
57109
ret
@@ -68,15 +120,15 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
68120
ExprKind::Call(call_expr, [first_param, ..]),
69121
) => {
70122
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
71-
if is_identical_segments(path.segments, call_path.segments)
123+
if has_identical_segments(path.segments, call_path.segments)
72124
&& has_same_non_ref_symbol(first_pat, first_param)
73125
{
74126
return true;
75127
}
76128
}
77129
},
78130
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
79-
return is_identical_segments(p_path.segments, e_path.segments);
131+
return has_identical_segments(p_path.segments, e_path.segments);
80132
},
81133
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
82134
if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
@@ -89,7 +141,7 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
89141
false
90142
}
91143

92-
fn is_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
144+
fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
93145
if left_segs.len() != right_segs.len() {
94146
return false;
95147
}
@@ -105,8 +157,7 @@ fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
105157
if_chain! {
106158
if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
107159
if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
108-
if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind;
109-
if let Some(first_seg) = path.segments.first();
160+
if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
110161
then {
111162
return pat_ident.name == first_seg.ident.name;
112163
}

tests/ui/nop_match.fixed

Lines changed: 40 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,67 @@
11
// run-rustfix
22
#![warn(clippy::nop_match)]
33
#![allow(clippy::manual_map)]
4-
#![allow(clippy::question_mark)]
54
#![allow(dead_code)]
65

7-
fn func_ret_err<T>(err: T) -> Result<(), T> {
8-
Err(err)
9-
}
10-
11-
enum SampleEnum {
6+
enum Choice {
127
A,
138
B,
149
C,
10+
D,
1511
}
1612

17-
fn useless_prim_type_match(x: i32) -> i32 {
18-
x
19-
}
20-
21-
fn useless_custom_type_match(se: SampleEnum) -> SampleEnum {
22-
se
23-
}
24-
25-
// Don't trigger
26-
fn mingled_custom_type(se: SampleEnum) -> SampleEnum {
27-
match se {
28-
SampleEnum::A => SampleEnum::B,
29-
SampleEnum::B => SampleEnum::C,
30-
SampleEnum::C => SampleEnum::A,
31-
}
32-
}
33-
34-
fn option_match() -> Option<i32> {
35-
Some(1)
13+
fn useless_match(x: i32) {
14+
let _: i32 = x;
3615
}
3716

38-
fn result_match() -> Result<i32, i32> {
39-
Ok(1)
17+
fn custom_type_match(se: Choice) {
18+
let _: Choice = se;
19+
// Don't trigger
20+
let _: Choice = match se {
21+
Choice::A => Choice::A,
22+
Choice::B => Choice::B,
23+
_ => Choice::C,
24+
};
25+
// Mingled, don't trigger
26+
let _: Choice = match se {
27+
Choice::A => Choice::B,
28+
Choice::B => Choice::C,
29+
Choice::C => Choice::D,
30+
Choice::D => Choice::A,
31+
};
4032
}
4133

42-
fn result_match_func_call() {
43-
let _ = func_ret_err(0_i32);
34+
fn option_match(x: Option<i32>) {
35+
let _: Option<i32> = x;
36+
// Don't trigger, this is the case for manual_map_option
37+
let _: Option<i32> = match x {
38+
Some(a) => Some(-a),
39+
None => None,
40+
};
4441
}
4542

46-
fn option_check() -> Option<i32> {
47-
if let Some(a) = Some(1) { Some(a) } else { None }
48-
}
49-
50-
fn option_check_no_else() -> Option<i32> {
51-
if let Some(a) = Some(1) {
52-
return Some(a);
53-
}
54-
None
55-
}
56-
57-
fn result_check_no_else() -> Result<(), i32> {
58-
if let Err(e) = func_ret_err(0_i32) {
59-
return Err(e);
60-
}
61-
Ok(())
43+
fn func_ret_err<T>(err: T) -> Result<i32, T> {
44+
Err(err)
6245
}
6346

64-
fn result_check_a() -> Result<(), i32> {
65-
if let Err(e) = func_ret_err(0_i32) {
66-
Err(e)
67-
} else {
68-
Ok(())
69-
}
47+
fn result_match() {
48+
let _: Result<i32, i32> = Ok(1);
49+
let _: Result<i32, i32> = func_ret_err(0_i32);
7050
}
7151

72-
// Don't trigger
73-
fn result_check_b() -> Result<(), i32> {
74-
if let Err(e) = Ok(1) { Err(e) } else { Ok(()) }
52+
fn if_let_option() -> Option<i32> {
53+
Some(1)
7554
}
7655

77-
fn result_check_c() -> Result<(), i32> {
78-
let example = Ok(());
79-
if let Err(e) = example { Err(e) } else { example }
56+
fn if_let_result(x: Result<(), i32>) {
57+
let _: Result<(), i32> = x;
58+
let _: Result<(), i32> = x;
59+
// Input type mismatch, don't trigger
60+
let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
8061
}
8162

82-
// Don't trigger
83-
fn result_check_d() -> Result<(), i32> {
84-
let example = Ok(1);
85-
if let Err(e) = example { Err(e) } else { Ok(()) }
63+
fn custom_enum_a(x: Choice) -> Choice {
64+
x
8665
}
8766

8867
fn main() {}

0 commit comments

Comments
 (0)