]> git.lizzy.rs Git - rust.git/commitdiff
Auto merge of #5597 - esamudera:slice_iter_next, r=flip1995
authorbors <bors@rust-lang.org>
Tue, 2 Jun 2020 11:42:22 +0000 (11:42 +0000)
committerbors <bors@rust-lang.org>
Tue, 2 Jun 2020 11:42:22 +0000 (11:42 +0000)
New lint: iter_next_slice

Hello, this is a work-in-progress PR for issue: https://github.com/rust-lang/rust-clippy/issues/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()`

14 files changed:
CHANGELOG.md
clippy_lints/src/lib.rs
clippy_lints/src/loops.rs
clippy_lints/src/methods/mod.rs
clippy_lints/src/needless_continue.rs
src/lintlist/mod.rs
tests/ui/into_iter_on_ref.fixed
tests/ui/into_iter_on_ref.rs
tests/ui/into_iter_on_ref.stderr
tests/ui/iter_next_slice.fixed [new file with mode: 0644]
tests/ui/iter_next_slice.rs [new file with mode: 0644]
tests/ui/iter_next_slice.stderr [new file with mode: 0644]
tests/ui/needless_collect.fixed
tests/ui/needless_collect.stderr

index 086a1141be55219015bafec79a39b532ea701bca..7f26d2b169a268676f98ea0708f9c5015ee96c12 100644 (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
 [`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
index 03addf1f4a40c03112f7342f5623dea6b36bf1d7..7e9a52fe69f33257919a0beaaa10be6e6650a47b 100644 (file)
@@ -666,6 +666,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &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,
@@ -1310,6 +1311,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         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),
@@ -1491,6 +1493,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         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),
index 38a5829b3f74530d7b38c0090fec56043ae1ab3c..dbe41823a9cf6874ef5f88977f6d76fee9ef6933 100644 (file)
@@ -27,7 +27,7 @@
 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;
@@ -2381,32 +2381,32 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
                 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,
@@ -2422,7 +2422,7 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, '
                                 span,
                                 "replace with",
                                 format!(
-                                    ".any(|{}| x == {})",
+                                    "any(|{}| x == {})",
                                     arg, pred
                                 ),
                                 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 {
-    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!()
 }
index 52ca962e7ef978e6ad80474ea52b30d94fbbb270..4b10496280ff6a0de808e28a75ff0db280d2ceef 100644 (file)
@@ -26,7 +26,7 @@
 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,
@@ -1320,6 +1347,7 @@ fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>)
             },
             ["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]),
@@ -2199,6 +2227,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>(
     cx: &LateContext<'a, 'tcx>,
     expr: &hir::Expr<'_>,
index 28183810df48977e3c776f568b58ab92633b6479..a971d041ca6613182a5166e70d2b3a31f1792854 100644 (file)
@@ -424,7 +424,7 @@ fn erode_from_back(s: &str) -> String {
 }
 
 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)]
index ab9542a7b9c8cc745a89e3c96f5f5288eada8766..f44a3e148bffdb5c6bdfd7bf89dc56e28623e306 100644 (file)
         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",
index c30d23de3f86921a891dbce97af00cea427cb71d..7f92d0dbdc973a50fd4964ed0312600e49c02619 100644 (file)
@@ -40,4 +40,6 @@ fn main() {
     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()
 }
index 94bc1689619a297b84116d0efebcc14c7e74f5e1..416056d3fdb9cb3fd2bc5aec5234e9ea0d0630d1 100644 (file)
@@ -40,4 +40,6 @@ fn main() {
     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()
 }
index 80e2d104f824f440461fd5f3b039bc42eeae2aca..1cd6400b0195baedc7a1e09d8849859d2361f1a1 100644 (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()
    |                                               ^^^^^^^^^ 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
 
diff --git a/tests/ui/iter_next_slice.fixed b/tests/ui/iter_next_slice.fixed
new file mode 100644 (file)
index 0000000..79c1db8
--- /dev/null
@@ -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
+}
diff --git a/tests/ui/iter_next_slice.rs b/tests/ui/iter_next_slice.rs
new file mode 100644 (file)
index 0000000..ef9a55f
--- /dev/null
@@ -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
+}
diff --git a/tests/ui/iter_next_slice.stderr b/tests/ui/iter_next_slice.stderr
new file mode 100644 (file)
index 0000000..bbf61df
--- /dev/null
@@ -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
+
index b4227eaf2f8bbb8846e191bf888e2b99114619d9..be37dc16b9a3effcc6aec5b4fd92f1de7a2412c5 100644 (file)
@@ -9,7 +9,7 @@ use std::collections::{BTreeSet, HashMap, HashSet};
 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);
index 8884c8e161293c11bed7b32cf2a5473fa85cc28a..9113aad90dd7cc120e89748f5df590f74878f174 100644 (file)
@@ -1,28 +1,28 @@
 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