Skip to content

Commit 42879bc

Browse files
committed
Add a USELESS_LET_IF_SEQ lint
1 parent ed7ac0d commit 42879bc

File tree

5 files changed

+264
-1
lines changed

5 files changed

+264
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4+
## 0.0.71 — TBD
5+
* New lint: [`useless_let_if_seq`]
6+
47
## 0.0.70 — 2016-05-28
58
* Rustup to *rustc 1.10.0-nightly (7bddce693 2016-05-27)*
69
* [`invalid_regex`] and [`trivial_regex`] can now warn on `RegexSet::new`,
@@ -240,6 +243,7 @@ All notable changes to this project will be documented in this file.
240243
[`use_debug`]: https://github.com/Manishearth/rust-clippy/wiki#use_debug
241244
[`used_underscore_binding`]: https://github.com/Manishearth/rust-clippy/wiki#used_underscore_binding
242245
[`useless_format`]: https://github.com/Manishearth/rust-clippy/wiki#useless_format
246+
[`useless_let_if_seq`]: https://github.com/Manishearth/rust-clippy/wiki#useless_let_if_seq
243247
[`useless_transmute`]: https://github.com/Manishearth/rust-clippy/wiki#useless_transmute
244248
[`useless_vec`]: https://github.com/Manishearth/rust-clippy/wiki#useless_vec
245249
[`while_let_loop`]: https://github.com/Manishearth/rust-clippy/wiki#while_let_loop

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Table of contents:
1717

1818
## Lints
1919

20-
There are 151 lints included in this crate:
20+
There are 152 lints included in this crate:
2121

