Skip to content

Commit 8a1f0cd

Browse files
committed
Auto merge of #10935 - Alexendoo:needless-if-cases, r=Manishearth
Don't lint non-statement/faux empty `needless_if`s Also has a basic fall-back for `if` statements that have attributes applied to them and incorporates #10921 (review) while I was there r? `@Manishearth` changelog: none
2 parents 7c2bf28 + 5e20a57 commit 8a1f0cd

File tree

4 files changed

+161
-94
lines changed

4 files changed

+161
-94
lines changed

clippy_lints/src/needless_if.rs

Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
use clippy_utils::{diagnostics::span_lint_and_sugg, is_from_proc_macro, source::snippet_with_applicability};
1+
use clippy_utils::{diagnostics::span_lint_and_sugg, higher::If, is_from_proc_macro, source::snippet_opt};
22
use rustc_errors::Applicability;
3-
use rustc_hir::{
4-
intravisit::{walk_expr, Visitor},
5-
Expr, ExprKind, Node,
6-
};
3+
use rustc_hir::{ExprKind, Stmt, StmtKind};
74
use rustc_lint::{LateContext, LateLintPass, LintContext};
85
use rustc_middle::lint::in_external_macro;
96
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -37,67 +34,42 @@ declare_clippy_lint! {
3734
declare_lint_pass!(NeedlessIf => [NEEDLESS_IF]);
3835

3936
impl LateLintPass<'_> for NeedlessIf {
40-
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
41-
if let ExprKind::If(if_expr, block, else_expr) = &expr.kind
42-
&& let ExprKind::Block(block, ..) = block.kind
37+
fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) {
38+
if let StmtKind::Expr(expr) = stmt.kind
39+
&& let Some(If {cond, then, r#else: None }) = If::hir(expr)
40+
&& let ExprKind::Block(block, ..) = then.kind
4341
&& block.stmts.is_empty()
4442
&& block.expr.is_none()
45-
&& else_expr.is_none()
4643
&& !in_external_macro(cx.sess(), expr.span)
44+
&& !is_from_proc_macro(cx, expr)
45+
&& let Some(then_snippet) = snippet_opt(cx, then.span)
46+
// Ignore
47+
// - empty macro expansions
48+
// - empty reptitions in macro expansions
49+
// - comments
50+
// - #[cfg]'d out code
51+
&& then_snippet.chars().all(|ch| matches!(ch, '{' | '}') || ch.is_ascii_whitespace())
52+
&& let Some(cond_snippet) = snippet_opt(cx, cond.span)
4753
{
48-
// Ignore `else if`
49-
if let Some(parent_id) = cx.tcx.hir().opt_parent_id(expr.hir_id)
50-
&& let Some(Node::Expr(Expr {
51-
kind: ExprKind::If(_, _, Some(else_expr)),
52-
..
53-
})) = cx.tcx.hir().find(parent_id)
54-
&& else_expr.hir_id == expr.hir_id
55-
{
56-
return;
57-
}
58-
59-
if is_any_if_let(if_expr) || is_from_proc_macro(cx, expr) {
60-
return;
61-
}
62-
63-
let mut app = Applicability::MachineApplicable;
64-
let snippet = snippet_with_applicability(cx, if_expr.span, "{ ... }", &mut app);
65-
6654
span_lint_and_sugg(
6755
cx,
6856
NEEDLESS_IF,
69-
expr.span,
57+
stmt.span,
7058
"this `if` branch is empty",
7159
"you can remove it",
72-
if if_expr.can_have_side_effects() {
73-
format!("{snippet};")
60+
if cond.can_have_side_effects() || !cx.tcx.hir().attrs(stmt.hir_id).is_empty() {
61+
// `{ foo }` or `{ foo } && bar` placed into a statement position would be
62+
// interpreted as a block statement, force it to be an expression
63+
if cond_snippet.starts_with('{') {
64+
format!("({cond_snippet});")
65+
} else {
66+
format!("{cond_snippet};")
67+
}
7468
} else {
7569
String::new()
7670
},
77-
app,
71+
Applicability::MachineApplicable,
7872
);
7973
}
8074
}
8175
}
82-
83-
/// Returns true if any `Expr` contained within this `Expr` is a `Let`, else false.
84-
///
85-
/// Really wish `Expr` had a `walk` method...
86-
fn is_any_if_let(expr: &Expr<'_>) -> bool {
87-
let mut v = IsAnyLetVisitor(false);
88-
89-
v.visit_expr(expr);
90-
v.0
91-
}
92-
93-
struct IsAnyLetVisitor(bool);
94-
95-
impl Visitor<'_> for IsAnyLetVisitor {
96-
fn visit_expr(&mut self, expr: &Expr<'_>) {
97-
if matches!(expr.kind, ExprKind::Let(..)) {
98-
self.0 = true;
99-
} else {
100-
walk_expr(self, expr);
101-
}
102-
}
103-
}

tests/ui/needless_if.fixed

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
clippy::blocks_in_if_conditions,
66
clippy::if_same_then_else,
77
clippy::ifs_same_cond,
8+
clippy::let_unit_value,
89
clippy::needless_else,
910
clippy::no_effect,
1011
clippy::nonminimal_bool,
12+
clippy::short_circuit_statement,
13+
clippy::unnecessary_operation,
1114
unused
1215
)]
1316
#![warn(clippy::needless_if)]
@@ -16,45 +19,75 @@ extern crate proc_macros;
1619
use proc_macros::external;
1720
use proc_macros::with_span;
1821

