Skip to content

Commit 96c9303

Browse files
authored
Merge pull request #1549 from Manishearth/never_loop
New never loop lint
2 parents 51931c7 + 6c8a6c1 commit 96c9303

File tree

8 files changed

+149
-4
lines changed

8 files changed

+149
-4
lines changed

CHANGELOG.md

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

4+
* New [`never_loop`] lint
45
* New [`mut_from_ref`] lint
56

67
## 0.0.114 — 2017-02-08
@@ -384,6 +385,7 @@ All notable changes to this project will be documented in this file.
384385
[`needless_return`]: https://github.com/Manishearth/rust-clippy/wiki#needless_return
385386
[`needless_update`]: https://github.com/Manishearth/rust-clippy/wiki#needless_update
386387
[`neg_multiply`]: https://github.com/Manishearth/rust-clippy/wiki#neg_multiply
388+
[`never_loop`]: https://github.com/Manishearth/rust-clippy/wiki#never_loop
387389
[`new_ret_no_self`]: https://github.com/Manishearth/rust-clippy/wiki#new_ret_no_self
388390
[`new_without_default`]: https://github.com/Manishearth/rust-clippy/wiki#new_without_default
389391
[`new_without_default_derive`]: https://github.com/Manishearth/rust-clippy/wiki#new_without_default_derive

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ transparently:
180180

181181
## Lints
182182

183-
There are 189 lints included in this crate:
183+
There are 190 lints included in this crate:
184184

