Skip to content

Commit 05c38db

Browse files
committed
first finished stacked_if_match
1 parent 09a64fa commit 05c38db

File tree

2 files changed

+182
-11
lines changed

2 files changed

+182
-11
lines changed

clippy_lints/src/stacked_if_match.rs

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1+
use clippy_utils::source::snippet;
2+
use rustc_middle::lint::in_external_macro;
13
use rustc_hir::*;
2-
use rustc_lint::{LateContext, LateLintPass};
4+
use rustc_lint::{LateContext, LateLintPass, LintContext};
35
use rustc_session::declare_lint_pass;
6+
use rustc_errors::Applicability;
7+
use clippy_utils::visitors::{for_each_expr, Descend};
8+
use clippy_utils::diagnostics::span_lint_and_sugg;
9+
use std::ops::ControlFlow;
410

511
declare_clippy_lint! {
612
/// ### What it does
7-
/// Checks for stacked `if` and `match`, e.g., `if if`.
13+
/// Checks for `if if` and `match match`.
814
///
915
/// ### Why is this bad?
10-
/// Stacked `if`'s and `match`'s are hard to read.
16+
/// `if if` and `match match` are hard to read.
1117
///
1218
/// ### Example
1319
/// ```no_run
@@ -17,23 +23,19 @@ declare_clippy_lint! {
1723
/// e == f
1824
/// } {
1925
/// println!("true");
20-
/// } else {
21-
/// println!("false");
2226
/// }
2327
/// ```
2428
///
2529
/// Use instead:
2630
/// ```no_run
27-
/// let cond = if a == b {
31+
/// let result = if a == b {
2832
/// c == d
2933
/// } else {
3034
/// e == f
3135
/// };
3236
///
33-
/// if cond {
37+
/// if result {
3438
/// println!("true");
35-
/// } else {
36-
/// println!("false");
3739
/// }
3840
/// ```
3941
#[clippy::version = "1.82.0"]
@@ -46,6 +48,48 @@ declare_lint_pass!(StackedIfMatch => [STACKED_IF_MATCH]);
4648

4749
impl<'tcx> LateLintPass<'tcx> for StackedIfMatch {
4850
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
51+
if expr.span.from_expansion() || in_external_macro(cx.sess(), expr.span) {
52+
return;
53+
}
54+
55+
let Some((cond, keyword)) = (match expr.kind {
56+
ExprKind::If(if_expr, _, _) => Some((if_expr, "if")),
57+
ExprKind::Match(match_expr, _, MatchSource::Normal) => Some((match_expr, "match")),
58+
_ => None,
59+
}) else {
60+
return;
61+
};
62+
63+
let cond_snippet = snippet(cx, cond.span, "");
64+
if !cond_snippet.starts_with("if") && !cond_snippet.starts_with("match") {
65+
return;
66+
}
67+
68+
for_each_expr(cx, cond, |sub_expr| {
69+
if matches!(sub_expr.kind, ExprKind::DropTemps(..)) {
70+
return ControlFlow::Continue(Descend::Yes);
71+
}
72+
73+
if !sub_expr.span.eq_ctxt(expr.span) || sub_expr.span.lo() != cond.span.lo() {
74+
return ControlFlow::Continue(Descend::No);
75+
}
4976

77+
if (keyword == "if" && matches!(sub_expr.kind, ExprKind::If(..)))
78+
|| (keyword == "match" && matches!(sub_expr.kind, ExprKind::Match(.., MatchSource::Normal))) {
79+
let inner_snippet = snippet(cx, sub_expr.span, "..");
80+
span_lint_and_sugg(
81+
cx,
82+
STACKED_IF_MATCH,
83+
expr.span.with_hi(sub_expr.span.hi()),
84+
format!("avoid using `{keyword} {keyword}`"),
85+
format!("try binding inner `{keyword}` with `let`"),
86+
format!("let result = {inner_snippet}; {keyword} result"),
87+
Applicability::MachineApplicable,
88+
);
89+
ControlFlow::Break(())
90+
} else {
91+
ControlFlow::Continue(Descend::Yes)
92+
}
93+
});
5094
}
5195
}

tests/ui/stacked_if_match.rs

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,132 @@
11
#![warn(clippy::stacked_if_match)]
2+
#![allow(unused)]
23

3-
fn main() {
4-
// test code goes here
4+
5+
fn stacked_if() {
6+
let x = 0;
7+
if if x == 1 {
8+
x == 2
9+
} else {
10+
x == 3
11+
} {
12+
println!("true");
13+
}
14+
15+
if if x == 1 {
16+
2
17+
} else {
18+
3
19+
} == 4 {
20+
println!("true");
21+
}
22+
}
23+
24+
fn stacked_match() {
25+
let x = 0;
26+
match match x {
27+
1 => 2,
28+
_ => 3,
29+
} {
30+
1 => {},
31+
2 => {},
32+
_ => {},
33+
}
34+
35+
match match x {
36+
1 => 2,
37+
_ => 3,
38+
} + 1 {
39+
1 => {},
40+
2 => {},
41+
_ => {},
42+
}
43+
}
44+
45+
fn if_no_lint() {
46+
let x = 0;
47+
48+
if (if x == 1 {
49+
x == 2
50+
} else {
51+
x == 3
52+
}) {
53+
println!("true");
54+
}
55+
56+
if 1 == if x == 1 {
57+
1
58+
} else {
59+
2
60+
} {
61+
println!("true");
62+
}
63+
}
64+
65+
fn match_no_lint() {
66+
let x = 0;
67+
match (match x {
68+
1 => 2,
69+
_ => 3,
70+
}) {
71+
1 => {},
72+
2 => {},
73+
_ => {},
74+
}
75+
76+
match 1 + match x {
77+
1 => 2,
78+
_ => 3,
79+
} {
80+
1 => {},
81+
2 => {},
82+
_ => {},
83+
}
584
}
85+
86+
macro_rules! if_macro {
87+
($var:ident) => {
88+
if $var == 1 {
89+
true
90+
} else {
91+
false
92+
}
93+
};
94+
}
95+
96+
macro_rules! match_macro {
97+
($var:ident) => {
98+
match $var {
99+
1 => 2,
100+
_ => 3,
101+
}
102+
};
103+
}
104+
105+
macro_rules! if_if_macro {
106+
() => {
107+
if if true {
108+
true
109+
} else {
110+
false
111+
} {
112+
println!("true");
113+
}
114+
};
115+
}
116+
117+
fn macro_no_lint() {
118+
let x = 0;
119+
if if_macro!(x) {
120+
println!("true");
121+
}
122+
123+
match match_macro!(x) {
124+
1 => {},
125+
2 => {},
126+
_ => {},
127+
}
128+
129+
if_if_macro!();
130+
}
131+
132+
fn main() {}

0 commit comments

Comments
 (0)