Skip to content

Commit 58002b0

Browse files
authored
Merge pull request #2119 from camsteffen/never_loop
Another never_loop fix
2 parents 2b491d5 + 752900c commit 58002b0

File tree

3 files changed

+99
-100
lines changed

3 files changed

+99
-100
lines changed

clippy_lints/src/loops.rs

Lines changed: 73 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use rustc::ty::{self, Ty};
1616
use rustc::ty::subst::{Subst, Substs};
1717
use rustc_const_eval::ConstContext;
1818
use std::collections::{HashMap, HashSet};
19+
use std::iter::{Iterator, once};
1920
use syntax::ast;
2021
use syntax::codemap::Span;
2122
use utils::sugg;
@@ -378,7 +379,12 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
378379
match expr.node {
379380
ExprWhile(_, ref block, _) |
380381
ExprLoop(ref block, _, _) => {
381-
if never_loop(block, expr.id) {
382+
let mut state = NeverLoopState {
383+
breaks: HashSet::new(),
384+
continues: HashSet::new(),
385+
};
386+
let may_complete = never_loop_block(block, &mut state);
387+
if !may_complete && !state.continues.contains(&expr.id) {
382388
span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops");
383389
}
384390
},
@@ -485,140 +491,107 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
485491
}
486492
}
487493

