Skip to content

Commit 01c2a9b

Browse files
committed
lint on unnecessary collections
1 parent cd61be7 commit 01c2a9b

File tree

6 files changed

+157
-0
lines changed

6 files changed

+157
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6966,6 +6966,7 @@ Released 2018-09-13
69666966
[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns
69676967
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
69686968
[`unnecessary_clippy_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_clippy_cfg
6969+
[`unnecessary_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_collect
69696970
[`unnecessary_debug_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_debug_formatting
69706971
[`unnecessary_fallible_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fallible_conversions
69716972
[`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
@@ -751,6 +751,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
751751
crate::unit_types::UNIT_ARG_INFO,
752752
crate::unit_types::UNIT_CMP_INFO,
753753
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
754+
crate::unnecessary_collect::UNNECESSARY_COLLECT_INFO,
754755
crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO,
755756
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
756757
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
@@ -361,6 +361,7 @@ mod uninit_vec;
361361
mod unit_return_expecting_ord;
362362
mod unit_types;
363363
mod unnecessary_box_returns;
364+
mod unnecessary_collect;
364365
mod unnecessary_literal_bound;
365366
mod unnecessary_map_on_constructor;
366367
mod unnecessary_mut_passed;
@@ -847,6 +848,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
847848
Box::new(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)),
848849
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
849850
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
851+
Box::new(move |_| Box::new(unnecessary_collect::UnnecessaryCollect::new(conf))),
850852
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
851853
// add late passes here, used by `cargo dev new_lint`
852854
];
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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;
10+
use rustc_span::Span;
11+
12+
use rustc_hir::{Block, Body, ExprKind, FnDecl, Stmt, StmtKind};
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 often more performant to 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+
/// If all uses of this function `collect` anyways, one ought simply `#[expect(clippy::unnecessary_collect)]`.
29+
///
30+
/// ### Example
31+
/// ```no_run
32+
/// fn squares() -> Vec<(u8, u8)> {
33+
/// (0..8u8)
34+
/// .flat_map(|x| (0..8).map(move |y| (x, y)))
35+
/// .collect::<Vec<_>>()
36+
/// }
37+
/// ```
38+
/// Use instead:
39+
/// ```no_run
40+
/// fn squares() -> impl Iterator<Item = (u8, u8)> {
41+
/// (0..8u8).flat_map(|x| (0..8).map(move |y| (x, y)))
42+
/// }
43+
/// ```
44+
#[clippy::version = "1.92.0"]
45+
pub UNNECESSARY_COLLECT,
46+
nursery,
47+
"default lint description"
48+
}
49+
50+
pub struct UnnecessaryCollect {
51+
avoid_breaking_exported_api: bool,
52+
}
53+
54+
impl UnnecessaryCollect {
55+
pub fn new(&Conf { avoid_breaking_exported_api, .. }: &'static Conf) -> Self {
56+
Self { avoid_breaking_exported_api }
57+
}
58+
}
59+
60+
impl_lint_pass!(UnnecessaryCollect => [UNNECESSARY_COLLECT]);
61+
62+
impl<'tcx> LateLintPass<'tcx> for UnnecessaryCollect {
63+
fn check_fn(
64+
&mut self,
65+
cx: &LateContext<'tcx>,
66+
kind: FnKind<'tcx>,
67+
decl: &'tcx FnDecl<'tcx>,
68+
body: &'tcx Body<'tcx>,
69+
_: Span,
70+
def_id: LocalDefId,
71+
) {
72+
if let FnKind::ItemFn(..) | FnKind::Method(..) = kind
73+
&& let ExprKind::Block(
74+
Block { expr: Some(expr), .. }
75+
| Block { stmts: [.., Stmt { kind: StmtKind::Expr(expr), .. }], .. },
76+
_,
77+
) = &body.value.kind
78+
&& let expr = expr.peel_drop_temps()
79+
&& let ExprKind::MethodCall(path, iter_expr, args, call_span) = expr.kind
80+
&& !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id))
81+
&& !args.iter().any(|e| e.span.from_expansion())
82+
&& path.ident.name == sym::collect
83+
&& let iter_ty = cx.typeck_results().expr_ty(iter_expr)
84+
&& !has_non_owning_mutable_access(cx, iter_ty)
85+
&& let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
86+
&& implements_trait(cx, iter_ty, iterator_trait_id, &[])
87+
&& let Some(item) = get_iterator_item_ty(cx, iter_ty)
88+
&& let vec_ty = cx.typeck_results().expr_ty(expr)
89+
&& let ty::Adt(v, args) = vec_ty.kind()
90+
&& cx.tcx.is_diagnostic_item(sym::Vec, v.did())
91+
// make sure we're collecting to just a vec, and not a vec<result> String or other such tomfoolery
92+
&& args.first().and_then(|x| x.as_type()) == Some(item)
93+
{
94+
span_lint_and_then(
95+
cx,
96+
UNNECESSARY_COLLECT,
97+
call_span,
98+
"this collection may not be necessary. return the iterator itself".to_string(),
99+
|diag| {
100+
diag.span_label(call_span, "remove this collect");
101+
diag.span_suggestion(
102+
decl.output.span(),
103+
"consider",
104+
format!("impl Iterator<Item = {item}>"),
105+
Applicability::MaybeIncorrect,
106+
);
107+
},
108+
);
109+
}
110+
}
111+
}

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. return the iterator itself
2+
--> tests/ui/unnecessary_collect.rs:5:12
3+
|
4+
LL | fn bad() -> Vec<u32> {
5+
| -------- help: consider: `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. return the iterator itself
13+
--> tests/ui/unnecessary_collect.rs:9:54
14+
|
15+
LL | unsafe fn bad2() -> Vec<(u8, u8)> {
16+
| ------------- help: consider: `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)