Skip to content

New never loop lint #1549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 17, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change Log
All notable changes to this project will be documented in this file.

* New [`never_loop`] lint
* New [`mut_from_ref`] lint

## 0.0.114 — 2017-02-08
Expand Down Expand Up @@ -384,6 +385,7 @@ All notable changes to this project will be documented in this file.
[`needless_return`]: https://github.com/Manishearth/rust-clippy/wiki#needless_return
[`needless_update`]: https://github.com/Manishearth/rust-clippy/wiki#needless_update
[`neg_multiply`]: https://github.com/Manishearth/rust-clippy/wiki#neg_multiply
[`never_loop`]: https://github.com/Manishearth/rust-clippy/wiki#never_loop
[`new_ret_no_self`]: https://github.com/Manishearth/rust-clippy/wiki#new_ret_no_self
[`new_without_default`]: https://github.com/Manishearth/rust-clippy/wiki#new_without_default
[`new_without_default_derive`]: https://github.com/Manishearth/rust-clippy/wiki#new_without_default_derive
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ transparently:

## Lints

There are 189 lints included in this crate:
There are 190 lints included in this crate:

name | default | triggers on
-----------------------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -291,6 +291,7 @@ name
[needless_return](https://github.com/Manishearth/rust-clippy/wiki#needless_return) | warn | using a return statement like `return expr;` where an expression would suffice
[needless_update](https://github.com/Manishearth/rust-clippy/wiki#needless_update) | warn | using `Foo { ..base }` when there are no missing fields
[neg_multiply](https://github.com/Manishearth/rust-clippy/wiki#neg_multiply) | warn | multiplying integers with -1
[never_loop](https://github.com/Manishearth/rust-clippy/wiki#never_loop) | warn | any loop with an unconditional `break` statement
[new_ret_no_self](https://github.com/Manishearth/rust-clippy/wiki#new_ret_no_self) | warn | not returning `Self` in a `new` method
[new_without_default](https://github.com/Manishearth/rust-clippy/wiki#new_without_default) | warn | `fn new() -> Self` method without `Default` implementation
[new_without_default_derive](https://github.com/Manishearth/rust-clippy/wiki#new_without_default_derive) | warn | `fn new() -> Self` without `#[derive]`able `Default` implementation
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
loops::FOR_LOOP_OVER_RESULT,
loops::ITER_NEXT_LOOP,
loops::NEEDLESS_RANGE_LOOP,
loops::NEVER_LOOP,
loops::REVERSE_RANGE_LOOP,
loops::UNUSED_COLLECT,
loops::WHILE_LET_LOOP,
Expand Down
68 changes: 67 additions & 1 deletion clippy_lints/src/loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,23 @@ declare_lint! {
"looping on a map using `iter` when `keys` or `values` would do"
}

/// **What it does:** Checks for loops that contain an unconditional `break`.
///
/// **Why is this bad?** This loop never loops, all it does is obfuscating the
/// code.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// loop { ..; break; }
/// ```
declare_lint! {
pub NEVER_LOOP,
Warn,
"any loop with an unconditional `break` statement"
}

#[derive(Copy, Clone)]
pub struct Pass;

Expand All @@ -303,7 +320,8 @@ impl LintPass for Pass {
EXPLICIT_COUNTER_LOOP,
EMPTY_LOOP,
WHILE_LET_ON_ITERATOR,
FOR_KV_MAP)
FOR_KV_MAP,
NEVER_LOOP)
}
}

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

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

fn never_loop_block(block: &Block) -> bool {
block.stmts.iter().any(never_loop_stmt) || block.expr.as_ref().map_or(false, |e| never_loop_expr(e))
}

fn never_loop_stmt(stmt: &Stmt) -> bool {
match stmt.node {
StmtSemi(ref e, _) |
StmtExpr(ref e, _) => never_loop_expr(e),
StmtDecl(ref d, _) => never_loop_decl(d),
}
}

fn never_loop_decl(decl: &Decl) -> bool {
if let DeclLocal(ref local) = decl.node {
local.init.as_ref().map_or(false, |e| never_loop_expr(e))
} else {
false
}
}

fn never_loop_expr(expr: &Expr) -> bool {
match expr.node {
ExprBreak(..) | ExprRet(..) => true,
ExprBox(ref e) |
ExprUnary(_, ref e) |
ExprCast(ref e, _) |
ExprType(ref e, _) |
ExprField(ref e, _) |
ExprTupField(ref e, _) |
ExprRepeat(ref e, _) |
ExprAddrOf(_, ref e) => never_loop_expr(e),
ExprBinary(_, ref e1, ref e2) |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binary ops are short circuiting, so this can only be done on the first operand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

ExprAssign(ref e1, ref e2) |
ExprAssignOp(_, ref e1, ref e2) |
ExprIndex(ref e1, ref e2) => never_loop_expr(e1) || never_loop_expr(e2),
ExprArray(ref es) |
ExprTup(ref es) |
ExprMethodCall(_, _, ref es) => es.iter().any(|e| never_loop_expr(e)),
ExprCall(ref e, ref es) => never_loop_expr(e) || es.iter().any(|e| never_loop_expr(e)),
ExprBlock(ref block) => never_loop_block(block),
ExprStruct(_, _, ref base) => base.as_ref().map_or(false, |e| never_loop_expr(e)),
_ => false,
}
}

fn check_for_loop<'a, 'tcx>(
cx: &LateContext<'a, 'tcx>,
pat: &'tcx Pat,
Expand Down
34 changes: 34 additions & 0 deletions tests/ui/never_loop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![feature(plugin)]
#![plugin(clippy)]

#![deny(never_loop)]
#![allow(dead_code, unused)]

fn main() {
loop {
println!("This is only ever printed once");
break;
}

let x = 1;
loop {
println!("This, too"); // but that's OK
if x == 1 {
break;
}
}

loop {
loop {
// another one
break;
}
break;
}

loop {
loop {
if x == 1 { return; }
}
}
}
41 changes: 41 additions & 0 deletions tests/ui/never_loop.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
error: this loop never actually loops
--> $DIR/never_loop.rs:8:5
|
8 | loop {
| _____^ starting here...
9 | | println!("This is only ever printed once");
10 | | break;
11 | | }
| |_____^ ...ending here
|
note: lint level defined here
--> $DIR/never_loop.rs:4:9
|
4 | #![deny(never_loop)]
| ^^^^^^^^^^

error: this loop never actually loops
--> $DIR/never_loop.rs:21:5
|
21 | loop {
| _____^ starting here...
22 | | loop {
23 | | // another one
24 | | break;
25 | | }
26 | | break;
27 | | }
| |_____^ ...ending here

error: this loop never actually loops
--> $DIR/never_loop.rs:22:9
|
22 | loop {
| _________^ starting here...
23 | | // another one
24 | | break;
25 | | }
| |_________^ ...ending here

error: aborting due to 3 previous errors

2 changes: 1 addition & 1 deletion tests/ui/unused_labels.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![plugin(clippy)]
#![feature(plugin)]

#![allow(dead_code, items_after_statements)]
#![allow(dead_code, items_after_statements, never_loop)]
#![deny(unused_label)]

fn unused_label() {
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/while_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![plugin(clippy)]

#![deny(while_let_loop, empty_loop, while_let_on_iterator)]
#![allow(dead_code, unused, cyclomatic_complexity)]
#![allow(dead_code, never_loop, unused, cyclomatic_complexity)]

fn main() {
let y = Some(true);
Expand Down