2222
name | default | meaning
2323
---------------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -164,6 +164,7 @@ name
164164
[use_debug](https://github.com/Manishearth/rust-clippy/wiki#use_debug) | allow | use `Debug`-based formatting
165165
[used_underscore_binding](https://github.com/Manishearth/rust-clippy/wiki#used_underscore_binding) | allow | using a binding which is prefixed with an underscore
166166
[useless_format](https://github.com/Manishearth/rust-clippy/wiki#useless_format) | warn | useless use of `format!`
167+
[useless_let_if_seq](https://github.com/Manishearth/rust-clippy/wiki#useless_let_if_seq) | warn | Checks for unidiomatic `let mut` declaration followed by initialization in `if`
167168
[useless_transmute](https://github.com/Manishearth/rust-clippy/wiki#useless_transmute) | warn | transmutes that have the same to and from types
168169
[useless_vec](https://github.com/Manishearth/rust-clippy/wiki#useless_vec) | warn | useless `vec!`
169170
[while_let_loop](https://github.com/Manishearth/rust-clippy/wiki#while_let_loop) | warn | `loop { if let { ... } else break }` can be written as a `while let` loop

clippy_lints/src/let_if_seq.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
use rustc::lint::*;
2+
use rustc::hir;
3+
use syntax::codemap;
4+
use utils::{snippet, span_lint_and_then};
5+
6+
/// **What it does:** This lint checks for variable declarations immediately followed by a
7+
/// conditional affectation.
8+
///
9+
/// **Why is this bad?** This is not idiomatic Rust.
10+
///
11+
/// **Known problems:** None.
12+
///
13+
/// **Example:**
14+
/// ```rust,ignore
15+
/// let foo;
16+
///
17+
/// if bar() {
18+
/// foo = 42;
19+
/// } else {
20+
/// foo = 0;
21+
/// }
22+
///
23+
/// let mut baz = None;
24+
///
25+
/// if bar() {
26+
/// baz = Some(42);
27+
/// }
28+
/// ```
29+
///
30+
/// should be written
31+
///
32+
/// ```rust,ignore
33+
/// let foo = if bar() {
34+
/// 42;
35+
/// } else {
36+
/// 0;
37+
/// };
38+
///
39+
/// let baz = if bar() {
40+
/// Some(42);
41+
/// } else {
42+
/// None
43+
/// };
44+
/// ```
45+
declare_lint! {
46+
pub USELESS_LET_IF_SEQ,
47+
Warn,
48+
"Checks for unidiomatic `let mut` declaration followed by initialization in `if`"
49+
}
50+
51+
#[derive(Copy,Clone)]
52+
pub struct LetIfSeq;
53+
54+
impl LintPass for LetIfSeq {
55+
fn get_lints(&self) -> LintArray {
56+
lint_array!(USELESS_LET_IF_SEQ)
57+
}
58+
}
59+
60+
impl LateLintPass for LetIfSeq {
61+
fn check_block(&mut self, cx: &LateContext, block: &hir::Block) {
62+
let mut it = block.stmts.iter().peekable();
63+
while let Some(ref stmt) = it.next() {
64+
if_let_chain! {[
65+
let Some(expr) = it.peek(),
66+
let hir::StmtDecl(ref decl, _) = stmt.node,
67+
let hir::DeclLocal(ref decl) = decl.node,
68+
let hir::PatKind::Ident(mode, ref name, None) = decl.pat.node,
69+
let Some(def) = cx.tcx.def_map.borrow().get(&decl.pat.id),
70+
let hir::StmtExpr(ref if_, _) = expr.node,
71+
let hir::ExprIf(ref cond, ref then, ref else_) = if_.node,
72+
let Some(value) = check_assign(cx, def.def_id(), then),
73+
], {
74+
let span = codemap::mk_sp(stmt.span.lo, if_.span.hi);
75+
76+
let (default_multi_stmts, default) = if let Some(ref else_) = *else_ {
77+
if let hir::ExprBlock(ref else_) = else_.node {
78+
if let Some(default) = check_assign(cx, def.def_id(), else_) {
79+
(else_.stmts.len() > 1, default)
80+
} else if let Some(ref default) = decl.init {
81+
(true, &**default)
82+
} else {
83+
continue;
84+
}
85+
} else {
86+
continue;
87+
}
88+
} else if let Some(ref default) = decl.init {
89+
(false, &**default)
90+
} else {
91+
continue;
92+
};
93+
94+
let mutability = match mode {
95+
hir::BindByRef(hir::MutMutable) | hir::BindByValue(hir::MutMutable) => "<mut> ",
96+
_ => "",
97+
};
98+
99+
// FIXME: this should not suggest `mut` if we can detect that the variable is not
100+
// use mutably after the `if`
101+
102+
let sug = format!(
103+
"let {mut}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
104+
mut=mutability,
105+
name=name.node,
106+
cond=snippet(cx, cond.span, "_"),
107+
then=if then.stmts.len() > 1 { " ..;" } else { "" },
108+
else=if default_multi_stmts { " ..;" } else { "" },
109+
value=snippet(cx, value.span, "<value>"),
110+
default=snippet(cx, default.span, "<default>"),
111+
);
112+
span_lint_and_then(cx,
113+
USELESS_LET_IF_SEQ,
114+
span,
115+
"`if _ { .. } else { .. }` is an expression",
116+
|db| {
117+
db.span_suggestion(span,
118+
"it is more idiomatic to write",
119+
sug);
120+
if !mutability.is_empty() {
121+
db.note("you might not need `mut` at all");
122+
}
123+
});
124+
}}
125+
}
126+
}
127+
}
128+
129+
struct UsedVisitor<'a, 'tcx: 'a> {
130+
cx: &'a LateContext<'a, 'tcx>,
131+
id: hir::def_id::DefId,
132+
used: bool,
133+
}
134+
135+
impl<'a, 'tcx, 'v> hir::intravisit::Visitor<'v> for UsedVisitor<'a, 'tcx> {
136+
fn visit_expr(&mut self, expr: &'v hir::Expr) {
137+
if_let_chain! {[
138+
let hir::ExprPath(None, _) = expr.node,
139+
let Some(def) = self.cx.tcx.def_map.borrow().get(&expr.id),
140+
self.id == def.def_id(),
141+
], {
142+
self.used = true;
143+
return;
144+
}}
145+
hir::intravisit::walk_expr(self, expr);
146+
}
147+
}
148+
149+
fn check_assign<'e>(cx: &LateContext, decl: hir::def_id::DefId, block: &'e hir::Block) -> Option<&'e hir::Expr> {
150+
if_let_chain! {[
151+
let Some(expr) = block.stmts.iter().last(),
152+
let hir::StmtSemi(ref expr, _) = expr.node,
153+
let hir::ExprAssign(ref var, ref value) = expr.node,
154+
let hir::ExprPath(None, _) = var.node,
155+
let Some(def) = cx.tcx.def_map.borrow().get(&var.id),
156+
decl == def.def_id(),
157+
], {
158+
let mut v = UsedVisitor {
159+
cx: cx,
160+
id: decl,
161+
used: false,
162+
};
163+
164+
for s in block.stmts.iter().take(block.stmts.len()-1) {
165+
hir::intravisit::walk_stmt(&mut v, s);
166+
}
167+
168+
return if v.used {
169+
None
170+
} else {
171+
Some(value)
172+
};
173+
}}
174+
175+
None
176+
}

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub mod identity_op;
8080
pub mod if_not_else;
8181
pub mod items_after_statements;
8282
pub mod len_zero;
83+
pub mod let_if_seq;
8384
pub mod lifetimes;
8485
pub mod loops;
8586
pub mod map_clone;
@@ -246,6 +247,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
246247
reg.register_late_lint_pass(box mem_forget::MemForget);
247248
reg.register_late_lint_pass(box arithmetic::Arithmetic::default());
248249
reg.register_late_lint_pass(box assign_ops::AssignOps);
250+
reg.register_late_lint_pass(box let_if_seq::LetIfSeq);
249251

250252
reg.register_lint_group("clippy_restrictions", vec![
251253
arithmetic::FLOAT_ARITHMETIC,
@@ -318,6 +320,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
318320
identity_op::IDENTITY_OP,
319321
len_zero::LEN_WITHOUT_IS_EMPTY,
320322
len_zero::LEN_ZERO,
323+
let_if_seq::USELESS_LET_IF_SEQ,
321324
lifetimes::NEEDLESS_LIFETIMES,
322325
lifetimes::UNUSED_LIFETIMES,
323326
loops::EMPTY_LOOP,

tests/compile-fail/let_if_seq.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
4+
#![allow(unused_variables, unused_assignments, similar_names, blacklisted_name)]
5+
#![deny(useless_let_if_seq)]
6+
7+
fn f() -> bool { true }
8+
9+
fn early_return() -> u8 {
10+
// FIXME: we could extend the lint to include such cases:
11+
let foo;
12+
13+
if f() {
14+
return 42;
15+
} else {
16+
foo = 0;
17+
}
18+
19+
foo
20+
}
21+
22+
fn main() {
23+
early_return();
24+
25+
let mut foo = 0;
26+
//~^ ERROR `if _ { .. } else { .. }` is an expression
27+
//~| HELP more idiomatic
28+
//~| SUGGESTION let <mut> foo = if f() { 42 } else { 0 };
29+
if f() {
30+
foo = 42;
31+
}
32+
33+
let mut bar = 0;
34+
//~^ ERROR `if _ { .. } else { .. }` is an expression
35+
//~| HELP more idiomatic
36+
//~| SUGGESTION let <mut> bar = if f() { ..; 42 } else { ..; 0 };
37+
if f() {
38+
f();
39+
bar = 42;
40+
}
41+
else {
42+
f();
43+
}
44+
45+
let quz;
46+
//~^ ERROR `if _ { .. } else { .. }` is an expression
47+
//~| HELP more idiomatic
48+
//~| SUGGESTION let quz = if f() { 42 } else { 0 };
49+
50+
if f() {
51+
quz = 42;
52+
} else {
53+
quz = 0;
54+
}
55+
56+
// `toto` is used several times
57+
let mut toto;
58+
59+
if f() {
60+
toto = 42;
61+
} else {
62+
for i in &[1, 2] {
63+
toto = *i;
64+
}
65+
66+
toto = 2;
67+
}
68+
69+
// baz needs to be mut
70+
let mut baz = 0;
71+
//~^ ERROR `if _ { .. } else { .. }` is an expression
72+
//~| HELP more idiomatic
73+
//~| SUGGESTION let <mut> baz = if f() { 42 } else { 0 };
74+
if f() {
75+
baz = 42;
76+
}
77+
78+
baz = 1337;
79+
}

0 commit comments

Comments
 (0)