Skip to content

Commit ee67c79

Browse files
committed
Auto merge of rust-lang#10835 - y21:drain-collect, r=dswij
new lint: `drain_collect` Closes rust-lang#10818. This adds a new lint that looks for `.drain(..).collect()` and suggests replacing it with `mem::take`. changelog: [`drain_collect`]: new lint
2 parents cda13a8 + 5821fbb commit ee67c79

File tree

9 files changed

+352
-2
lines changed

9 files changed

+352
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4737,6 +4737,7 @@ Released 2018-09-13
47374737
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
47384738
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
47394739
[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens
4740+
[`drain_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#drain_collect
47404741
[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds
47414742
[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
47424743
[`drop_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_non_drop

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
324324
crate::methods::CLONE_ON_COPY_INFO,
325325
crate::methods::CLONE_ON_REF_PTR_INFO,
326326
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
327+
crate::methods::DRAIN_COLLECT_INFO,
327328
crate::methods::ERR_EXPECT_INFO,
328329
crate::methods::EXPECT_FUN_CALL_INFO,
329330
crate::methods::EXPECT_USED_INFO,
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use crate::methods::DRAIN_COLLECT;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::is_range_full;
4+
use clippy_utils::source::snippet;
5+
use clippy_utils::ty::is_type_lang_item;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::Expr;
8+
use rustc_hir::ExprKind;
9+
use rustc_hir::LangItem;
10+
use rustc_hir::Path;
11+
use rustc_hir::QPath;
12+
use rustc_lint::LateContext;
13+
use rustc_middle::query::Key;
14+
use rustc_middle::ty;
15+
use rustc_middle::ty::Ty;
16+
use rustc_span::sym;
17+
use rustc_span::Symbol;
18+
19+
/// Checks if both types match the given diagnostic item, e.g.:
20+
///
21+
/// `vec![1,2].drain(..).collect::<Vec<_>>()`
22+
/// ^^^^^^^^^ ^^^^^^ true
23+
/// `vec![1,2].drain(..).collect::<HashSet<_>>()`
24+
/// ^^^^^^^^^ ^^^^^^^^^^ false
25+
fn types_match_diagnostic_item(cx: &LateContext<'_>, expr: Ty<'_>, recv: Ty<'_>, sym: Symbol) -> bool {
26+
if let Some(expr_adt_did) = expr.ty_adt_id()
27+
&& let Some(recv_adt_did) = recv.ty_adt_id()
28+
{
29+
cx.tcx.is_diagnostic_item(sym, expr_adt_did) && cx.tcx.is_diagnostic_item(sym, recv_adt_did)
30+
} else {
31+
false
32+
}
33+
}
34+
35+
/// Checks `std::{vec::Vec, collections::VecDeque}`.
36+
fn check_vec(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool {
37+
(types_match_diagnostic_item(cx, expr, recv, sym::Vec)
38+
|| types_match_diagnostic_item(cx, expr, recv, sym::VecDeque))
39+
&& matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path)))
40+
}
41+
42+
/// Checks `std::string::String`
43+
fn check_string(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool {
44+
is_type_lang_item(cx, expr, LangItem::String)
45+
&& is_type_lang_item(cx, recv, LangItem::String)
46+
&& matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path)))
47+
}
48+
49+
/// Checks `std::collections::{HashSet, HashMap, BinaryHeap}`.
50+
fn check_collections(cx: &LateContext<'_>, expr: Ty<'_>, recv: Ty<'_>) -> Option<&'static str> {
51+
types_match_diagnostic_item(cx, expr, recv, sym::HashSet)
52+
.then_some("HashSet")
53+
.or_else(|| types_match_diagnostic_item(cx, expr, recv, sym::HashMap).then_some("HashMap"))
54+
.or_else(|| types_match_diagnostic_item(cx, expr, recv, sym::BinaryHeap).then_some("BinaryHeap"))
55+
}
56+
57+
pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], expr: &Expr<'_>, recv: &Expr<'_>) {
58+
let expr_ty = cx.typeck_results().expr_ty(expr);
59+
let recv_ty = cx.typeck_results().expr_ty(recv);
60+
let recv_ty_no_refs = recv_ty.peel_refs();
61+
62+
if let ExprKind::Path(QPath::Resolved(_, recv_path)) = recv.kind
63+
&& let Some(typename) = check_vec(cx, args, expr_ty, recv_ty_no_refs, recv_path)
64+
.then_some("Vec")
65+
.or_else(|| check_string(cx, args, expr_ty, recv_ty_no_refs, recv_path).then_some("String"))
66+
.or_else(|| check_collections(cx, expr_ty, recv_ty_no_refs))
67+
{
68+
let recv = snippet(cx, recv.span, "<expr>");
69+
let sugg = if let ty::Ref(..) = recv_ty.kind() {
70+
format!("std::mem::take({recv})")
71+
} else {
72+
format!("std::mem::take(&mut {recv})")
73+
};
74+
75+
span_lint_and_sugg(
76+
cx,
77+
DRAIN_COLLECT,
78+
expr.span,
79+
&format!("you seem to be trying to move all elements into a new `{typename}`"),
80+
"consider using `mem::take`",
81+
sugg,
82+
Applicability::MachineApplicable,
83+
);
84+
}
85+
}

