Skip to content

Commit 998cbd1

Browse files
committed
new lint: using for i in 0..x { .. vec[i] .. } instead of iterator (fixes rust-lang#3)
1 parent bbe0342 commit 998cbd1

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod unicode;
3232
pub mod strings;
3333
pub mod methods;
3434
pub mod returns;
35+
pub mod loops;
3536

3637
#[plugin_registrar]
3738
pub fn plugin_registrar(reg: &mut Registry) {
@@ -59,6 +60,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
5960
reg.register_lint_pass(box returns::ReturnPass as LintPassObject);
6061
reg.register_lint_pass(box methods::MethodsPass as LintPassObject);
6162
reg.register_lint_pass(box types::LetPass as LintPassObject);
63+
reg.register_lint_pass(box loops::LoopsPass as LintPassObject);
6264

6365
reg.register_lint_group("clippy", vec![types::BOX_VEC, types::LINKEDLIST,
6466
misc::SINGLE_MATCH,
@@ -86,5 +88,6 @@ pub fn plugin_registrar(reg: &mut Registry) {
8688
methods::STR_TO_STRING,
8789
methods::STRING_TO_STRING,
8890
types::LET_UNIT_VALUE,
91+
loops::NEEDLESS_RANGE_LOOP,
8992
]);
9093
}

src/loops.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use rustc::lint::*;
2+
use syntax::ast::*;
3+
use syntax::visit::{Visitor, walk_expr};
4+
use std::collections::HashSet;
5+
6+
use utils::{span_lint, get_parent_expr};
7+
8+
declare_lint!{ pub NEEDLESS_RANGE_LOOP, Warn,
9+
"Warn about looping over a range of indices if a normal iterator would do" }
10+
11+
#[derive(Copy, Clone)]
12+
pub struct LoopsPass;
13+
14+
impl LintPass for LoopsPass {
15+
fn get_lints(&self) -> LintArray {
16+
lint_array!(NEEDLESS_RANGE_LOOP)
17+
}
18+
19+
fn check_expr(&mut self, cx: &Context, expr: &Expr) {
20+
if let Some((pat, arg, body)) = recover_for_loop(expr) {
21+
// the var must be a single name
22+
if let PatIdent(_, ref ident, _) = pat.node {
23+
// the iteratee must be a range literal
24+
if let ExprRange(_, _) = arg.node {
25+
let mut visitor = VarVisitor { cx: cx, var: ident.node.name,
26+
indexed: HashSet::new(), nonindex: false };
27+
walk_expr(&mut visitor, body);
28+
// linting condition: we only indexed with the var, and only one variable
29+
if !visitor.nonindex && visitor.indexed.len() == 1 {
30+
let indexed = visitor.indexed.into_iter().next().unwrap();
31+
span_lint(cx, NEEDLESS_RANGE_LOOP, expr.span, &format!(
32+
"the loop variable `{}` is only used to index `{}`. \
33+
Consider using `for item in {}` or similar iterators.",
34+
ident.node.name.as_str(), indexed.as_str(), indexed.as_str()));
35+
}
36+
}
37+
}
38+
}
39+
}
40+
}
41+
42+
/// Recover the essential nodes of a desugared for loop:
43+
/// `for pat in arg { body }` becomes `(pat, arg, body)`.
44+
fn recover_for_loop<'a>(expr: &'a Expr) -> Option<(&'a Pat, &'a Expr, &'a Expr)> {
45+
if_chain! {
46+
[
47+
let ExprMatch(ref iterexpr, ref arms, _) = expr.node,
48+
let ExprCall(_, ref iterargs) = iterexpr.node,
49+
iterargs.len() == 1,
50+
arms.len() == 1 && arms[0].guard.is_none(),
51+
let ExprLoop(ref block, _) = arms[0].body.node,
52+
block.stmts.is_empty(),
53+
let Some(ref loopexpr) = block.expr,
54+
let ExprMatch(_, ref innerarms, MatchSource::ForLoopDesugar) = loopexpr.node,
55+
innerarms.len() == 2 && innerarms[0].pats.len() == 1,
56+
let PatEnum(_, Some(ref somepats)) = innerarms[0].pats[0].node,
57+
somepats.len() == 1
58+
], {
59+
return Some((&*somepats[0],
60+
&*iterargs[0],
61+
&*innerarms[0].body));
62+
}
63+
}
64+
None
65+
}
66+
67+
struct VarVisitor<'v, 't: 'v> {
68+
cx: &'v Context<'v, 't>, // context reference
69+
var: Name, // var name to look for as index
70+
indexed: HashSet<Name>, // indexed variables
71+
nonindex: bool, // has the var been used otherwise?
72+
}
73+
74+
impl<'v, 't> Visitor<'v> for VarVisitor<'v, 't> {
75+
fn visit_expr(&mut self, expr: &'v Expr) {
76+
if let ExprPath(None, ref path) = expr.node {
77+
if path.segments.len() == 1 && path.segments[0].identifier.name == self.var {
78+
// we are referencing our variable! now check if it's as an index
79+
if_chain! {
80+
[
81+
let Some(parexpr) = get_parent_expr(self.cx, expr),
82+
let ExprIndex(ref seqexpr, _) = parexpr.node,
83+
let ExprPath(None, ref seqvar) = seqexpr.node,
84+
seqvar.segments.len() == 1
85+
], {
86+
self.indexed.insert(seqvar.segments[0].identifier.name);
87+
return; // no need to walk further
88+
}
89+
}
90+
// we are not indexing anything, record that
91+
self.nonindex = true;
92+
return;
93+
}
94+
}
95+
walk_expr(self, expr);
96+
}
97+
}

tests/compile-fail/for_loop.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
4+
#[deny(needless_range_loop)]
5+
fn main() {
6+
let vec = vec![1, 2, 3, 4];
7+
for i in 0..vec.len() { //~ERROR the loop variable `i` is only used to index `vec`.
8+
println!("{}", vec[i]);
9+
}
10+
for i in 0..vec.len() { // not an error, using i outside of indexing
11+
println!("{} {}", vec[i], i);
12+
}
13+
}

0 commit comments

Comments
 (0)