Skip to content

Commit aee42f7

Browse files
committed
Merge pull request #396 from fhartwig/while-let-for
Suggest for loop instead of while-let when looping over iterators
2 parents 3fe6ca9 + c5b6fda commit aee42f7

File tree

5 files changed

+130
-4
lines changed

5 files changed

+130
-4
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
66
[Jump to usage instructions](#usage)
77

88
##Lints
9-
There are 68 lints included in this crate:
9+
There are 69 lints included in this crate:
1010

1111
name | default | meaning
1212
-------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -74,6 +74,7 @@ name
7474
[unstable_as_slice](https://github.com/Manishearth/rust-clippy/wiki#unstable_as_slice) | warn | as_slice is not stable and can be replaced by & v[..]see https://github.com/rust-lang/rust/issues/27729
7575
[unused_collect](https://github.com/Manishearth/rust-clippy/wiki#unused_collect) | warn | `collect()`ing an iterator without using the result; this is usually better written as a for loop
7676
[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
77+
[while_let_on_iterator](https://github.com/Manishearth/rust-clippy/wiki#while_let_on_iterator) | warn | using a while-let loop instead of a for loop on an iterator
7778
[wrong_pub_self_convention](https://github.com/Manishearth/rust-clippy/wiki#wrong_pub_self_convention) | allow | defining a public method named with an established prefix (like "into_") that takes `self` with the wrong convention
7879
[wrong_self_convention](https://github.com/Manishearth/rust-clippy/wiki#wrong_self_convention) | warn | defining a method named with an established prefix (like "into_") that takes `self` with the wrong convention
7980
[zero_divided_by_zero](https://github.com/Manishearth/rust-clippy/wiki#zero_divided_by_zero) | warn | usage of `0.0 / 0.0` to obtain NaN instead of std::f32::NaN or std::f64::NaN

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
138138
loops::REVERSE_RANGE_LOOP,
139139
loops::UNUSED_COLLECT,
140140
loops::WHILE_LET_LOOP,
141+
loops::WHILE_LET_ON_ITERATOR,
141142
matches::MATCH_BOOL,
142143
matches::MATCH_REF_PATS,
143144
matches::SINGLE_MATCH,

src/loops.rs

+68-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use std::collections::{HashSet,HashMap};
1111
use syntax::ast::Lit_::*;
1212

1313
use utils::{snippet, span_lint, get_parent_expr, match_trait_method, match_type,
14-
in_external_macro, expr_block, span_help_and_lint, is_integer_literal};
14+
in_external_macro, expr_block, span_help_and_lint, is_integer_literal,
15+
get_enclosing_block};
1516
use utils::{VEC_PATH, LL_PATH};
1617

1718
declare_lint!{ pub NEEDLESS_RANGE_LOOP, Warn,
@@ -38,14 +39,17 @@ declare_lint!{ pub EXPLICIT_COUNTER_LOOP, Warn,
3839

3940
declare_lint!{ pub EMPTY_LOOP, Warn, "empty `loop {}` detected" }
4041

42+
declare_lint!{ pub WHILE_LET_ON_ITERATOR, Warn, "using a while-let loop instead of a for loop on an iterator" }
43+
4144
#[derive(Copy, Clone)]
4245
pub struct LoopsPass;
4346

4447
impl LintPass for LoopsPass {
4548
fn get_lints(&self) -> LintArray {
4649
lint_array!(NEEDLESS_RANGE_LOOP, EXPLICIT_ITER_LOOP, ITER_NEXT_LOOP,
4750
WHILE_LET_LOOP, UNUSED_COLLECT, REVERSE_RANGE_LOOP,
48-
EXPLICIT_COUNTER_LOOP, EMPTY_LOOP)
51+
EXPLICIT_COUNTER_LOOP, EMPTY_LOOP,
52+
WHILE_LET_ON_ITERATOR)
4953
}
5054
}
5155

@@ -228,6 +232,28 @@ impl LateLintPass for LoopsPass {
228232
}
229233
}
230234
}
235+
if let ExprMatch(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.node {
236+
let pat = &arms[0].pats[0].node;
237+
if let (&PatEnum(ref path, Some(ref pat_args)),
238+
&ExprMethodCall(method_name, _, ref method_args)) =
239+
(pat, &match_expr.node) {
240+
let iter_expr = &method_args[0];
241+
if let Some(lhs_constructor) = path.segments.last() {
242+
if method_name.node.as_str() == "next" &&
243+
match_trait_method(cx, match_expr, &["core", "iter", "Iterator"]) &&
244+
lhs_constructor.identifier.name.as_str() == "Some" &&
245+
!is_iterator_used_after_while_let(cx, iter_expr) {
246+
let iterator = snippet(cx, method_args[0].span, "_");
247+
let loop_var = snippet(cx, pat_args[0].span, "_");
248+
span_help_and_lint(cx, WHILE_LET_ON_ITERATOR, expr.span,
249+
"this loop could be written as a `for` loop",
250+
&format!("try\nfor {} in {} {{...}}",
251+
loop_var,
252+
iterator));
253+
}
254+
}
255+
}
256+
}
231257
}
232258

233259
fn check_stmt(&mut self, cx: &LateContext, stmt: &Stmt) {
@@ -300,6 +326,46 @@ impl<'v, 't> Visitor<'v> for VarVisitor<'v, 't> {
300326
}
301327
}
302328

329+
fn is_iterator_used_after_while_let(cx: &LateContext, iter_expr: &Expr) -> bool {
330+
let def_id = match var_def_id(cx, iter_expr) {
331+
Some(id) => id,
332+
None => return false
333+
};
334+
let mut visitor = VarUsedAfterLoopVisitor {
335+
cx: cx,
336+
def_id: def_id,
337+
iter_expr_id: iter_expr.id,
338+
past_while_let: false,
339+
var_used_after_while_let: false
340+
};
341+
if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
342+
walk_block(&mut visitor, enclosing_block);
343+
}
344+
visitor.var_used_after_while_let
345+
}
346+
347+
struct VarUsedAfterLoopVisitor<'v, 't: 'v> {
348+
cx: &'v LateContext<'v, 't>,
349+
def_id: NodeId,
350+
iter_expr_id: NodeId,
351+
past_while_let: bool,
352+
var_used_after_while_let: bool
353+
}
354+
355+
impl <'v, 't> Visitor<'v> for VarUsedAfterLoopVisitor<'v, 't> {
356+
fn visit_expr(&mut self, expr: &'v Expr) {
357+
if self.past_while_let {
358+
if Some(self.def_id) == var_def_id(self.cx, expr) {
359+
self.var_used_after_while_let = true;
360+
}
361+
} else if self.iter_expr_id == expr.id {
362+
self.past_while_let = true;
363+
}
364+
walk_expr(self, expr);
365+
}
366+
}
367+
368+
303369
/// Return true if the type of expr is one that provides IntoIterator impls
304370
/// for &T and &mut T, such as Vec.
305371
fn is_ref_iterable_type(cx: &LateContext, e: &Expr) -> bool {

src/utils.rs

+14
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,20 @@ pub fn get_parent_expr<'c>(cx: &'c LateContext, e: &Expr) -> Option<&'c Expr> {
255255
if let NodeExpr(parent) = node { Some(parent) } else { None } )
256256
}
257257