clippy_lints/src/methods/mod.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod clone_on_copy;
1414
mod clone_on_ref_ptr;
1515
mod cloned_instead_of_copied;
1616
mod collapsible_str_replace;
17+
mod drain_collect;
1718
mod err_expect;
1819
mod expect_fun_call;
1920
mod expect_used;
@@ -3247,6 +3248,42 @@ declare_clippy_lint! {
32473248
"manual reverse iteration of `DoubleEndedIterator`"
32483249
}
32493250

3251+
declare_clippy_lint! {
3252+
/// ### What it does
3253+
/// Checks for calls to `.drain()` that clear the collection, immediately followed by a call to `.collect()`.
3254+
///
3255+
/// > "Collection" in this context refers to any type with a `drain` method:
3256+
/// > `Vec`, `VecDeque`, `BinaryHeap`, `HashSet`,`HashMap`, `String`
3257+
///
3258+
/// ### Why is this bad?
3259+
/// Using `mem::take` is faster as it avoids the allocation.
3260+
/// When using `mem::take`, the old collection is replaced with an empty one and ownership of
3261+
/// the old collection is returned.
3262+
///
3263+
/// ### Known issues
3264+
/// `mem::take(&mut vec)` is almost equivalent to `vec.drain(..).collect()`, except that
3265+
/// it also moves the **capacity**. The user might have explicitly written it this way
3266+
/// to keep the capacity on the original `Vec`.
3267+
///
3268+
/// ### Example
3269+
/// ```rust
3270+
/// fn remove_all(v: &mut Vec<i32>) -> Vec<i32> {
3271+
/// v.drain(..).collect()
3272+
/// }
3273+
/// ```
3274+
/// Use instead:
3275+
/// ```rust
3276+
/// use std::mem;
3277+
/// fn remove_all(v: &mut Vec<i32>) -> Vec<i32> {
3278+
/// mem::take(v)
3279+
/// }
3280+
/// ```
3281+
#[clippy::version = "1.71.0"]
3282+
pub DRAIN_COLLECT,
3283+
perf,
3284+
"calling `.drain(..).collect()` to move all elements into a new collection"
3285+
}
3286+
32503287
pub struct Methods {
32513288
avoid_breaking_exported_api: bool,
32523289
msrv: Msrv,
@@ -3377,6 +3414,7 @@ impl_lint_pass!(Methods => [
33773414
CLEAR_WITH_DRAIN,
33783415
MANUAL_NEXT_BACK,
33793416
UNNECESSARY_LITERAL_UNWRAP,
3417+
DRAIN_COLLECT
33803418
]);
33813419

33823420
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3606,6 +3644,9 @@ impl Methods {
36063644
manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
36073645
}
36083646
},
3647+
Some(("drain", recv, args, ..)) => {
3648+
drain_collect::check(cx, args, expr, recv);
3649+
}
36093650
_ => {},
36103651
}
36113652
},