488-
fn never_loop(block: &Block, id: NodeId) -> bool {
489-
!contains_continue_block(block, Some(id)) && loop_exit_block(block, &mut vec![id])
494+
struct NeverLoopState {
495+
breaks: HashSet<NodeId>,
496+
continues: HashSet<NodeId>,
490497
}
491498

492-
fn contains_continue_block(block: &Block, dest: Option<NodeId>) -> bool {
493-
block.stmts.iter().any(|e| contains_continue_stmt(e, dest)) ||
494-
block.expr.as_ref().map_or(
495-
false,
496-
|e| contains_continue_expr(e, dest),
497-
)
499+
fn never_loop_block(block: &Block, state: &mut NeverLoopState) -> bool {
500+
let stmts = block.stmts.iter().map(stmt_to_expr);
501+
let expr = once(block.expr.as_ref().map(|p| &**p));
502+
let mut iter = stmts.chain(expr).filter_map(|e| e);
503+
never_loop_expr_seq(&mut iter, state)
498504
}
499505

500-
fn contains_continue_stmt(stmt: &Stmt, dest: Option<NodeId>) -> bool {
506+
fn stmt_to_expr(stmt: &Stmt) -> Option<&Expr> {
501507
match stmt.node {
502-
StmtSemi(ref e, _) |
503-
StmtExpr(ref e, _) => contains_continue_expr(e, dest),
504-
StmtDecl(ref d, _) => contains_continue_decl(d, dest),
508+
StmtSemi(ref e, ..) |
509+
StmtExpr(ref e, ..) => Some(e),
510+
StmtDecl(ref d, ..) => decl_to_expr(d),
505511
}
506512
}
507513

508-
fn contains_continue_decl(decl: &Decl, dest: Option<NodeId>) -> bool {
514+
fn decl_to_expr(decl: &Decl) -> Option<&Expr> {
509515
match decl.node {
510-
DeclLocal(ref local) => {
511-
local.init.as_ref().map_or(
512-
false,
513-
|e| contains_continue_expr(e, dest),
514-
)
515-
},
516-
_ => false,
516+
DeclLocal(ref local) => local.init.as_ref().map(|p| &**p),
517+
_ => None,
517518
}
518519
}
519520

520-
fn contains_continue_expr(expr: &Expr, dest: Option<NodeId>) -> bool {
521+
fn never_loop_expr(expr: &Expr, state: &mut NeverLoopState) -> bool {
521522
match expr.node {
522-
ExprRet(Some(ref e)) |
523523
ExprBox(ref e) |
524524
ExprUnary(_, ref e) |
525525
ExprCast(ref e, _) |
526526
ExprType(ref e, _) |
527527
ExprField(ref e, _) |
528528
ExprTupField(ref e, _) |
529529
ExprAddrOf(_, ref e) |
530-
ExprRepeat(ref e, _) => contains_continue_expr(e, dest),
530+
ExprRepeat(ref e, _) => never_loop_expr(e, state),
531531
ExprArray(ref es) |
532532
ExprMethodCall(_, _, ref es) |
533-
ExprTup(ref es) => es.iter().any(|e| contains_continue_expr(e, dest)),
534-
ExprCall(ref e, ref es) => {
535-
contains_continue_expr(e, dest) || es.iter().any(|e| contains_continue_expr(e, dest))
536-
},
533+
ExprTup(ref es) => never_loop_expr_seq(&mut es.iter(), state),
534+
ExprCall(ref e, ref es) => never_loop_expr_seq(&mut once(&**e).chain(es.iter()), state),
537535
ExprBinary(_, ref e1, ref e2) |
538536
ExprAssign(ref e1, ref e2) |
539537
ExprAssignOp(_, ref e1, ref e2) |
540-
ExprIndex(ref e1, ref e2) => [e1, e2].iter().any(|e| contains_continue_expr(e, dest)),
538+
ExprIndex(ref e1, ref e2) => never_loop_expr_seq(&mut [&**e1, &**e2].iter().cloned(), state),
541539
ExprIf(ref e, ref e2, ref e3) => {
542-
[e, e2].iter().chain(e3.as_ref().iter()).any(|e| {
543-
contains_continue_expr(e, dest)
544-
})
540+
let e1 = never_loop_expr(e, state);
541+
let e2 = never_loop_expr(e2, state);
542+
match *e3 {
543+
Some(ref e3) => {
544+
let e3 = never_loop_expr(e3, state);
545+
e1 && (e2 || e3)
546+
},
547+
None => e1,
548+
}
549+
},
550+
ExprLoop(ref b, _, _) => {
551+
let block_may_complete = never_loop_block(b, state);
552+
let has_break = state.breaks.remove(&expr.id);
553+
state.continues.remove(&expr.id);
554+
block_may_complete || has_break
555+
},
556+
ExprWhile(ref e, ref b, _) => {
557+
let e = never_loop_expr(e, state);
558+
let block_may_complete = never_loop_block(b, state);
559+
let has_break = state.breaks.remove(&expr.id);
560+
let has_continue = state.continues.remove(&expr.id);
561+
e && (block_may_complete || has_break || has_continue)
545562
},
546-
ExprWhile(ref e, ref b, _) => contains_continue_expr(e, dest) || contains_continue_block(b, dest),
547563
ExprMatch(ref e, ref arms, _) => {
548-
contains_continue_expr(e, dest) || arms.iter().any(|a| contains_continue_expr(&a.body, dest))
564+
let e = never_loop_expr(e, state);
565+
let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), state);
566+
e && arms
549567
},
550-
ExprBlock(ref block) |
551-
ExprLoop(ref block, ..) => contains_continue_block(block, dest),
552-
ExprStruct(_, _, ref base) => {
553-
base.as_ref().map_or(
554-
false,
555-
|e| contains_continue_expr(e, dest),
556-
)
568+
ExprBlock(ref b) => never_loop_block(b, state),
569+
ExprAgain(d) => {
570+
let id = d.target_id.opt_id().expect("target id can only be missing in the presence of compilation errors");
571+
state.continues.insert(id);
572+
false
557573
},
558-
ExprAgain(d) => dest.map_or(true, |dest| d.target_id.opt_id().map_or(false, |id| id == dest)),
559-
_ => false,
560-
}
561-
}
562-
563-
fn loop_exit_block(block: &Block, loops: &mut Vec<NodeId>) -> bool {
564-
block.stmts.iter().take_while(|s| !contains_continue_stmt(s, None)).any(|s| loop_exit_stmt(s, loops))
565-
|| block.expr.as_ref().map_or(false, |e| loop_exit_expr(e, loops))
566-
}
567-
568-
fn loop_exit_stmt(stmt: &Stmt, loops: &mut Vec<NodeId>) -> bool {
569-
match stmt.node {
570-
StmtSemi(ref e, _) |
571-
StmtExpr(ref e, _) => loop_exit_expr(e, loops),
572-
StmtDecl(ref d, _) => loop_exit_decl(d, loops),
574+
ExprBreak(d, _) => {
575+
let id = d.target_id.opt_id().expect("target id can only be missing in the presence of compilation errors");
576+
state.breaks.insert(id);
577+
false
578+
},
579+
ExprRet(ref e) => {
580+
if let Some(ref e) = *e {
581+
never_loop_expr(e, state);
582+
}
583+
false
584+
},
585+
_ => true,
573586
}
574587
}
575588

576-
fn loop_exit_decl(decl: &Decl, loops: &mut Vec<NodeId>) -> bool {
577-
match decl.node {
578-
DeclLocal(ref local) => local.init.as_ref().map_or(false, |e| loop_exit_expr(e, loops)),
579-
_ => false,
580-
}
589+
fn never_loop_expr_seq<'a, T: Iterator<Item=&'a Expr>>(es: &mut T, state: &mut NeverLoopState) -> bool {
590+
es.map(|e| never_loop_expr(e, state)).fold(true, |a, b| a && b)
581591
}
582592

583-
fn loop_exit_expr(expr: &Expr, loops: &mut Vec<NodeId>) -> bool {
584-
match expr.node {
585-
ExprBox(ref e) |
586-
ExprUnary(_, ref e) |
587-
ExprCast(ref e, _) |
588-
ExprType(ref e, _) |
589-
ExprField(ref e, _) |
590-
ExprTupField(ref e, _) |
591-
ExprAddrOf(_, ref e) |
592-
ExprRepeat(ref e, _) => loop_exit_expr(e, loops),
593-
ExprArray(ref es) |
594-
ExprMethodCall(_, _, ref es) |
595-
ExprTup(ref es) => es.iter().any(|e| loop_exit_expr(e, loops)),
596-
ExprCall(ref e, ref es) => loop_exit_expr(e, loops) || es.iter().any(|e| loop_exit_expr(e, loops)),
597-
ExprBinary(_, ref e1, ref e2) |
598-
ExprAssign(ref e1, ref e2) |
599-
ExprAssignOp(_, ref e1, ref e2) |
600-
ExprIndex(ref e1, ref e2) => [e1, e2].iter().any(|e| loop_exit_expr(e, loops)),
601-
ExprIf(ref e, ref e2, ref e3) => loop_exit_expr(e, loops)
602-
|| e3.as_ref().map_or(false, |e3| loop_exit_expr(e3, loops)) && loop_exit_expr(e2, loops),
603-
ExprLoop(ref b, _, _) => {
604-
loops.push(expr.id);
605-
let val = loop_exit_block(b, loops);
606-
loops.pop();
607-
val
608-
},
609-
ExprWhile(ref e, ref b, _) => {
610-
loops.push(expr.id);
611-
let val = loop_exit_expr(e, loops) || loop_exit_block(b, loops);
612-
loops.pop();
613-
val
614-
},
615-
ExprMatch(ref e, ref arms, _) => loop_exit_expr(e, loops) || arms.iter().all(|a| loop_exit_expr(&a.body, loops)),
616-
ExprBlock(ref b) => loop_exit_block(b, loops),
617-
ExprAgain(d) => d.target_id.opt_id().map_or(false, |id| loops.iter().skip(1).all(|&id2| id != id2)),
618-
ExprBreak(d, _) => d.target_id.opt_id().map_or(false, |id| loops[0] == id),
619-
ExprRet(_) => true,
620-
_ => false,
621-
}
593+
fn never_loop_expr_branch<'a, T: Iterator<Item=&'a Expr>>(e: &mut T, state: &mut NeverLoopState) -> bool {
594+
e.map(|e| never_loop_expr(e, state)).fold(false, |a, b| a || b)
622595
}
623596

624597
fn check_for_loop<'a, 'tcx>(

tests/ui/never_loop.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ pub fn test13() {
139139
}
140140
}
141141

142+
pub fn test14() {
143+
let mut a = true;
144+
'outer: while a { // never loops
145+
while a {
146+
if a {
147+
a = false;
148+
continue
149+
}
150+
}
151+
break 'outer;
152+
}
153+
}
154+
142155
fn main() {
143156
test1();
144157
test2();
@@ -153,5 +166,6 @@ fn main() {
153166
test11(|| 0);
154167
test12(true, false);
155168
test13();
169+
test14();
156170
}
157171

tests/ui/never_loop.stderr

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,15 @@ error: this loop never actually loops
6868
103 | | }
6969
| |_____^
7070

71+
error: this loop never actually loops
72+
--> $DIR/never_loop.rs:144:5
73+
|
74+
144 | / 'outer: while a { // never loops
75+
145 | | while a {
76+
146 | | if a {
77+
147 | | a = false;
78+
... |
79+
151 | | break 'outer;
80+
152 | | }
81+
| |_____^
82+

0 commit comments

Comments
 (0)