|
| 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 one variable |
| 29 | + if visitor.indexed.len() == 1 { |
| 30 | + let indexed = visitor.indexed.into_iter().next().unwrap(); |
| 31 | + if visitor.nonindex { |
| 32 | + span_lint(cx, NEEDLESS_RANGE_LOOP, expr.span, &format!( |
| 33 | + "the loop variable `{}` is used to index `{}`. Consider using \ |
| 34 | + `for ({}, item) in {}.iter().enumerate()` or similar iterators.", |
| 35 | + ident.node.name.as_str(), indexed.as_str(), |
| 36 | + ident.node.name.as_str(), indexed.as_str())); |
| 37 | + } else { |
| 38 | + span_lint(cx, NEEDLESS_RANGE_LOOP, expr.span, &format!( |
| 39 | + "the loop variable `{}` is only used to index `{}`. \ |
| 40 | + Consider using `for item in &{}` or similar iterators.", |
| 41 | + ident.node.name.as_str(), indexed.as_str(), indexed.as_str())); |
| 42 | + } |
| 43 | + } |
| 44 | + } |
| 45 | + } |
| 46 | + } |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +/// Recover the essential nodes of a desugared for loop: |
| 51 | +/// `for pat in arg { body }` becomes `(pat, arg, body)`. |
| 52 | +fn recover_for_loop<'a>(expr: &'a Expr) -> Option<(&'a Pat, &'a Expr, &'a Expr)> { |
| 53 | + if_let_chain! { |
| 54 | + [ |
| 55 | + let ExprMatch(ref iterexpr, ref arms, _) = expr.node, |
| 56 | + let ExprCall(_, ref iterargs) = iterexpr.node, |
| 57 | + iterargs.len() == 1, |
| 58 | + arms.len() == 1 && arms[0].guard.is_none(), |
| 59 | + let ExprLoop(ref block, _) = arms[0].body.node, |
| 60 | + block.stmts.is_empty(), |
| 61 | + let Some(ref loopexpr) = block.expr, |
| 62 | + let ExprMatch(_, ref innerarms, MatchSource::ForLoopDesugar) = loopexpr.node, |
| 63 | + innerarms.len() == 2 && innerarms[0].pats.len() == 1, |
| 64 | + let PatEnum(_, Some(ref somepats)) = innerarms[0].pats[0].node, |
| 65 | + somepats.len() == 1 |
| 66 | + ], { |
| 67 | + return Some((&*somepats[0], |
| 68 | + &*iterargs[0], |
| 69 | + &*innerarms[0].body)); |
| 70 | + } |
| 71 | + } |
| 72 | + None |
| 73 | +} |
| 74 | + |
| 75 | +struct VarVisitor<'v, 't: 'v> { |
| 76 | + cx: &'v Context<'v, 't>, // context reference |
| 77 | + var: Name, // var name to look for as index |
| 78 | + indexed: HashSet<Name>, // indexed variables |
| 79 | + nonindex: bool, // has the var been used otherwise? |
| 80 | +} |
| 81 | + |
| 82 | +impl<'v, 't> Visitor<'v> for VarVisitor<'v, 't> { |
| 83 | + fn visit_expr(&mut self, expr: &'v Expr) { |
| 84 | + if let ExprPath(None, ref path) = expr.node { |
| 85 | + if path.segments.len() == 1 && path.segments[0].identifier.name == self.var { |
| 86 | + // we are referencing our variable! now check if it's as an index |
| 87 | + if_let_chain! { |
| 88 | + [ |
| 89 | + let Some(parexpr) = get_parent_expr(self.cx, expr), |
| 90 | + let ExprIndex(ref seqexpr, _) = parexpr.node, |
| 91 | + let ExprPath(None, ref seqvar) = seqexpr.node, |
| 92 | + seqvar.segments.len() == 1 |
| 93 | + ], { |
| 94 | + self.indexed.insert(seqvar.segments[0].identifier.name); |
| 95 | + return; // no need to walk further |
| 96 | + } |
| 97 | + } |
| 98 | + // we are not indexing anything, record that |
| 99 | + self.nonindex = true; |
| 100 | + return; |
| 101 | + } |
| 102 | + } |
| 103 | + walk_expr(self, expr); |
| 104 | + } |
| 105 | +} |
0 commit comments