New lint: iter_next_slice

This commit is contained in:
Ericko Samudera 2020-05-25 23:22:01 +07:00
parent ce86f907ef
commit 32fde0b511
14 changed files with 204 additions and 25 deletions

View file

@ -1401,6 +1401,7 @@ Released 2018-09-13
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements [`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect [`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop [`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth [`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero [`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next [`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next

View file

@ -669,6 +669,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&methods::INTO_ITER_ON_REF, &methods::INTO_ITER_ON_REF,
&methods::ITERATOR_STEP_BY_ZERO, &methods::ITERATOR_STEP_BY_ZERO,
&methods::ITER_CLONED_COLLECT, &methods::ITER_CLONED_COLLECT,
&methods::ITER_NEXT_SLICE,
&methods::ITER_NTH, &methods::ITER_NTH,
&methods::ITER_NTH_ZERO, &methods::ITER_NTH_ZERO,
&methods::ITER_SKIP_NEXT, &methods::ITER_SKIP_NEXT,
@ -1303,6 +1304,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&methods::INTO_ITER_ON_REF), LintId::of(&methods::INTO_ITER_ON_REF),
LintId::of(&methods::ITERATOR_STEP_BY_ZERO), LintId::of(&methods::ITERATOR_STEP_BY_ZERO),
LintId::of(&methods::ITER_CLONED_COLLECT), LintId::of(&methods::ITER_CLONED_COLLECT),
LintId::of(&methods::ITER_NEXT_SLICE),
LintId::of(&methods::ITER_NTH), LintId::of(&methods::ITER_NTH),
LintId::of(&methods::ITER_NTH_ZERO), LintId::of(&methods::ITER_NTH_ZERO),
LintId::of(&methods::ITER_SKIP_NEXT), LintId::of(&methods::ITER_SKIP_NEXT),
@ -1483,6 +1485,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&methods::CHARS_NEXT_CMP), LintId::of(&methods::CHARS_NEXT_CMP),
LintId::of(&methods::INTO_ITER_ON_REF), LintId::of(&methods::INTO_ITER_ON_REF),
LintId::of(&methods::ITER_CLONED_COLLECT), LintId::of(&methods::ITER_CLONED_COLLECT),
LintId::of(&methods::ITER_NEXT_SLICE),
LintId::of(&methods::ITER_NTH_ZERO), LintId::of(&methods::ITER_NTH_ZERO),
LintId::of(&methods::ITER_SKIP_NEXT), LintId::of(&methods::ITER_SKIP_NEXT),
LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),

View file

@ -27,7 +27,7 @@ use rustc_middle::middle::region;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span; use rustc_span::source_map::Span;
use rustc_span::BytePos; use rustc_span::symbol::Symbol;
use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Place, PlaceBase}; use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Place, PlaceBase};
use std::iter::{once, Iterator}; use std::iter::{once, Iterator};
use std::mem; use std::mem;
@ -2381,32 +2381,32 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
match_type(cx, ty, &paths::BTREEMAP) || match_type(cx, ty, &paths::BTREEMAP) ||
is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) { is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) {
if method.ident.name == sym!(len) { if method.ident.name == sym!(len) {
let span = shorten_needless_collect_span(expr); let span = shorten_span(expr, sym!(collect));
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
NEEDLESS_COLLECT, NEEDLESS_COLLECT,
span, span,
NEEDLESS_COLLECT_MSG, NEEDLESS_COLLECT_MSG,
"replace with", "replace with",
".count()".to_string(), "count()".to_string(),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} }
if method.ident.name == sym!(is_empty) { if method.ident.name == sym!(is_empty) {
let span = shorten_needless_collect_span(expr); let span = shorten_span(expr, sym!(iter));
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
NEEDLESS_COLLECT, NEEDLESS_COLLECT,
span, span,
NEEDLESS_COLLECT_MSG, NEEDLESS_COLLECT_MSG,
"replace with", "replace with",
".next().is_none()".to_string(), "get(0).is_none()".to_string(),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} }
if method.ident.name == sym!(contains) { if method.ident.name == sym!(contains) {
let contains_arg = snippet(cx, args[1].span, "??"); let contains_arg = snippet(cx, args[1].span, "??");
let span = shorten_needless_collect_span(expr); let span = shorten_span(expr, sym!(collect));
span_lint_and_then( span_lint_and_then(
cx, cx,
NEEDLESS_COLLECT, NEEDLESS_COLLECT,
@ -2422,7 +2422,7 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
span, span,
"replace with", "replace with",
format!( format!(
".any(|{}| x == {})", "any(|{}| x == {})",
arg, pred arg, pred
), ),
Applicability::MachineApplicable, Applicability::MachineApplicable,
@ -2435,13 +2435,13 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
} }
} }
fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span {
if_chain! { let mut current_expr = expr;
if let ExprKind::MethodCall(_, _, ref args) = expr.kind; while let ExprKind::MethodCall(ref path, ref span, ref args) = current_expr.kind {
if let ExprKind::MethodCall(_, ref span, _) = args[0].kind; if path.ident.name == target_fn_name {
then { return expr.span.with_lo(span.lo());
return expr.span.with_lo(span.lo() - BytePos(1));
} }
current_expr = &args[0];
} }
unreachable!() unreachable!()
} }