19-
fn no_side_effects() -> bool {
20-
true
21-
}
22-
23-
fn has_side_effects(a: &mut u32) -> bool {
24-
*a = 1;
22+
fn maybe_side_effect() -> bool {
2523
true
2624
}
2725

2826
fn main() {
2927
// Lint
3028

3129
// Do not remove the condition
32-
no_side_effects();
33-
let mut x = 0;
34-
has_side_effects(&mut x);
35-
assert_eq!(x, 1);
30+
maybe_side_effect();
3631
// Do not lint
3732
if (true) {
3833
} else {
3934
}
40-
{
35+
({
4136
return;
42-
};
37+
});
4338
// Do not lint if `else if` is present
4439
if (true) {
4540
} else if (true) {
4641
}
47-
// Do not lint if any `let` is present
42+
// Do not lint `if let` or let chains
4843
if let true = true {}
4944
if let true = true && true {}
5045
if true && let true = true {}
51-
if {
46+
// Can lint nested `if let`s
47+
({
5248
if let true = true && true { true } else { false }
53-
} && true
54-
{}
49+
} && true);
5550
external! { if (true) {} }
5651
with_span! {
5752
span
5853
if (true) {}
5954
}
55+
56+
if true {
57+
// comment
58+
}
59+
60+
if true {
61+
#[cfg(any())]
62+
foo;
63+
}
64+
65+
macro_rules! empty_expansion {
66+
() => {};
67+
}
68+
69+
if true {
70+
empty_expansion!();
71+
}
72+
73+
macro_rules! empty_repetition {
74+
($($t:tt)*) => {
75+
if true {
76+
$($t)*
77+
}
78+
}
79+
}
80+
81+
empty_repetition!();
82+
83+
// Must be placed into an expression context to not be interpreted as a block
84+
({ maybe_side_effect() });
85+
// Would be a block followed by `&&true` - a double reference to `true`
86+
({ maybe_side_effect() } && true);
87+
88+
// Don't leave trailing attributes
89+
#[allow(unused)]
90+
true;
91+
92+
let () = if maybe_side_effect() {};
6093
}

tests/ui/needless_if.rs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
clippy::blocks_in_if_conditions,
66
clippy::if_same_then_else,
77
clippy::ifs_same_cond,
8+
clippy::let_unit_value,
89
clippy::needless_else,
910
clippy::no_effect,
1011
clippy::nonminimal_bool,
12+
clippy::short_circuit_statement,
13+
clippy::unnecessary_operation,
1114
unused
1215
)]
1316
#![warn(clippy::needless_if)]
@@ -16,23 +19,15 @@ extern crate proc_macros;
1619
use proc_macros::external;
1720
use proc_macros::with_span;
1821

