2017-09-04 19:16:34 -05:00
use itertools ::Itertools ;
2016-02-07 11:10:03 -06:00
use reexport ::* ;
2016-04-07 10:46:48 -05:00
use rustc ::hir ::* ;
use rustc ::hir ::def ::Def ;
2017-09-05 04:33:04 -05:00
use rustc ::hir ::intravisit ::{ walk_block , walk_decl , walk_expr , walk_pat , walk_stmt , NestedVisitorMap , Visitor } ;
2017-08-02 17:41:46 -05:00
use rustc ::hir ::map ::Node ::{ NodeBlock , NodeExpr , NodeStmt } ;
2015-08-12 14:56:27 -05:00
use rustc ::lint ::* ;
2016-03-31 10:05:43 -05:00
use rustc ::middle ::const_val ::ConstVal ;
2017-09-04 09:10:36 -05:00
use rustc ::middle ::region ;
2017-06-10 21:57:25 -05:00
use rustc ::ty ::{ self , Ty } ;
2017-07-31 05:37:38 -05:00
use rustc ::ty ::subst ::{ Subst , Substs } ;
2017-01-13 10:04:56 -06:00
use rustc_const_eval ::ConstContext ;
2017-07-10 08:30:28 -05:00
use std ::collections ::{ HashMap , HashSet } ;
2016-03-07 09:55:12 -06:00
use syntax ::ast ;
2016-07-01 12:31:14 -05:00
use utils ::sugg ;
2017-09-13 08:34:04 -05:00
use utils ::const_to_u64 ;
2015-08-12 14:56:27 -05:00
2017-09-05 04:33:04 -05:00
use utils ::{ get_enclosing_block , get_parent_expr , higher , in_external_macro , is_integer_literal , is_refutable ,
2017-09-16 02:10:26 -05:00
last_path_segment , match_trait_method , match_type , match_var , multispan_sugg , snippet , snippet_opt ,
2017-09-04 19:16:34 -05:00
span_help_and_lint , span_lint , span_lint_and_sugg , span_lint_and_then } ;
2016-04-14 11:13:15 -05:00
use utils ::paths ;
2015-08-12 14:56:27 -05:00
2017-09-05 14:10:53 -05:00
/// **What it does:** Checks for for-loops that manually copy items between
2017-09-04 19:16:34 -05:00
/// slices that could be optimized by having a memcpy.
///
/// **Why is this bad?** It is not as fast as a memcpy.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// for i in 0..src.len() {
/// dst[i + 64] = src[i];
/// }
/// ```
declare_lint! {
pub MANUAL_MEMCPY ,
Warn ,
" manually copying items between slices "
}
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for looping over the range of `0..len` of some
/// collection just to get the values by index.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Why is this bad?** Just iterating the collection itself makes the intent
/// more clear and is probably faster.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** None.
2015-12-10 18:22:27 -06:00
///
/// **Example:**
2016-07-15 17:25:44 -05:00
/// ```rust
2015-12-10 18:22:27 -06:00
/// for i in 0..vec.len() {
/// println!("{}", vec[i]);
/// }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub NEEDLESS_RANGE_LOOP ,
Warn ,
" for-looping over a range of indices where an iterator over items would do "
}
2015-08-12 14:56:27 -05:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for loops on `x.iter()` where `&x` will do, and
/// suggests the latter.
2015-12-10 18:22:27 -06:00
///
/// **Why is this bad?** Readability.
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** False negatives. We currently only warn on some known
/// types.
2015-12-10 18:22:27 -06:00
///
2016-07-15 17:25:44 -05:00
/// **Example:**
/// ```rust
/// // with `y` a `Vec` or slice:
/// for x in y.iter() { .. }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub EXPLICIT_ITER_LOOP ,
Warn ,
" for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do "
}
2015-08-13 08:36:31 -05:00
2016-09-30 19:01:30 -05:00
/// **What it does:** Checks for loops on `y.into_iter()` where `y` will do, and
/// suggests the latter.
///
/// **Why is this bad?** Readability.
///
/// **Known problems:** None
///
/// **Example:**
/// ```rust
/// // with `y` a `Vec` or slice:
/// for x in y.into_iter() { .. }
/// ```
declare_lint! {
pub EXPLICIT_INTO_ITER_LOOP ,
Warn ,
" for-looping over `_.into_iter()` when `_` would do "
}
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for loops on `x.next()`.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Why is this bad?** `next()` returns either `Some(value)` if there was a
/// value, or `None` otherwise. The insidious thing is that `Option<_>`
/// implements `IntoIterator`, so that possibly one value will be iterated,
/// leading to some hard to find bugs. No one will want to write such code
/// [except to win an Underhanded Rust
2017-08-09 02:30:56 -05:00
/// Contest](https://www.reddit.
/// com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr).
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** None.
2015-12-10 18:22:27 -06:00
///
2016-07-15 17:25:44 -05:00
/// **Example:**
/// ```rust
/// for x in y.next() { .. }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub ITER_NEXT_LOOP ,
Warn ,
" for-looping over `_.next()` which is probably not intended "
}
2015-08-17 00:23:57 -05:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for `for` loops over `Option` values.
2016-01-29 01:34:09 -06:00
///
2017-08-09 02:30:56 -05:00
/// **Why is this bad?** Readability. This is more clearly expressed as an `if
/// let`.
2016-01-29 01:34:09 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** None.
2016-01-29 01:34:09 -06:00
///
2016-07-15 17:25:44 -05:00
/// **Example:**
/// ```rust
/// for x in option { .. }
/// ```
///
/// This should be
/// ```rust
/// if let Some(x) = option { .. }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub FOR_LOOP_OVER_OPTION ,
Warn ,
" for-looping over an `Option`, which is more clearly expressed as an `if let` "
}
2016-01-29 17:15:57 -06:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for `for` loops over `Result` values.
2016-01-29 17:15:57 -06:00
///
2017-08-09 02:30:56 -05:00
/// **Why is this bad?** Readability. This is more clearly expressed as an `if
/// let`.
2016-01-29 17:15:57 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** None.
2016-01-29 17:15:57 -06:00
///
2016-07-15 17:25:44 -05:00
/// **Example:**
/// ```rust
/// for x in result { .. }
/// ```
///
/// This should be
/// ```rust
/// if let Ok(x) = result { .. }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub FOR_LOOP_OVER_RESULT ,
Warn ,
" for-looping over a `Result`, which is more clearly expressed as an `if let` "
}
2016-01-29 01:34:09 -06:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Detects `loop + match` combinations that are easier
/// written as a `while let` loop.
2015-12-10 18:22:27 -06:00
///
2017-08-09 02:30:56 -05:00
/// **Why is this bad?** The `while let` loop is usually shorter and more
/// readable.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** Sometimes the wrong binding is displayed (#383).
2015-12-10 18:22:27 -06:00
///
/// **Example:**
2016-07-15 17:25:44 -05:00
/// ```rust
2015-12-10 18:22:27 -06:00
/// loop {
/// let x = match y {
/// Some(x) => x,
/// None => break,
/// }
/// // .. do something with x
/// }
/// // is easier written as
/// while let Some(x) = y {
/// // .. do something with x
/// }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub WHILE_LET_LOOP ,
Warn ,
2016-08-06 03:18:36 -05:00
" `loop { if let { ... } else break }`, which can be written as a `while let` loop "
2016-02-05 17:13:29 -06:00
}
2015-08-29 04:41:06 -05:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for using `collect()` on an iterator without using
/// the result.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Why is this bad?** It is more idiomatic to use a `for` loop over the
/// iterator instead.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** None.
2015-12-10 18:22:27 -06:00
///
2016-07-15 17:25:44 -05:00
/// **Example:**
/// ```rust
/// vec.iter().map(|x| /* some operation returning () */).collect::<Vec<_>>();
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub UNUSED_COLLECT ,
Warn ,
" `collect()`ing an iterator without using the result; this is usually better \
written as a for loop "
}
2015-08-30 06:10:59 -05:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for loops over ranges `x..y` where both `x` and `y`
/// are constant and `x` is greater or equal to `y`, unless the range is
/// reversed or has a negative `.step_by(_)`.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Why is it bad?** Such loops will either be skipped or loop until
/// wrap-around (in debug code, this may `panic!()`). Both options are probably
/// not intended.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** The lint cannot catch loops over dynamically defined
/// ranges. Doing this would require simulating all possible inputs and code
/// paths through the program, which would be complex and error-prone.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Example:**
2016-07-15 17:25:44 -05:00
/// ```rust
/// for x in 5..10-5 { .. } // oops, stray `-`
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub REVERSE_RANGE_LOOP ,
Warn ,
2016-08-06 03:18:36 -05:00
" iteration over an empty range, such as `10..0` or `5..5` "
2016-02-05 17:13:29 -06:00
}
2015-09-14 19:19:05 -05:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks `for` loops over slices with an explicit counter
/// and suggests the use of `.enumerate()`.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Why is it bad?** Not only is the version using `.enumerate()` more
/// readable, the compiler is able to remove bounds checks which can lead to
/// faster code in some instances.
2015-12-10 18:22:27 -06:00
///
/// **Known problems:** None.
///
2016-07-15 17:25:44 -05:00
/// **Example:**
/// ```rust
/// for i in 0..v.len() { foo(v[i]);
/// for i in 0..v.len() { bar(i, v[i]); }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub EXPLICIT_COUNTER_LOOP ,
Warn ,
" for-looping with an explicit counter when `_.enumerate()` would do "
}
2015-08-23 12:25:45 -05:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for empty `loop` expressions.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Why is this bad?** Those busy loops burn CPU cycles without doing
/// anything. Think of the environment and either block on something or at least
/// make the thread sleep for some microseconds.
2015-12-10 18:22:27 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** None.
2015-12-10 18:22:27 -06:00
///
2016-07-15 17:25:44 -05:00
/// **Example:**
/// ```rust
/// loop {}
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub EMPTY_LOOP ,
Warn ,
2016-08-06 03:18:36 -05:00
" empty `loop {}`, which should block or sleep "
2016-02-05 17:13:29 -06:00
}
2015-10-12 06:38:18 -05:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for `while let` expressions on iterators.
2015-12-14 15:16:56 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Why is this bad?** Readability. A simple `for` loop is shorter and conveys
/// the intent better.
2015-12-14 15:16:56 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** None.
2015-12-14 15:16:56 -06:00
///
2016-07-15 17:25:44 -05:00
/// **Example:**
/// ```rust
/// while let Some(val) = iter() { .. }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub WHILE_LET_ON_ITERATOR ,
Warn ,
" using a while-let loop instead of a for loop on an iterator "
}
2015-10-16 13:27:13 -05:00
2016-08-06 02:55:04 -05:00
/// **What it does:** Checks for iterating a map (`HashMap` or `BTreeMap`) and
/// ignoring either the keys or values.
2016-01-19 14:10:00 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Why is this bad?** Readability. There are `keys` and `values` methods that
/// can be used to express that don't need the values or keys.
2016-01-19 14:10:00 -06:00
///
2016-08-06 02:55:04 -05:00
/// **Known problems:** None.
2016-01-19 14:10:00 -06:00
///
/// **Example:**
/// ```rust
/// for (k, _) in &map { .. }
/// ```
2016-07-15 17:25:44 -05:00
///
2016-01-19 14:10:00 -06:00
/// could be replaced by
2016-07-15 17:25:44 -05:00
///
2016-01-19 14:10:00 -06:00
/// ```rust
/// for k in map.keys() { .. }
/// ```
2016-02-05 17:13:29 -06:00
declare_lint! {
pub FOR_KV_MAP ,
Warn ,
" looping on a map using `iter` when `keys` or `values` would do "
}
2016-01-19 14:10:00 -06:00
2017-05-30 20:44:01 -05:00
/// **What it does:** Checks for loops that will always `break`, `return` or
/// `continue` an outer loop.
2017-02-16 21:53:14 -06:00
///
/// **Why is this bad?** This loop never loops, all it does is obfuscating the
/// code.
///
2017-05-30 20:44:01 -05:00
/// **Known problems:** None
2017-02-16 21:53:14 -06:00
///
/// **Example:**
/// ```rust
/// loop { ..; break; }
/// ```
declare_lint! {
pub NEVER_LOOP ,
2017-05-30 20:44:01 -05:00
Warn ,
" any loop that will always `break` or `return` "
2017-02-16 21:53:14 -06:00
}
2017-08-10 18:21:43 -05:00
/// TODO: add documentation
declare_lint! {
pub MUT_RANGE_BOUND ,
Warn ,
" for loop over a range where one of the bounds is a mutable variable "
}
2017-07-31 17:58:26 -05:00
#[ derive(Copy, Clone) ]
pub struct Pass ;
2015-08-12 14:56:27 -05:00
2016-06-10 09:17:20 -05:00
impl LintPass for Pass {
2015-08-12 14:56:27 -05:00
fn get_lints ( & self ) -> LintArray {
2017-08-09 02:30:56 -05:00
lint_array! (
2017-09-04 19:16:34 -05:00
MANUAL_MEMCPY ,
2017-08-09 02:30:56 -05:00
NEEDLESS_RANGE_LOOP ,
EXPLICIT_ITER_LOOP ,
EXPLICIT_INTO_ITER_LOOP ,
ITER_NEXT_LOOP ,
FOR_LOOP_OVER_RESULT ,
FOR_LOOP_OVER_OPTION ,
WHILE_LET_LOOP ,
UNUSED_COLLECT ,
REVERSE_RANGE_LOOP ,
EXPLICIT_COUNTER_LOOP ,
EMPTY_LOOP ,
WHILE_LET_ON_ITERATOR ,
FOR_KV_MAP ,
2017-08-10 18:21:43 -05:00
NEVER_LOOP ,
MUT_RANGE_BOUND
2017-08-09 02:30:56 -05:00
)
2015-08-12 14:56:27 -05:00
}
2015-09-18 21:53:04 -05:00
}
2015-08-12 14:56:27 -05:00
2016-12-07 06:13:40 -06:00
impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for Pass {
fn check_expr ( & mut self , cx : & LateContext < ' a , ' tcx > , expr : & ' tcx Expr ) {
2016-06-29 17:08:43 -05:00
if let Some ( ( pat , arg , body ) ) = higher ::for_loop ( expr ) {
2015-11-18 05:35:18 -06:00
check_for_loop ( cx , pat , arg , body , expr ) ;
2015-08-12 14:56:27 -05:00
}
2017-05-31 23:22:15 -05:00
// check for never_loop
match expr . node {
2017-09-16 17:45:28 -05:00
ExprWhile ( _ , ref block , _ ) |
ExprLoop ( ref block , _ , _ ) = > {
if never_loop ( block , & expr . id ) {
span_lint ( cx , NEVER_LOOP , expr . span , " this loop never actually loops " ) ;
}
2017-05-31 23:22:15 -05:00
} ,
_ = > ( ) ,
}
2015-08-29 04:41:06 -05:00
// check for `loop { if let {} else break }` that could be `while let`
2015-09-27 02:39:42 -05:00
// (also matches an explicit "match" instead of "if let")
// (even if the "match" or "if let" is used for declaration)
2016-11-25 12:24:55 -06:00
if let ExprLoop ( ref block , _ , LoopSource ::Loop ) = expr . node {
2015-10-12 06:38:18 -05:00
// also check for empty `loop {}` statements
if block . stmts . is_empty ( ) & & block . expr . is_none ( ) {
2017-08-09 02:30:56 -05:00
span_lint (
cx ,
EMPTY_LOOP ,
expr . span ,
" empty `loop {}` detected. You may want to either use `panic!()` or add \
2017-09-05 04:33:04 -05:00
` std ::thread ::sleep ( .. ) ; ` to the loop body . " ,
2017-08-09 02:30:56 -05:00
) ;
2015-10-12 06:38:18 -05:00
}
2015-10-14 04:44:09 -05:00
2015-10-11 11:49:01 -05:00
// extract the expression from the first statement (if any) in a block
let inner_stmt_expr = extract_expr_from_first_stmt ( block ) ;
2015-12-14 07:30:09 -06:00
// or extract the first expression (if any) from the block
if let Some ( inner ) = inner_stmt_expr . or_else ( | | extract_first_expr ( block ) ) {
2015-08-29 04:41:06 -05:00
if let ExprMatch ( ref matchexpr , ref arms , ref source ) = inner . node {
// ensure "if let" compatible match structure
match * source {
2017-09-16 17:45:28 -05:00
MatchSource ::Normal |
MatchSource ::IfLetDesugar { .. } = > {
2016-12-20 11:21:30 -06:00
if arms . len ( ) = = 2 & & arms [ 0 ] . pats . len ( ) = = 1 & & arms [ 0 ] . guard . is_none ( ) & &
2017-08-09 02:30:56 -05:00
arms [ 1 ] . pats . len ( ) = = 1 & & arms [ 1 ] . guard . is_none ( ) & &
2017-09-05 15:28:30 -05:00
is_simple_break_expr ( & arms [ 1 ] . body )
2017-08-09 02:30:56 -05:00
{
2016-01-03 22:26:12 -06:00
if in_external_macro ( cx , expr . span ) {
return ;
}
2016-02-27 15:59:15 -06:00
// NOTE: we used to make build a body here instead of using
// ellipsis, this was removed because:
// 1) it was ugly with big bodies;
// 2) it was not indented properly;
// 3) it wasn’ t very smart (see #675).
2017-08-09 02:30:56 -05:00
span_lint_and_sugg (
cx ,
WHILE_LET_LOOP ,
expr . span ,
" this loop could be written as a `while let` loop " ,
" try " ,
format! (
" while let {} = {} {{ .. }} " ,
snippet ( cx , arms [ 0 ] . pats [ 0 ] . span , " .. " ) ,
snippet ( cx , matchexpr . span , " .. " )
) ,
) ;
2016-01-03 22:26:12 -06:00
}
2016-12-20 11:21:30 -06:00
} ,
2016-01-03 22:26:12 -06:00
_ = > ( ) ,
2015-08-29 04:41:06 -05:00
}
}
}
}
2015-10-22 16:16:58 -05:00
if let ExprMatch ( ref match_expr , ref arms , MatchSource ::WhileLetDesugar ) = expr . node {
2015-10-16 13:27:13 -05:00
let pat = & arms [ 0 ] . pats [ 0 ] . node ;
2017-09-16 17:45:28 -05:00
if let ( & PatKind ::TupleStruct ( ref qpath , ref pat_args , _ ) ,
& ExprMethodCall ( ref method_path , _ , ref method_args ) ) = ( pat , & match_expr . node )
2017-08-09 02:30:56 -05:00
{
2015-10-26 17:49:37 -05:00
let iter_expr = & method_args [ 0 ] ;
2016-12-02 10:38:31 -06:00
let lhs_constructor = last_path_segment ( qpath ) ;
2017-07-31 17:58:26 -05:00
if method_path . name = = " next " & & match_trait_method ( cx , match_expr , & paths ::ITERATOR ) & &
2017-08-09 02:30:56 -05:00
lhs_constructor . name = = " Some " & & ! is_refutable ( cx , & pat_args [ 0 ] ) & &
! is_iterator_used_after_while_let ( cx , iter_expr ) & &
! is_nested ( cx , expr , & method_args [ 0 ] )
{
2016-12-02 10:38:31 -06:00
let iterator = snippet ( cx , method_args [ 0 ] . span , " _ " ) ;
let loop_var = snippet ( cx , pat_args [ 0 ] . span , " _ " ) ;
2017-08-09 02:30:56 -05:00
span_lint_and_sugg (
cx ,
WHILE_LET_ON_ITERATOR ,
expr . span ,
" this loop could be written as a `for` loop " ,
" try " ,
format! ( " for {} in {} {{ .. }} " , loop_var , iterator ) ,
) ;
2015-10-16 13:27:13 -05:00
}
}
}
2015-08-12 14:56:27 -05:00
}
2015-08-30 06:10:59 -05:00
2016-12-07 06:13:40 -06:00
fn check_stmt ( & mut self , cx : & LateContext < ' a , ' tcx > , stmt : & ' tcx Stmt ) {
2015-08-30 06:10:59 -05:00
if let StmtSemi ( ref expr , _ ) = stmt . node {
if let ExprMethodCall ( ref method , _ , ref args ) = expr . node {
2017-07-10 03:17:40 -05:00
if args . len ( ) = = 1 & & method . name = = " collect " & & match_trait_method ( cx , expr , & paths ::ITERATOR ) {
2017-08-09 02:30:56 -05:00
span_lint (
cx ,
UNUSED_COLLECT ,
expr . span ,
" you are collect()ing an iterator and throwing away the result. \
2017-09-05 04:33:04 -05:00
Consider using an explicit for loop to exhaust the iterator " ,
2017-08-09 02:30:56 -05:00
) ;
2015-08-30 06:10:59 -05:00
}
}
}
}
2015-08-12 14:56:27 -05:00
}
2017-05-30 20:44:01 -05:00
fn never_loop ( block : & Block , id : & NodeId ) -> bool {
! contains_continue_block ( block , id ) & & loop_exit_block ( block )
}
fn contains_continue_block ( block : & Block , dest : & NodeId ) -> bool {
2017-06-29 09:07:43 -05:00
block . stmts . iter ( ) . any ( | e | contains_continue_stmt ( e , dest ) ) | |
2017-09-16 17:45:28 -05:00
block . expr . as_ref ( ) . map_or (
false ,
| e | contains_continue_expr ( e , dest ) ,
)
2017-02-16 21:53:14 -06:00
}
2017-05-30 20:44:01 -05:00
fn contains_continue_stmt ( stmt : & Stmt , dest : & NodeId ) -> bool {
2017-02-16 21:53:14 -06:00
match stmt . node {
2017-09-16 17:45:28 -05:00
StmtSemi ( ref e , _ ) |
StmtExpr ( ref e , _ ) = > contains_continue_expr ( e , dest ) ,
2017-05-30 20:44:01 -05:00
StmtDecl ( ref d , _ ) = > contains_continue_decl ( d , dest ) ,
2017-02-16 21:53:14 -06:00
}
}
2017-05-30 20:44:01 -05:00
fn contains_continue_decl ( decl : & Decl , dest : & NodeId ) -> bool {
match decl . node {
2017-09-16 17:45:28 -05:00
DeclLocal ( ref local ) = > {
local . init . as_ref ( ) . map_or (
false ,
| e | contains_continue_expr ( e , dest ) ,
)
} ,
2017-06-29 09:07:43 -05:00
_ = > false ,
2017-02-16 21:53:14 -06:00
}
}
2017-05-30 20:44:01 -05:00
fn contains_continue_expr ( expr : & Expr , dest : & NodeId ) -> bool {
2017-02-16 21:53:14 -06:00
match expr . node {
2017-07-04 20:55:37 -05:00
ExprRet ( Some ( ref e ) ) |
2017-02-16 21:53:14 -06:00
ExprBox ( ref e ) |
ExprUnary ( _ , ref e ) |
ExprCast ( ref e , _ ) |
ExprType ( ref e , _ ) |
ExprField ( ref e , _ ) |
ExprTupField ( ref e , _ ) |
2017-05-30 20:44:01 -05:00
ExprAddrOf ( _ , ref e ) |
ExprRepeat ( ref e , _ ) = > contains_continue_expr ( e , dest ) ,
2017-09-16 17:45:28 -05:00
ExprArray ( ref es ) |
ExprMethodCall ( _ , _ , ref es ) |
ExprTup ( ref es ) = > es . iter ( ) . any ( | e | contains_continue_expr ( e , dest ) ) ,
2017-06-29 09:07:43 -05:00
ExprCall ( ref e , ref es ) = > {
contains_continue_expr ( e , dest ) | | es . iter ( ) . any ( | e | contains_continue_expr ( e , dest ) )
} ,
2017-05-30 20:44:01 -05:00
ExprBinary ( _ , ref e1 , ref e2 ) |
2017-02-16 21:53:14 -06:00
ExprAssign ( ref e1 , ref e2 ) |
ExprAssignOp ( _ , ref e1 , ref e2 ) |
2017-05-30 20:44:01 -05:00
ExprIndex ( ref e1 , ref e2 ) = > [ e1 , e2 ] . iter ( ) . any ( | e | contains_continue_expr ( e , dest ) ) ,
2017-09-16 17:45:28 -05:00
ExprIf ( ref e , ref e2 , ref e3 ) = > {
[ e , e2 ] . iter ( ) . chain ( e3 . as_ref ( ) . iter ( ) ) . any ( | e | {
contains_continue_expr ( e , dest )
} )
} ,
2017-05-31 23:22:15 -05:00
ExprWhile ( ref e , ref b , _ ) = > contains_continue_expr ( e , dest ) | | contains_continue_block ( b , dest ) ,
2017-06-29 09:07:43 -05:00
ExprMatch ( ref e , ref arms , _ ) = > {
contains_continue_expr ( e , dest ) | | arms . iter ( ) . any ( | a | contains_continue_expr ( & a . body , dest ) )
} ,
2017-09-16 17:45:28 -05:00
ExprBlock ( ref block ) |
ExprLoop ( ref block , .. ) = > contains_continue_block ( block , dest ) ,
ExprStruct ( _ , _ , ref base ) = > {
base . as_ref ( ) . map_or (
false ,
| e | contains_continue_expr ( e , dest ) ,
)
} ,
2017-05-30 20:44:01 -05:00
ExprAgain ( d ) = > d . target_id . opt_id ( ) . map_or ( false , | id | id = = * dest ) ,
_ = > false ,
}
}
fn loop_exit_block ( block : & Block ) -> bool {
2017-06-29 09:07:43 -05:00
block . stmts . iter ( ) . any ( | e | loop_exit_stmt ( e ) ) | | block . expr . as_ref ( ) . map_or ( false , | e | loop_exit_expr ( e ) )
2017-05-30 20:44:01 -05:00
}
fn loop_exit_stmt ( stmt : & Stmt ) -> bool {
match stmt . node {
2017-09-16 17:45:28 -05:00
StmtSemi ( ref e , _ ) |
StmtExpr ( ref e , _ ) = > loop_exit_expr ( e ) ,
2017-05-30 20:44:01 -05:00
StmtDecl ( ref d , _ ) = > loop_exit_decl ( d ) ,
}
}
fn loop_exit_decl ( decl : & Decl ) -> bool {
match decl . node {
DeclLocal ( ref local ) = > local . init . as_ref ( ) . map_or ( false , | e | loop_exit_expr ( e ) ) ,
2017-06-29 09:07:43 -05:00
_ = > false ,
2017-05-30 20:44:01 -05:00
}
}
fn loop_exit_expr ( expr : & Expr ) -> bool {
match expr . node {
ExprBox ( ref e ) |
ExprUnary ( _ , ref e ) |
ExprCast ( ref e , _ ) |
ExprType ( ref e , _ ) |
ExprField ( ref e , _ ) |
ExprTupField ( ref e , _ ) |
ExprAddrOf ( _ , ref e ) |
ExprRepeat ( ref e , _ ) = > loop_exit_expr ( e ) ,
2017-09-16 17:45:28 -05:00
ExprArray ( ref es ) |
ExprMethodCall ( _ , _ , ref es ) |
ExprTup ( ref es ) = > es . iter ( ) . any ( | e | loop_exit_expr ( e ) ) ,
2017-05-30 20:44:01 -05:00
ExprCall ( ref e , ref es ) = > loop_exit_expr ( e ) | | es . iter ( ) . any ( | e | loop_exit_expr ( e ) ) ,
ExprBinary ( _ , ref e1 , ref e2 ) |
ExprAssign ( ref e1 , ref e2 ) |
ExprAssignOp ( _ , ref e1 , ref e2 ) |
ExprIndex ( ref e1 , ref e2 ) = > [ e1 , e2 ] . iter ( ) . any ( | e | loop_exit_expr ( e ) ) ,
2017-06-29 09:07:43 -05:00
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 )
} ,
2017-05-30 20:44:01 -05:00
ExprWhile ( ref e , ref b , _ ) = > loop_exit_expr ( e ) | | loop_exit_block ( b ) ,
ExprMatch ( ref e , ref arms , _ ) = > loop_exit_expr ( e ) | | arms . iter ( ) . all ( | a | loop_exit_expr ( & a . body ) ) ,
ExprBlock ( ref b ) = > loop_exit_block ( b ) ,
2017-06-29 09:07:43 -05:00
ExprBreak ( _ , _ ) | ExprAgain ( _ ) | ExprRet ( _ ) = > true ,
2017-02-16 21:53:14 -06:00
_ = > false ,
}
}
2016-12-21 05:14:54 -06:00
fn check_for_loop < ' a , ' tcx > (
cx : & LateContext < ' a , ' tcx > ,
pat : & ' tcx Pat ,
arg : & ' tcx Expr ,
body : & ' tcx Expr ,
2017-08-09 02:30:56 -05:00
expr : & ' tcx Expr ,
2016-12-21 05:14:54 -06:00
) {
2016-01-14 13:58:32 -06:00
check_for_loop_range ( cx , pat , arg , body , expr ) ;
check_for_loop_reverse_range ( cx , arg , expr ) ;
2016-01-29 01:34:09 -06:00
check_for_loop_arg ( cx , pat , arg , expr ) ;
2016-01-14 13:58:32 -06:00
check_for_loop_explicit_counter ( cx , arg , body , expr ) ;
2016-02-05 12:14:02 -06:00
check_for_loop_over_map_kv ( cx , pat , arg , body , expr ) ;
2017-08-15 11:41:59 -05:00
check_for_mut_range_bound ( cx , arg , expr ) ;
2017-09-04 19:16:34 -05:00
detect_manual_memcpy ( cx , pat , arg , body , expr ) ;
}
2017-09-12 07:26:40 -05:00
fn same_var < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & Expr , var : ast ::NodeId ) -> bool {
2017-09-04 19:16:34 -05:00
if_let_chain! { [
let ExprPath ( ref qpath ) = expr . node ,
let QPath ::Resolved ( None , ref path ) = * qpath ,
path . segments . len ( ) = = 1 ,
2017-09-12 07:26:40 -05:00
let Def ::Local ( local_id ) = cx . tables . qpath_def ( qpath , expr . hir_id ) ,
2017-09-04 19:16:34 -05:00
// our variable!
2017-09-12 07:26:40 -05:00
local_id = = var
2017-09-04 19:16:34 -05:00
] , {
return true ;
} }
false
}
struct Offset {
value : String ,
negate : bool ,
}
impl Offset {
fn negative ( s : String ) -> Self {
Self {
value : s ,
negate : true ,
}
}
fn positive ( s : String ) -> Self {
Self {
value : s ,
negate : false ,
}
}
}
struct FixedOffsetVar {
var_name : String ,
offset : Offset ,
}
fn is_slice_like < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , ty : Ty ) -> bool {
let is_slice = match ty . sty {
ty ::TyRef ( _ , ref subty ) = > is_slice_like ( cx , subty . ty ) ,
ty ::TySlice ( .. ) | ty ::TyArray ( .. ) = > true ,
_ = > false ,
} ;
is_slice | | match_type ( cx , ty , & paths ::VEC ) | | match_type ( cx , ty , & paths ::VEC_DEQUE )
}
2017-09-12 07:26:40 -05:00
fn get_fixed_offset_var < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & Expr , var : ast ::NodeId ) -> Option < FixedOffsetVar > {
fn extract_offset < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , e : & Expr , var : ast ::NodeId ) -> Option < String > {
2017-09-04 19:16:34 -05:00
match e . node {
2017-09-16 17:45:28 -05:00
ExprLit ( ref l ) = > {
match l . node {
ast ::LitKind ::Int ( x , _ty ) = > Some ( x . to_string ( ) ) ,
_ = > None ,
}
2017-09-04 19:16:34 -05:00
} ,
ExprPath ( .. ) if ! same_var ( cx , e , var ) = > Some ( snippet_opt ( cx , e . span ) . unwrap_or_else ( | | " ?? " . into ( ) ) ) ,
_ = > None ,
}
}
if let ExprIndex ( ref seqexpr , ref idx ) = expr . node {
let ty = cx . tables . expr_ty ( seqexpr ) ;
if ! is_slice_like ( cx , ty ) {
return None ;
}
let offset = match idx . node {
2017-09-16 17:45:28 -05:00
ExprBinary ( op , ref lhs , ref rhs ) = > {
match op . node {
BinOp_ ::BiAdd = > {
let offset_opt = if same_var ( cx , lhs , var ) {
extract_offset ( cx , rhs , var )
} else if same_var ( cx , rhs , var ) {
extract_offset ( cx , lhs , var )
} else {
None
} ;
2017-09-04 19:16:34 -05:00
2017-09-16 17:45:28 -05:00
offset_opt . map ( Offset ::positive )
} ,
BinOp_ ::BiSub if same_var ( cx , lhs , var ) = > extract_offset ( cx , rhs , var ) . map ( Offset ::negative ) ,
_ = > None ,
}
2017-09-04 19:16:34 -05:00
} ,
2017-09-16 17:45:28 -05:00
ExprPath ( .. ) = > {
if same_var ( cx , idx , var ) {
Some ( Offset ::positive ( " 0 " . into ( ) ) )
} else {
None
}
2017-09-04 19:16:34 -05:00
} ,
_ = > None ,
} ;
offset . map ( | o | {
FixedOffsetVar {
var_name : snippet_opt ( cx , seqexpr . span ) . unwrap_or_else ( | | " ??? " . into ( ) ) ,
offset : o ,
}
} )
} else {
None
}
}
2017-09-16 18:17:22 -05:00
fn fetch_cloned_fixed_offset_var < ' a , ' tcx > (
cx : & LateContext < ' a , ' tcx > ,
expr : & Expr ,
var : ast ::NodeId ,
) -> Option < FixedOffsetVar > {
if_let_chain! { [
let ExprMethodCall ( ref method , _ , ref args ) = expr . node ,
method . name = = " clone " ,
args . len ( ) = = 1 ,
let Some ( arg ) = args . get ( 0 ) ,
] , {
return get_fixed_offset_var ( cx , arg , var ) ;
} }
get_fixed_offset_var ( cx , expr , var )
}
2017-09-04 19:16:34 -05:00
fn get_indexed_assignments < ' a , ' tcx > (
cx : & LateContext < ' a , ' tcx > ,
body : & Expr ,
2017-09-12 07:26:40 -05:00
var : ast ::NodeId ,
2017-09-04 19:16:34 -05:00
) -> Vec < ( FixedOffsetVar , FixedOffsetVar ) > {
fn get_assignment < ' a , ' tcx > (
cx : & LateContext < ' a , ' tcx > ,
e : & Expr ,
2017-09-12 07:26:40 -05:00
var : ast ::NodeId ,
2017-09-04 19:16:34 -05:00
) -> Option < ( FixedOffsetVar , FixedOffsetVar ) > {
if let Expr_ ::ExprAssign ( ref lhs , ref rhs ) = e . node {
2017-09-16 18:17:22 -05:00
match ( get_fixed_offset_var ( cx , lhs , var ) , fetch_cloned_fixed_offset_var ( cx , rhs , var ) ) {
2017-09-04 19:16:34 -05:00
( Some ( offset_left ) , Some ( offset_right ) ) = > Some ( ( offset_left , offset_right ) ) ,
_ = > None ,
}
} else {
None
}
}
if let Expr_ ::ExprBlock ( ref b ) = body . node {
let Block {
ref stmts ,
ref expr ,
..
} = * * b ;
stmts
. iter ( )
. map ( | stmt | match stmt . node {
Stmt_ ::StmtDecl ( .. ) = > None ,
2017-09-16 17:45:28 -05:00
Stmt_ ::StmtExpr ( ref e , _node_id ) |
Stmt_ ::StmtSemi ( ref e , _node_id ) = > Some ( get_assignment ( cx , e , var ) ) ,
2017-09-04 19:16:34 -05:00
} )
2017-09-16 17:45:28 -05:00
. chain ( expr . as_ref ( ) . into_iter ( ) . map ( | e | {
Some ( get_assignment ( cx , & * e , var ) )
} ) )
2017-09-04 19:16:34 -05:00
. filter_map ( | op | op )
. collect ::< Option < Vec < _ > > > ( )
. unwrap_or_else ( | | vec! [ ] )
} else {
get_assignment ( cx , body , var ) . into_iter ( ) . collect ( )
}
}
/// Check for for loops that sequentially copy items from one slice-like
/// object to another.
fn detect_manual_memcpy < ' a , ' tcx > (
cx : & LateContext < ' a , ' tcx > ,
pat : & ' tcx Pat ,
arg : & ' tcx Expr ,
body : & ' tcx Expr ,
expr : & ' tcx Expr ,
) {
if let Some ( higher ::Range {
2017-09-16 17:45:28 -05:00
start : Some ( start ) ,
ref end ,
limits ,
} ) = higher ::range ( arg )
2017-09-04 19:16:34 -05:00
{
// the var must be a single name
2017-09-12 07:26:40 -05:00
if let PatKind ::Binding ( _ , canonical_id , _ , _ ) = pat . node {
2017-09-04 19:16:34 -05:00
let print_sum = | arg1 : & Offset , arg2 : & Offset | -> String {
match ( & arg1 . value [ .. ] , arg1 . negate , & arg2 . value [ .. ] , arg2 . negate ) {
( " 0 " , _ , " 0 " , _ ) = > " " . into ( ) ,
2017-09-16 17:45:28 -05:00
( " 0 " , _ , x , false ) |
( x , false , " 0 " , false ) = > x . into ( ) ,
( " 0 " , _ , x , true ) |
( x , false , " 0 " , true ) = > format! ( " - {} " , x ) ,
2017-09-04 19:16:34 -05:00
( x , false , y , false ) = > format! ( " ( {} + {} ) " , x , y ) ,
( x , false , y , true ) = > format! ( " ( {} - {} ) " , x , y ) ,
( x , true , y , false ) = > format! ( " ( {} - {} ) " , y , x ) ,
( x , true , y , true ) = > format! ( " -( {} + {} ) " , x , y ) ,
}
} ;
let print_limit = | end : & Option < & Expr > , offset : Offset , var_name : & str | if let Some ( end ) = * end {
if_let_chain! { [
let ExprMethodCall ( ref method , _ , ref len_args ) = end . node ,
method . name = = " len " ,
len_args . len ( ) = = 1 ,
let Some ( arg ) = len_args . get ( 0 ) ,
snippet ( cx , arg . span , " ?? " ) = = var_name ,
] , {
return if offset . negate {
format! ( " ( {} - {} ) " , snippet ( cx , end . span , " <src>.len() " ) , offset . value )
} else {
" " . to_owned ( )
} ;
} }
let end_str = match limits {
ast ::RangeLimits ::Closed = > {
let end = sugg ::Sugg ::hir ( cx , end , " <count> " ) ;
format! ( " {} " , end + sugg ::ONE )
} ,
ast ::RangeLimits ::HalfOpen = > format! ( " {} " , snippet ( cx , end . span , " .. " ) ) ,
} ;
print_sum ( & Offset ::positive ( end_str ) , & offset )
} else {
" .. " . into ( )
} ;
// The only statements in the for loops can be indexed assignments from
// indexed retrievals.
2017-09-12 07:26:40 -05:00
let manual_copies = get_indexed_assignments ( cx , body , canonical_id ) ;
2017-09-04 19:16:34 -05:00
let big_sugg = manual_copies
. into_iter ( )
. map ( | ( dst_var , src_var ) | {
let start_str = Offset ::positive ( snippet_opt ( cx , start . span ) . unwrap_or_else ( | | " " . into ( ) ) ) ;
let dst_offset = print_sum ( & start_str , & dst_var . offset ) ;
let dst_limit = print_limit ( end , dst_var . offset , & dst_var . var_name ) ;
let src_offset = print_sum ( & start_str , & src_var . offset ) ;
let src_limit = print_limit ( end , src_var . offset , & src_var . var_name ) ;
let dst = if dst_offset = = " " & & dst_limit = = " " {
dst_var . var_name
} else {
format! ( " {} [ {} .. {} ] " , dst_var . var_name , dst_offset , dst_limit )
} ;
format! ( " {} .clone_from_slice(& {} [ {} .. {} ]) " , dst , src_var . var_name , src_offset , src_limit )
} )
. join ( " \n " ) ;
if ! big_sugg . is_empty ( ) {
span_lint_and_sugg (
cx ,
MANUAL_MEMCPY ,
expr . span ,
" it looks like you're manually copying between slices " ,
" try replacing the loop by " ,
big_sugg ,
) ;
}
}
}
2016-01-14 13:58:32 -06:00
}
2015-11-18 05:35:18 -06:00
2016-01-14 13:58:32 -06:00
/// Check for looping over a range and then indexing a sequence with it.
/// The iteratee must be a range literal.
2016-12-21 05:14:54 -06:00
fn check_for_loop_range < ' a , ' tcx > (
cx : & LateContext < ' a , ' tcx > ,
pat : & ' tcx Pat ,
arg : & ' tcx Expr ,
body : & ' tcx Expr ,
2017-08-09 02:30:56 -05:00
expr : & ' tcx Expr ,
2016-12-21 05:14:54 -06:00
) {
2017-08-09 02:30:56 -05:00
if let Some ( higher ::Range {
2017-09-16 17:45:28 -05:00
start : Some ( start ) ,
ref end ,
limits ,
} ) = higher ::range ( arg )
2017-08-09 02:30:56 -05:00
{
2016-01-14 13:58:32 -06:00
// the var must be a single name
2017-09-12 07:26:40 -05:00
if let PatKind ::Binding ( _ , canonical_id , ref ident , _ ) = pat . node {
2016-01-14 13:58:32 -06:00
let mut visitor = VarVisitor {
cx : cx ,
2017-09-12 07:26:40 -05:00
var : canonical_id ,
2016-02-13 15:09:17 -06:00
indexed : HashMap ::new ( ) ,
2017-07-10 08:30:28 -05:00
referenced : HashSet ::new ( ) ,
2016-01-14 13:58:32 -06:00
nonindex : false ,
} ;
walk_expr ( & mut visitor , body ) ;
2016-02-13 15:09:17 -06:00
2016-01-14 13:58:32 -06:00
// linting condition: we only indexed one variable
if visitor . indexed . len ( ) = = 1 {
2017-09-16 17:45:28 -05:00
let ( indexed , indexed_extent ) = visitor . indexed . into_iter ( ) . next ( ) . expect (
" already checked that we have exactly 1 element " ,
) ;
2016-01-14 13:58:32 -06:00
2016-02-13 15:09:17 -06:00
// ensure that the indexed variable was declared before the loop, see #601
2016-03-07 16:24:11 -06:00
if let Some ( indexed_extent ) = indexed_extent {
2017-05-03 05:51:47 -05:00
let parent_id = cx . tcx . hir . get_parent ( expr . id ) ;
let parent_def_id = cx . tcx . hir . local_def_id ( parent_id ) ;
2017-09-04 09:10:36 -05:00
let region_scope_tree = cx . tcx . region_scope_tree ( parent_def_id ) ;
let pat_extent = region_scope_tree . var_scope ( pat . hir_id . local_id ) ;
if region_scope_tree . is_subscope_of ( indexed_extent , pat_extent ) {
2016-03-07 16:24:11 -06:00
return ;
}
2016-02-13 15:09:17 -06:00
}
2017-08-09 02:30:56 -05:00
// don't lint if the container that is indexed into is also used without
// indexing
2017-07-10 08:30:28 -05:00
if visitor . referenced . contains ( & indexed ) {
return ;
}
2016-03-07 09:31:38 -06:00
let starts_at_zero = is_integer_literal ( start , 0 ) ;
2016-01-14 13:58:32 -06:00
2016-07-01 12:31:14 -05:00
let skip = if starts_at_zero {
" " . to_owned ( )
2016-01-30 06:48:39 -06:00
} else {
2016-07-01 12:31:14 -05:00
format! ( " .skip( {} ) " , snippet ( cx , start . span , " .. " ) )
2016-01-14 13:58:32 -06:00
} ;
2016-08-01 09:59:14 -05:00
let take = if let Some ( end ) = * end {
2016-04-26 10:05:39 -05:00
if is_len_call ( end , & indexed ) {
2016-07-01 12:31:14 -05:00
" " . to_owned ( )
2016-02-29 02:45:36 -06:00
} else {
2016-07-01 12:31:14 -05:00
match limits {
ast ::RangeLimits ::Closed = > {
let end = sugg ::Sugg ::hir ( cx , end , " <count> " ) ;
format! ( " .take( {} ) " , end + sugg ::ONE )
2016-12-20 11:21:30 -06:00
} ,
ast ::RangeLimits ::HalfOpen = > format! ( " .take( {} ) " , snippet ( cx , end . span , " .. " ) ) ,
2016-07-01 12:31:14 -05:00
}
2016-01-14 13:58:32 -06:00
}
} else {
2016-07-01 12:31:14 -05:00
" " . to_owned ( )
2016-01-14 13:58:32 -06:00
} ;
if visitor . nonindex {
2017-09-05 04:33:04 -05:00
span_lint_and_then (
cx ,
NEEDLESS_RANGE_LOOP ,
expr . span ,
& format! ( " the loop variable ` {} ` is used to index ` {} ` " , ident . node , indexed ) ,
| db | {
multispan_sugg (
db ,
" consider using an iterator " . to_string ( ) ,
vec! [
( pat . span , format! ( " ( {} , <item>) " , ident . node ) ) ,
( arg . span , format! ( " {} .iter().enumerate() {} {} " , indexed , take , skip ) ) ,
] ,
) ;
} ,
) ;
2016-01-14 13:58:32 -06:00
} else {
let repl = if starts_at_zero & & take . is_empty ( ) {
format! ( " & {} " , indexed )
2016-01-30 06:48:39 -06:00
} else {
2016-01-14 13:58:32 -06:00
format! ( " {} .iter() {} {} " , indexed , take , skip )
} ;
2017-09-05 04:33:04 -05:00
span_lint_and_then (
cx ,
NEEDLESS_RANGE_LOOP ,
expr . span ,
& format! ( " the loop variable ` {} ` is only used to index ` {} `. " , ident . node , indexed ) ,
| db | {
multispan_sugg (
db ,
" consider using an iterator " . to_string ( ) ,
vec! [ ( pat . span , " <item> " . to_string ( ) ) , ( arg . span , repl ) ] ,
) ;
} ,
) ;
2015-11-18 05:35:18 -06:00
}
}
}
}
2016-01-14 13:58:32 -06:00
}
fn is_len_call ( expr : & Expr , var : & Name ) -> bool {
if_let_chain! { [
2017-07-10 03:17:40 -05:00
let ExprMethodCall ( ref method , _ , ref len_args ) = expr . node ,
2016-01-14 13:58:32 -06:00
len_args . len ( ) = = 1 ,
2017-07-10 03:17:40 -05:00
method . name = = " len " ,
2016-12-01 15:31:56 -06:00
let ExprPath ( QPath ::Resolved ( _ , ref path ) ) = len_args [ 0 ] . node ,
2016-01-14 13:58:32 -06:00
path . segments . len ( ) = = 1 ,
2017-03-09 03:58:31 -06:00
path . segments [ 0 ] . name = = * var
2016-01-14 13:58:32 -06:00
] , {
return true ;
} }
2015-11-18 05:35:18 -06:00
2016-01-14 13:58:32 -06:00
false
}
2017-09-13 08:34:04 -05:00
fn check_for_loop_reverse_range < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , arg : & ' tcx Expr , expr : & ' tcx Expr ) {
2015-11-18 05:35:18 -06:00
// if this for loop is iterating over a two-sided range...
2017-08-09 02:30:56 -05:00
if let Some ( higher ::Range {
2017-09-16 17:45:28 -05:00
start : Some ( start ) ,
end : Some ( end ) ,
limits ,
} ) = higher ::range ( arg )
2017-08-09 02:30:56 -05:00
{
2015-11-18 05:35:18 -06:00
// ...and both sides are compile-time constant integers...
2017-07-31 05:37:38 -05:00
let parent_item = cx . tcx . hir . get_parent ( arg . id ) ;
let parent_def_id = cx . tcx . hir . local_def_id ( parent_item ) ;
let substs = Substs ::identity_for_item ( cx . tcx , parent_def_id ) ;
let constcx = ConstContext ::new ( cx . tcx , cx . param_env . and ( substs ) , cx . tables ) ;
2017-03-01 06:24:19 -06:00
if let Ok ( start_idx ) = constcx . eval ( start ) {
if let Ok ( end_idx ) = constcx . eval ( end ) {
2016-03-07 09:31:38 -06:00
// ...and the start index is greater than the end index,
2015-11-18 05:35:18 -06:00
// this loop will never run. This is often confusing for developers
// who think that this will iterate from the larger value to the
// smaller value.
2016-03-07 09:31:38 -06:00
let ( sup , eq ) = match ( start_idx , end_idx ) {
2017-09-16 17:45:28 -05:00
( & ty ::Const { val : ConstVal ::Integral ( start_idx ) , .. } ,
& ty ::Const { val : ConstVal ::Integral ( end_idx ) , .. } ) = > {
2016-03-07 09:31:38 -06:00
( start_idx > end_idx , start_idx = = end_idx )
2016-12-20 11:21:30 -06:00
} ,
2016-02-07 11:10:03 -06:00
_ = > ( false , false ) ,
} ;
if sup {
2016-03-07 09:31:38 -06:00
let start_snippet = snippet ( cx , start . span , " _ " ) ;
let end_snippet = snippet ( cx , end . span , " _ " ) ;
2016-06-09 16:05:48 -05:00
let dots = if limits = = ast ::RangeLimits ::Closed {
" ... "
} else {
" .. "
} ;
2016-02-07 11:10:03 -06:00
2017-09-05 04:33:04 -05:00
span_lint_and_then (
cx ,
REVERSE_RANGE_LOOP ,
expr . span ,
" this range is empty so this for loop will never run " ,
| db | {
db . span_suggestion (
arg . span ,
" consider using the following if you are attempting to iterate over this \
range in reverse " ,
format! (
" ({end}{dots}{start}).rev() " ,
end = end_snippet ,
dots = dots ,
start = start_snippet
) ,
) ;
} ,
) ;
2016-03-07 09:55:12 -06:00
} else if eq & & limits ! = ast ::RangeLimits ::Closed {
2015-11-18 05:35:18 -06:00
// if they are equal, it's also problematic - this loop
// will never run.
2017-08-09 02:30:56 -05:00
span_lint (
cx ,
REVERSE_RANGE_LOOP ,
expr . span ,
" this range is empty so this for loop will never run " ,
) ;
2015-11-18 05:35:18 -06:00
}
}
}
}
2016-01-14 13:58:32 -06:00
}
2015-11-18 05:35:18 -06:00
2017-01-30 05:43:27 -06:00
fn lint_iter_method ( cx : & LateContext , args : & [ Expr ] , arg : & Expr , method_name : & str ) {
2017-01-09 09:59:55 -06:00
let object = snippet ( cx , args [ 0 ] . span , " _ " ) ;
2017-01-30 05:43:27 -06:00
let muta = if method_name = = " iter_mut " {
" mut "
} else {
" "
} ;
2017-08-09 02:30:56 -05:00
span_lint_and_sugg (
cx ,
EXPLICIT_ITER_LOOP ,
arg . span ,
" it is more idiomatic to loop over references to containers instead of using explicit \
2017-09-05 04:33:04 -05:00
iteration methods " ,
2017-08-09 02:30:56 -05:00
" to write this more concisely, try " ,
format! ( " & {} {} " , muta , object ) ,
)
2017-01-09 09:59:55 -06:00
}
2016-01-29 01:34:09 -06:00
fn check_for_loop_arg ( cx : & LateContext , pat : & Pat , arg : & Expr , expr : & Expr ) {
let mut next_loop_linted = false ; // whether or not ITER_NEXT_LOOP lint was used
2015-11-18 05:35:18 -06:00
if let ExprMethodCall ( ref method , _ , ref args ) = arg . node {
// just the receiver, no arguments
if args . len ( ) = = 1 {
2017-07-10 03:17:40 -05:00
let method_name = & * method . name . as_str ( ) ;
2015-11-18 05:35:18 -06:00
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
2017-01-09 09:59:55 -06:00
if method_name = = " iter " | | method_name = = " iter_mut " {
2015-11-18 05:35:18 -06:00
if is_ref_iterable_type ( cx , & args [ 0 ] ) {
2017-07-10 03:17:40 -05:00
lint_iter_method ( cx , args , arg , method_name ) ;
2017-01-09 09:59:55 -06:00
}
} else if method_name = = " into_iter " & & match_trait_method ( cx , arg , & paths ::INTO_ITERATOR ) {
2017-08-15 04:10:49 -05:00
let def_id = cx . tables . type_dependent_defs ( ) [ arg . hir_id ] . def_id ( ) ;
let substs = cx . tables . node_substs ( arg . hir_id ) ;
2017-06-04 16:28:01 -05:00
let method_type = cx . tcx . type_of ( def_id ) . subst ( cx . tcx , substs ) ;
2017-06-29 08:38:25 -05:00
let fn_arg_tys = method_type . fn_sig ( cx . tcx ) . inputs ( ) ;
2017-01-09 09:59:55 -06:00
assert_eq! ( fn_arg_tys . skip_binder ( ) . len ( ) , 1 ) ;
if fn_arg_tys . skip_binder ( ) [ 0 ] . is_region_ptr ( ) {
2017-07-10 03:17:40 -05:00
lint_iter_method ( cx , args , arg , method_name ) ;
2017-01-09 09:59:55 -06:00
} else {
2015-11-18 05:35:18 -06:00
let object = snippet ( cx , args [ 0 ] . span , " _ " ) ;
2017-08-09 02:30:56 -05:00
span_lint_and_sugg (
cx ,
EXPLICIT_INTO_ITER_LOOP ,
arg . span ,
" it is more idiomatic to loop over containers instead of using explicit \
2017-09-05 04:33:04 -05:00
iteration methods ` " ,
2017-08-09 02:30:56 -05:00
" to write this more concisely, try " ,
object . to_string ( ) ,
) ;
2015-11-18 05:35:18 -06:00
}
2017-01-09 09:59:55 -06:00
} else if method_name = = " next " & & match_trait_method ( cx , arg , & paths ::ITERATOR ) {
2017-08-09 02:30:56 -05:00
span_lint (
cx ,
ITER_NEXT_LOOP ,
expr . span ,
" you are iterating over `Iterator::next()` which is an Option; this will compile but is \
2017-09-05 04:33:04 -05:00
probably not what you want " ,
2017-08-09 02:30:56 -05:00
) ;
2016-01-29 01:34:09 -06:00
next_loop_linted = true ;
2015-11-18 05:35:18 -06:00
}
}
}
2016-01-29 01:34:09 -06:00
if ! next_loop_linted {
2016-01-29 17:15:57 -06:00
check_arg_type ( cx , pat , arg ) ;
2016-01-29 01:34:09 -06:00
}
}
2015-11-18 05:35:18 -06:00
2016-01-29 17:15:57 -06:00
/// Check for `for` loops over `Option`s and `Results`
fn check_arg_type ( cx : & LateContext , pat : & Pat , arg : & Expr ) {
2017-01-13 10:04:56 -06:00
let ty = cx . tables . expr_ty ( arg ) ;
2016-04-14 11:13:15 -05:00
if match_type ( cx , ty , & paths ::OPTION ) {
2017-08-09 02:30:56 -05:00
span_help_and_lint (
cx ,
FOR_LOOP_OVER_OPTION ,
arg . span ,
& format! (
" for loop over `{0}`, which is an `Option`. This is more readably written as an \
2017-09-05 04:33:04 -05:00
` if let ` statement . " ,
2017-08-09 02:30:56 -05:00
snippet ( cx , arg . span , " _ " )
) ,
& format! (
" consider replacing `for {0} in {1}` with `if let Some({0}) = {1}` " ,
snippet ( cx , pat . span , " _ " ) ,
snippet ( cx , arg . span , " _ " )
) ,
) ;
2016-04-14 11:13:15 -05:00
} else if match_type ( cx , ty , & paths ::RESULT ) {
2017-08-09 02:30:56 -05:00
span_help_and_lint (
cx ,
FOR_LOOP_OVER_RESULT ,
arg . span ,
& format! (
" for loop over `{0}`, which is a `Result`. This is more readably written as an \
2017-09-05 04:33:04 -05:00
` if let ` statement . " ,
2017-08-09 02:30:56 -05:00
snippet ( cx , arg . span , " _ " )
) ,
& format! (
" consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}` " ,
snippet ( cx , pat . span , " _ " ) ,
snippet ( cx , arg . span , " _ " )
) ,
) ;
2016-01-29 17:15:57 -06:00
}
2016-01-14 13:58:32 -06:00
}
2016-12-21 05:14:54 -06:00
fn check_for_loop_explicit_counter < ' a , ' tcx > (
cx : & LateContext < ' a , ' tcx > ,
arg : & ' tcx Expr ,
body : & ' tcx Expr ,
2017-08-09 02:30:56 -05:00
expr : & ' tcx Expr ,
2016-12-21 05:14:54 -06:00
) {
2015-11-18 05:35:18 -06:00
// Look for variables that are incremented once per loop iteration.
2016-01-03 22:26:12 -06:00
let mut visitor = IncrementVisitor {
cx : cx ,
states : HashMap ::new ( ) ,
depth : 0 ,
done : false ,
} ;
2015-11-18 05:35:18 -06:00
walk_expr ( & mut visitor , body ) ;
// For each candidate, check the parent block to see if
// it's initialized to zero at the start of the loop.
2017-02-02 10:53:28 -06:00
let map = & cx . tcx . hir ;
2017-09-16 17:45:28 -05:00
let parent_scope = map . get_enclosing_scope ( expr . id ) . and_then ( | id | {
map . get_enclosing_scope ( id )
} ) ;
2015-11-18 05:35:18 -06:00
if let Some ( parent_id ) = parent_scope {
if let NodeBlock ( block ) = map . get ( parent_id ) {
2017-09-16 17:45:28 -05:00
for ( id , _ ) in visitor . states . iter ( ) . filter (
| & ( _ , v ) | * v = = VarState ::IncrOnce ,
)
2017-08-09 02:30:56 -05:00
{
2016-01-03 22:26:12 -06:00
let mut visitor2 = InitializeVisitor {
cx : cx ,
end_expr : expr ,
2016-02-02 15:35:01 -06:00
var_id : * id ,
2016-01-03 22:26:12 -06:00
state : VarState ::IncrOnce ,
name : None ,
depth : 0 ,
past_loop : false ,
} ;
2015-11-18 05:35:18 -06:00
walk_block ( & mut visitor2 , block ) ;
if visitor2 . state = = VarState ::Warn {
if let Some ( name ) = visitor2 . name {
2017-08-09 02:30:56 -05:00
span_lint (
cx ,
EXPLICIT_COUNTER_LOOP ,
expr . span ,
& format! (
" the variable `{0}` is used as a loop counter. Consider using `for ({0}, \
2017-09-05 04:33:04 -05:00
item ) in { 1 } . enumerate ( ) ` or similar iterators " ,
2017-08-09 02:30:56 -05:00
name ,
snippet ( cx , arg . span , " _ " )
) ,
) ;
2015-11-18 05:35:18 -06:00
}
}
}
}
}
}
2016-03-19 11:48:29 -05:00
/// Check for the `FOR_KV_MAP` lint.
2016-12-21 05:14:54 -06:00
fn check_for_loop_over_map_kv < ' a , ' tcx > (
cx : & LateContext < ' a , ' tcx > ,
pat : & ' tcx Pat ,
arg : & ' tcx Expr ,
body : & ' tcx Expr ,
2017-08-09 02:30:56 -05:00
expr : & ' tcx Expr ,
2016-12-21 05:14:54 -06:00
) {
2016-07-01 13:55:45 -05:00
let pat_span = pat . span ;
2016-05-27 07:24:28 -05:00
if let PatKind ::Tuple ( ref pat , _ ) = pat . node {
2016-01-19 14:10:00 -06:00
if pat . len ( ) = = 2 {
2017-01-10 01:33:20 -06:00
let arg_span = arg . span ;
2017-01-13 10:04:56 -06:00
let ( new_pat_span , kind , ty , mutbl ) = match cx . tables . expr_ty ( arg ) . sty {
2017-09-16 17:45:28 -05:00
ty ::TyRef ( _ , ref tam ) = > {
match ( & pat [ 0 ] . node , & pat [ 1 ] . node ) {
( key , _ ) if pat_is_wild ( key , body ) = > ( pat [ 1 ] . span , " value " , tam . ty , tam . mutbl ) ,
( _ , value ) if pat_is_wild ( value , body ) = > ( pat [ 0 ] . span , " key " , tam . ty , MutImmutable ) ,
_ = > return ,
}
2017-01-10 01:33:20 -06:00
} ,
2016-02-24 10:38:57 -06:00
_ = > return ,
2016-01-19 14:10:00 -06:00
} ;
2017-01-10 01:33:20 -06:00
let mutbl = match mutbl {
MutImmutable = > " " ,
MutMutable = > " _mut " ,
} ;
let arg = match arg . node {
ExprAddrOf ( _ , ref expr ) = > & * * expr ,
_ = > arg ,
2016-01-19 14:10:00 -06:00
} ;
2016-04-14 11:13:15 -05:00
if match_type ( cx , ty , & paths ::HASHMAP ) | | match_type ( cx , ty , & paths ::BTREEMAP ) {
2017-09-05 04:33:04 -05:00
span_lint_and_then (
cx ,
FOR_KV_MAP ,
expr . span ,
& format! ( " you seem to want to iterate on a map's {} s " , kind ) ,
| db | {
let map = sugg ::Sugg ::hir ( cx , arg , " map " ) ;
multispan_sugg (
db ,
" use the corresponding method " . into ( ) ,
vec! [
( pat_span , snippet ( cx , new_pat_span , kind ) . into_owned ( ) ) ,
( arg_span , format! ( " {} . {} s {} () " , map . maybe_par ( ) , kind , mutbl ) ) ,
] ,
) ;
} ,
) ;
2016-01-19 14:10:00 -06:00
}
}
}
}
2017-08-15 11:41:59 -05:00
fn check_for_mut_range_bound ( cx : & LateContext , arg : & Expr , expr : & Expr ) {
if let Some ( higher ::Range { start : Some ( start ) , end : Some ( end ) , limits } ) = higher ::range ( arg ) {
let bounds = vec! [ start , end ] ;
for bound in & bounds {
if check_for_mutability ( cx , bound ) {
span_lint ( cx , MUT_RANGE_BOUND , expr . span , " you are looping over a range where at least one bound was defined as a mutable variable. keep in mind that mutating this variable inside the loop will not affect the range " ) ;
return ;
}
}
}
}
fn check_for_mutability ( cx : & LateContext , bound : & Expr ) -> bool {
if_let_chain! { [
let ExprPath ( ref qpath ) = bound . node ,
let QPath ::Resolved ( None , ref path ) = * qpath ,
path . segments . len ( ) = = 1 ,
] , {
let def = cx . tables . qpath_def ( qpath , bound . id ) ;
match def {
Def ::Local ( .. ) | Def ::Upvar ( .. ) = > {
let def_id = def . def_id ( ) ;
let node_id = cx . tcx . hir . as_local_node_id ( def_id ) . expect ( " local/upvar are local nodes " ) ;
let node_str = cx . tcx . hir . get ( node_id ) ;
if_let_chain! { [
let map ::Node ::NodeLocal ( pat ) = node_str ,
let PatKind ::Binding ( bind_ann , _ , _ , _ ) = pat . node ,
let BindingAnnotation ::Mutable = bind_ann ,
] , {
return true ;
} }
} ,
_ = > ( ) ,
} }
}
return false ;
}
2016-03-19 11:48:29 -05:00
/// Return true if the pattern is a `PatWild` or an ident prefixed with `'_'`.
2017-05-12 05:02:42 -05:00
fn pat_is_wild < ' tcx > ( pat : & ' tcx PatKind , body : & ' tcx Expr ) -> bool {
2016-01-19 14:10:00 -06:00
match * pat {
2016-02-18 14:16:39 -06:00
PatKind ::Wild = > true ,
2016-12-01 15:31:56 -06:00
PatKind ::Binding ( _ , _ , ident , None ) if ident . node . as_str ( ) . starts_with ( '_' ) = > {
2016-02-05 12:14:02 -06:00
let mut visitor = UsedVisitor {
var : ident . node ,
used : false ,
} ;
walk_expr ( & mut visitor , body ) ;
! visitor . used
2016-12-20 11:21:30 -06:00
} ,
2016-01-19 14:10:00 -06:00
_ = > false ,
}
}
2017-05-12 05:02:42 -05:00
struct UsedVisitor {
2016-05-19 16:14:34 -05:00
var : ast ::Name , // var to look for
2017-09-16 17:45:28 -05:00
used : bool , // has the var been used otherwise?
2016-02-05 12:14:02 -06:00
}
2017-05-12 05:02:42 -05:00
impl < ' tcx > Visitor < ' tcx > for UsedVisitor {
2016-12-06 04:32:21 -06:00
fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
2017-07-31 17:58:26 -05:00
if match_var ( expr , self . var ) {
self . used = true ;
2017-09-04 19:16:34 -05:00
} else {
walk_expr ( self , expr ) ;
}
}
fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
NestedVisitorMap ::None
}
}
2017-09-16 17:45:28 -05:00
struct LocalUsedVisitor < ' a , ' tcx : ' a > {
2017-09-04 19:16:34 -05:00
cx : & ' a LateContext < ' a , ' tcx > ,
2017-09-12 07:26:40 -05:00
local : ast ::NodeId ,
2017-09-04 19:16:34 -05:00
used : bool ,
}
2017-09-12 07:26:40 -05:00
impl < ' a , ' tcx : ' a > Visitor < ' tcx > for LocalUsedVisitor < ' a , ' tcx > {
2017-09-04 19:16:34 -05:00
fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
2017-09-12 07:26:40 -05:00
if same_var ( self . cx , expr , self . local ) {
2017-09-04 19:16:34 -05:00
self . used = true ;
} else {
walk_expr ( self , expr ) ;
2016-02-05 12:14:02 -06:00
}
}
2017-07-31 17:58:26 -05:00
2016-12-06 04:32:21 -06:00
fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
2017-05-12 05:02:42 -05:00
NestedVisitorMap ::None
2016-12-06 04:32:21 -06:00
}
2016-02-05 12:14:02 -06:00
}
2016-12-06 04:32:21 -06:00
struct VarVisitor < ' a , ' tcx : ' a > {
2017-07-10 08:30:28 -05:00
/// context reference
cx : & ' a LateContext < ' a , ' tcx > ,
/// var name to look for as index
2017-09-12 07:26:40 -05:00
var : ast ::NodeId ,
2017-07-10 08:30:28 -05:00
/// indexed variables, the extend is `None` for global
2017-09-04 09:10:36 -05:00
indexed : HashMap < Name , Option < region ::Scope > > ,
2017-07-10 08:30:28 -05:00
/// Any names that are used outside an index operation.
/// Used to detect things like `&mut vec` used together with `vec[i]`
referenced : HashSet < Name > ,
2017-08-09 02:30:56 -05:00
/// has the loop variable been used in expressions other than the index of
/// an index op?
2017-07-10 08:30:28 -05:00
nonindex : bool ,
2015-08-12 14:56:27 -05:00
}
2016-12-06 04:32:21 -06:00
impl < ' a , ' tcx > Visitor < ' tcx > for VarVisitor < ' a , ' tcx > {
fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
2017-07-10 08:30:28 -05:00
if_let_chain! { [
// an index op
let ExprIndex ( ref seqexpr , ref idx ) = expr . node ,
// the indexed container is referenced by a name
let ExprPath ( ref seqpath ) = seqexpr . node ,
let QPath ::Resolved ( None , ref seqvar ) = * seqpath ,
seqvar . segments . len ( ) = = 1 ,
] , {
2017-09-04 19:16:34 -05:00
let index_used = same_var ( self . cx , idx , self . var ) | | {
2017-09-12 07:26:40 -05:00
let mut used_visitor = LocalUsedVisitor {
2017-09-04 19:16:34 -05:00
cx : self . cx ,
2017-09-12 07:26:40 -05:00
local : self . var ,
2017-09-04 19:16:34 -05:00
used : false ,
} ;
walk_expr ( & mut used_visitor , idx ) ;
used_visitor . used
} ;
if index_used {
let def = self . cx . tables . qpath_def ( seqpath , seqexpr . hir_id ) ;
match def {
2017-09-12 07:26:40 -05:00
Def ::Local ( node_id ) | Def ::Upvar ( node_id , .. ) = > {
2017-09-04 19:16:34 -05:00
let hir_id = self . cx . tcx . hir . node_to_hir_id ( node_id ) ;
let parent_id = self . cx . tcx . hir . get_parent ( expr . id ) ;
let parent_def_id = self . cx . tcx . hir . local_def_id ( parent_id ) ;
let extent = self . cx . tcx . region_scope_tree ( parent_def_id ) . var_scope ( hir_id . local_id ) ;
self . indexed . insert ( seqvar . segments [ 0 ] . name , Some ( extent ) ) ;
return ; // no need to walk further *on the variable*
}
Def ::Static ( .. ) | Def ::Const ( .. ) = > {
self . indexed . insert ( seqvar . segments [ 0 ] . name , None ) ;
return ; // no need to walk further *on the variable*
}
_ = > ( ) ,
2016-12-01 15:31:56 -06:00
}
2015-08-12 14:56:27 -05:00
}
2017-07-10 08:30:28 -05:00
} }
if_let_chain! { [
2017-09-04 19:16:34 -05:00
// directly using a variable
2017-07-10 08:30:28 -05:00
let ExprPath ( ref qpath ) = expr . node ,
let QPath ::Resolved ( None , ref path ) = * qpath ,
path . segments . len ( ) = = 1 ,
2017-09-12 07:26:40 -05:00
let Def ::Local ( local_id ) = self . cx . tables . qpath_def ( qpath , expr . hir_id ) ,
2017-07-10 08:30:28 -05:00
] , {
2017-09-12 07:26:40 -05:00
if local_id = = self . var {
2017-07-10 08:30:28 -05:00
// we are not indexing anything, record that
self . nonindex = true ;
} else {
// not the correct variable, but still a variable
self . referenced . insert ( path . segments [ 0 ] . name ) ;
}
} }
2015-08-12 14:56:27 -05:00
walk_expr ( self , expr ) ;
}
2016-12-06 04:32:21 -06:00
fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
2017-05-12 05:02:42 -05:00
NestedVisitorMap ::None
2016-12-06 04:32:21 -06:00
}
2015-08-12 14:56:27 -05:00
}
2015-08-25 11:26:20 -05:00
2016-12-06 04:32:21 -06:00
fn is_iterator_used_after_while_let < ' a , ' tcx : ' a > ( cx : & LateContext < ' a , ' tcx > , iter_expr : & ' tcx Expr ) -> bool {
2015-10-26 17:49:37 -05:00
let def_id = match var_def_id ( cx , iter_expr ) {
Some ( id ) = > id ,
2016-01-03 22:26:12 -06:00
None = > return false ,
2015-10-26 17:49:37 -05:00
} ;
let mut visitor = VarUsedAfterLoopVisitor {
cx : cx ,
def_id : def_id ,
iter_expr_id : iter_expr . id ,
past_while_let : false ,
2016-01-03 22:26:12 -06:00
var_used_after_while_let : false ,
2015-10-26 17:49:37 -05:00
} ;
if let Some ( enclosing_block ) = get_enclosing_block ( cx , def_id ) {
walk_block ( & mut visitor , enclosing_block ) ;
2015-10-19 18:04:21 -05:00
}
2015-10-26 17:49:37 -05:00
visitor . var_used_after_while_let
2015-10-19 18:04:21 -05:00
}
2016-12-06 04:32:21 -06:00
struct VarUsedAfterLoopVisitor < ' a , ' tcx : ' a > {
cx : & ' a LateContext < ' a , ' tcx > ,
2015-10-19 18:04:21 -05:00
def_id : NodeId ,
2015-10-26 17:49:37 -05:00
iter_expr_id : NodeId ,
past_while_let : bool ,
2016-01-03 22:26:12 -06:00
var_used_after_while_let : bool ,
2015-10-19 18:04:21 -05:00
}
2016-12-06 04:32:21 -06:00
impl < ' a , ' tcx > Visitor < ' tcx > for VarUsedAfterLoopVisitor < ' a , ' tcx > {
fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
2015-10-26 17:49:37 -05:00
if self . past_while_let {
if Some ( self . def_id ) = = var_def_id ( self . cx , expr ) {
self . var_used_after_while_let = true ;
}
} else if self . iter_expr_id = = expr . id {
self . past_while_let = true ;
2015-10-19 18:04:21 -05:00
}
walk_expr ( self , expr ) ;
}
2016-12-06 04:32:21 -06:00
fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
2017-05-12 05:02:42 -05:00
NestedVisitorMap ::None
2016-12-06 04:32:21 -06:00
}
2015-10-19 18:04:21 -05:00
}
2015-10-26 17:49:37 -05:00
2016-03-19 11:48:29 -05:00
/// Return true if the type of expr is one that provides `IntoIterator` impls
/// for `&T` and `&mut T`, such as `Vec`.
2016-02-29 05:19:32 -06:00
#[ cfg_attr(rustfmt, rustfmt_skip) ]
2015-09-18 21:53:04 -05:00
fn is_ref_iterable_type ( cx : & LateContext , e : & Expr ) -> bool {
2015-08-31 01:29:34 -05:00
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
// will allow further borrows afterwards
2017-01-13 10:04:56 -06:00
let ty = cx . tables . expr_ty ( e ) ;
2016-01-19 14:10:00 -06:00
is_iterable_array ( ty ) | |
2016-04-14 11:13:15 -05:00
match_type ( cx , ty , & paths ::VEC ) | |
2016-04-14 11:41:38 -05:00
match_type ( cx , ty , & paths ::LINKED_LIST ) | |
2016-04-14 11:13:15 -05:00
match_type ( cx , ty , & paths ::HASHMAP ) | |
2016-04-26 06:31:52 -05:00
match_type ( cx , ty , & paths ::HASHSET ) | |
match_type ( cx , ty , & paths ::VEC_DEQUE ) | |
match_type ( cx , ty , & paths ::BINARY_HEAP ) | |
2016-04-14 11:13:15 -05:00
match_type ( cx , ty , & paths ::BTREEMAP ) | |
2016-04-26 06:31:52 -05:00
match_type ( cx , ty , & paths ::BTREESET )
2015-08-25 11:26:20 -05:00
}
2017-06-10 21:57:25 -05:00
fn is_iterable_array ( ty : Ty ) -> bool {
2015-09-27 02:39:42 -05:00
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
2015-08-25 11:26:20 -05:00
match ty . sty {
2017-09-13 08:34:04 -05:00
ty ::TyArray ( _ , n ) = > ( 0 .. . 32 ) . contains ( const_to_u64 ( n ) ) ,
2016-01-03 22:26:12 -06:00
_ = > false ,
2015-08-25 11:26:20 -05:00
}
}
2015-08-29 04:41:06 -05:00
2017-08-09 02:30:56 -05:00
/// If a block begins with a statement (possibly a `let` binding) and has an
/// expression, return it.
2015-09-27 02:39:42 -05:00
fn extract_expr_from_first_stmt ( block : & Block ) -> Option < & Expr > {
2016-01-03 22:26:12 -06:00
if block . stmts . is_empty ( ) {
return None ;
}
2015-10-14 04:44:09 -05:00
if let StmtDecl ( ref decl , _ ) = block . stmts [ 0 ] . node {
if let DeclLocal ( ref local ) = decl . node {
2016-01-03 22:26:12 -06:00
if let Some ( ref expr ) = local . init {
Some ( expr )
} else {
None
}
} else {
None
}
} else {
None
}
2015-09-27 02:39:42 -05:00
}
/// If a block begins with an expression (with or without semicolon), return it.
fn extract_first_expr ( block : & Block ) -> Option < & Expr > {
match block . expr {
2016-06-16 09:19:17 -05:00
Some ( ref expr ) if block . stmts . is_empty ( ) = > Some ( expr ) ,
2017-09-16 17:45:28 -05:00
None if ! block . stmts . is_empty ( ) = > {
match block . stmts [ 0 ] . node {
StmtExpr ( ref expr , _ ) |
StmtSemi ( ref expr , _ ) = > Some ( expr ) ,
StmtDecl ( .. ) = > None ,
}
2016-12-20 11:21:30 -06:00
} ,
2015-10-02 02:55:34 -05:00
_ = > None ,
2015-08-29 04:41:06 -05:00
}
}
2017-09-16 17:45:28 -05:00
/// Return true if expr contains a single break expr without destination label
/// and
2017-09-05 15:28:30 -05:00
/// passed expression. The expression may be within a block.
fn is_simple_break_expr ( expr : & Expr ) -> bool {
2015-08-29 04:41:06 -05:00
match expr . node {
2017-09-05 15:28:30 -05:00
ExprBreak ( dest , ref passed_expr ) if dest . ident . is_none ( ) & & passed_expr . is_none ( ) = > true ,
ExprBlock ( ref b ) = > {
match extract_first_expr ( b ) {
Some ( subexpr ) = > is_simple_break_expr ( subexpr ) ,
None = > false ,
}
2016-12-20 11:21:30 -06:00
} ,
2015-08-29 04:41:06 -05:00
_ = > false ,
}
}
2015-08-23 12:25:45 -05:00
// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be
// incremented exactly once in the loop body, and initialized to zero
// at the start of the loop.
#[ derive(PartialEq) ]
enum VarState {
2017-09-16 17:45:28 -05:00
Initial , // Not examined yet
2016-01-03 22:26:12 -06:00
IncrOnce , // Incremented exactly once, may be a loop counter
Declared , // Declared but not (yet) initialized to zero
2015-08-23 12:25:45 -05:00
Warn ,
2016-01-03 22:26:12 -06:00
DontWarn ,
2015-08-23 12:25:45 -05:00
}
2016-02-26 05:45:55 -06:00
/// Scan a for loop for variables that are incremented exactly once.
2016-12-06 04:32:21 -06:00
struct IncrementVisitor < ' a , ' tcx : ' a > {
2017-09-16 17:45:28 -05:00
cx : & ' a LateContext < ' a , ' tcx > , // context reference
2016-01-03 22:26:12 -06:00
states : HashMap < NodeId , VarState > , // incremented variables
2017-09-16 17:45:28 -05:00
depth : u32 , // depth of conditional expressions
2016-01-03 22:26:12 -06:00
done : bool ,
2015-08-23 12:25:45 -05:00
}
2016-12-06 04:32:21 -06:00
impl < ' a , ' tcx > Visitor < ' tcx > for IncrementVisitor < ' a , ' tcx > {
fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
2015-08-23 12:25:45 -05:00
if self . done {
return ;
}
// If node is a variable
if let Some ( def_id ) = var_def_id ( self . cx , expr ) {
if let Some ( parent ) = get_parent_expr ( self . cx , expr ) {
let state = self . states . entry ( def_id ) . or_insert ( VarState ::Initial ) ;
match parent . node {
2016-01-03 22:26:12 -06:00
ExprAssignOp ( op , ref lhs , ref rhs ) = > {
2015-08-23 12:25:45 -05:00
if lhs . id = = expr . id {
2015-09-04 08:26:58 -05:00
if op . node = = BiAdd & & is_integer_literal ( rhs , 1 ) {
2015-08-23 12:25:45 -05:00
* state = match * state {
VarState ::Initial if self . depth = = 0 = > VarState ::IncrOnce ,
2016-01-03 22:26:12 -06:00
_ = > VarState ::DontWarn ,
2015-08-23 12:25:45 -05:00
} ;
2016-01-03 22:26:12 -06:00
} else {
2015-08-23 12:25:45 -05:00
// Assigned some other value
* state = VarState ::DontWarn ;
}
2016-01-03 22:26:12 -06:00
}
2016-12-20 11:21:30 -06:00
} ,
2015-08-23 12:25:45 -05:00
ExprAssign ( ref lhs , _ ) if lhs . id = = expr . id = > * state = VarState ::DontWarn ,
2016-01-03 22:26:12 -06:00
ExprAddrOf ( mutability , _ ) if mutability = = MutMutable = > * state = VarState ::DontWarn ,
_ = > ( ) ,
2015-08-23 12:25:45 -05:00
}
}
2016-01-03 22:26:12 -06:00
} else if is_loop ( expr ) {
2015-08-23 12:25:45 -05:00
self . states . clear ( ) ;
self . done = true ;
return ;
2016-01-03 22:26:12 -06:00
} else if is_conditional ( expr ) {
2015-08-23 12:25:45 -05:00
self . depth + = 1 ;
walk_expr ( self , expr ) ;
self . depth - = 1 ;
return ;
}
walk_expr ( self , expr ) ;
}
2016-12-06 04:32:21 -06:00
fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
2017-05-12 05:02:42 -05:00
NestedVisitorMap ::None
2016-12-06 04:32:21 -06:00
}
2015-08-23 12:25:45 -05:00
}
2016-02-26 05:45:55 -06:00
/// Check whether a variable is initialized to zero at the start of a loop.
2016-12-06 04:32:21 -06:00
struct InitializeVisitor < ' a , ' tcx : ' a > {
cx : & ' a LateContext < ' a , ' tcx > , // context reference
2017-09-16 17:45:28 -05:00
end_expr : & ' tcx Expr , // the for loop. Stop scanning here.
2015-08-23 12:25:45 -05:00
var_id : NodeId ,
state : VarState ,
name : Option < Name > ,
2016-01-03 22:26:12 -06:00
depth : u32 , // depth of conditional expressions
past_loop : bool ,
2015-08-23 12:25:45 -05:00
}
2016-12-06 04:32:21 -06:00
impl < ' a , ' tcx > Visitor < ' tcx > for InitializeVisitor < ' a , ' tcx > {
fn visit_decl ( & mut self , decl : & ' tcx Decl ) {
2015-08-23 12:25:45 -05:00
// Look for declarations of the variable
if let DeclLocal ( ref local ) = decl . node {
if local . pat . id = = self . var_id {
2016-12-01 15:31:56 -06:00
if let PatKind ::Binding ( _ , _ , ref ident , _ ) = local . pat . node {
2016-05-19 16:14:34 -05:00
self . name = Some ( ident . node ) ;
2015-08-23 12:25:45 -05:00
self . state = if let Some ( ref init ) = local . init {
2015-09-04 08:26:58 -05:00
if is_integer_literal ( init , 0 ) {
2015-08-23 12:25:45 -05:00
VarState ::Warn
} else {
VarState ::Declared
}
2016-01-03 22:26:12 -06:00
} else {
2015-08-23 12:25:45 -05:00
VarState ::Declared
}
}
}
}
walk_decl ( self , decl ) ;
}
2016-12-06 04:32:21 -06:00
fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
2015-11-25 17:09:01 -06:00
if self . state = = VarState ::DontWarn {
return ;
}
if expr = = self . end_expr {
self . past_loop = true ;
return ;
2015-08-23 12:25:45 -05:00
}
// No need to visit expressions before the variable is
2015-11-25 17:09:01 -06:00
// declared
if self . state = = VarState ::IncrOnce {
2015-08-23 12:25:45 -05:00
return ;
}
// If node is the desired variable, see how it's used
if var_def_id ( self . cx , expr ) = = Some ( self . var_id ) {
if let Some ( parent ) = get_parent_expr ( self . cx , expr ) {
match parent . node {
ExprAssignOp ( _ , ref lhs , _ ) if lhs . id = = expr . id = > {
self . state = VarState ::DontWarn ;
2016-12-20 11:21:30 -06:00
} ,
2015-08-23 12:25:45 -05:00
ExprAssign ( ref lhs , ref rhs ) if lhs . id = = expr . id = > {
2015-09-04 08:26:58 -05:00
self . state = if is_integer_literal ( rhs , 0 ) & & self . depth = = 0 {
2015-08-23 12:25:45 -05:00
VarState ::Warn
} else {
VarState ::DontWarn
2016-01-03 22:26:12 -06:00
}
2016-12-20 11:21:30 -06:00
} ,
2016-01-03 22:26:12 -06:00
ExprAddrOf ( mutability , _ ) if mutability = = MutMutable = > self . state = VarState ::DontWarn ,
_ = > ( ) ,
2015-08-23 12:25:45 -05:00
}
}
2015-11-25 17:09:01 -06:00
if self . past_loop {
self . state = VarState ::DontWarn ;
return ;
}
2016-01-03 22:26:12 -06:00
} else if ! self . past_loop & & is_loop ( expr ) {
2015-08-23 12:25:45 -05:00
self . state = VarState ::DontWarn ;
return ;
2016-01-03 22:26:12 -06:00
} else if is_conditional ( expr ) {
2015-08-23 12:25:45 -05:00
self . depth + = 1 ;
walk_expr ( self , expr ) ;
self . depth - = 1 ;
return ;
}
walk_expr ( self , expr ) ;
}
2016-12-06 04:32:21 -06:00
fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
2017-05-12 05:02:42 -05:00
NestedVisitorMap ::None
2016-12-06 04:32:21 -06:00
}
2015-08-23 12:25:45 -05:00
}
2015-09-18 21:53:04 -05:00
fn var_def_id ( cx : & LateContext , expr : & Expr ) -> Option < NodeId > {
2016-12-01 15:31:56 -06:00
if let ExprPath ( ref qpath ) = expr . node {
2017-08-15 04:10:49 -05:00
let path_res = cx . tables . qpath_def ( qpath , expr . hir_id ) ;
2017-09-12 07:26:40 -05:00
if let Def ::Local ( node_id ) = path_res {
2016-01-03 22:26:12 -06:00
return Some ( node_id ) ;
2015-08-23 12:25:45 -05:00
}
}
None
}
fn is_loop ( expr : & Expr ) -> bool {
match expr . node {
2016-01-03 22:26:12 -06:00
ExprLoop ( .. ) | ExprWhile ( .. ) = > true ,
_ = > false ,
2015-08-23 12:25:45 -05:00
}
}
fn is_conditional ( expr : & Expr ) -> bool {
match expr . node {
ExprIf ( .. ) | ExprMatch ( .. ) = > true ,
2016-01-03 22:26:12 -06:00
_ = > false ,
2015-08-23 12:25:45 -05:00
}
}
2017-07-31 17:58:26 -05:00
fn is_nested ( cx : & LateContext , match_expr : & Expr , iter_expr : & Expr ) -> bool {
if_let_chain! { [
let Some ( loop_block ) = get_enclosing_block ( cx , match_expr . id ) ,
let Some ( map ::Node ::NodeExpr ( loop_expr ) ) = cx . tcx . hir . find ( cx . tcx . hir . get_parent_node ( loop_block . id ) ) ,
] , {
2017-08-02 17:41:46 -05:00
return is_loop_nested ( cx , loop_expr , iter_expr )
2017-07-31 17:58:26 -05:00
} }
false
}
2017-08-02 17:41:46 -05:00
fn is_loop_nested ( cx : & LateContext , loop_expr : & Expr , iter_expr : & Expr ) -> bool {
let mut id = loop_expr . id ;
let iter_name = if let Some ( name ) = path_name ( iter_expr ) {
name
} else {
return true ;
} ;
loop {
let parent = cx . tcx . hir . get_parent_node ( id ) ;
if parent = = id {
return false ;
}
match cx . tcx . hir . find ( parent ) {
2017-09-16 17:45:28 -05:00
Some ( NodeExpr ( expr ) ) = > {
match expr . node {
ExprLoop ( .. ) | ExprWhile ( .. ) = > {
return true ;
} ,
_ = > ( ) ,
}
2017-08-02 17:41:46 -05:00
} ,
Some ( NodeBlock ( block ) ) = > {
let mut block_visitor = LoopNestVisitor {
2017-08-09 02:30:56 -05:00
id : id ,
iterator : iter_name ,
nesting : Unknown ,
} ;
2017-08-02 17:41:46 -05:00
walk_block ( & mut block_visitor , block ) ;
if block_visitor . nesting = = RuledOut {
2017-07-31 17:58:26 -05:00
return false ;
}
2017-08-02 17:41:46 -05:00
} ,
Some ( NodeStmt ( _ ) ) = > ( ) ,
_ = > {
2017-07-31 17:58:26 -05:00
return false ;
2017-08-09 02:30:56 -05:00
} ,
2017-07-31 17:58:26 -05:00
}
2017-08-02 17:41:46 -05:00
id = parent ;
2017-07-31 17:58:26 -05:00
}
}
2017-08-02 17:41:46 -05:00
#[ derive(PartialEq, Eq) ]
enum Nesting {
2017-09-16 17:45:28 -05:00
Unknown , // no nesting detected yet
RuledOut , // the iterator is initialized or assigned within scope
2017-08-09 02:30:56 -05:00
LookFurther , // no nesting detected, no further walk required
2017-07-31 17:58:26 -05:00
}
2017-09-05 04:33:04 -05:00
use self ::Nesting ::{ LookFurther , RuledOut , Unknown } ;
2017-07-31 17:58:26 -05:00
2017-08-02 17:41:46 -05:00
struct LoopNestVisitor {
id : NodeId ,
iterator : Name ,
2017-08-09 02:30:56 -05:00
nesting : Nesting ,
2017-07-31 17:58:26 -05:00
}
2017-08-02 17:41:46 -05:00
impl < ' tcx > Visitor < ' tcx > for LoopNestVisitor {
fn visit_stmt ( & mut self , stmt : & ' tcx Stmt ) {
if stmt . node . id ( ) = = self . id {
self . nesting = LookFurther ;
} else if self . nesting = = Unknown {
walk_stmt ( self , stmt ) ;
}
}
2017-07-31 17:58:26 -05:00
fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
2017-08-09 02:30:56 -05:00
if self . nesting ! = Unknown {
return ;
}
2017-08-02 17:41:46 -05:00
if expr . id = = self . id {
self . nesting = LookFurther ;
return ;
}
2017-07-31 17:58:26 -05:00
match expr . node {
2017-09-16 17:45:28 -05:00
ExprAssign ( ref path , _ ) |
ExprAssignOp ( _ , ref path , _ ) = > {
if match_var ( path , self . iterator ) {
self . nesting = RuledOut ;
}
2017-08-02 17:41:46 -05:00
} ,
2017-08-09 02:30:56 -05:00
_ = > walk_expr ( self , expr ) ,
2017-07-31 17:58:26 -05:00
}
}
2017-08-02 17:41:46 -05:00
fn visit_pat ( & mut self , pat : & ' tcx Pat ) {
2017-08-09 02:30:56 -05:00
if self . nesting ! = Unknown {
return ;
}
2017-08-02 17:41:46 -05:00
if let PatKind ::Binding ( _ , _ , span_name , _ ) = pat . node {
if self . iterator = = span_name . node {
self . nesting = RuledOut ;
return ;
}
}
walk_pat ( self , pat )
2017-07-31 17:58:26 -05:00
}
2017-08-02 17:41:46 -05:00
fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
NestedVisitorMap ::None
2017-07-31 17:58:26 -05:00
}
}
2017-08-02 17:41:46 -05:00
fn path_name ( e : & Expr ) -> Option < Name > {
if let ExprPath ( QPath ::Resolved ( _ , ref path ) ) = e . node {
let segments = & path . segments ;
if segments . len ( ) = = 1 {
return Some ( segments [ 0 ] . name ) ;
}
} ;
None
2017-07-31 17:58:26 -05:00
}