Suggest into_iter() over drain(..)

Add doc

Add description

iter_with_drain dogfood

Disable emiting on struct field.

Fix clippy

Add eq_path for SpanlessEq

Fix tests

Better error message

Fix doc test

Fix version

Apply suggestions
This commit is contained in:
Liu Dingming 2022-02-13 03:14:04 +08:00
parent 6e211eac7c
commit 6cc2eeaa56
16 changed files with 276 additions and 14 deletions

View file

@ -3225,6 +3225,7 @@ Released 2018-09-13
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
[`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_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
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays

View file

@ -164,6 +164,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::ITER_NTH_ZERO),
LintId::of(methods::ITER_OVEREAGER_CLONED),
LintId::of(methods::ITER_SKIP_NEXT),
LintId::of(methods::ITER_WITH_DRAIN),
LintId::of(methods::MANUAL_FILTER_MAP),
LintId::of(methods::MANUAL_FIND_MAP),
LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),

View file

@ -299,6 +299,7 @@ store.register_lints(&[
methods::ITER_NTH_ZERO,
methods::ITER_OVEREAGER_CLONED,
methods::ITER_SKIP_NEXT,
methods::ITER_WITH_DRAIN,
methods::MANUAL_FILTER_MAP,
methods::MANUAL_FIND_MAP,
methods::MANUAL_SATURATING_ARITHMETIC,

View file

@ -16,6 +16,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(methods::EXTEND_WITH_DRAIN),
LintId::of(methods::ITER_NTH),
LintId::of(methods::ITER_OVEREAGER_CLONED),
LintId::of(methods::ITER_WITH_DRAIN),
LintId::of(methods::MANUAL_STR_REPEAT),
LintId::of(methods::OR_FUN_CALL),
LintId::of(methods::SINGLE_CHAR_PATTERN),

View file

@ -0,0 +1,72 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_integer_const;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{
higher::{self, Range},
SpanlessEq,
};
use rustc_ast::ast::RangeLimits;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
use super::ITER_WITH_DRAIN;
const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque];
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) {
// Refuse to emit `into_iter` suggestion on draining struct fields due
// to the strong possibility of processing unmovable field.
if let ExprKind::Field(..) = recv.kind {
return;
}
if let Some(range) = higher::Range::hir(arg) {
let left_full = match range {
Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true,
Range { start: None, .. } => true,
_ => false,
};
let full = left_full
&& match range {
Range {
end: Some(end),
limits: RangeLimits::HalfOpen,
..
} => {
// `x.drain(..x.len())` call
if_chain! {
if let ExprKind::MethodCall(len_path, len_args, _) = end.kind;
if len_path.ident.name == sym::len && len_args.len() == 1;
if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind;
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
if SpanlessEq::new(cx).eq_path(drain_path, len_path);
then { true }
else { false }
}
},
Range {
end: None,
limits: RangeLimits::HalfOpen,
..
} => true,
_ => false,
};
if full {
span_lint_and_sugg(
cx,
ITER_WITH_DRAIN,
span.with_hi(expr.span.hi()),
&format!("`drain(..)` used on a `{}`", drained_type),
"try this",
"into_iter()".to_string(),
Applicability::MaybeIncorrect,
);
}
}
}
}

View file

@ -32,6 +32,7 @@ mod iter_nth;
mod iter_nth_zero;
mod iter_overeager_cloned;
mod iter_skip_next;
mod iter_with_drain;
mod iterator_step_by_zero;
mod manual_saturating_arithmetic;
mod manual_str_repeat;
@ -1118,6 +1119,31 @@ declare_clippy_lint! {
"using `.skip(x).next()` on an iterator"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration.
///
/// ### Why is this bad?
/// `.into_iter()` is simpler with better performance.
///
/// ### Example
/// ```rust
/// # use std::collections::HashSet;
/// let mut foo = vec![0, 1, 2, 3];
/// let bar: HashSet<usize> = foo.drain(..).collect();
/// ```
/// Use instead:
/// ```rust
/// # use std::collections::HashSet;
/// let foo = vec![0, 1, 2, 3];
/// let bar: HashSet<usize> = foo.into_iter().collect();
/// ```
#[clippy::version = "1.61.0"]
pub ITER_WITH_DRAIN,
perf,
"replace `.drain(..)` with `.into_iter()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for use of `.get().unwrap()` (or
@ -2047,6 +2073,7 @@ impl_lint_pass!(Methods => [
GET_UNWRAP,
STRING_EXTEND_CHARS,
ITER_CLONED_COLLECT,
ITER_WITH_DRAIN,
USELESS_ASREF,
UNNECESSARY_FOLD,
UNNECESSARY_FILTER_MAP,
@ -2327,6 +2354,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
_ => {},
},
("drain", [arg]) => {
iter_with_drain::check(cx, expr, recv, span, arg);
},
("expect", [_]) => match method_call(recv) {
Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
_ => expect_used::check(cx, expr, recv),

View file

@ -399,9 +399,9 @@ fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> {
match (target_opt, source_opt) {
(Some(mut target), Some(mut source)) => {
(Some(mut target), Some(source)) => {
target.reserve(source.len());
for op in source.drain(..) {
for op in source {
target.push(op);
}
Some(target)
@ -436,9 +436,9 @@ fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Exp
chained_binops_helper(left_left, left_right),
chained_binops_helper(right_left, right_right),
) {
(Some(mut left_ops), Some(mut right_ops)) => {
(Some(mut left_ops), Some(right_ops)) => {
left_ops.reserve(right_ops.len());
for op in right_ops.drain(..) {
for op in right_ops {
left_ops.push(op);
}
Some(left_ops)

View file

@ -473,7 +473,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
/// ```
fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
if let Some(args) = match_lint_emission(cx, expr) {
let mut emission_info = extract_emission_info(cx, args);
let emission_info = extract_emission_info(cx, args);
if emission_info.is_empty() {
// See:
// - src/misc.rs:734:9
@ -483,7 +483,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
return;
}
for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
for (lint_name, applicability, is_multi_part) in emission_info {
let app_info = self.applicability_info.entry(lint_name).or_default();
app_info.applicability = applicability;
app_info.is_multi_part_suggestion = is_multi_part;
@ -693,7 +693,7 @@ fn extract_emission_info<'hir>(
}
lints
.drain(..)
.into_iter()
.map(|lint_name| (lint_name, applicability, multi_part))
.collect()
}

View file

@ -74,6 +74,10 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
self.inter_expr().eq_expr(left, right)
}
pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
self.inter_expr().eq_path(left, right)
}
pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
self.inter_expr().eq_path_segment(left, right)
}
@ -362,7 +366,7 @@ impl HirEqInterExpr<'_, '_, '_> {
}
}
fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
match (left.res, right.res) {
(Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
(Res::Local(_), _) | (_, Res::Local(_)) => false,

View file

@ -1,11 +1,11 @@
// run-rustfix
#![warn(clippy::extend_with_drain)]
#![allow(clippy::iter_with_drain)]
use std::collections::BinaryHeap;
fn main() {
//gets linted
let mut vec1 = vec![0u8; 1024];
let mut vec2: std::vec::Vec<u8> = Vec::new();
vec2.append(&mut vec1);
let mut vec3 = vec![0u8; 1024];
@ -17,7 +17,7 @@ fn main() {
vec11.append(&mut return_vector());
//won't get linted it dosen't move the entire content of a vec into another
//won't get linted it doesn't move the entire content of a vec into another
let mut test1 = vec![0u8, 10];
let mut test2: std::vec::Vec<u8> = Vec::new();

View file

@ -1,11 +1,11 @@
// run-rustfix
#![warn(clippy::extend_with_drain)]
#![allow(clippy::iter_with_drain)]
use std::collections::BinaryHeap;
fn main() {
//gets linted
let mut vec1 = vec![0u8; 1024];
let mut vec2: std::vec::Vec<u8> = Vec::new();
vec2.extend(vec1.drain(..));
let mut vec3 = vec![0u8; 1024];
@ -17,7 +17,7 @@ fn main() {
vec11.extend(return_vector().drain(..));
//won't get linted it dosen't move the entire content of a vec into another
//won't get linted it doesn't move the entire content of a vec into another
let mut test1 = vec![0u8, 10];
let mut test2: std::vec::Vec<u8> = Vec::new();

View file

@ -0,0 +1,56 @@
// run-rustfix
// will emits unused mut warnings after fixing
#![allow(unused_mut)]
// will emits needless collect warnings after fixing
#![allow(clippy::needless_collect)]
#![warn(clippy::iter_with_drain)]
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
fn full() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.into_iter().collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.into_iter().collect();
let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
}
fn closed() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.into_iter().collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.into_iter().collect();
let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
}
fn should_not_help() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.drain(1..).collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
let mut b = vec!["aaa".to_string(), "bbb".to_string()];
let _: Vec<_> = b.drain(0..a.len()).collect();
}
#[derive(Default)]
struct Bomb {
fire: Vec<u8>,
}
fn should_not_help_0(bomb: &mut Bomb) {
let _: Vec<u8> = bomb.fire.drain(..).collect();
}
fn main() {
full();
closed();
should_not_help();
should_not_help_0(&mut Bomb::default());
}

View file

@ -0,0 +1,56 @@
// run-rustfix
// will emits unused mut warnings after fixing
#![allow(unused_mut)]
// will emits needless collect warnings after fixing
#![allow(clippy::needless_collect)]
#![warn(clippy::iter_with_drain)]
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
fn full() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.drain(..).collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.drain(..).collect();
let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
}
fn closed() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.drain(0..).collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.drain(..a.len()).collect();
let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
}
fn should_not_help() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.drain(1..).collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
let mut b = vec!["aaa".to_string(), "bbb".to_string()];
let _: Vec<_> = b.drain(0..a.len()).collect();
}
#[derive(Default)]
struct Bomb {
fire: Vec<u8>,
}
fn should_not_help_0(bomb: &mut Bomb) {
let _: Vec<u8> = bomb.fire.drain(..).collect();
}
fn main() {
full();
closed();
should_not_help();
should_not_help_0(&mut Bomb::default());
}

View file

@ -0,0 +1,40 @@
error: `drain(..)` used on a `Vec`
--> $DIR/iter_with_drain.rs:11:34
|
LL | let mut a: BinaryHeap<_> = a.drain(..).collect();
| ^^^^^^^^^ help: try this: `into_iter()`
|
= note: `-D clippy::iter-with-drain` implied by `-D warnings`
error: `drain(..)` used on a `VecDeque`
--> $DIR/iter_with_drain.rs:14:27
|
LL | let mut a: Vec<_> = a.drain(..).collect();
| ^^^^^^^^^ help: try this: `into_iter()`
error: `drain(..)` used on a `Vec`
--> $DIR/iter_with_drain.rs:15:34
|
LL | let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
| ^^^^^^^^^ help: try this: `into_iter()`
error: `drain(..)` used on a `Vec`
--> $DIR/iter_with_drain.rs:21:34
|
LL | let mut a: BinaryHeap<_> = a.drain(0..).collect();
| ^^^^^^^^^^ help: try this: `into_iter()`
error: `drain(..)` used on a `VecDeque`
--> $DIR/iter_with_drain.rs:24:27
|
LL | let mut a: Vec<_> = a.drain(..a.len()).collect();
| ^^^^^^^^^^^^^^^^ help: try this: `into_iter()`
error: `drain(..)` used on a `Vec`
--> $DIR/iter_with_drain.rs:25:34
|
LL | let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
| ^^^^^^^^^^^^^^^^^ help: try this: `into_iter()`
error: aborting due to 6 previous errors

View file

@ -1,7 +1,7 @@
// run-rustfix
#![allow(unused_parens)]
#![allow(clippy::iter_with_drain)]
fn f() -> usize {
42
}

View file

@ -1,7 +1,7 @@
// run-rustfix
#![allow(unused_parens)]
#![allow(clippy::iter_with_drain)]
fn f() -> usize {
42
}