1 use super::NEEDLESS_COLLECT;
2 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
3 use clippy_utils::source::{snippet, snippet_with_applicability};
4 use clippy_utils::sugg::Sugg;
5 use clippy_utils::ty::is_type_diagnostic_item;
6 use clippy_utils::{is_trait_method, path_to_local_id};
7 use if_chain::if_chain;
8 use rustc_errors::Applicability;
9 use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
10 use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, StmtKind};
11 use rustc_lint::LateContext;
12 use rustc_middle::hir::map::Map;
14 use rustc_span::{MultiSpan, Span};
16 const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
18 pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
19 check_needless_collect_direct_usage(expr, cx);
20 check_needless_collect_indirect_usage(expr, cx);
22 fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
24 if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
25 if let ExprKind::MethodCall(chain_method, method0_span, _, _) = args[0].kind;
26 if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
28 let ty = cx.typeck_results().expr_ty(&args[0]);
29 let mut applicability = Applicability::MaybeIncorrect;
30 let is_empty_sugg = "next().is_none()".to_string();
31 let method_name = &*method.ident.name.as_str();
32 let sugg = if is_type_diagnostic_item(cx, ty, sym::Vec) ||
33 is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
34 is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
35 is_type_diagnostic_item(cx, ty, sym::BinaryHeap) {
37 "len" => "count()".to_string(),
38 "is_empty" => is_empty_sugg,
40 let contains_arg = snippet_with_applicability(cx, args[1].span, "??", &mut applicability);
41 let (arg, pred) = contains_arg
43 .map_or(("&x", &*contains_arg), |s| ("x", s));
44 format!("any(|{}| x == {})", arg, pred)
49 else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
50 is_type_diagnostic_item(cx, ty, sym::HashMap) {
52 "is_empty" => is_empty_sugg,
62 method0_span.with_hi(expr.span.hi()),
72 fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
73 if let ExprKind::Block(block, _) = expr.kind {
74 for stmt in block.stmts {
76 if let StmtKind::Local(local) = stmt.kind;
77 if let PatKind::Binding(_, id, ..) = local.pat.kind;
78 if let Some(init_expr) = local.init;
79 if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
80 if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
81 let ty = cx.typeck_results().expr_ty(init_expr);
82 if is_type_diagnostic_item(cx, ty, sym::Vec) ||
83 is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
84 is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
85 is_type_diagnostic_item(cx, ty, sym::LinkedList);
86 if let Some(iter_calls) = detect_iter_and_into_iters(block, id);
87 if let [iter_call] = &*iter_calls;
89 let mut used_count_visitor = UsedCountVisitor {
94 walk_block(&mut used_count_visitor, block);
95 if used_count_visitor.count > 1 {
99 // Suggest replacing iter_call with iter_replacement, and removing stmt
100 let mut span = MultiSpan::from_span(collect_span);
101 span.push_span_label(iter_call.span, "the iterator could be used here instead".into());
102 span_lint_hir_and_then(
104 super::NEEDLESS_COLLECT,
107 NEEDLESS_COLLECT_MSG,
109 let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
110 diag.multipart_suggestion(
111 iter_call.get_suggestion_text(),
113 (stmt.span, String::new()),
114 (iter_call.span, iter_replacement)
116 Applicability::MaybeIncorrect,
126 struct IterFunction {
127 func: IterFunctionKind,
131 fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
133 IterFunctionKind::IntoIter => String::new(),
134 IterFunctionKind::Len => String::from(".count()"),
135 IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
136 IterFunctionKind::Contains(span) => {
137 let s = snippet(cx, *span, "..");
138 if let Some(stripped) = s.strip_prefix('&') {
139 format!(".any(|x| x == {})", stripped)
141 format!(".any(|x| x == *{})", s)
146 fn get_suggestion_text(&self) -> &'static str {
148 IterFunctionKind::IntoIter => {
149 "use the original Iterator instead of collecting it and then producing a new one"
151 IterFunctionKind::Len => {
152 "take the original Iterator's count instead of collecting it and finding the length"
154 IterFunctionKind::IsEmpty => {
155 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
157 IterFunctionKind::Contains(_) => {
158 "check if the original Iterator contains an element instead of collecting then checking"
163 enum IterFunctionKind {
170 struct IterFunctionVisitor {
171 uses: Vec<IterFunction>,
175 impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
176 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
177 // Check function calls on our collection
178 if let ExprKind::MethodCall(method_name, _, [recv, args @ ..], _) = &expr.kind {
179 if path_to_local_id(recv, self.target) {
180 match &*method_name.ident.name.as_str() {
181 "into_iter" => self.uses.push(IterFunction {
182 func: IterFunctionKind::IntoIter,
185 "len" => self.uses.push(IterFunction {
186 func: IterFunctionKind::Len,
189 "is_empty" => self.uses.push(IterFunction {
190 func: IterFunctionKind::IsEmpty,
193 "contains" => self.uses.push(IterFunction {
194 func: IterFunctionKind::Contains(args[0].span),
197 _ => self.seen_other = true,
202 // Check if the collection is used for anything else
203 if path_to_local_id(expr, self.target) {
204 self.seen_other = true;
206 walk_expr(self, expr);
210 type Map = Map<'tcx>;
211 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
212 NestedVisitorMap::None
216 struct UsedCountVisitor<'a, 'tcx> {
217 cx: &'a LateContext<'tcx>,
222 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
223 type Map = Map<'tcx>;
225 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
226 if path_to_local_id(expr, self.id) {
229 walk_expr(self, expr);
233 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
234 NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
238 /// Detect the occurrences of calls to `iter` or `into_iter` for the
240 fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, id: HirId) -> Option<Vec<IterFunction>> {
241 let mut visitor = IterFunctionVisitor {
246 visitor.visit_block(block);
247 if visitor.seen_other { None } else { Some(visitor.uses) }