View file

@ -26,7 +26,7 @@ use rustc_span::symbol::{sym, SymbolStr};
use crate::consts::{constant, Constant}; use crate::consts::{constant, Constant};
use crate::utils::usage::mutated_variables; use crate::utils::usage::mutated_variables;
use crate::utils::{ use crate::utils::{
get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, is_copy, get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro, is_copy,
is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment,
match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths, match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths,
remove_blocks, return_ty, same_tys, single_segment_path, snippet, snippet_with_applicability, remove_blocks, return_ty, same_tys, single_segment_path, snippet, snippet_with_applicability,
@ -1242,6 +1242,32 @@ declare_clippy_lint! {
"using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`" "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
} }
declare_clippy_lint! {
/// **What it does:** Checks for usage of `iter().next()` on a Slice or an Array
///
/// **Why is this bad?** These can be shortened into `.get()`
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # let a = [1, 2, 3];
/// # let b = vec![1, 2, 3];
/// a[2..].iter().next();
/// b.iter().next();
/// ```
/// should be written as:
/// ```rust
/// # let a = [1, 2, 3];
/// # let b = vec![1, 2, 3];
/// a.get(2);
/// b.get(0);
/// ```
pub ITER_NEXT_SLICE,
style,
"using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
}
declare_lint_pass!(Methods => [ declare_lint_pass!(Methods => [
UNWRAP_USED, UNWRAP_USED,
EXPECT_USED, EXPECT_USED,
@ -1273,6 +1299,7 @@ declare_lint_pass!(Methods => [
FIND_MAP, FIND_MAP,
MAP_FLATTEN, MAP_FLATTEN,
ITERATOR_STEP_BY_ZERO, ITERATOR_STEP_BY_ZERO,
ITER_NEXT_SLICE,
ITER_NTH, ITER_NTH,
ITER_NTH_ZERO, ITER_NTH_ZERO,
ITER_SKIP_NEXT, ITER_SKIP_NEXT,
@ -1320,6 +1347,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
}, },
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]), ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]), ["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]),
["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]),
["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]), ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]),
["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]), ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]), ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]),
@ -2184,6 +2212,60 @@ fn lint_step_by<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &hir::Expr<'_>, args
} }
} }
fn lint_iter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) {
let caller_expr = &iter_args[0];
// Skip lint if the `iter().next()` expression is a for loop argument,
// since it is already covered by `&loops::ITER_NEXT_LOOP`
let mut parent_expr_opt = get_parent_expr(cx, expr);
while let Some(parent_expr) = parent_expr_opt {
if higher::for_loop(parent_expr).is_some() {
return;
}
parent_expr_opt = get_parent_expr(cx, parent_expr);
}
if derefs_to_slice(cx, caller_expr, cx.tables.expr_ty(caller_expr)).is_some() {
// caller is a Slice
if_chain! {
if let hir::ExprKind::Index(ref caller_var, ref index_expr) = &caller_expr.kind;
if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen })
= higher::range(cx, index_expr);
if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind;
if let ast::LitKind::Int(start_idx, _) = start_lit.node;
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
ITER_NEXT_SLICE,
expr.span,
"Using `.iter().next()` on a Slice without end index.",
"try calling",
format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx),
applicability,
);
}
}
} else if is_type_diagnostic_item(cx, cx.tables.expr_ty(caller_expr), sym!(vec_type))
|| matches!(&walk_ptrs_ty(cx.tables.expr_ty(caller_expr)).kind, ty::Array(_, _))
{
// caller is a Vec or an Array
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
ITER_NEXT_SLICE,
expr.span,
"Using `.iter().next()` on an array",
"try calling",
format!(
"{}.get(0)",
snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability)
),
applicability,
);
}
}
fn lint_iter_nth<'a, 'tcx>( fn lint_iter_nth<'a, 'tcx>(
cx: &LateContext<'a, 'tcx>, cx: &LateContext<'a, 'tcx>,
expr: &hir::Expr<'_>, expr: &hir::Expr<'_>,

View file

@ -424,7 +424,7 @@ fn erode_from_back(s: &str) -> String {
} }
fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> { fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
block.stmts.iter().next().map(|stmt| stmt.span) block.stmts.get(0).map(|stmt| stmt.span)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -934,6 +934,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
deprecation: None, deprecation: None,
module: "loops", module: "loops",
}, },
Lint {
name: "iter_next_slice",
group: "style",
desc: "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`",
deprecation: None,
module: "methods",
},
Lint { Lint {
name: "iter_nth", name: "iter_nth",
group: "perf", group: "perf",

View file

@ -40,4 +40,6 @@ fn main() {
let _ = (&HashSet::<i32>::new()).iter(); //~ WARN equivalent to .iter() let _ = (&HashSet::<i32>::new()).iter(); //~ WARN equivalent to .iter()
let _ = std::path::Path::new("12/34").iter(); //~ WARN equivalent to .iter() let _ = std::path::Path::new("12/34").iter(); //~ WARN equivalent to .iter()
let _ = std::path::PathBuf::from("12/34").iter(); //~ ERROR equivalent to .iter() let _ = std::path::PathBuf::from("12/34").iter(); //~ ERROR equivalent to .iter()
let _ = (&[1, 2, 3]).iter().next(); //~ WARN equivalent to .iter()
} }

View file

@ -40,4 +40,6 @@ fn main() {
let _ = (&HashSet::<i32>::new()).into_iter(); //~ WARN equivalent to .iter() let _ = (&HashSet::<i32>::new()).into_iter(); //~ WARN equivalent to .iter()
let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter() let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter()
let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter()
let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter()
} }

