Skip to content

Commit d56f457

Browse files
committed
Auto merge of rust-lang#8483 - ldm0:iter_with_drain_simple, r=flip1995,giraffate
Use `.into_iter()` rather than `.drain(..)` Replacing `.drain(..)` with `.into_iter()` makes my project's binary size smaller. Fixes rust-lang#1908 Applicability of this suggestion is `MaybeIncorrect` rather than `MachineApplicable` due to the complexity of "checking otherwise usage" X-| changelog: Add new lint [`iter_with_drain`]
2 parents ef4af1d + 6cc2eea commit d56f457

16 files changed

+276
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3225,6 +3225,7 @@ Released 2018-09-13
32253225
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
32263226
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
32273227
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
3228+
[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
32283229
[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
32293230
[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
32303231
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
164164
LintId::of(methods::ITER_NTH_ZERO),
165165
LintId::of(methods::ITER_OVEREAGER_CLONED),
166166
LintId::of(methods::ITER_SKIP_NEXT),
167+
LintId::of(methods::ITER_WITH_DRAIN),
167168
LintId::of(methods::MANUAL_FILTER_MAP),
168169
LintId::of(methods::MANUAL_FIND_MAP),
169170
LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ store.register_lints(&[
299299
methods::ITER_NTH_ZERO,
300300
methods::ITER_OVEREAGER_CLONED,
301301
methods::ITER_SKIP_NEXT,
302+
methods::ITER_WITH_DRAIN,
302303
methods::MANUAL_FILTER_MAP,
303304
methods::MANUAL_FIND_MAP,
304305
methods::MANUAL_SATURATING_ARITHMETIC,

clippy_lints/src/lib.register_perf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
1616
LintId::of(methods::EXTEND_WITH_DRAIN),
1717
LintId::of(methods::ITER_NTH),
1818
LintId::of(methods::ITER_OVEREAGER_CLONED),
19+
LintId::of(methods::ITER_WITH_DRAIN),
1920
LintId::of(methods::MANUAL_STR_REPEAT),
2021
LintId::of(methods::OR_FUN_CALL),
2122
LintId::of(methods::SINGLE_CHAR_PATTERN),
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_integer_const;
3+
use clippy_utils::ty::is_type_diagnostic_item;
4+
use clippy_utils::{
5+
higher::{self, Range},
6+
SpanlessEq,
7+
};
8+
use rustc_ast::ast::RangeLimits;
9+
use rustc_errors::Applicability;
10+
use rustc_hir::{Expr, ExprKind, QPath};
11+
use rustc_lint::LateContext;
12+
use rustc_span::symbol::{sym, Symbol};
13+
use rustc_span::Span;
14+
15+
use super::ITER_WITH_DRAIN;
16+
17+
const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque];
18+
19+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
20+
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
21+
if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) {
22+
// Refuse to emit `into_iter` suggestion on draining struct fields due
23+
// to the strong possibility of processing unmovable field.
24+
if let ExprKind::Field(..) = recv.kind {
25+
return;
26+
}
27+
28+
if let Some(range) = higher::Range::hir(arg) {
29+
let left_full = match range {
30+
Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true,
31+
Range { start: None, .. } => true,
32+
_ => false,
33+
};
34+
let full = left_full
35+
&& match range {
36+
Range {
37+
end: Some(end),
38+
limits: RangeLimits::HalfOpen,
39+
..
40+
} => {
41+
// `x.drain(..x.len())` call
42+
if_chain! {
43+
if let ExprKind::MethodCall(len_path, len_args, _) = end.kind;
44+
if len_path.ident.name == sym::len && len_args.len() == 1;
45+
if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind;
46+
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
47+
if SpanlessEq::new(cx).eq_path(drain_path, len_path);
48+
then { true }
49+
else { false }
50+
}
51+
},
52+
Range {
53+
end: None,
54+
limits: RangeLimits::HalfOpen,
55+
..
56+
} => true,
57+
_ => false,
58+
};
59+
if full {
60+
span_lint_and_sugg(
61+
cx,
62+
ITER_WITH_DRAIN,
63+
span.with_hi(expr.span.hi()),
64+
&format!("`drain(..)` used on a `{}`", drained_type),
65+
"try this",
66+
"into_iter()".to_string(),
67+
Applicability::MaybeIncorrect,
68+
);
69+
}
70+
}
71+
}
72+
}

clippy_lints/src/methods/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mod iter_nth;
3232
mod iter_nth_zero;
3333
mod iter_overeager_cloned;
3434
mod iter_skip_next;
35+
mod iter_with_drain;
3536
mod iterator_step_by_zero;
3637
mod manual_saturating_arithmetic;
3738
mod manual_str_repeat;
@@ -1118,6 +1119,31 @@ declare_clippy_lint! {
11181119
"using `.skip(x).next()` on an iterator"
11191120
}
11201121

1122+
declare_clippy_lint! {
1123+
/// ### What it does
1124+
/// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration.
1125+
///
1126+
/// ### Why is this bad?
1127+
/// `.into_iter()` is simpler with better performance.
1128+
///
1129+
/// ### Example
1130+
/// ```rust
1131+
/// # use std::collections::HashSet;
1132+
/// let mut foo = vec![0, 1, 2, 3];
1133+
/// let bar: HashSet<usize> = foo.drain(..).collect();
1134+
/// ```
1135+
/// Use instead:
1136+
/// ```rust
1137+
/// # use std::collections::HashSet;
1138+
/// let foo = vec![0, 1, 2, 3];
1139+
/// let bar: HashSet<usize> = foo.into_iter().collect();
1140+
/// ```
1141+
#[clippy::version = "1.61.0"]
1142+
pub ITER_WITH_DRAIN,
1143+
perf,
1144+
"replace `.drain(..)` with `.into_iter()`"
1145+
}
1146+
11211147
declare_clippy_lint! {
11221148
/// ### What it does
11231149
/// Checks for use of `.get().unwrap()` (or
@@ -2047,6 +2073,7 @@ impl_lint_pass!(Methods => [
20472073
GET_UNWRAP,
20482074
STRING_EXTEND_CHARS,
20492075
ITER_CLONED_COLLECT,
2076+
ITER_WITH_DRAIN,
20502077
USELESS_ASREF,
20512078
UNNECESSARY_FOLD,
20522079
UNNECESSARY_FILTER_MAP,
@@ -2327,6 +2354,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
23272354
Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
23282355
_ => {},
23292356
},
2357+
("drain", [arg]) => {
2358+
iter_with_drain::check(cx, expr, recv, span, arg);
2359+
},
23302360
("expect", [_]) => match method_call(recv) {
23312361
Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
23322362
_ => expect_used::check(cx, expr, recv),

clippy_lints/src/suspicious_operation_groupings.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -399,9 +399,9 @@ fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
399399

400400
fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> {
401401
match (target_opt, source_opt) {
402-
(Some(mut target), Some(mut source)) => {
402+
(Some(mut target), Some(source)) => {
403403
target.reserve(source.len());
404-
for op in source.drain(..) {
404+
for op in source {
405405
target.push(op);
406406
}
407407
Some(target)
@@ -436,9 +436,9 @@ fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Exp
436436
chained_binops_helper(left_left, left_right),
437437
chained_binops_helper(right_left, right_right),
438438
) {
439-
(Some(mut left_ops), Some(mut right_ops)) => {
439+
(Some(mut left_ops), Some(right_ops)) => {
440440
left_ops.reserve(right_ops.len());
441-
for op in right_ops.drain(..) {
441+
for op in right_ops {
442442
left_ops.push(op);
443443
}
444444
Some(left_ops)

clippy_lints/src/utils/internal_lints/metadata_collector.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
473473
/// ```
474474
fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
475475
if let Some(args) = match_lint_emission(cx, expr) {
476-
let mut emission_info = extract_emission_info(cx, args);
476+
let emission_info = extract_emission_info(cx, args);
477477
if emission_info.is_empty() {
478478
// See:
479479
// - src/misc.rs:734:9
@@ -483,7 +483,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
483483
return;
484484
}
485485

486-
for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
486+
for (lint_name, applicability, is_multi_part) in emission_info {
487487
let app_info = self.applicability_info.entry(lint_name).or_default();
488488
app_info.applicability = applicability;
489489
app_info.is_multi_part_suggestion = is_multi_part;
@@ -693,7 +693,7 @@ fn extract_emission_info<'hir>(
693693
}
694694

695695
lints
696-
.drain(..)
696+
.into_iter()
697697
.map(|lint_name| (lint_name, applicability, multi_part))
698698
.collect()
699699
}

clippy_utils/src/hir_utils.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
7474
self.inter_expr().eq_expr(left, right)
7575
}
7676

77+
pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
78+
self.inter_expr().eq_path(left, right)
79+
}
80+
7781
pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
7882
self.inter_expr().eq_path_segment(left, right)
7983
}
@@ -362,7 +366,7 @@ impl HirEqInterExpr<'_, '_, '_> {
362366
}
363367
}
364368

365-
fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
369+
pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
366370
match (left.res, right.res) {
367371
(Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
368372
(Res::Local(_), _) | (_, Res::Local(_)) => false,

tests/ui/extend_with_drain.fixed

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// run-rustfix
22
#![warn(clippy::extend_with_drain)]
3+
#![allow(clippy::iter_with_drain)]
34
use std::collections::BinaryHeap;
45
fn main() {
56
//gets linted
67
let mut vec1 = vec![0u8; 1024];
78
let mut vec2: std::vec::Vec<u8> = Vec::new();
8-
99
vec2.append(&mut vec1);
1010

1111
let mut vec3 = vec![0u8; 1024];
@@ -17,7 +17,7 @@ fn main() {
1717

1818
vec11.append(&mut return_vector());
1919

20-
//won't get linted it dosen't move the entire content of a vec into another
20+
//won't get linted it doesn't move the entire content of a vec into another
2121
let mut test1 = vec![0u8, 10];
2222
let mut test2: std::vec::Vec<u8> = Vec::new();
2323

tests/ui/extend_with_drain.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// run-rustfix
22
#![warn(clippy::extend_with_drain)]
3+
#![allow(clippy::iter_with_drain)]
34
use std::collections::BinaryHeap;
45
fn main() {
56
//gets linted
67
let mut vec1 = vec![0u8; 1024];
78
let mut vec2: std::vec::Vec<u8> = Vec::new();
8-
99
vec2.extend(vec1.drain(..));
1010

1111
let mut vec3 = vec![0u8; 1024];
@@ -17,7 +17,7 @@ fn main() {
1717

1818
vec11.extend(return_vector().drain(..));
1919

20-
//won't get linted it dosen't move the entire content of a vec into another
20+
//won't get linted it doesn't move the entire content of a vec into another
2121
let mut test1 = vec![0u8, 10];
2222
let mut test2: std::vec::Vec<u8> = Vec::new();
2323

tests/ui/iter_with_drain.fixed

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// run-rustfix
2+
// will emits unused mut warnings after fixing
3+
#![allow(unused_mut)]
4+
// will emits needless collect warnings after fixing
5+
#![allow(clippy::needless_collect)]
6+
#![warn(clippy::iter_with_drain)]
7+
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
8+
9+
fn full() {
10+
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
11+
let mut a: BinaryHeap<_> = a.into_iter().collect();
12+
let mut a: HashSet<_> = a.drain().collect();
13+
let mut a: VecDeque<_> = a.drain().collect();
14+
let mut a: Vec<_> = a.into_iter().collect();
15+
let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
16+
let _: Vec<(String, String)> = a.drain().collect();
17+
}
18+
19+
fn closed() {
20+
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
21+
let mut a: BinaryHeap<_> = a.into_iter().collect();
22+
let mut a: HashSet<_> = a.drain().collect();
23+
let mut a: VecDeque<_> = a.drain().collect();
24+
let mut a: Vec<_> = a.into_iter().collect();
25+
let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
26+
let _: Vec<(String, String)> = a.drain().collect();
27+
}
28+
29+
fn should_not_help() {
30+
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
31+
let mut a: BinaryHeap<_> = a.drain(1..).collect();
32+
let mut a: HashSet<_> = a.drain().collect();
33+
let mut a: VecDeque<_> = a.drain().collect();
34+
let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
35+
let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
36+
let _: Vec<(String, String)> = a.drain().collect();
37+
38+
let mut b = vec!["aaa".to_string(), "bbb".to_string()];
39+
let _: Vec<_> = b.drain(0..a.len()).collect();
40+
}
41+
42+
#[derive(Default)]
43+
struct Bomb {
44+
fire: Vec<u8>,
45+
}
46+
47+
fn should_not_help_0(bomb: &mut Bomb) {
48+
let _: Vec<u8> = bomb.fire.drain(..).collect();
49+
}
50+
51+
fn main() {
52+
full();
53+
closed();
54+
should_not_help();
55+
should_not_help_0(&mut Bomb::default());
56+
}

tests/ui/iter_with_drain.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// run-rustfix
2+
// will emits unused mut warnings after fixing
3+
#![allow(unused_mut)]
4+
// will emits needless collect warnings after fixing
5+
#![allow(clippy::needless_collect)]
6+
#![warn(clippy::iter_with_drain)]
7+
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
8+
9+
fn full() {
10+
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
11+
let mut a: BinaryHeap<_> = a.drain(..).collect();
12+
let mut a: HashSet<_> = a.drain().collect();
13+
let mut a: VecDeque<_> = a.drain().collect();
14+
let mut a: Vec<_> = a.drain(..).collect();
15+
let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
16+
let _: Vec<(String, String)> = a.drain().collect();
17+
}
18+
19+
fn closed() {
20+
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
21+
let mut a: BinaryHeap<_> = a.drain(0..).collect();
22+
let mut a: HashSet<_> = a.drain().collect();
23+
let mut a: VecDeque<_> = a.drain().collect();
24+
let mut a: Vec<_> = a.drain(..a.len()).collect();
25+
let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
26+
let _: Vec<(String, String)> = a.drain().collect();
27+
}
28+
29+
fn should_not_help() {
30+
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
31+
let mut a: BinaryHeap<_> = a.drain(1..).collect();
32+
let mut a: HashSet<_> = a.drain().collect();
33+
let mut a: VecDeque<_> = a.drain().collect();
34+
let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
35+
let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
36+
let _: Vec<(String, String)> = a.drain().collect();
37+
38+
let mut b = vec!["aaa".to_string(), "bbb".to_string()];
39+
let _: Vec<_> = b.drain(0..a.len()).collect();
40+
}
41+
42+
#[derive(Default)]
43+
struct Bomb {
44+
fire: Vec<u8>,
45+
}
46+
47+
fn should_not_help_0(bomb: &mut Bomb) {
48+
let _: Vec<u8> = bomb.fire.drain(..).collect();
49+
}
50+
51+
fn main() {
52+
full();
53+
closed();
54+
should_not_help();
55+
should_not_help_0(&mut Bomb::default());
56+
}

0 commit comments

Comments
 (0)