Skip to content

Commit ebdb55b

Browse files
committed
lint on unnecessary collections
1 parent c1f6124 commit ebdb55b

File tree

6 files changed

+169
-0
lines changed

6 files changed

+169
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6722,6 +6722,7 @@ Released 2018-09-13
67226722
[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns
67236723
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
67246724
[`unnecessary_clippy_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_clippy_cfg
6725+
[`unnecessary_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_collect
67256726
[`unnecessary_debug_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_debug_formatting
67266727
[`unnecessary_fallible_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fallible_conversions
67276728
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
749749
crate::unit_types::UNIT_ARG_INFO,
750750
crate::unit_types::UNIT_CMP_INFO,
751751
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
752+
crate::unnecessary_collect::UNNECESSARY_COLLECT_INFO,
752753
crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO,
753754
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
754755
crate::unnecessary_mut_passed::UNNECESSARY_MUT_PASSED_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ mod uninit_vec;
371371
mod unit_return_expecting_ord;
372372
mod unit_types;
373373
mod unnecessary_box_returns;
374+
mod unnecessary_collect;
374375
mod unnecessary_literal_bound;
375376
mod unnecessary_map_on_constructor;
376377
mod unnecessary_mut_passed;
@@ -832,5 +833,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
832833
store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny));
833834
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
834835
store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites));
836+
store.register_late_pass(move |_| Box::new(unnecessary_collect::UnnecessaryCollect::new(conf)));
835837
// add lints here, do not remove this comment, it's used in `new_lint`
836838
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::sym;
4+
use clippy_utils::ty::{get_iterator_item_ty, has_non_owning_mutable_access, implements_trait};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::def_id::LocalDefId;
7+
use rustc_hir::intravisit::FnKind;
8+
use rustc_lint::LateContext;
9+
use rustc_middle::ty::{self};
10+
use rustc_span::Span;
11+
12+
use rustc_hir::*;
13+
use rustc_lint::LateLintPass;
14+
use rustc_session::impl_lint_pass;
15+
16+
declare_clippy_lint! {
17+
/// ### What it does
18+
/// Checks for functions returning `Vec<T>` where the `T` is produced from a `_.collect::<Vec<T>>()`
19+
///
20+
/// ### Why is this bad?
21+
///
22+
/// This might not be necessary, and its more idiomatic to simply return the iterator, giving the caller the option to use it as they see fit.
23+
/// Its often silly to have the equivalent of `iterator.collect::<Vec<_>>().into_iter()` in your code.
24+
///
25+
/// ### Potential Issues
26+
///
27+
/// In some cases, there may be some lifetimes attached to your iterator that make it difficult, or even impossible, to avoid a collection.
28+
///
29+
/// ### Example
30+
/// ```no_run
31+
/// fn squares() -> Vec<(u8, u8)> {
32+
/// (0..8u8)
33+
/// .flat_map(|x| (0..8).map(move |y| (x, y)))
34+
/// .collect::<Vec<_>>()
35+
/// }
36+
/// ```
37+
/// Use instead:
38+
/// ```no_run
39+
/// fn squares() -> impl Iterator<Item = (u8, u8)> {
40+
/// (0..8u8).flat_map(|x| (0..8).map(move |y| (x, y)))
41+
/// }
42+
/// ```
43+
#[clippy::version = "1.92.0"]
44+
pub UNNECESSARY_COLLECT,
45+
pedantic,
46+
"default lint description"
47+
}
48+
49+
pub struct UnnecessaryCollect {
50+
avoid_breaking_exported_api: bool,
51+
}
52+
53+
impl UnnecessaryCollect {
54+
pub fn new(conf: &'static Conf) -> Self {
55+
Self {
56+
avoid_breaking_exported_api: conf.avoid_breaking_exported_api,
57+
}
58+
}
59+
}
60+
61+
impl_lint_pass!(UnnecessaryCollect => [UNNECESSARY_COLLECT]);
62+
63+
impl<'tcx> LateLintPass<'tcx> for UnnecessaryCollect {
64+
fn check_fn(
65+
&mut self,
66+
cx: &LateContext<'tcx>,
67+
kind: FnKind<'tcx>,
68+
decl: &'tcx FnDecl<'tcx>,
69+
body: &'tcx Body<'tcx>,
70+
_: Span,
71+
def_id: LocalDefId,
72+
) {
73+
let expr = match kind {
74+
FnKind::ItemFn(..) | FnKind::Method(..) if let ExprKind::Block(block, _) = &body.value.kind => {
75+
if let Some(block_expr) = block.expr {
76+
block_expr
77+
} else if let Some(Stmt {
78+
kind: StmtKind::Expr(expr),
79+
..
80+
}) = block.stmts.last()
81+
{
82+
expr
83+
} else {
84+
return;
85+
}
86+
},
87+
_ => return,
88+
}
89+
.peel_drop_temps();
90+
if let ExprKind::MethodCall(path, iter_expr, args, call_span) = expr.kind
91+
&& !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id))
92+
&& !args.iter().any(|e| e.span.from_expansion())
93+
&& !iter_expr.span.from_expansion()
94+
&& path.ident.name == sym::collect
95+
&& let iter_ty = cx.typeck_results().expr_ty(iter_expr)
96+
&& !has_non_owning_mutable_access(cx, iter_ty)
97+
&& let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
98+
&& implements_trait(cx, iter_ty, iterator_trait_id, &[])
99+
&& let Some(item) = get_iterator_item_ty(cx, iter_ty)
100+
&& let vec_ty = cx.typeck_results().expr_ty(expr)
101+
&& let ty::Adt(v, args) = vec_ty.kind()
102+
&& cx.tcx.is_diagnostic_item(sym::Vec, v.did())
103+
// make sure we're collecting to just a vec, and not a vec<result> String or other such tomfoolery
104+
&& args.get(0).and_then(|x| x.as_type()) == Some(item)
105+
{
106+
span_lint_and_then(
107+
cx,
108+
UNNECESSARY_COLLECT,
109+
call_span,
110+
"this collection may not be necessary. consider returning the iterator itself".to_string(),
111+
|diag| {
112+
diag.span_label(call_span, "remove this collect");
113+
diag.span_suggestion(
114+
decl.output.span(),
115+
"perhaps",
116+
format!("impl Iterator<Item = {item}>"),
117+
Applicability::MaybeIncorrect,
118+
);
119+
},
120+
);
121+
}
122+
}
123+
}