tests/ui/drain_collect.fixed

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//@run-rustfix
2+
3+
#![deny(clippy::drain_collect)]
4+
#![allow(dead_code)]
5+
6+
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
7+
8+
fn binaryheap(b: &mut BinaryHeap<i32>) -> BinaryHeap<i32> {
9+
std::mem::take(b)
10+
}
11+
12+
fn binaryheap_dont_lint(b: &mut BinaryHeap<i32>) -> HashSet<i32> {
13+
b.drain().collect()
14+
}
15+
16+
fn hashmap(b: &mut HashMap<i32, i32>) -> HashMap<i32, i32> {
17+
std::mem::take(b)
18+
}
19+
20+
fn hashmap_dont_lint(b: &mut HashMap<i32, i32>) -> Vec<(i32, i32)> {
21+
b.drain().collect()
22+
}
23+
24+
fn hashset(b: &mut HashSet<i32>) -> HashSet<i32> {
25+
std::mem::take(b)
26+
}
27+
28+
fn hashset_dont_lint(b: &mut HashSet<i32>) -> Vec<i32> {
29+
b.drain().collect()
30+
}
31+
32+
fn vecdeque(b: &mut VecDeque<i32>) -> VecDeque<i32> {
33+
std::mem::take(b)
34+
}
35+
36+
fn vecdeque_dont_lint(b: &mut VecDeque<i32>) -> HashSet<i32> {
37+
b.drain(..).collect()
38+
}
39+
40+
fn vec(b: &mut Vec<i32>) -> Vec<i32> {
41+
std::mem::take(b)
42+
}
43+
44+
fn vec2(b: &mut Vec<i32>) -> Vec<i32> {
45+
std::mem::take(b)
46+
}
47+
48+
fn vec3(b: &mut Vec<i32>) -> Vec<i32> {
49+
std::mem::take(b)
50+
}
51+
52+
fn vec4(b: &mut Vec<i32>) -> Vec<i32> {
53+
std::mem::take(b)
54+
}
55+
56+
fn vec_no_reborrow() -> Vec<i32> {
57+
let mut b = vec![1, 2, 3];
58+
std::mem::take(&mut b)
59+
}
60+
61+
fn vec_dont_lint(b: &mut Vec<i32>) -> HashSet<i32> {
62+
b.drain(..).collect()
63+
}
64+
65+
fn string(b: &mut String) -> String {
66+
std::mem::take(b)
67+
}
68+
69+
fn string_dont_lint(b: &mut String) -> HashSet<char> {
70+
b.drain(..).collect()
71+
}
72+
73+
fn not_whole_length(v: &mut Vec<i32>) -> Vec<i32> {
74+
v.drain(1..).collect()
75+
}
76+
77+
fn main() {}

