1 use super::NEEDLESS_COLLECT;
2 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
3 use clippy_utils::source::snippet;
4 use clippy_utils::sugg::Sugg;
5 use clippy_utils::ty::{is_type_diagnostic_item, match_type};
6 use clippy_utils::{is_trait_method, path_to_local_id, paths};
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, GenericArg, GenericArgs, HirId, Local, Pat, PatKind, QPath, StmtKind, Ty};
11 use rustc_lint::LateContext;
12 use rustc_middle::hir::map::Map;
14 use rustc_span::symbol::{sym, Ident};
15 use rustc_span::{MultiSpan, Span};
17 const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
19 pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
20 check_needless_collect_direct_usage(expr, cx);
21 check_needless_collect_indirect_usage(expr, cx);
23 fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
25 if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
26 if let ExprKind::MethodCall(chain_method, method0_span, _, _) = args[0].kind;
27 if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
28 if let Some(generic_args) = chain_method.args;
29 if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
30 if let Some(ty) = cx.typeck_results().node_type_opt(ty.hir_id);
31 if is_type_diagnostic_item(cx, ty, sym::vec_type)
32 || is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
33 || match_type(cx, ty, &paths::BTREEMAP)
34 || is_type_diagnostic_item(cx, ty, sym::hashmap_type);
35 if let Some(sugg) = match &*method.ident.name.as_str() {
36 "len" => Some("count()".to_string()),
37 "is_empty" => Some("next().is_none()".to_string()),
39 let contains_arg = snippet(cx, args[1].span, "??");
40 let (arg, pred) = contains_arg
42 .map_or(("&x", &*contains_arg), |s| ("x", s));
43 Some(format!("any(|{}| x == {})", arg, pred))
51 method0_span.with_hi(expr.span.hi()),
55 Applicability::MachineApplicable,
61 fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
62 fn get_hir_id<'tcx>(ty: Option<&Ty<'tcx>>, method_args: Option<&GenericArgs<'tcx>>) -> Option<HirId> {
63 if let Some(ty) = ty {
64 return Some(ty.hir_id);
67 if let Some(generic_args) = method_args {
68 if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0) {
69 return Some(ty.hir_id);
75 if let ExprKind::Block(block, _) = expr.kind {
76 for stmt in block.stmts {
78 if let StmtKind::Local(
79 Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
80 init: Some(init_expr), ty, .. }
82 if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
83 if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
84 if let Some(hir_id) = get_hir_id(*ty, method_name.args);
85 if let Some(ty) = cx.typeck_results().node_type_opt(hir_id);
86 if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
87 is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
88 is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
89 match_type(cx, ty, &paths::LINKED_LIST);
90 if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
91 if let [iter_call] = &*iter_calls;
93 let mut used_count_visitor = UsedCountVisitor {
98 walk_block(&mut used_count_visitor, block);
99 if used_count_visitor.count > 1 {
103 // Suggest replacing iter_call with iter_replacement, and removing stmt
104 let mut span = MultiSpan::from_span(collect_span);
105 span.push_span_label(iter_call.span, "the iterator could be used here instead".into());
108 super::NEEDLESS_COLLECT,
110 NEEDLESS_COLLECT_MSG,
112 let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
113 diag.multipart_suggestion(
114 iter_call.get_suggestion_text(),
116 (stmt.span, String::new()),
117 (iter_call.span, iter_replacement)
119 Applicability::MachineApplicable,// MaybeIncorrect,
129 struct IterFunction {
130 func: IterFunctionKind,
134 fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
136 IterFunctionKind::IntoIter => String::new(),
137 IterFunctionKind::Len => String::from(".count()"),
138 IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
139 IterFunctionKind::Contains(span) => {
140 let s = snippet(cx, *span, "..");
141 if let Some(stripped) = s.strip_prefix('&') {
142 format!(".any(|x| x == {})", stripped)
144 format!(".any(|x| x == *{})", s)
149 fn get_suggestion_text(&self) -> &'static str {
151 IterFunctionKind::IntoIter => {
152 "use the original Iterator instead of collecting it and then producing a new one"
154 IterFunctionKind::Len => {
155 "take the original Iterator's count instead of collecting it and finding the length"
157 IterFunctionKind::IsEmpty => {
158 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
160 IterFunctionKind::Contains(_) => {
161 "check if the original Iterator contains an element instead of collecting then checking"
166 enum IterFunctionKind {
173 struct IterFunctionVisitor {
174 uses: Vec<IterFunction>,
178 impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
179 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
180 // Check function calls on our collection
182 if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
183 if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }) = args.get(0);
184 if let &[name] = &path.segments;
185 if name.ident == self.target;
188 let is_empty = sym!(is_empty);
189 let contains = sym!(contains);
190 match method_name.ident.name {
191 sym::into_iter => self.uses.push(
192 IterFunction { func: IterFunctionKind::IntoIter, span: expr.span }
194 name if name == len => self.uses.push(
195 IterFunction { func: IterFunctionKind::Len, span: expr.span }
197 name if name == is_empty => self.uses.push(
198 IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
200 name if name == contains => self.uses.push(
201 IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
203 _ => self.seen_other = true,
208 // Check if the collection is used for anything else
210 if let Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. } = expr;
211 if let &[name] = &path.segments;
212 if name.ident == self.target;
214 self.seen_other = true;
216 walk_expr(self, expr);
221 type Map = Map<'tcx>;
222 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
223 NestedVisitorMap::None
227 struct UsedCountVisitor<'a, 'tcx> {
228 cx: &'a LateContext<'tcx>,
233 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
234 type Map = Map<'tcx>;
236 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
237 if path_to_local_id(expr, self.id) {
240 walk_expr(self, expr);
244 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
245 NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
249 /// Detect the occurrences of calls to `iter` or `into_iter` for the
251 fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> {
252 let mut visitor = IterFunctionVisitor {
257 visitor.visit_block(block);
258 if visitor.seen_other { None } else { Some(visitor.uses) }