185185
name | default | triggers on
186186
-----------------------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------
@@ -291,6 +291,7 @@ name
291291
[needless_return](https://github.com/Manishearth/rust-clippy/wiki#needless_return) | warn | using a return statement like `return expr;` where an expression would suffice
292292
[needless_update](https://github.com/Manishearth/rust-clippy/wiki#needless_update) | warn | using `Foo { ..base }` when there are no missing fields
293293
[neg_multiply](https://github.com/Manishearth/rust-clippy/wiki#neg_multiply) | warn | multiplying integers with -1
294+
[never_loop](https://github.com/Manishearth/rust-clippy/wiki#never_loop) | warn | any loop with an unconditional `break` statement
294295
[new_ret_no_self](https://github.com/Manishearth/rust-clippy/wiki#new_ret_no_self) | warn | not returning `Self` in a `new` method
295296
[new_without_default](https://github.com/Manishearth/rust-clippy/wiki#new_without_default) | warn | `fn new() -> Self` method without `Default` implementation
296297
[new_without_default_derive](https://github.com/Manishearth/rust-clippy/wiki#new_without_default_derive) | warn | `fn new() -> Self` without `#[derive]`able `Default` implementation

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
405405
loops::FOR_LOOP_OVER_RESULT,
406406
loops::ITER_NEXT_LOOP,
407407
loops::NEEDLESS_RANGE_LOOP,
408+
loops::NEVER_LOOP,
408409
loops::REVERSE_RANGE_LOOP,
409410
loops::UNUSED_COLLECT,
410411
loops::WHILE_LET_LOOP,

clippy_lints/src/loops.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,23 @@ declare_lint! {
286286
"looping on a map using `iter` when `keys` or `values` would do"
287287
}
288288

289+
/// **What it does:** Checks for loops that contain an unconditional `break`.
290+
///
291+
/// **Why is this bad?** This loop never loops, all it does is obfuscating the
292+
/// code.
293+
///
294+
/// **Known problems:** None.
295+
///
296+
/// **Example:**
297+
/// ```rust
298+
/// loop { ..; break; }
299+
/// ```
300+
declare_lint! {
301+
pub NEVER_LOOP,
302+
Warn,
303+
"any loop with an unconditional `break` statement"
304+
}
305+
289306
#[derive(Copy, Clone)]
290307
pub struct Pass;
291308

@@ -303,7 +320,8 @@ impl LintPass for Pass {
303320
EXPLICIT_COUNTER_LOOP,
304321
EMPTY_LOOP,
305322
WHILE_LET_ON_ITERATOR,
306-
FOR_KV_MAP)
323+
FOR_KV_MAP,
324+
NEVER_LOOP)
307325
}
308326
}
309327

@@ -324,6 +342,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
324342
"empty `loop {}` detected. You may want to either use `panic!()` or add \
325343
`std::thread::sleep(..);` to the loop body.");
326344
}
345+
if never_loop_block(block) {
346+
span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops");
347+
}
327348

328349
// extract the expression from the first statement (if any) in a block
329350
let inner_stmt_expr = extract_expr_from_first_stmt(block);
@@ -402,6 +423,51 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
402423
}
403424
}
404425

426+
fn never_loop_block(block: &Block) -> bool {
427+
block.stmts.iter().any(never_loop_stmt) || block.expr.as_ref().map_or(false, |e| never_loop_expr(e))
428+
}
429+
430+
fn never_loop_stmt(stmt: &Stmt) -> bool {
431+
match stmt.node {
432+
StmtSemi(ref e, _) |
433+
StmtExpr(ref e, _) => never_loop_expr(e),
434+
StmtDecl(ref d, _) => never_loop_decl(d),
435+
}
436+
}
437+
438+
fn never_loop_decl(decl: &Decl) -> bool {
439+
if let DeclLocal(ref local) = decl.node {
440+
local.init.as_ref().map_or(false, |e| never_loop_expr(e))
441+
} else {
442+
false
443+
}
444+
}
445+
446+
fn never_loop_expr(expr: &Expr) -> bool {
447+
match expr.node {
448+
ExprBreak(..) | ExprRet(..) => true,
449+
ExprBox(ref e) |
450+
ExprUnary(_, ref e) |
451+
ExprBinary(_, ref e, _) | // because short-circuiting
452+
ExprCast(ref e, _) |
453+
ExprType(ref e, _) |
454+
ExprField(ref e, _) |
455+
ExprTupField(ref e, _) |
456+
ExprRepeat(ref e, _) |
457+
ExprAddrOf(_, ref e) => never_loop_expr(e),
458+
ExprAssign(ref e1, ref e2) |
459+
ExprAssignOp(_, ref e1, ref e2) |
460+
ExprIndex(ref e1, ref e2) => never_loop_expr(e1) || never_loop_expr(e2),
461+
ExprArray(ref es) |
462+
ExprTup(ref es) |
463+
ExprMethodCall(_, _, ref es) => es.iter().any(|e| never_loop_expr(e)),
464+
ExprCall(ref e, ref es) => never_loop_expr(e) || es.iter().any(|e| never_loop_expr(e)),
465+
ExprBlock(ref block) => never_loop_block(block),
466+
ExprStruct(_, _, ref base) => base.as_ref().map_or(false, |e| never_loop_expr(e)),
467+
_ => false,
468+
}
469+
}
470+
405471
fn check_for_loop<'a, 'tcx>(
406472
cx: &LateContext<'a, 'tcx>,
407473
pat: &'tcx Pat,

tests/ui/never_loop.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
4+
#![deny(never_loop)]
5+
#![allow(dead_code, unused)]
6+
7+
fn main() {
8+
loop {
9+
println!("This is only ever printed once");
10+
break;
11+
}
12+
13+
let x = 1;
14+
loop {
15+
println!("This, too"); // but that's OK
16+
if x == 1 {
17+
break;
18+
}
19+
}
20+
21+
loop {
22+
loop {
23+
// another one
24+
break;
25+
}
26+
break;
27+
}
28+
29+
loop {
30+
loop {
31+
if x == 1 { return; }
32+
}
33+
}
34+
}

tests/ui/never_loop.stderr

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
error: this loop never actually loops
2+
--> $DIR/never_loop.rs:8:5
3+
|
4+
8 | loop {
5+
| _____^ starting here...
6+
9 | | println!("This is only ever printed once");
7+
10 | | break;
8+
11 | | }
9+
| |_____^ ...ending here
10+
|
11+
note: lint level defined here
12+
--> $DIR/never_loop.rs:4:9
13+
|
14+
4 | #![deny(never_loop)]
15+
| ^^^^^^^^^^
16+
17+
error: this loop never actually loops
18+
--> $DIR/never_loop.rs:21:5
19+
|
20+
21 | loop {
21+
| _____^ starting here...
22+
22 | | loop {
23+
23 | | // another one
24+
24 | | break;
25+
25 | | }
26+
26 | | break;
27+
27 | | }
28+
| |_____^ ...ending here
29+
30+
error: this loop never actually loops
31+
--> $DIR/never_loop.rs:22:9
32+
|
33+
22 | loop {
34+
| _________^ starting here...
35+
23 | | // another one
36+
24 | | break;
37+
25 | | }
38+
| |_________^ ...ending here
39+
40+
error: aborting due to 3 previous errors
41+

tests/ui/unused_labels.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![plugin(clippy)]
22
#![feature(plugin)]
33

4-
#![allow(dead_code, items_after_statements)]
4+
#![allow(dead_code, items_after_statements, never_loop)]
55
#![deny(unused_label)]
66

77
fn unused_label() {

tests/ui/while_loop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#![plugin(clippy)]
33

44
#![deny(while_let_loop, empty_loop, while_let_on_iterator)]
5-
#![allow(dead_code, unused, cyclomatic_complexity)]
5+
#![allow(dead_code, never_loop, unused, cyclomatic_complexity)]
66

77
fn main() {
88
let y = Some(true);

0 commit comments

Comments
 (0)