tests/ui/unnecessary_collect.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::unnecessary_collect)]
2+
//@no-rustfix
3+
4+
fn bad() -> Vec<u32> {
5+
(0..5).collect()
6+
//~^ unnecessary_collect
7+
}
8+
unsafe fn bad2() -> Vec<(u8, u8)> {
9+
(0..8).flat_map(|x| (0..8).map(move |y| (x, y))).collect()
10+
//~^ unnecessary_collect
11+
}
12+
fn okay() -> String {
13+
('a'..='z').collect()
14+
}
15+
fn hmm() -> std::collections::HashSet<u32> {
16+
(0..5).chain(3..12).collect()
17+
}
18+
fn good() -> impl Iterator<Item = u32> {
19+
(5..10).rev()
20+
}
21+
fn main() {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error: this collection may not be necessary. consider returning the iterator itself
2+
--> tests/ui/unnecessary_collect.rs:5:12
3+
|
4+
LL | fn bad() -> Vec<u32> {
5+
| -------- help: perhaps: `impl Iterator<Item = u32>`
6+
LL | (0..5).collect()
7+
| ^^^^^^^^^ remove this collect
8+
|
9+
= note: `-D clippy::unnecessary-collect` implied by `-D warnings`
10+
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_collect)]`
11+
12+
error: this collection may not be necessary. consider returning the iterator itself
13+
--> tests/ui/unnecessary_collect.rs:9:54
14+
|
15+
LL | unsafe fn bad2() -> Vec<(u8, u8)> {
16+
| ------------- help: perhaps: `impl Iterator<Item = (u8, u8)>`
17+
LL | (0..8).flat_map(|x| (0..8).map(move |y| (x, y))).collect()
18+
| ^^^^^^^^^ remove this collect
19+
20+
error: aborting due to 2 previous errors
21+

0 commit comments

Comments
 (0)