Skip to content

Commit b1e9c1b

Browse files
authored
Merge pull request #1804 from camsteffen/never_loop
fix never_loop
2 parents 55cb63a + 1a453bf commit b1e9c1b

File tree

8 files changed

+306
-78
lines changed

8 files changed

+306
-78
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ name
295295
[needless_return](https://github.com/Manishearth/rust-clippy/wiki#needless_return) | warn | using a return statement like `return expr;` where an expression would suffice
296296
[needless_update](https://github.com/Manishearth/rust-clippy/wiki#needless_update) | warn | using `Foo { ..base }` when there are no missing fields
297297
[neg_multiply](https://github.com/Manishearth/rust-clippy/wiki#neg_multiply) | warn | multiplying integers with -1
298-
[never_loop](https://github.com/Manishearth/rust-clippy/wiki#never_loop) | allow | any loop with an unconditional `break` or `return` statement
298+
[never_loop](https://github.com/Manishearth/rust-clippy/wiki#never_loop) | warn | any loop that will always `break` or `return`
299299
[new_ret_no_self](https://github.com/Manishearth/rust-clippy/wiki#new_ret_no_self) | warn | not returning `Self` in a `new` method
300300
[new_without_default](https://github.com/Manishearth/rust-clippy/wiki#new_without_default) | warn | `fn new() -> Self` method without `Default` implementation
301301
[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/attrs.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,15 @@ fn is_relevant_trait(tcx: TyCtxt, item: &TraitItem) -> bool {
184184
}
185185

186186
fn is_relevant_block(tcx: TyCtxt, tables: &ty::TypeckTables, block: &Block) -> bool {
187-
for stmt in &block.stmts {
187+
if let Some(stmt) = block.stmts.first() {
188188
match stmt.node {
189-
StmtDecl(_, _) => return true,
189+
StmtDecl(_, _) => true,
190190
StmtExpr(ref expr, _) |
191-
StmtSemi(ref expr, _) => {
192-
return is_relevant_expr(tcx, tables, expr);
193-
},
191+
StmtSemi(ref expr, _) => is_relevant_expr(tcx, tables, expr),
194192
}
193+
} else {
194+
block.expr.as_ref().map_or(false, |e| is_relevant_expr(tcx, tables, e))
195195
}
196-
block.expr.as_ref().map_or(false, |e| is_relevant_expr(tcx, tables, e))
197196
}
198197

199198
fn is_relevant_expr(tcx: TyCtxt, tables: &ty::TypeckTables, expr: &Expr) -> bool {

clippy_lints/src/doc.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(String, Span)]
200200
type Item = (bool, char);
201201

202202
fn next(&mut self) -> Option<(bool, char)> {
203-
while self.line < self.docs.len() {
203+
if self.line < self.docs.len() {
204204
if self.reset {
205205
self.line += 1;
206206
self.reset = false;
@@ -215,18 +215,18 @@ fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(String, Span)]
215215
self.pos += c.len_utf8();
216216
let new_line = self.new_line;
217217
self.new_line = c == '\n' || (self.new_line && c.is_whitespace());
218-
return Some((new_line, c));
218+
Some((new_line, c))
219219
} else if self.line == self.docs.len() - 1 {
220-
return None;
220+
None
221221
} else {
222222
self.new_line = true;
223223
self.reset = true;
224224
self.pos += 1;
225-
return Some((true, '\n'));
225+
Some((true, '\n'))
226226
}
227+
} else {
228+
None
227229
}
228-
229-
None
230230
}
231231
}
232232

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,6 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
324324
enum_variants::STUTTER,
325325
if_not_else::IF_NOT_ELSE,
326326
items_after_statements::ITEMS_AFTER_STATEMENTS,
327-
loops::NEVER_LOOP,
328327
matches::SINGLE_MATCH_ELSE,
329328
mem_forget::MEM_FORGET,
330329
methods::FILTER_MAP,
@@ -420,6 +419,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
420419
loops::FOR_LOOP_OVER_RESULT,
421420
loops::ITER_NEXT_LOOP,
422421
loops::NEEDLESS_RANGE_LOOP,
422+
loops::NEVER_LOOP,
423423
loops::REVERSE_RANGE_LOOP,
424424
loops::UNUSED_COLLECT,
425425
loops::WHILE_LET_LOOP,

clippy_lints/src/loops.rs

Lines changed: 94 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -286,24 +286,22 @@ 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-
/// or `return`.
289+
/// **What it does:** Checks for loops that will always `break`, `return` or
290+
/// `continue` an outer loop.
291291
///
292292
/// **Why is this bad?** This loop never loops, all it does is obfuscating the
293293
/// code.
294294
///
295-
/// **Known problems:** Ignores `continue` statements in the loop that create
296-
/// nontrivial control flow. Therefore set to `Allow` by default.
297-
/// See https://github.com/Manishearth/rust-clippy/issues/1586
295+
/// **Known problems:** None
298296
///
299297
/// **Example:**
300298
/// ```rust
301299
/// loop { ..; break; }
302300
/// ```
303301
declare_lint! {
304302
pub NEVER_LOOP,
305-
Allow,
306-
"any loop with an unconditional `break` or `return` statement"
303+
Warn,
304+
"any loop that will always `break` or `return`"
307305
}
308306

309307
#[derive(Copy, Clone)]
@@ -333,6 +331,18 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
333331
if let Some((pat, arg, body)) = higher::for_loop(expr) {
334332
check_for_loop(cx, pat, arg, body, expr);
335333
}
334+
335+
// check for never_loop
336+
match expr.node {
337+
ExprWhile(_, ref block, _) |
338+
ExprLoop(ref block, _, _) => {
339+
if never_loop(block, &expr.id) {
340+
span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops");
341+
}
342+
},
343+
_ => (),
344+
}
345+
336346
// check for `loop { if let {} else break }` that could be `while let`
337347
// (also matches an explicit "match" instead of "if let")
338348
// (even if the "match" or "if let" is used for declaration)
@@ -345,9 +355,6 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
345355
"empty `loop {}` detected. You may want to either use `panic!()` or add \
346356
`std::thread::sleep(..);` to the loop body.");
347357
}
348-
if never_loop_block(block) {
349-
span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops");
350-
}
351358

352359
// extract the expression from the first statement (if any) in a block
353360
let inner_stmt_expr = extract_expr_from_first_stmt(block);
@@ -425,47 +432,103 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
425432
}
426433
}
427434

428-
fn never_loop_block(block: &Block) -> bool {
429-
block.stmts.iter().any(never_loop_stmt) || block.expr.as_ref().map_or(false, |e| never_loop_expr(e))
435+
fn never_loop(block: &Block, id: &NodeId) -> bool {
436+
!contains_continue_block(block, id) && loop_exit_block(block)
437+
}
438+
439+
fn contains_continue_block(block: &Block, dest: &NodeId) -> bool {
440+
block.stmts.iter().any(|e| contains_continue_stmt(e, dest))
441+
|| block.expr.as_ref().map_or(false, |e| contains_continue_expr(e, dest))
430442
}
431443

432-
fn never_loop_stmt(stmt: &Stmt) -> bool {
444+
fn contains_continue_stmt(stmt: &Stmt, dest: &NodeId) -> bool {
433445
match stmt.node {
434446
StmtSemi(ref e, _) |
435-
StmtExpr(ref e, _) => never_loop_expr(e),
436-
StmtDecl(ref d, _) => never_loop_decl(d),
447+
StmtExpr(ref e, _) => contains_continue_expr(e, dest),
448+
StmtDecl(ref d, _) => contains_continue_decl(d, dest),
437449
}
438450
}
439451

440-
fn never_loop_decl(decl: &Decl) -> bool {
441-
if let DeclLocal(ref local) = decl.node {
442-
local.init.as_ref().map_or(false, |e| never_loop_expr(e))
443-
} else {
444-
false
452+
fn contains_continue_decl(decl: &Decl, dest: &NodeId) -> bool {
453+
match decl.node {
454+
DeclLocal(ref local) => local.init.as_ref().map_or(false, |e| contains_continue_expr(e, dest)),
455+
_ => false
445456
}
446457
}
447458

448-
fn never_loop_expr(expr: &Expr) -> bool {
459+
fn contains_continue_expr(expr: &Expr, dest: &NodeId) -> bool {
449460
match expr.node {
450-
ExprBreak(..) | ExprRet(..) => true,
451461
ExprBox(ref e) |
452462
ExprUnary(_, ref e) |
453-
ExprBinary(_, ref e, _) | // because short-circuiting
454463
ExprCast(ref e, _) |
455464
ExprType(ref e, _) |
456465
ExprField(ref e, _) |
457466
ExprTupField(ref e, _) |
458-
ExprRepeat(ref e, _) |
459-
ExprAddrOf(_, ref e) => never_loop_expr(e),
467+
ExprAddrOf(_, ref e) |
468+
ExprRepeat(ref e, _) => contains_continue_expr(e, dest),
469+
ExprArray(ref es) |
470+
ExprMethodCall(_, _, ref es) |
471+
ExprTup(ref es) => es.iter().any(|e| contains_continue_expr(e, dest)),
472+
ExprCall(ref e, ref es) => contains_continue_expr(e, dest) || es.iter().any(|e| contains_continue_expr(e, dest)),
473+
ExprBinary(_, ref e1, ref e2) |
460474
ExprAssign(ref e1, ref e2) |
461475
ExprAssignOp(_, ref e1, ref e2) |
462-
ExprIndex(ref e1, ref e2) => never_loop_expr(e1) || never_loop_expr(e2),
476+
ExprIndex(ref e1, ref e2) => [e1, e2].iter().any(|e| contains_continue_expr(e, dest)),
477+
ExprIf(ref e, ref e2, ref e3) => [e, e2].iter().chain(e3.as_ref().iter()).any(|e| contains_continue_expr(e, dest)),
478+
ExprWhile(ref e, ref b, _) => contains_continue_expr(e, dest) || contains_continue_block(b, dest),
479+
ExprMatch(ref e, ref arms, _) => contains_continue_expr(e, dest) || arms.iter().any(|a| contains_continue_expr(&a.body, dest)),
480+
ExprBlock(ref block) => contains_continue_block(block, dest),
481+
ExprStruct(_, _, ref base) => base.as_ref().map_or(false, |e| contains_continue_expr(e, dest)),
482+
ExprAgain(d) => d.target_id.opt_id().map_or(false, |id| id == *dest),
483+
_ => false,
484+
}
485+
}
486+
487+
fn loop_exit_block(block: &Block) -> bool {
488+
block.stmts.iter().any(|e| loop_exit_stmt(e))
489+
|| block.expr.as_ref().map_or(false, |e| loop_exit_expr(e))
490+
}
491+
492+
fn loop_exit_stmt(stmt: &Stmt) -> bool {
493+
match stmt.node {
494+
StmtSemi(ref e, _) |
495+
StmtExpr(ref e, _) => loop_exit_expr(e),
496+
StmtDecl(ref d, _) => loop_exit_decl(d),
497+
}
498+
}
499+
500+
fn loop_exit_decl(decl: &Decl) -> bool {
501+
match decl.node {
502+
DeclLocal(ref local) => local.init.as_ref().map_or(false, |e| loop_exit_expr(e)),
503+
_ => false
504+
}
505+
}
506+
507+
fn loop_exit_expr(expr: &Expr) -> bool {
508+
match expr.node {
509+
ExprBox(ref e) |
510+
ExprUnary(_, ref e) |
511+
ExprCast(ref e, _) |
512+
ExprType(ref e, _) |
513+
ExprField(ref e, _) |
514+
ExprTupField(ref e, _) |
515+
ExprAddrOf(_, ref e) |
516+
ExprRepeat(ref e, _) => loop_exit_expr(e),
463517
ExprArray(ref es) |
464-
ExprTup(ref es) |
465-
ExprMethodCall(_, _, ref es) => es.iter().any(|e| never_loop_expr(e)),
466-
ExprCall(ref e, ref es) => never_loop_expr(e) || es.iter().any(|e| never_loop_expr(e)),
467-
ExprBlock(ref block) => never_loop_block(block),
468-
ExprStruct(_, _, ref base) => base.as_ref().map_or(false, |e| never_loop_expr(e)),
518+
ExprMethodCall(_, _, ref es) |
519+
ExprTup(ref es) => es.iter().any(|e| loop_exit_expr(e)),
520+
ExprCall(ref e, ref es) => loop_exit_expr(e) || es.iter().any(|e| loop_exit_expr(e)),
521+
ExprBinary(_, ref e1, ref e2) |
522+
ExprAssign(ref e1, ref e2) |
523+
ExprAssignOp(_, ref e1, ref e2) |
524+
ExprIndex(ref e1, ref e2) => [e1, e2].iter().any(|e| loop_exit_expr(e)),
525+
ExprIf(ref e, ref e2, ref e3) => loop_exit_expr(e) || e3.as_ref().map_or(false, |e| loop_exit_expr(e)) && loop_exit_expr(e2),
526+
ExprWhile(ref e, ref b, _) => loop_exit_expr(e) || loop_exit_block(b),
527+
ExprMatch(ref e, ref arms, _) => loop_exit_expr(e) || arms.iter().all(|a| loop_exit_expr(&a.body)),
528+
ExprBlock(ref b) => loop_exit_block(b),
529+
ExprBreak(_, _) |
530+
ExprAgain(_) |
531+
ExprRet(_) => true,
469532
_ => false,
470533
}
471534
}

clippy_tests/examples/for_loop.stderr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`
5353
= note: `-D for-loop-over-result` implied by `-D warnings`
5454
= help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")`
5555

56+
error: this loop never actually loops
57+
--> for_loop.rs:52:5
58+
|
59+
52 | / while let Some(x) = option {
60+
53 | | println!("{}", x);
61+
54 | | break;
62+
55 | | }
63+
| |_____^
64+
|
65+
= note: `-D never-loop` implied by `-D warnings`
66+
67+
error: this loop never actually loops
68+
--> for_loop.rs:58:5
69+
|
70+
58 | / while let Ok(x) = result {
71+
59 | | println!("{}", x);
72+
60 | | break;
73+
61 | | }
74+
| |_____^
75+
|
76+
= note: `-D never-loop` implied by `-D warnings`
77+
5678
error: the loop variable `i` is only used to index `vec`.
5779
--> for_loop.rs:84:5
5880
|

0 commit comments

Comments
 (0)