-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
New lint [iter_skip_zero
]
#11046
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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"); | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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<_>>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
@@ -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. | ||
|
@@ -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); | ||
|
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); | ||
} | ||
} |
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); | ||
} | ||
} |
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 | ||
|
There was a problem hiding this comment.
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 linkUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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