Skip to content

New lint [iter_skip_zero] #11046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4939,6 +4939,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
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/casts/unnecessary_cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure this change doesn't break anything? Won't this cause wrong suggestions? Probably the safest bet is to create a FIXME pointing to this commit and a brief explaination.


Just tested it, indeed .skip(0) to .skip(1) produce different results. Playground link

Copy link
Member Author

@Centri3 Centri3 Jun 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, .skip(0) does nothing. It's meant to skip anything before -> as that's known to not be the return type (and results in FPs if it contains ::std::primitive::u32 or the like). I wrote that code before so :D

.map(|s| {
s.trim() == cast_from.to_string()
|| s.split("where").any(|ty| ty.trim() == cast_from.to_string())
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,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,
Expand Down
34 changes: 34 additions & 0 deletions clippy_lints/src/methods/iter_skip_zero.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{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_then(cx, ITER_SKIP_ZERO, arg_expr.span, "usage of `.skip(0)`", |diag| {
diag.span_suggestion(
arg_expr.span,
"if you meant to skip the first element, use",
"1",
Applicability::MaybeIncorrect,
)
.note("this call to `skip` does nothing and is useless; remove it");
});
}
}
34 changes: 33 additions & 1 deletion clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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;
Expand Down Expand Up @@ -3443,6 +3444,27 @@ declare_clippy_lint! {
"`format!`ing every element in a collection, then collecting the strings into a new `String`"
}

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. If not, the call should be removed.
///
/// ### Example
/// ```rust
/// let v = vec![1, 2, 3];
/// let x = v.iter().skip(0).collect::<Vec<_>>();
/// let y = v.iter().collect::<Vec<_>>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you re-write this example section so it follows by the general docs. guidelines? (using "Could be written as" and such)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a good few others like that (only example, mostly self-explanatory lints)

/// 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,
Expand Down Expand Up @@ -3579,6 +3601,7 @@ impl_lint_pass!(Methods => [
MANUAL_TRY_FOLD,
FORMAT_COLLECT,
STRING_LIT_CHARS_ANY,
ITER_SKIP_ZERO,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -3901,7 +3924,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);
Expand Down
25 changes: 25 additions & 0 deletions tests/ui/iter_skip_zero.fixed
Original file line number Diff line number Diff line change
@@ -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);
}
}
25 changes: 25 additions & 0 deletions tests/ui/iter_skip_zero.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
43 changes: 43 additions & 0 deletions tests/ui/iter_skip_zero.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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, use: `1`
|
= note: this call to `skip` does nothing and is useless; remove it
= 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, use: `1`
|
= note: this call to `skip` does nothing and is useless; remove it

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, use: `1`
|
= note: this call to `skip` does nothing and is useless; remove it

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, use: `1`
|
= note: this call to `skip` does nothing and is useless; remove it

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, use: `1`
|
= note: this call to `skip` does nothing and is useless; remove it

error: aborting due to 5 previous errors