tests/ui/drain_collect.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//@run-rustfix
2+
3+
#![deny(clippy::drain_collect)]
4+
#![allow(dead_code)]
5+
6+
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
7+
8+
fn binaryheap(b: &mut BinaryHeap<i32>) -> BinaryHeap<i32> {
9+
b.drain().collect()
10+
}
11+
12+
fn binaryheap_dont_lint(b: &mut BinaryHeap<i32>) -> HashSet<i32> {
13+
b.drain().collect()
14+
}
15+
16+
fn hashmap(b: &mut HashMap<i32, i32>) -> HashMap<i32, i32> {
17+
b.drain().collect()
18+
}
19+
20+
fn hashmap_dont_lint(b: &mut HashMap<i32, i32>) -> Vec<(i32, i32)> {
21+
b.drain().collect()
22+
}
23+
24+
fn hashset(b: &mut HashSet<i32>) -> HashSet<i32> {
25+
b.drain().collect()
26+
}
27+
28+
fn hashset_dont_lint(b: &mut HashSet<i32>) -> Vec<i32> {
29+
b.drain().collect()
30+
}
31+
32+
fn vecdeque(b: &mut VecDeque<i32>) -> VecDeque<i32> {
33+
b.drain(..).collect()
34+
}
35+
36+
fn vecdeque_dont_lint(b: &mut VecDeque<i32>) -> HashSet<i32> {
37+
b.drain(..).collect()
38+
}
39+
40+
fn vec(b: &mut Vec<i32>) -> Vec<i32> {
41+
b.drain(..).collect()
42+
}
43+
44+
fn vec2(b: &mut Vec<i32>) -> Vec<i32> {
45+
b.drain(0..).collect()
46+
}
47+
48+
fn vec3(b: &mut Vec<i32>) -> Vec<i32> {
49+
b.drain(..b.len()).collect()
50+
}
51+
52+
fn vec4(b: &mut Vec<i32>) -> Vec<i32> {
53+
b.drain(0..b.len()).collect()
54+
}
55+
56+
fn vec_no_reborrow() -> Vec<i32> {
57+
let mut b = vec![1, 2, 3];
58+
b.drain(..).collect()
59+
}
60+
61+
fn vec_dont_lint(b: &mut Vec<i32>) -> HashSet<i32> {
62+
b.drain(..).collect()
63+
}
64+
65+
fn string(b: &mut String) -> String {
66+
b.drain(..).collect()
67+
}
68+
69+
fn string_dont_lint(b: &mut String) -> HashSet<char> {
70+
b.drain(..).collect()
71+
}
72+
73+
fn not_whole_length(v: &mut Vec<i32>) -> Vec<i32> {
74+
v.drain(1..).collect()
75+
}
76+
77+
fn main() {}

tests/ui/drain_collect.stderr

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
error: you seem to be trying to move all elements into a new `BinaryHeap`
2+
--> $DIR/drain_collect.rs:9:5
3+
|
4+
LL | b.drain().collect()
5+
| ^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/drain_collect.rs:3:9
9+
|
10+
LL | #![deny(clippy::drain_collect)]
11+
| ^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: you seem to be trying to move all elements into a new `HashMap`
14+
--> $DIR/drain_collect.rs:17:5
15+
|
16+
LL | b.drain().collect()
17+
| ^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
18+
19+
error: you seem to be trying to move all elements into a new `HashSet`
20+
--> $DIR/drain_collect.rs:25:5
21+
|
22+
LL | b.drain().collect()
23+
| ^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
24+
25+
error: you seem to be trying to move all elements into a new `Vec`
26+
--> $DIR/drain_collect.rs:33:5
27+
|
28+
LL | b.drain(..).collect()
29+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
30+
31+
error: you seem to be trying to move all elements into a new `Vec`
32+
--> $DIR/drain_collect.rs:41:5
33+
|
34+
LL | b.drain(..).collect()
35+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
36+
37+
error: you seem to be trying to move all elements into a new `Vec`
38+
--> $DIR/drain_collect.rs:45:5
39+
|
40+
LL | b.drain(0..).collect()
41+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
42+
43+
error: you seem to be trying to move all elements into a new `Vec`
44+
--> $DIR/drain_collect.rs:49:5
45+
|
46+
LL | b.drain(..b.len()).collect()
47+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
48+
49+
error: you seem to be trying to move all elements into a new `Vec`
50+
--> $DIR/drain_collect.rs:53:5
51+
|
52+
LL | b.drain(0..b.len()).collect()
53+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
54+
55+
error: you seem to be trying to move all elements into a new `Vec`
56+
--> $DIR/drain_collect.rs:58:5
57+
|
58+
LL | b.drain(..).collect()
59+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(&mut b)`
60+
61+
error: you seem to be trying to move all elements into a new `String`
62+
--> $DIR/drain_collect.rs:66:5
63+
|
64+
LL | b.drain(..).collect()
65+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `std::mem::take(b)`
66+
67+
error: aborting due to 10 previous errors
68+

0 commit comments

Comments
 (0)