diff --git a/CHANGELOG.md b/CHANGELOG.md index 14d822083d8b..6d12bfb1c670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4902,6 +4902,7 @@ Released 2018-09-13 [`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items [`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned [`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next +[`iter_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero [`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain [`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero [`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 71cf2aea0f8d..d343d357fc9b 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -257,7 +257,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx // Will this work for more complex types? Probably not! if !snippet .split("->") - .skip(0) + .skip(1) .map(|s| { s.trim() == cast_from.to_string() || s.split("where").any(|ty| ty.trim() == cast_from.to_string()) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 9d9ee6ba3079..942b56ebe0ad 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -356,6 +356,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::ITER_ON_SINGLE_ITEMS_INFO, crate::methods::ITER_OVEREAGER_CLONED_INFO, crate::methods::ITER_SKIP_NEXT_INFO, + crate::methods::ITER_SKIP_ZERO_INFO, crate::methods::ITER_WITH_DRAIN_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, crate::methods::MANUAL_FIND_MAP_INFO, diff --git a/clippy_lints/src/methods/iter_skip_zero.rs b/clippy_lints/src/methods/iter_skip_zero.rs new file mode 100644 index 000000000000..78c5440963e2 --- /dev/null +++ b/clippy_lints/src/methods/iter_skip_zero.rs @@ -0,0 +1,36 @@ +use clippy_utils::{ + consts::{constant, Constant}, + diagnostics::span_lint_and_sugg, + is_from_proc_macro, is_trait_method, +}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::ITER_SKIP_ZERO; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) { + if !expr.span.from_expansion() + && is_trait_method(cx, expr, sym::Iterator) + && let Some(arg) = constant(cx, cx.typeck_results(), arg_expr).and_then(|constant| { + if let Constant::Int(arg) = constant { + Some(arg) + } else { + None + } + }) + && arg == 0 + && !is_from_proc_macro(cx, expr) + { + span_lint_and_sugg( + cx, + ITER_SKIP_ZERO, + arg_expr.span, + "usage of `.skip(0)`", + "if you meant to skip the first element, use", + "1".to_owned(), + Applicability::MaybeIncorrect, + ); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 24dbe8c1d751..bcc3b52b02b3 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -44,6 +44,7 @@ mod iter_nth_zero; mod iter_on_single_or_empty_collections; mod iter_overeager_cloned; mod iter_skip_next; +mod iter_skip_zero; mod iter_with_drain; mod iterator_step_by_zero; mod manual_next_back; @@ -3316,6 +3317,27 @@ declare_clippy_lint! { "checks for usage of `Iterator::fold` with a type that implements `Try`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.skip(0)` on iterators. + /// + /// ### Why is this bad? + /// This was likely intended to be `.skip(1)` to skip the first element, as `.skip(0)` does + /// nothing. + /// + /// ### Example + /// ```rust + /// let v = vec![1, 2, 3]; + /// let x = v.iter().skip(0).collect::>(); + /// let y = v.iter().collect::>(); + /// assert_eq!(x, y); + /// ``` + #[clippy::version = "1.72.0"] + pub ITER_SKIP_ZERO, + correctness, + "disallows `.skip(0)`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3448,6 +3470,7 @@ impl_lint_pass!(Methods => [ UNNECESSARY_LITERAL_UNWRAP, DRAIN_COLLECT, MANUAL_TRY_FOLD, + ITER_SKIP_ZERO, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -3769,7 +3792,16 @@ impl Methods { unnecessary_join::check(cx, expr, recv, join_arg, span); } }, - ("last", []) | ("skip", [_]) => { + ("skip", [arg]) => { + iter_skip_zero::check(cx, expr, arg); + + if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { + if let ("cloned", []) = (name2, args2) { + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); + } + } + } + ("last", []) => { if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); diff --git a/tests/ui/iter_skip_zero.fixed b/tests/ui/iter_skip_zero.fixed new file mode 100644 index 000000000000..1eb0984fe073 --- /dev/null +++ b/tests/ui/iter_skip_zero.fixed @@ -0,0 +1,25 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::useless_vec, unused)] +#![warn(clippy::iter_skip_zero)] + +#[macro_use] +extern crate proc_macros; + +use std::iter::once; + +fn main() { + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + let _ = once([1, 2, 3]).skip(1); + let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(1)).skip(1); + // Don't lint + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + external! { + let _ = [1, 2, 3].iter().skip(0); + } + with_span! { + let _ = [1, 2, 3].iter().skip(0); + } +} diff --git a/tests/ui/iter_skip_zero.rs b/tests/ui/iter_skip_zero.rs new file mode 100644 index 000000000000..8c103ab1d5b3 --- /dev/null +++ b/tests/ui/iter_skip_zero.rs @@ -0,0 +1,25 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::useless_vec, unused)] +#![warn(clippy::iter_skip_zero)] + +#[macro_use] +extern crate proc_macros; + +use std::iter::once; + +fn main() { + let _ = [1, 2, 3].iter().skip(0); + let _ = vec![1, 2, 3].iter().skip(0); + let _ = once([1, 2, 3]).skip(0); + let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + // Don't lint + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + external! { + let _ = [1, 2, 3].iter().skip(0); + } + with_span! { + let _ = [1, 2, 3].iter().skip(0); + } +} diff --git a/tests/ui/iter_skip_zero.stderr b/tests/ui/iter_skip_zero.stderr new file mode 100644 index 000000000000..0793fe67f2c0 --- /dev/null +++ b/tests/ui/iter_skip_zero.stderr @@ -0,0 +1,34 @@ +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:12:35 + | +LL | let _ = [1, 2, 3].iter().skip(0); + | ^ help: if you meant to skip the first element, try: `1` + | + = note: `-D clippy::iter-skip-zero` implied by `-D warnings` + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:13:39 + | +LL | let _ = vec![1, 2, 3].iter().skip(0); + | ^ help: if you meant to skip the first element, try: `1` + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:14:34 + | +LL | let _ = once([1, 2, 3]).skip(0); + | ^ help: if you meant to skip the first element, try: `1` + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:15:71 + | +LL | let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + | ^ help: if you meant to skip the first element, try: `1` + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:15:62 + | +LL | let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + | ^ help: if you meant to skip the first element, try: `1` + +error: aborting due to 5 previous errors +