View file

@ -156,5 +156,11 @@ error: this `.into_iter()` call is equivalent to `.iter()` and will not move the
LL | let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() LL | let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter()
| ^^^^^^^^^ help: call directly: `iter` | ^^^^^^^^^ help: call directly: `iter`
error: aborting due to 26 previous errors error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `array`
--> $DIR/into_iter_on_ref.rs:44:26
|
LL | let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter()
| ^^^^^^^^^ help: call directly: `iter`
error: aborting due to 27 previous errors

View file

@ -0,0 +1,24 @@
// run-rustfix
#![warn(clippy::iter_next_slice)]
fn main() {
// test code goes here
let s = [1, 2, 3];
let v = vec![1, 2, 3];
s.get(0);
// Should be replaced by s.get(0)
s.get(2);
// Should be replaced by s.get(2)
v.get(5);
// Should be replaced by v.get(5)
v.get(0);
// Should be replaced by v.get(0)
let o = Some(5);
o.iter().next();
// Shouldn't be linted since this is not a Slice or an Array
}

View file

@ -0,0 +1,24 @@
// run-rustfix
#![warn(clippy::iter_next_slice)]
fn main() {
// test code goes here
let s = [1, 2, 3];
let v = vec![1, 2, 3];
s.iter().next();
// Should be replaced by s.get(0)
s[2..].iter().next();
// Should be replaced by s.get(2)
v[5..].iter().next();
// Should be replaced by v.get(5)
v.iter().next();
// Should be replaced by v.get(0)
let o = Some(5);
o.iter().next();
// Shouldn't be linted since this is not a Slice or an Array
}

