Skip to content

Commit f059feb

Browse files
committed
Add redundant else lint
1 parent 68cf94f commit f059feb

File tree

5 files changed

+374
-0
lines changed

5 files changed

+374
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,6 +2023,7 @@ Released 2018-09-13
20232023
[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
20242024
[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call
20252025
[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
2026+
[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
20262027
[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
20272028
[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
20282029
[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ mod question_mark;
293293
mod ranges;
294294
mod redundant_clone;
295295
mod redundant_closure_call;
296+
mod redundant_else;
296297
mod redundant_field_names;
297298
mod redundant_pub_crate;
298299
mod redundant_static_lifetimes;
@@ -810,6 +811,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
810811
&ranges::REVERSED_EMPTY_RANGES,
811812
&redundant_clone::REDUNDANT_CLONE,
812813
&redundant_closure_call::REDUNDANT_CLOSURE_CALL,
814+
&redundant_else::REDUNDANT_ELSE,
813815
&redundant_field_names::REDUNDANT_FIELD_NAMES,
814816
&redundant_pub_crate::REDUNDANT_PUB_CRATE,
815817
&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES,
@@ -1113,6 +1115,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11131115
store.register_early_pass(|| box items_after_statements::ItemsAfterStatements);
11141116
store.register_early_pass(|| box precedence::Precedence);
11151117
store.register_early_pass(|| box needless_continue::NeedlessContinue);
1118+
store.register_early_pass(|| box redundant_else::RedundantElse);
11161119
store.register_late_pass(|| box create_dir::CreateDir);
11171120
store.register_early_pass(|| box needless_arbitrary_self_type::NeedlessArbitrarySelfType);
11181121
store.register_early_pass(|| box redundant_static_lifetimes::RedundantStaticLifetimes);
@@ -1294,6 +1297,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12941297
LintId::of(&pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
12951298
LintId::of(&ranges::RANGE_MINUS_ONE),
12961299
LintId::of(&ranges::RANGE_PLUS_ONE),
1300+
LintId::of(&redundant_else::REDUNDANT_ELSE),
12971301
LintId::of(&ref_option_ref::REF_OPTION_REF),
12981302
LintId::of(&shadow::SHADOW_UNRELATED),
12991303
LintId::of(&strings::STRING_ADD_ASSIGN),

clippy_lints/src/redundant_else.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use crate::utils::span_lint_and_help;
2+
use rustc_ast::ast::{Block, Expr, ExprKind, Stmt, StmtKind};
3+
use rustc_ast::visit::{walk_expr, Visitor};
4+
use rustc_lint::{EarlyContext, EarlyLintPass};
5+
use rustc_middle::lint::in_external_macro;
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
8+
declare_clippy_lint! {
9+
/// **What it does:** Checks for `else` blocks that can be removed without changing semantics.
10+
///
11+
/// **Why is this bad?** The `else` block adds unnecessary indentation and verbosity.
12+
///
13+
/// **Known problems:** Some may prefer to keep the `else` block for clarity.
14+
///
15+
/// **Example:**
16+
///
17+
/// ```rust
18+
/// fn my_func(count: u32) {
19+
/// if count == 0 {
20+
/// print!("Nothing to do");
21+
/// return;
22+
/// } else {
23+
/// print!("Moving on...");
24+
/// }
25+
/// }
26+
/// ```
27+
/// Use instead:
28+
/// ```rust
29+
/// fn my_func(count: u32) {
30+
/// if count == 0 {
31+
/// print!("Nothing to do");
32+
/// return;
33+
/// }
34+
/// print!("Moving on...");
35+
/// }
36+
/// ```
37+
pub REDUNDANT_ELSE,
38+
pedantic,
39+
"`else` branch that can be removed without changing semantics"
40+
}
41+
42+
declare_lint_pass!(RedundantElse => [REDUNDANT_ELSE]);
43+
44+
impl EarlyLintPass for RedundantElse {
45+
fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &Stmt) {
46+
if in_external_macro(cx.sess, stmt.span) {
47+
return;
48+
}
49+
// Only look at expressions that are a whole statement
50+
let expr: &Expr = match &stmt.kind {
51+
StmtKind::Expr(expr) | StmtKind::Semi(expr) => expr,
52+
_ => return,
53+
};
54+
// if else
55+
let (mut then, mut els): (&Block, &Expr) = match &expr.kind {
56+
ExprKind::If(_, then, Some(els)) => (then, els),
57+
_ => return,
58+
};
59+
loop {
60+
if !BreakVisitor::default().check_block(then) {
61+
// then block does not always break
62+
return;
63+
}
64+
match &els.kind {
65+
// else if else
66+
ExprKind::If(_, next_then, Some(next_els)) => {
67+
then = next_then;
68+
els = next_els;
69+
continue;
70+
},
71+
// else if without else
72+
ExprKind::If(..) => return,
73+
// done
74+
_ => break,
75+
}
76+
}
77+
span_lint_and_help(
78+
cx,
79+
REDUNDANT_ELSE,
80+
els.span,
81+
"redundant else block",
82+
None,
83+
"remove the `else` block and move the contents out",
84+
);
85+
}
86+
}
87+
88+
/// Call `check` functions to check if an expression always breaks control flow
89+
#[derive(Default)]
90+
struct BreakVisitor {
91+
is_break: bool,
92+
}
93+
94+
impl<'ast> Visitor<'ast> for BreakVisitor {
95+
fn visit_block(&mut self, block: &'ast Block) {
96+
self.is_break = match block.stmts.as_slice() {
97+
[.., last] => self.check_stmt(last),
98+
_ => false,
99+
};
100+
}
101+
102+
fn visit_expr(&mut self, expr: &'ast Expr) {
103+
self.is_break = match expr.kind {
104+
ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) => true,
105+
ExprKind::Match(_, ref arms) => arms.iter().all(|arm| self.check_expr(&arm.body)),
106+
ExprKind::If(_, ref then, Some(ref els)) => self.check_block(then) && self.check_expr(els),
107+
ExprKind::If(_, _, None)
108+
// ignore loops for simplicity
109+
| ExprKind::While(..) | ExprKind::ForLoop(..) | ExprKind::Loop(..) => false,
110+
_ => {
111+
walk_expr(self, expr);
112+
return;
113+
},
114+
};
115+
}
116+
}
117+
118+
impl BreakVisitor {
119+
fn check<T>(&mut self, item: T, visit: fn(&mut Self, T)) -> bool {
120+
visit(self, item);
121+
std::mem::replace(&mut self.is_break, false)
122+
}
123+
124+
fn check_block(&mut self, block: &Block) -> bool {
125+
self.check(block, Self::visit_block)
126+
}
127+
128+
fn check_expr(&mut self, expr: &Expr) -> bool {
129+
self.check(expr, Self::visit_expr)
130+
}
131+
132+
fn check_stmt(&mut self, stmt: &Stmt) -> bool {
133+
self.check(stmt, Self::visit_stmt)
134+
}
135+
}

tests/ui/redundant_else.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#![warn(clippy::redundant_else)]
2+
#![allow(clippy::needless_return)]
3+
4+
fn main() {
5+
loop {
6+
// break
7+
if foo() {
8+
println!("Love your neighbor;");
9+
break;
10+
} else {
11+
println!("yet don't pull down your hedge.");
12+
}
13+
// continue
14+
if foo() {
15+
println!("He that lies down with Dogs,");
16+
continue;
17+
} else {
18+
println!("shall rise up with fleas.");
19+
}
20+
// match block
21+
if foo() {
22+
match foo() {
23+
1 => break,
24+
_ => return,
25+
}
26+
} else {
27+
println!("You may delay, but time will not.");
28+
}
29+
}
30+
// else if
31+
if foo() {
32+
return;
33+
} else if foo() {
34+
return;
35+
} else {
36+
println!("A fat kitchen makes a lean will.");
37+
}
38+
// let binding outside of block
39+
let _ = {
40+
if foo() {
41+
return;
42+
} else {
43+
1
44+
}
45+
};
46+
// else if with let binding outside of block
47+
let _ = {
48+
if foo() {
49+
return;
50+
} else if foo() {
51+
return;
52+
} else {
53+
2
54+
}
55+
};
56+
// inside if let
57+
let _ = if let Some(1) = foo() {
58+
let _ = 1;
59+
if foo() {
60+
return;
61+
} else {
62+
1
63+
}
64+
} else {
65+
1
66+
};
67+
68+
//
69+
// non-lint cases
70+
//
71+
72+
// sanity check
73+
if foo() {
74+
let _ = 1;
75+
} else {
76+
println!("Who is wise? He that learns from every one.");
77+
}
78+
// else if without else
79+
if foo() {
80+
return;
81+
} else if foo() {
82+
foo()
83+
};
84+
// nested if return
85+
if foo() {
86+
if foo() {
87+
return;
88+
}
89+
} else {
90+
foo()
91+
};
92+
// match with non-breaking branch
93+
if foo() {
94+
match foo() {
95+
1 => foo(),
96+
_ => return,
97+
}
98+
} else {
99+
println!("Three may keep a secret, if two of them are dead.");
100+
}
101+
// let binding
102+
let _ = if foo() {
103+
return;
104+
} else {
105+
1
106+
};
107+
// assign
108+
let a;
109+
a = if foo() {
110+
return;
111+
} else {
112+
1
113+
};
114+
// assign-op
115+
a += if foo() {
116+
return;
117+
} else {
118+
1
119+
};
120+
// if return else if else
121+
if foo() {
122+
return;
123+
} else if foo() {
124+
1
125+
} else {
126+
2
127+
};
128+
// if else if return else
129+
if foo() {
130+
1
131+
} else if foo() {
132+
return;
133+
} else {
134+
2
135+
};
136+
// else if with let binding
137+
let _ = if foo() {
138+
return;
139+
} else if foo() {
140+
return;
141+
} else {
142+
2
143+
};
144+
// inside function call
145+
Box::new(if foo() {
146+
return;
147+
} else {
148+
1
149+
});
150+
}
151+
152+
fn foo<T>() -> T {
153+
unimplemented!("I'm not Santa Claus")
154+
}

0 commit comments

Comments
 (0)