Skip to content

Commit f760d77

Browse files
committed
Auto merge of rust-lang#5597 - esamudera:slice_iter_next, r=flip1995
New lint: iter_next_slice Hello, this is a work-in-progress PR for issue: rust-lang/rust-clippy#5572 I have implemented lint to replace `iter().next()` for `slice[index..]` and `array` with `get(index)` and `get(0)` respectively. However since I made a lot of changes, I would like to request some feedback before continuing so that I could fix mistakes. Thank you! --- changelog: implement `iter_next_slice` lint and test, and modify `needless_continues`, `for_loop_over_options_result` UI tests since they have `iter().next()`
2 parents bfafb8e + 32fde0b commit f760d77

14 files changed

+204
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,7 @@ Released 2018-09-13
14011401
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
14021402
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
14031403
[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
1404+
[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
14041405
[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth
14051406
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
14061407
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
666666
&methods::INTO_ITER_ON_REF,
667667
&methods::ITERATOR_STEP_BY_ZERO,
668668
&methods::ITER_CLONED_COLLECT,
669+
&methods::ITER_NEXT_SLICE,
669670
&methods::ITER_NTH,
670671
&methods::ITER_NTH_ZERO,
671672
&methods::ITER_SKIP_NEXT,
@@ -1310,6 +1311,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13101311
LintId::of(&methods::INTO_ITER_ON_REF),
13111312
LintId::of(&methods::ITERATOR_STEP_BY_ZERO),
13121313
LintId::of(&methods::ITER_CLONED_COLLECT),
1314+
LintId::of(&methods::ITER_NEXT_SLICE),
13131315
LintId::of(&methods::ITER_NTH),
13141316
LintId::of(&methods::ITER_NTH_ZERO),
13151317
LintId::of(&methods::ITER_SKIP_NEXT),
@@ -1491,6 +1493,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14911493
LintId::of(&methods::CHARS_NEXT_CMP),
14921494
LintId::of(&methods::INTO_ITER_ON_REF),
14931495
LintId::of(&methods::ITER_CLONED_COLLECT),
1496+
LintId::of(&methods::ITER_NEXT_SLICE),
14941497
LintId::of(&methods::ITER_NTH_ZERO),
14951498
LintId::of(&methods::ITER_SKIP_NEXT),
14961499
LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),

clippy_lints/src/loops.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use rustc_middle::middle::region;
2727
use rustc_middle::ty::{self, Ty};
2828
use rustc_session::{declare_lint_pass, declare_tool_lint};
2929
use rustc_span::source_map::Span;
30-
use rustc_span::BytePos;
30+
use rustc_span::symbol::Symbol;
3131
use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Place, PlaceBase};
3232
use std::iter::{once, Iterator};
3333
use std::mem;
@@ -2381,32 +2381,32 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
23812381
match_type(cx, ty, &paths::BTREEMAP) ||
23822382
is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) {
23832383
if method.ident.name == sym!(len) {
2384-
let span = shorten_needless_collect_span(expr);
2384+
let span = shorten_span(expr, sym!(collect));
23852385
span_lint_and_sugg(
23862386
cx,
23872387
NEEDLESS_COLLECT,
23882388
span,
23892389
NEEDLESS_COLLECT_MSG,
23902390
"replace with",
2391-
".count()".to_string(),
2391+
"count()".to_string(),
23922392
Applicability::MachineApplicable,
23932393
);
23942394
}
23952395
if method.ident.name == sym!(is_empty) {
2396-
let span = shorten_needless_collect_span(expr);
2396+
let span = shorten_span(expr, sym!(iter));
23972397
span_lint_and_sugg(
23982398
cx,
23992399
NEEDLESS_COLLECT,
24002400
span,
24012401
NEEDLESS_COLLECT_MSG,
24022402
"replace with",
2403-
".next().is_none()".to_string(),
2403+
"get(0).is_none()".to_string(),
24042404
Applicability::MachineApplicable,
24052405
);
24062406
}
24072407
if method.ident.name == sym!(contains) {
24082408
let contains_arg = snippet(cx, args[1].span, "??");
2409-
let span = shorten_needless_collect_span(expr);
2409+
let span = shorten_span(expr, sym!(collect));
24102410
span_lint_and_then(
24112411
cx,
24122412
NEEDLESS_COLLECT,
@@ -2422,7 +2422,7 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
24222422
span,
24232423
"replace with",
24242424
format!(
2425-
".any(|{}| x == {})",
2425+
"any(|{}| x == {})",
24262426
arg, pred
24272427
),
24282428
Applicability::MachineApplicable,
@@ -2435,13 +2435,13 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
24352435
}
24362436
}
24372437

2438-
fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span {
2439-
if_chain! {
2440-
if let ExprKind::MethodCall(_, _, ref args) = expr.kind;
2441-
if let ExprKind::MethodCall(_, ref span, _) = args[0].kind;
2442-
then {
2443-
return expr.span.with_lo(span.lo() - BytePos(1));
2438+
fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span {
2439+
let mut current_expr = expr;
2440+
while let ExprKind::MethodCall(ref path, ref span, ref args) = current_expr.kind {
2441+
if path.ident.name == target_fn_name {
2442+
return expr.span.with_lo(span.lo());
24442443
}
2444+
current_expr = &args[0];
24452445
}
24462446
unreachable!()
24472447
}

clippy_lints/src/methods/mod.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use rustc_span::symbol::{sym, SymbolStr};
2626
use crate::consts::{constant, Constant};
2727
use crate::utils::usage::mutated_variables;
2828
use crate::utils::{
29-
get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, is_copy,
29+
get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro, is_copy,
3030
is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment,
3131
match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths,
3232
remove_blocks, return_ty, same_tys, single_segment_path, snippet, snippet_with_applicability,
@@ -1242,6 +1242,32 @@ declare_clippy_lint! {
12421242
"using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
12431243
}
12441244

1245+
declare_clippy_lint! {
1246+
/// **What it does:** Checks for usage of `iter().next()` on a Slice or an Array
1247+
///
1248+
/// **Why is this bad?** These can be shortened into `.get()`
1249+
///
1250+
/// **Known problems:** None.
1251+
///
1252+
/// **Example:**
1253+
/// ```rust
1254+
/// # let a = [1, 2, 3];
1255+
/// # let b = vec![1, 2, 3];
1256+
/// a[2..].iter().next();
1257+
/// b.iter().next();
1258+
/// ```
1259+
/// should be written as:
1260+
/// ```rust
1261+
/// # let a = [1, 2, 3];
1262+
/// # let b = vec![1, 2, 3];
1263+
/// a.get(2);
1264+
/// b.get(0);
1265+
/// ```
1266+
pub ITER_NEXT_SLICE,
1267+
style,
1268+
"using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
1269+
}
1270+
12451271
declare_lint_pass!(Methods => [
12461272
UNWRAP_USED,
12471273
EXPECT_USED,
@@ -1273,6 +1299,7 @@ declare_lint_pass!(Methods => [
12731299
FIND_MAP,
12741300
MAP_FLATTEN,
12751301
ITERATOR_STEP_BY_ZERO,
1302+
ITER_NEXT_SLICE,
12761303
ITER_NTH,
12771304
ITER_NTH_ZERO,
12781305
ITER_SKIP_NEXT,
@@ -1320,6 +1347,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
13201347
},
13211348
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
13221349
["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]),
1350+
["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]),
13231351
["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]),
13241352
["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
13251353
["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]),
@@ -2199,6 +2227,60 @@ fn lint_step_by<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &hir::Expr<'_>, args
21992227
}
22002228
}
22012229

2230+
fn lint_iter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) {
2231+
let caller_expr = &iter_args[0];
2232+
2233+
// Skip lint if the `iter().next()` expression is a for loop argument,
2234+
// since it is already covered by `&loops::ITER_NEXT_LOOP`
2235+
let mut parent_expr_opt = get_parent_expr(cx, expr);
2236+
while let Some(parent_expr) = parent_expr_opt {
2237+
if higher::for_loop(parent_expr).is_some() {
2238+
return;
2239+
}
2240+
parent_expr_opt = get_parent_expr(cx, parent_expr);
2241+
}
2242+
2243+
if derefs_to_slice(cx, caller_expr, cx.tables.expr_ty(caller_expr)).is_some() {
2244+
// caller is a Slice
2245+
if_chain! {
2246+
if let hir::ExprKind::Index(ref caller_var, ref index_expr) = &caller_expr.kind;
2247+
if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen })
2248+
= higher::range(cx, index_expr);
2249+
if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind;
2250+
if let ast::LitKind::Int(start_idx, _) = start_lit.node;
2251+
then {
2252+
let mut applicability = Applicability::MachineApplicable;
2253+
span_lint_and_sugg(
2254+
cx,
2255+
ITER_NEXT_SLICE,
2256+
expr.span,
2257+
"Using `.iter().next()` on a Slice without end index.",
2258+
"try calling",
2259+
format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx),
2260+
applicability,
2261+
);
2262+
}
2263+
}
2264+
} else if is_type_diagnostic_item(cx, cx.tables.expr_ty(caller_expr), sym!(vec_type))
2265+
|| matches!(&walk_ptrs_ty(cx.tables.expr_ty(caller_expr)).kind, ty::Array(_, _))
2266+
{
2267+
// caller is a Vec or an Array
2268+
let mut applicability = Applicability::MachineApplicable;
2269+
span_lint_and_sugg(
2270+
cx,
2271+
ITER_NEXT_SLICE,
2272+
expr.span,
2273+
"Using `.iter().next()` on an array",
2274+
"try calling",
2275+
format!(
2276+
"{}.get(0)",
2277+
snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability)
2278+
),
2279+
applicability,
2280+
);
2281+
}
2282+
}
2283+
22022284
fn lint_iter_nth<'a, 'tcx>(
22032285
cx: &LateContext<'a, 'tcx>,
22042286
expr: &hir::Expr<'_>,

clippy_lints/src/needless_continue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ fn erode_from_back(s: &str) -> String {
424424
}
425425

426426
fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
427-
block.stmts.iter().next().map(|stmt| stmt.span)
427+
block.stmts.get(0).map(|stmt| stmt.span)
428428
}
429429

430430
#[cfg(test)]

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
934934
deprecation: None,
935935
module: "loops",
936936
},
937+
Lint {
938+
name: "iter_next_slice",
939+
group: "style",
940+
desc: "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`",
941+
deprecation: None,
942+
module: "methods",
943+
},
937944
Lint {
938945
name: "iter_nth",
939946
group: "perf",

tests/ui/into_iter_on_ref.fixed

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ fn main() {
4040
let _ = (&HashSet::<i32>::new()).iter(); //~ WARN equivalent to .iter()
4141
let _ = std::path::Path::new("12/34").iter(); //~ WARN equivalent to .iter()
4242
let _ = std::path::PathBuf::from("12/34").iter(); //~ ERROR equivalent to .iter()
43+
44+
let _ = (&[1, 2, 3]).iter().next(); //~ WARN equivalent to .iter()
4345
}

tests/ui/into_iter_on_ref.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ fn main() {
4040
let _ = (&HashSet::<i32>::new()).into_iter(); //~ WARN equivalent to .iter()
4141
let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter()
4242
let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter()
43+
44+
let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter()
4345
}

tests/ui/into_iter_on_ref.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,5 +156,11 @@ error: this `.into_iter()` call is equivalent to `.iter()` and will not move the
156156
LL | let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter()
157157
| ^^^^^^^^^ help: call directly: `iter`
158158

159-
error: aborting due to 26 previous errors
159+
error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `array`
160+
--> $DIR/into_iter_on_ref.rs:44:26
161+
|
162+
LL | let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter()
163+
| ^^^^^^^^^ help: call directly: `iter`
164+
165+
error: aborting due to 27 previous errors
160166

tests/ui/iter_next_slice.fixed

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// run-rustfix
2+
#![warn(clippy::iter_next_slice)]
3+
4+
fn main() {
5+
// test code goes here
6+
let s = [1, 2, 3];
7+
let v = vec![1, 2, 3];
8+
9+
s.get(0);
10+
// Should be replaced by s.get(0)
11+
12+
s.get(2);
13+
// Should be replaced by s.get(2)
14+
15+
v.get(5);
16+
// Should be replaced by v.get(5)
17+
18+
v.get(0);
19+
// Should be replaced by v.get(0)
20+
21+
let o = Some(5);
22+
o.iter().next();
23+
// Shouldn't be linted since this is not a Slice or an Array
24+
}

tests/ui/iter_next_slice.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// run-rustfix
2+
#![warn(clippy::iter_next_slice)]
3+
4+
fn main() {
5+
// test code goes here
6+
let s = [1, 2, 3];
7+
let v = vec![1, 2, 3];
8+
9+
s.iter().next();
10+
// Should be replaced by s.get(0)
11+
12+
s[2..].iter().next();
13+
// Should be replaced by s.get(2)
14+
15+
v[5..].iter().next();
16+
// Should be replaced by v.get(5)
17+
18+
v.iter().next();
19+
// Should be replaced by v.get(0)
20+
21+
let o = Some(5);
22+
o.iter().next();
23+
// Shouldn't be linted since this is not a Slice or an Array
24+
}

tests/ui/iter_next_slice.stderr

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: Using `.iter().next()` on an array
2+
--> $DIR/iter_next_slice.rs:9:5
3+
|
4+
LL | s.iter().next();
5+
| ^^^^^^^^^^^^^^^ help: try calling: `s.get(0)`
6+
|
7+
= note: `-D clippy::iter-next-slice` implied by `-D warnings`
8+
9+
error: Using `.iter().next()` on a Slice without end index.
10+
--> $DIR/iter_next_slice.rs:12:5
11+
|
12+
LL | s[2..].iter().next();
13+
| ^^^^^^^^^^^^^^^^^^^^ help: try calling: `s.get(2)`
14+
15+
error: Using `.iter().next()` on a Slice without end index.
16+
--> $DIR/iter_next_slice.rs:15:5
17+
|
18+
LL | v[5..].iter().next();
19+
| ^^^^^^^^^^^^^^^^^^^^ help: try calling: `v.get(5)`
20+
21+
error: Using `.iter().next()` on an array
22+
--> $DIR/iter_next_slice.rs:18:5
23+
|
24+
LL | v.iter().next();
25+
| ^^^^^^^^^^^^^^^ help: try calling: `v.get(0)`
26+
27+
error: aborting due to 4 previous errors
28+

tests/ui/needless_collect.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::collections::{BTreeSet, HashMap, HashSet};
99
fn main() {
1010
let sample = [1; 5];
1111
let len = sample.iter().count();
12-
if sample.iter().next().is_none() {
12+
if sample.get(0).is_none() {
1313
// Empty
1414
}
1515
sample.iter().cloned().any(|x| x == 1);

0 commit comments

Comments
 (0)