258+
#[allow(needless_lifetimes)] // workaround for https://github.com/Manishearth/rust-clippy/issues/417
259+
pub fn get_enclosing_block<'c>(cx: &'c LateContext, node: NodeId) -> Option<&'c Block> {
260+
let map = &cx.tcx.map;
261+
let enclosing_node = map.get_enclosing_scope(node)
262+
.and_then(|enclosing_id| map.find(enclosing_id));
263+
if let Some(node) = enclosing_node {
264+
match node {
265+
NodeBlock(ref block) => Some(block),
266+
NodeItem(&Item{ node: ItemFn(_, _, _, _, _, ref block), .. }) => Some(block),
267+
_ => None
268+
}
269+
} else { None }
270+
}
271+
258272
#[cfg(not(feature="structured_logging"))]
259273
pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: Span, msg: &str) {
260274
cx.span_lint(lint, sp, msg);

tests/compile-fail/while_loop.rs

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![feature(plugin)]
22
#![plugin(clippy)]
33

4-
#![deny(while_let_loop, empty_loop)]
4+
#![deny(while_let_loop, empty_loop, while_let_on_iterator)]
55
#![allow(dead_code, unused)]
66

77
fn main() {
@@ -53,6 +53,50 @@ fn main() {
5353
while let Some(x) = y { // no error, obviously
5454
println!("{}", x);
5555
}
56+
57+
let mut iter = 1..20;
58+
while let Option::Some(x) = iter.next() { //~ERROR this loop could be written as a `for` loop
59+
println!("{}", x);
60+
}
61+
62+
let mut iter = 1..20;
63+
while let Some(x) = iter.next() { //~ERROR this loop could be written as a `for` loop
64+
println!("{}", x);
65+
}
66+
67+
let mut iter = 1..20;
68+
while let Some(_) = iter.next() {} //~ERROR this loop could be written as a `for` loop
69+
70+
let mut iter = 1..20;
71+
while let None = iter.next() {} // this is fine (if nonsensical)
72+
73+
let mut iter = 1..20;
74+
if let Some(x) = iter.next() { // also fine
75+
println!("{}", x)
76+
}
77+
78+
// the following shouldn't warn because it can't be written with a for loop
79+
let mut iter = 1u32..20;
80+
while let Some(x) = iter.next() {
81+
println!("next: {:?}", iter.next())
82+
}
83+
84+
// neither can this
85+
let mut iter = 1u32..20;
86+
while let Some(x) = iter.next() {
87+
println!("next: {:?}", iter.next());
88+
}
89+
90+
// or this
91+
let mut iter = 1u32..20;
92+
while let Some(x) = iter.next() {break;}
93+
println!("Remaining iter {:?}", iter);
94+
95+
// or this
96+
let mut iter = 1u32..20;
97+
while let Some(x) = iter.next() {
98+
iter = 1..20;
99+
}
56100
}
57101

58102
// regression test (#360)

0 commit comments

Comments
 (0)