Skip to content

Commit 3fdd753

Browse files
committed
Add a USELESS_LET_IF_SEQ lint
1 parent f98e3ec commit 3fdd753

File tree

4 files changed

+197
-1
lines changed

4 files changed

+197
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Table of contents:
1414
* [License](#license)
1515

1616
##Lints
17-
There are 139 lints included in this crate:
17+
There are 140 lints included in this crate:
1818

1919
name | default | meaning
2020
---------------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -149,6 +149,7 @@ name
149149
[use_debug](https://github.com/Manishearth/rust-clippy/wiki#use_debug) | allow | use `Debug`-based formatting
150150
[used_underscore_binding](https://github.com/Manishearth/rust-clippy/wiki#used_underscore_binding) | warn | using a binding which is prefixed with an underscore
151151
[useless_format](https://github.com/Manishearth/rust-clippy/wiki#useless_format) | warn | useless use of `format!`
152+
[useless_let_if_seq](https://github.com/Manishearth/rust-clippy/wiki#useless_let_if_seq) | warn | TODO
152153
[useless_transmute](https://github.com/Manishearth/rust-clippy/wiki#useless_transmute) | warn | transmutes that have the same to and from types
153154
[useless_vec](https://github.com/Manishearth/rust-clippy/wiki#useless_vec) | warn | useless `vec!`
154155
[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

src/let_if_seq.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use rustc::lint::*;
2+
use rustc_front::hir::*;
3+
use syntax::codemap;
4+
use utils::{snippet, span_lint_and_then};
5+
6+
/// **What it does:** This lint checks for variable declarations immediatly followed by a
7+
/// conditionnal 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 writen
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+
"TODO"
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: &Block) {
62+
let mut it = block.stmts.iter().peekable();
63+
while let Some(ref stmt) = it.next() {
64+
if_let_chain! {[
65+
let StmtDecl(ref decl, _) = stmt.node,
66+
let DeclLocal(ref decl) = decl.node,
67+
let PatKind::Ident(mode, ref name, None) = decl.pat.node,
68+
let Some(expr) = it.peek(),
69+
let StmtExpr(ref if_, _) = expr.node,
70+
let ExprIf(ref cond, ref then, ref else_) = if_.node,
71+
let Some(value) = check_assign(name.node, then),
72+
], {
73+
let span = codemap::mk_sp(stmt.span.lo, if_.span.hi);
74+
75+
let default = if let Some(ref else_) = *else_ {
76+
if let ExprBlock(ref else_) = else_.node {
77+
if let Some(default) = check_assign(name.node, else_) {
78+
default
79+
} else if let Some(ref default) = decl.init {
80+
&*default
81+
} else {
82+
return;
83+
}
84+
} else {
85+
return;
86+
}
87+
} else if let Some(ref default) = decl.init {
88+
&*default
89+
} else {
90+
return;
91+
};
92+
93+
let mutability = match mode {
94+
BindByRef(MutMutable) | BindByValue(MutMutable) => "<mut> ",
95+
_ => "",
96+
};
97+
98+
// FIXME: this should not suggest `mut` if we can detect that the variable is not
99+
// use mutably after the `if`
100+
101+
let sug = format!(
102+
"let {mut}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
103+
mut=mutability,
104+
name=name.node.name,
105+
cond=snippet(cx, cond.span, "_"),
106+
then=if then.stmts.len() > 1 { " ..;" } else { "" },
107+
else=if else_.is_some() { " ..;" } else { "" },
108+
value=snippet(cx, value.span, "<value>"),
109+
default=snippet(cx, default.span, "<default>"),
110+
);
111+
span_lint_and_then(cx,
112+
USELESS_LET_IF_SEQ,
113+
span,
114+
"`let foo;`/`if .. { foo = ..; }` sequence detected",
115+
|db| {
116+
db.span_suggestion(span,
117+
"it is more idiomatic to write",
118+
sug);
119+
if !mutability.is_empty() {
120+
db.note("you might not need `mut` at all");
121+
}
122+
});
123+
}}
124+
}
125+
}
126+
}
127+
128+
fn check_assign(name: Ident, block: &Block) -> Option<&Expr> {
129+
if_let_chain! {[
130+
let Some(expr) = block.stmts.iter().last(),
131+
let StmtSemi(ref expr, _) = expr.node,
132+
let ExprAssign(ref var, ref value) = expr.node,
133+
let ExprPath(None, ref path) = var.node,
134+
path.segments.len() == 1,
135+
name.name.as_str() == path.segments[0].identifier.name.as_str(),
136+
], {
137+
return Some(value);
138+
}}
139+
140+
None
141+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ pub mod identity_op;
7676
pub mod if_not_else;
7777
pub mod items_after_statements;
7878
pub mod len_zero;
79+
pub mod let_if_seq;
7980
pub mod lifetimes;
8081
pub mod loops;
8182
pub mod map_clone;
@@ -232,6 +233,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
232233
reg.register_late_lint_pass(box blacklisted_name::BlackListedName::new(conf.blacklisted_names));
233234
reg.register_late_lint_pass(box functions::Functions::new(conf.too_many_arguments_threshold));
234235
reg.register_early_lint_pass(box doc::Doc);
236+
reg.register_late_lint_pass(box let_if_seq::LetIfSeq);
235237

236238
reg.register_lint_group("clippy_pedantic", vec![
237239
array_indexing::INDEXING_SLICING,
@@ -293,6 +295,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
293295
items_after_statements::ITEMS_AFTER_STATEMENTS,
294296
len_zero::LEN_WITHOUT_IS_EMPTY,
295297
len_zero::LEN_ZERO,
298+
let_if_seq::USELESS_LET_IF_SEQ,
296299
lifetimes::NEEDLESS_LIFETIMES,
297300
lifetimes::UNUSED_LIFETIMES,
298301
loops::EMPTY_LOOP,

tests/compile-fail/let_if_seq.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 main() {
10+
let mut foo = 0;
11+
//~^ ERROR `let foo;`/`if .. { foo = ..; }` sequence detected
12+
//~| HELP more idiomatic
13+
//~| SUGGESTION let mut foo = if f() { 42 } else { 0 };
14+
if f() {
15+
foo = 42;
16+
}
17+
18+
let mut bar = 0;
19+
//~^ ERROR `let foo;`/`if .. { foo = ..; }` sequence detected
20+
//~| HELP more idiomatic
21+
//~| SUGGESTION let mut bar = if f() { ..; 42 } else { ..; 0 };
22+
if f() {
23+
f();
24+
bar = 42;
25+
}
26+
else {
27+
f();
28+
}
29+
30+
let quz;
31+
//~^ ERROR `let foo;`/`if .. { foo = ..; }` sequence detected
32+
//~| HELP more idiomatic
33+
//~| SUGGESTION let mut quz = if f() { ..; 42 } else { ..; 0 };
34+
35+
if f() {
36+
quz = 42;
37+
} else {
38+
quz = 0;
39+
}
40+
41+
// baz needs to be mut
42+
let mut baz = 0;
43+
//~^ ERROR `let foo;`/`if .. { foo = ..; }` sequence detected
44+
//~| HELP more idiomatic
45+
//~| SUGGESTION let mut baz = if f() { ..; 42 } else { ..; 0 };
46+
if f() {
47+
baz = 42;
48+
}
49+
50+
baz = 1337;
51+
}

0 commit comments

Comments
 (0)