Skip to content

Commit

Permalink
Suggest into_iter() over drain(..)
Browse files Browse the repository at this point in the history
Add doc

Add description

iter_with_drain dogfood
  • Loading branch information
ldm0 committed Mar 1, 2022
1 parent 8d12cd4 commit 275a1b0
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,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),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,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,
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_perf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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),
Expand Down
68 changes: 68 additions & 0 deletions clippy_lints/src/methods/iter_with_drain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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 DRAIN_TYPES.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym)) {
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! {
// should we delete the drain_path?
if let ExprKind::MethodCall(_drain_path, drain_args, _) = expr.kind;
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)) = drain_args[0].kind;
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
if SpanlessEq::new(cx).eq_path_segments(drain_path.segments, len_path.segments);
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()),
"use `into_iter` instead of `drain` for iterating all elements by value in a container",
"try this",
"into_iter()".to_string(),
Applicability::MaybeIncorrect,
);
}
}
}
}
28 changes: 28 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1118,6 +1119,29 @@ 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
/// let mut foo = vec![0, 1, 2, 3];
/// let bar: HashSet<usize> = foo.drain(..).collect();
/// ```
/// Use instead:
/// ```rust
/// let foo = vec![0, 1, 2, 3];
/// let bar: HashSet<usize> = foo.into_iter().collect();
/// ```
#[clippy::version = "1.60.0"]
pub ITER_WITH_DRAIN,
perf,
"replace `.drain(..)` with `.into_iter()`"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for use of `.get().unwrap()` (or
Expand Down Expand Up @@ -2017,6 +2041,7 @@ impl_lint_pass!(Methods => [
GET_UNWRAP,
STRING_EXTEND_CHARS,
ITER_CLONED_COLLECT,
ITER_WITH_DRAIN,
USELESS_ASREF,
UNNECESSARY_FOLD,
UNNECESSARY_FILTER_MAP,
Expand Down Expand Up @@ -2296,6 +2321,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),
Expand Down
8 changes: 4 additions & 4 deletions clippy_lints/src/suspicious_operation_groupings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions clippy_lints/src/utils/internal_lints/metadata_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -693,7 +693,7 @@ fn extract_emission_info<'hir>(
}

lints
.drain(..)
.into_iter()
.map(|lint_name| (lint_name, applicability, multi_part))
.collect()
}
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/extend_with_drain.fixed
Original file line number Diff line number Diff line change
@@ -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];
Expand All @@ -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();

Expand Down
4 changes: 2 additions & 2 deletions tests/ui/extend_with_drain.rs
Original file line number Diff line number Diff line change
@@ -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];
Expand All @@ -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();

Expand Down
41 changes: 41 additions & 0 deletions tests/ui/iter_with_drain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#![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();
}

fn main() {
full();
closed();
should_not_help();
}
40 changes: 40 additions & 0 deletions tests/ui/iter_with_drain.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
error: use `into_iter` instead of `drain` for iterating all elements by value in a container
--> $DIR/iter_with_drain.rs:6: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: use `into_iter` instead of `drain` for iterating all elements by value in a container
--> $DIR/iter_with_drain.rs:9:27
|
LL | let mut a: Vec<_> = a.drain(..).collect();
| ^^^^^^^^^ help: try this: `into_iter()`

error: use `into_iter` instead of `drain` for iterating all elements by value in a container
--> $DIR/iter_with_drain.rs:10:34
|
LL | let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
| ^^^^^^^^^ help: try this: `into_iter()`

error: use `into_iter` instead of `drain` for iterating all elements by value in a container
--> $DIR/iter_with_drain.rs:16:34
|
LL | let mut a: BinaryHeap<_> = a.drain(0..).collect();
| ^^^^^^^^^^ help: try this: `into_iter()`

error: use `into_iter` instead of `drain` for iterating all elements by value in a container
--> $DIR/iter_with_drain.rs:19:27
|
LL | let mut a: Vec<_> = a.drain(..a.len()).collect();
| ^^^^^^^^^^^^^^^^ help: try this: `into_iter()`

error: use `into_iter` instead of `drain` for iterating all elements by value in a container
--> $DIR/iter_with_drain.rs:20: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

2 changes: 1 addition & 1 deletion tests/ui/range_plus_minus_one.fixed
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// run-rustfix

#![allow(unused_parens)]

#![allow(clippy::iter_with_drain)]
fn f() -> usize {
42
}
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/range_plus_minus_one.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// run-rustfix

#![allow(unused_parens)]

#![allow(clippy::iter_with_drain)]
fn f() -> usize {
42
}
Expand Down

0 comments on commit 275a1b0

Please sign in to comment.