19-
fn no_side_effects() -> bool {
20-
true
21-
}
22-
23-
fn has_side_effects(a: &mut u32) -> bool {
24-
*a = 1;
22+
fn maybe_side_effect() -> bool {
2523
true
2624
}
2725

2826
fn main() {
2927
// Lint
3028
if (true) {}
3129
// Do not remove the condition
32-
if no_side_effects() {}
33-
let mut x = 0;
34-
if has_side_effects(&mut x) {}
35-
assert_eq!(x, 1);
30+
if maybe_side_effect() {}
3631
// Do not lint
3732
if (true) {
3833
} else {
@@ -44,10 +39,11 @@ fn main() {
4439
if (true) {
4540
} else if (true) {
4641
}
47-
// Do not lint if any `let` is present
42+
// Do not lint `if let` or let chains
4843
if let true = true {}
4944
if let true = true && true {}
5045
if true && let true = true {}
46+
// Can lint nested `if let`s
5147
if {
5248
if let true = true && true { true } else { false }
5349
} && true
@@ -57,4 +53,42 @@ fn main() {
5753
span
5854
if (true) {}
5955
}
56+
57+
if true {
58+
// comment
59+
}
60+
61+
if true {
62+
#[cfg(any())]
63+
foo;
64+
}
65+
66+
macro_rules! empty_expansion {
67+
() => {};
68+
}
69+
70+
if true {
71+
empty_expansion!();
72+
}
73+
74+
macro_rules! empty_repetition {
75+
($($t:tt)*) => {
76+
if true {
77+
$($t)*
78+
}
79+
}
80+
}
81+
82+
empty_repetition!();
83+
84+
// Must be placed into an expression context to not be interpreted as a block
85+
if { maybe_side_effect() } {}
86+
// Would be a block followed by `&&true` - a double reference to `true`
87+
if { maybe_side_effect() } && true {}
88+
89+
// Don't leave trailing attributes
90+
#[allow(unused)]
91+
if true {}
92+
93+
let () = if maybe_side_effect() {};
6094
}

tests/ui/needless_if.stderr

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
error: this `if` branch is empty
2-
--> $DIR/needless_if.rs:30:5
2+
--> $DIR/needless_if.rs:28:5
33
|
44
LL | if (true) {}
55
| ^^^^^^^^^^^^ help: you can remove it
66
|
77
= note: `-D clippy::needless-if` implied by `-D warnings`
88

99
error: this `if` branch is empty
10-
--> $DIR/needless_if.rs:32:5
11-
|
12-
LL | if no_side_effects() {}
13-
| ^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `no_side_effects();`
14-
15-
error: this `if` branch is empty
16-
--> $DIR/needless_if.rs:34:5
10+
--> $DIR/needless_if.rs:30:5
1711
|
18-
LL | if has_side_effects(&mut x) {}
19-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `has_side_effects(&mut x);`
12+
LL | if maybe_side_effect() {}
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `maybe_side_effect();`
2014

2115
error: this `if` branch is empty
22-
--> $DIR/needless_if.rs:40:5
16+
--> $DIR/needless_if.rs:35:5
2317
|
2418
LL | / if {
2519
LL | | return;
@@ -28,10 +22,44 @@ LL | | } {}
2822
|
2923
help: you can remove it
3024
|
31-
LL ~ {
25+
LL ~ ({
3226
LL + return;
33-
LL + };
27+
LL + });
28+
|
29+
30+
error: this `if` branch is empty
31+
--> $DIR/needless_if.rs:47:5
32+
|
33+
LL | / if {
34+
LL | | if let true = true && true { true } else { false }
35+
LL | | } && true
36+
LL | | {}
37+
| |______^
38+
|
39+
help: you can remove it
40+
|
41+
LL ~ ({
42+
LL + if let true = true && true { true } else { false }
43+
LL + } && true);
44+
|
45+
46+
error: this `if` branch is empty
47+
--> $DIR/needless_if.rs:85:5
48+
|
49+
LL | if { maybe_side_effect() } {}
50+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() });`
51+
52+
error: this `if` branch is empty
53+
--> $DIR/needless_if.rs:87:5
54+
|
55+
LL | if { maybe_side_effect() } && true {}
56+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() } && true);`
57+
58+
error: this `if` branch is empty
59+
--> $DIR/needless_if.rs:91:5
3460
|
61+
LL | if true {}
62+
| ^^^^^^^^^^ help: you can remove it: `true;`
3563

36-
error: aborting due to 4 previous errors
64+
error: aborting due to 7 previous errors
3765

0 commit comments

Comments
 (0)