View file

@ -0,0 +1,28 @@
error: Using `.iter().next()` on an array
--> $DIR/iter_next_slice.rs:9:5
|
LL | s.iter().next();
| ^^^^^^^^^^^^^^^ help: try calling: `s.get(0)`
|
= note: `-D clippy::iter-next-slice` implied by `-D warnings`
error: Using `.iter().next()` on a Slice without end index.
--> $DIR/iter_next_slice.rs:12:5
|
LL | s[2..].iter().next();
| ^^^^^^^^^^^^^^^^^^^^ help: try calling: `s.get(2)`
error: Using `.iter().next()` on a Slice without end index.
--> $DIR/iter_next_slice.rs:15:5
|
LL | v[5..].iter().next();
| ^^^^^^^^^^^^^^^^^^^^ help: try calling: `v.get(5)`
error: Using `.iter().next()` on an array
--> $DIR/iter_next_slice.rs:18:5
|
LL | v.iter().next();
| ^^^^^^^^^^^^^^^ help: try calling: `v.get(0)`
error: aborting due to 4 previous errors

View file

@ -9,7 +9,7 @@ use std::collections::{BTreeSet, HashMap, HashSet};
fn main() { fn main() {
let sample = [1; 5]; let sample = [1; 5];
let len = sample.iter().count(); let len = sample.iter().count();
if sample.iter().next().is_none() { if sample.get(0).is_none() {
// Empty // Empty
} }
sample.iter().cloned().any(|x| x == 1); sample.iter().cloned().any(|x| x == 1);

View file

@ -1,28 +1,28 @@
error: avoid using `collect()` when not needed error: avoid using `collect()` when not needed
--> $DIR/needless_collect.rs:11:28 --> $DIR/needless_collect.rs:11:29
| |
LL | let len = sample.iter().collect::<Vec<_>>().len(); LL | let len = sample.iter().collect::<Vec<_>>().len();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()` | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
| |
= note: `-D clippy::needless-collect` implied by `-D warnings` = note: `-D clippy::needless-collect` implied by `-D warnings`
error: avoid using `collect()` when not needed error: avoid using `collect()` when not needed
--> $DIR/needless_collect.rs:12:21 --> $DIR/needless_collect.rs:12:15
| |
LL | if sample.iter().collect::<Vec<_>>().is_empty() { LL | if sample.iter().collect::<Vec<_>>().is_empty() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.next().is_none()` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get(0).is_none()`
error: avoid using `collect()` when not needed error: avoid using `collect()` when not needed
--> $DIR/needless_collect.rs:15:27 --> $DIR/needless_collect.rs:15:28
| |
LL | sample.iter().cloned().collect::<Vec<_>>().contains(&1); LL | sample.iter().cloned().collect::<Vec<_>>().contains(&1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.any(|x| x == 1)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)`
error: avoid using `collect()` when not needed error: avoid using `collect()` when not needed
--> $DIR/needless_collect.rs:16:34 --> $DIR/needless_collect.rs:16:35
| |
LL | sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().len(); LL | sample.iter().map(|x| (x, x)).collect::<HashMap<_, _>>().len();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
error: aborting due to 4 previous errors error: aborting due to 4 previous errors