[`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_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_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
&methods::INTO_ITER_ON_REF,
&methods::ITERATOR_STEP_BY_ZERO,
&methods::ITER_CLONED_COLLECT,
+ &methods::ITER_NEXT_SLICE,
&methods::ITER_NTH,
&methods::ITER_NTH_ZERO,
&methods::ITER_SKIP_NEXT,
LintId::of(&methods::INTO_ITER_ON_REF),
LintId::of(&methods::ITERATOR_STEP_BY_ZERO),
LintId::of(&methods::ITER_CLONED_COLLECT),
+ LintId::of(&methods::ITER_NEXT_SLICE),
LintId::of(&methods::ITER_NTH),
LintId::of(&methods::ITER_NTH_ZERO),
LintId::of(&methods::ITER_SKIP_NEXT),
LintId::of(&methods::CHARS_NEXT_CMP),
LintId::of(&methods::INTO_ITER_ON_REF),
LintId::of(&methods::ITER_CLONED_COLLECT),
+ LintId::of(&methods::ITER_NEXT_SLICE),
LintId::of(&methods::ITER_NTH_ZERO),
LintId::of(&methods::ITER_SKIP_NEXT),
LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC),
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
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 std::iter::{once, Iterator};
use std::mem;
match_type(cx, ty, &paths::BTREEMAP) ||
is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) {
if method.ident.name == sym!(len) {
- let span = shorten_needless_collect_span(expr);
+ let span = shorten_span(expr, sym!(collect));
span_lint_and_sugg(
cx,
NEEDLESS_COLLECT,
span,
NEEDLESS_COLLECT_MSG,
"replace with",
- ".count()".to_string(),
+ "count()".to_string(),
Applicability::MachineApplicable,
);
}
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(
cx,
NEEDLESS_COLLECT,
span,
NEEDLESS_COLLECT_MSG,
"replace with",
- ".next().is_none()".to_string(),
+ "get(0).is_none()".to_string(),
Applicability::MachineApplicable,
);
}
if method.ident.name == sym!(contains) {
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(
cx,
NEEDLESS_COLLECT,
span,
"replace with",
format!(
- ".any(|{}| x == {})",
+ "any(|{}| x == {})",
arg, pred
),
Applicability::MachineApplicable,
}
}
-fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span {
- if_chain! {
- if let ExprKind::MethodCall(_, _, ref args) = expr.kind;
- if let ExprKind::MethodCall(_, ref span, _) = args[0].kind;
- then {
- return expr.span.with_lo(span.lo() - BytePos(1));
+fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span {
+ let mut current_expr = expr;
+ while let ExprKind::MethodCall(ref path, ref span, ref args) = current_expr.kind {
+ if path.ident.name == target_fn_name {
+ return expr.span.with_lo(span.lo());
}
+ current_expr = &args[0];
}
unreachable!()
}
use crate::consts::{constant, Constant};
use crate::utils::usage::mutated_variables;
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,
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,
"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 => [
UNWRAP_USED,
EXPECT_USED,
FIND_MAP,
MAP_FLATTEN,
ITERATOR_STEP_BY_ZERO,
+ ITER_NEXT_SLICE,
ITER_NTH,
ITER_NTH_ZERO,
ITER_SKIP_NEXT,
},
["next", "filter"] => lint_filter_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_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]),
}
}
+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>(
cx: &LateContext<'a, 'tcx>,
expr: &hir::Expr<'_>,
}
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)]
deprecation: None,
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 {
name: "iter_nth",
group: "perf",
let _ = (&HashSet::<i32>::new()).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 _ = (&[1, 2, 3]).iter().next(); //~ 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::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter()
+
+ let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter()
}
LL | let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .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
--- /dev/null
+// 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
+}
--- /dev/null
+// 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
+}
--- /dev/null
+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
+
fn main() {
let sample = [1; 5];
let len = sample.iter().count();
- if sample.iter().next().is_none() {
+ if sample.get(0).is_none() {
// Empty
}
sample.iter().cloned().any(|x| x == 1);
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();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()`
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
|
= note: `-D clippy::needless-collect` implied by `-D warnings`
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() {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.next().is_none()`
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get(0).is_none()`
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);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.any(|x| x == 1)`
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)`
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();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()`
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()`
error: aborting due to 4 previous errors