- Notifications
You must be signed in to change notification settings - Fork 1.9k
lint on unnecessary collections #15830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -394,7 +394,7 @@ fn non_elidable_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ide | |
| let mut visitor = RefVisitor::new(cx); | ||
| visitor.visit_ty_unambig(self_ty); | ||
| | ||
| !visitor.all_lts().is_empty() | ||
| visitor.all_lts().next().is_some() | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand how this is better than Contributor Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you avoid iterating all of the items just to check the length, and it doesnt impl exactsizeiterator | ||
| } else { | ||
| false | ||
| } | ||
| | @@ -444,12 +444,8 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> { | |
| } | ||
| } | ||
| | ||
| fn all_lts(&self) -> Vec<Lifetime> { | ||
| self.lts | ||
| .iter() | ||
| .chain(self.nested_elision_site_lts.iter()) | ||
| .copied() | ||
| .collect::<Vec<_>>() | ||
| fn all_lts(&self) -> impl Iterator<Item = Lifetime> { | ||
| self.lts.iter().chain(&self.nested_elision_site_lts).copied() | ||
| } | ||
| | ||
| fn abort(&self) -> bool { | ||
| | @@ -472,7 +468,7 @@ impl<'tcx> Visitor<'tcx> for RefVisitor<'_, 'tcx> { | |
| { | ||
| let mut sub_visitor = RefVisitor::new(self.cx); | ||
| sub_visitor.visit_trait_ref(trait_ref); | ||
| self.nested_elision_site_lts.append(&mut sub_visitor.all_lts()); | ||
| self.nested_elision_site_lts.extend(sub_visitor.all_lts()); | ||
| } else { | ||
| walk_poly_trait_ref(self, poly_tref); | ||
| } | ||
| | @@ -483,7 +479,7 @@ impl<'tcx> Visitor<'tcx> for RefVisitor<'_, 'tcx> { | |
| TyKind::FnPtr(&FnPtrTy { decl, .. }) => { | ||
| let mut sub_visitor = RefVisitor::new(self.cx); | ||
| sub_visitor.visit_fn_decl(decl); | ||
| self.nested_elision_site_lts.append(&mut sub_visitor.all_lts()); | ||
| self.nested_elision_site_lts.extend(sub_visitor.all_lts()); | ||
| }, | ||
| TyKind::TraitObject(bounds, lt) => { | ||
| if !lt.is_elided() { | ||
| | @@ -509,7 +505,7 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_ | |
| let mut visitor = RefVisitor::new(cx); | ||
| // walk the type F, it may not contain LT refs | ||
| walk_unambig_ty(&mut visitor, pred.bounded_ty); | ||
| if !visitor.all_lts().is_empty() { | ||
| if visitor.all_lts().next().is_some() { | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. | ||
| return true; | ||
| } | ||
| // if the bounds define new lifetimes, they are fine to occur | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| use clippy_config::Conf; | ||
| use clippy_utils::diagnostics::span_lint_and_then; | ||
| use clippy_utils::sym; | ||
| use clippy_utils::ty::{get_iterator_item_ty, has_non_owning_mutable_access, implements_trait}; | ||
| use rustc_errors::Applicability; | ||
| use rustc_hir::def_id::LocalDefId; | ||
| use rustc_hir::intravisit::FnKind; | ||
| use rustc_lint::LateContext; | ||
| use rustc_middle::ty; | ||
| use rustc_span::Span; | ||
| | ||
| use rustc_hir::{Block, Body, ExprKind, FnDecl, Stmt, StmtKind}; | ||
| use rustc_lint::LateLintPass; | ||
| use rustc_session::impl_lint_pass; | ||
| | ||
| declare_clippy_lint! { | ||
| /// ### What it does | ||
| /// Checks for functions returning `Vec<T>` where the `T` is produced from a `_.collect::<Vec<T>>()` | ||
| /// | ||
| /// ### Why is this bad? | ||
| /// | ||
| /// 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. | ||
| /// Its often silly to have the equivalent of `iterator.collect::<Vec<_>>().into_iter()` in your code. | ||
| /// | ||
| /// ### Potential Issues | ||
| /// | ||
| /// In some cases, there may be some lifetimes attached to your iterator that make it difficult, or even impossible, to avoid a collection. | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also if no caller can do without allocating a | ||
| /// If all uses of this function `collect` anyways, one ought simply `#[expect(clippy::unnecessary_collect)]`. | ||
| /// | ||
| /// ### Example | ||
| /// ```no_run | ||
| /// fn squares() -> Vec<(u8, u8)> { | ||
| /// (0..8u8) | ||
| /// .flat_map(|x| (0..8).map(move |y| (x, y))) | ||
| /// .collect::<Vec<_>>() | ||
| /// } | ||
| /// ``` | ||
| /// Use instead: | ||
| /// ```no_run | ||
| /// fn squares() -> impl Iterator<Item = (u8, u8)> { | ||
| /// (0..8u8).flat_map(|x| (0..8).map(move |y| (x, y))) | ||
| /// } | ||
| /// ``` | ||
| #[clippy::version = "1.92.0"] | ||
| pub UNNECESSARY_COLLECT, | ||
| nursery, | ||
| "checks for functions returning vecs produced from vec::from_iter" | ||
| } | ||
| | ||
| pub struct UnnecessaryCollect { | ||
| avoid_breaking_exported_api: bool, | ||
| } | ||
| | ||
| impl UnnecessaryCollect { | ||
| pub fn new( | ||
| &Conf { | ||
| avoid_breaking_exported_api, | ||
| .. | ||
| }: &'static Conf, | ||
| ) -> Self { | ||
| Self { | ||
| avoid_breaking_exported_api, | ||
| } | ||
| } | ||
| } | ||
| | ||
| impl_lint_pass!(UnnecessaryCollect => [UNNECESSARY_COLLECT]); | ||
| | ||
| impl<'tcx> LateLintPass<'tcx> for UnnecessaryCollect { | ||
| fn check_fn( | ||
| &mut self, | ||
| cx: &LateContext<'tcx>, | ||
| kind: FnKind<'tcx>, | ||
| decl: &'tcx FnDecl<'tcx>, | ||
| body: &'tcx Body<'tcx>, | ||
| _: Span, | ||
| def_id: LocalDefId, | ||
| ) { | ||
| if let FnKind::ItemFn(..) | FnKind::Method(..) = kind | ||
| && let ExprKind::Block( | ||
| Block { expr: Some(expr), .. } | ||
| | Block { stmts: [.., Stmt { kind: StmtKind::Expr(expr), .. }], .. }, | ||
| _, | ||
| ) = &body.value.kind | ||
| && let expr = expr.peel_drop_temps() | ||
| && let ExprKind::MethodCall(path, iter_expr, args, call_span) = expr.kind | ||
| && !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id)) | ||
| && !args.iter().any(|e| e.span.from_expansion()) | ||
| && path.ident.name == sym::collect | ||
| && let iter_ty = cx.typeck_results().expr_ty(iter_expr) | ||
| && !has_non_owning_mutable_access(cx, iter_ty) | ||
| && let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator) | ||
| && implements_trait(cx, iter_ty, iterator_trait_id, &[]) | ||
| && let Some(item) = get_iterator_item_ty(cx, iter_ty) | ||
| && let vec_ty = cx.typeck_results().expr_ty(expr) | ||
| && let ty::Adt(v, args) = vec_ty.kind() | ||
| && cx.tcx.is_diagnostic_item(sym::Vec, v.did()) | ||
| // make sure we're collecting to just a vec, and not a vec<result> String or other such tomfoolery | ||
| && args.first().and_then(|x| x.as_type()) == Some(item) | ||
| { | ||
| span_lint_and_then( | ||
| cx, | ||
| UNNECESSARY_COLLECT, | ||
| call_span, | ||
| "this collection may not be necessary. return the iterator itself".to_string(), | ||
| |diag| { | ||
| diag.span_label(call_span, "remove this collect"); | ||
| diag.span_suggestion( | ||
| decl.output.span(), | ||
| "consider", | ||
| format!("impl Iterator<Item = {item}>"), | ||
| Applicability::MaybeIncorrect, | ||
| ); | ||
| }, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -2777,12 +2777,13 @@ pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String { | |
| /// Returns all the comments a given span contains. | ||
| /// | ||
| /// Comments are returned wrapped with their relevant delimiters. | ||
| #[allow(clippy::unnecessary_collect)] | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That we have three Luckily, it is certainly possible to improve it, so that shouldn't discourage you. | ||
| pub fn span_extract_comments(sm: &SourceMap, span: Span) -> Vec<String> { | ||
| let snippet = sm.span_to_snippet(span).unwrap_or_default(); | ||
| tokenize_with_text(&snippet) | ||
| .filter(|(t, ..)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. })) | ||
| .map(|(_, s, _)| s.to_string()) | ||
| .collect::<Vec<_>>() | ||
| .collect() | ||
| } | ||
| | ||
| pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span { | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| #![warn(clippy::unnecessary_collect)] | ||
| //@no-rustfix | ||
| | ||
| fn bad() -> Vec<u32> { | ||
| (0..5).collect() | ||
| //~^ unnecessary_collect | ||
| } | ||
| unsafe fn bad2() -> Vec<(u8, u8)> { | ||
| (0..8).flat_map(|x| (0..8).map(move |y| (x, y))).collect() | ||
| //~^ unnecessary_collect | ||
| } | ||
| fn okay() -> String { | ||
| ('a'..='z').collect() | ||
| } | ||
| fn hmm() -> std::collections::HashSet<u32> { | ||
| (0..5).chain(3..12).collect() | ||
| } | ||
| fn good() -> impl Iterator<Item = u32> { | ||
| (5..10).rev() | ||
| } | ||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| error: this collection may not be necessary. return the iterator itself | ||
| --> tests/ui/unnecessary_collect.rs:5:12 | ||
| | | ||
| LL | fn bad() -> Vec<u32> { | ||
| | -------- help: consider: `impl Iterator<Item = u32>` | ||
| LL | (0..5).collect() | ||
| | ^^^^^^^^^ remove this collect | ||
| | | ||
| = note: `-D clippy::unnecessary-collect` implied by `-D warnings` | ||
| = help: to override `-D warnings` add `#[allow(clippy::unnecessary_collect)]` | ||
| | ||
| error: this collection may not be necessary. return the iterator itself | ||
| --> tests/ui/unnecessary_collect.rs:9:54 | ||
| | | ||
| LL | unsafe fn bad2() -> Vec<(u8, u8)> { | ||
| | ------------- help: consider: `impl Iterator<Item = (u8, u8)>` | ||
| LL | (0..8).flat_map(|x| (0..8).map(move |y| (x, y))).collect() | ||
| | ^^^^^^^^^ remove this collect | ||
| | ||
| error: aborting due to 2 previous errors | ||
| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use
#[expect]to ignore a lint, and put it as close as possible to the lint emission.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bit difficult, its define_conf!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This convo seems smart I just wanna say smth so I feel like I'm a part of it