1 use super::NEEDLESS_COLLECT;
2 use crate::utils::sugg::Sugg;
4 is_type_diagnostic_item, match_trait_method, match_type, path_to_local_id, paths, snippet, span_lint_and_sugg,
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, HirId, Local, Pat, PatKind, QPath, StmtKind};
11 use rustc_lint::LateContext;
12 use rustc_middle::hir::map::Map;
13 use rustc_span::source_map::Span;
14 use rustc_span::symbol::{sym, Ident};
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(ref method, _, ref args, _) = expr.kind;
25 if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind;
26 if chain_method.ident.name == sym!(collect) && match_trait_method(cx, &args[0], &paths::ITERATOR);
27 if let Some(ref generic_args) = chain_method.args;
28 if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
30 let ty = cx.typeck_results().node_type(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 method.ident.name == sym!(len) {
36 let span = shorten_needless_collect_span(expr);
43 "count()".to_string(),
44 Applicability::MachineApplicable,
47 if method.ident.name == sym!(is_empty) {
48 let span = shorten_needless_collect_span(expr);
55 "next().is_none()".to_string(),
56 Applicability::MachineApplicable,
59 if method.ident.name == sym!(contains) {
60 let contains_arg = snippet(cx, args[1].span, "??");
61 let span = shorten_needless_collect_span(expr);
68 let (arg, pred) = contains_arg
70 .map_or(("&x", &*contains_arg), |s| ("x", s));
78 Applicability::MachineApplicable,
88 fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
89 if let ExprKind::Block(ref block, _) = expr.kind {
90 for ref stmt in block.stmts {
92 if let StmtKind::Local(
93 Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
94 init: Some(ref init_expr), .. }
96 if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind;
97 if method_name.ident.name == sym!(collect) && match_trait_method(cx, &init_expr, &paths::ITERATOR);
98 if let Some(ref generic_args) = method_name.args;
99 if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
100 if let ty = cx.typeck_results().node_type(ty.hir_id);
101 if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
102 is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
103 match_type(cx, ty, &paths::LINKED_LIST);
104 if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
105 if iter_calls.len() == 1;
107 let mut used_count_visitor = UsedCountVisitor {
112 walk_block(&mut used_count_visitor, block);
113 if used_count_visitor.count > 1 {
117 // Suggest replacing iter_call with iter_replacement, and removing stmt
118 let iter_call = &iter_calls[0];
121 super::NEEDLESS_COLLECT,
122 stmt.span.until(iter_call.span),
123 NEEDLESS_COLLECT_MSG,
125 let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
126 diag.multipart_suggestion(
127 iter_call.get_suggestion_text(),
129 (stmt.span, String::new()),
130 (iter_call.span, iter_replacement)
132 Applicability::MachineApplicable,// MaybeIncorrect,
142 struct IterFunction {
143 func: IterFunctionKind,
147 fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
149 IterFunctionKind::IntoIter => String::new(),
150 IterFunctionKind::Len => String::from(".count()"),
151 IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
152 IterFunctionKind::Contains(span) => {
153 let s = snippet(cx, *span, "..");
154 if let Some(stripped) = s.strip_prefix('&') {
155 format!(".any(|x| x == {})", stripped)
157 format!(".any(|x| x == *{})", s)
162 fn get_suggestion_text(&self) -> &'static str {
164 IterFunctionKind::IntoIter => {
165 "use the original Iterator instead of collecting it and then producing a new one"
167 IterFunctionKind::Len => {
168 "take the original Iterator's count instead of collecting it and finding the length"
170 IterFunctionKind::IsEmpty => {
171 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
173 IterFunctionKind::Contains(_) => {
174 "check if the original Iterator contains an element instead of collecting then checking"
179 enum IterFunctionKind {
186 struct IterFunctionVisitor {
187 uses: Vec<IterFunction>,
191 impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
192 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
193 // Check function calls on our collection
195 if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind;
196 if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0);
197 if let &[name] = &path.segments;
198 if name.ident == self.target;
201 let is_empty = sym!(is_empty);
202 let contains = sym!(contains);
203 match method_name.ident.name {
204 sym::into_iter => self.uses.push(
205 IterFunction { func: IterFunctionKind::IntoIter, span: expr.span }
207 name if name == len => self.uses.push(
208 IterFunction { func: IterFunctionKind::Len, span: expr.span }
210 name if name == is_empty => self.uses.push(
211 IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
213 name if name == contains => self.uses.push(
214 IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
216 _ => self.seen_other = true,
221 // Check if the collection is used for anything else
223 if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr;
224 if let &[name] = &path.segments;
225 if name.ident == self.target;
227 self.seen_other = true;
229 walk_expr(self, expr);
234 type Map = Map<'tcx>;
235 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
236 NestedVisitorMap::None
240 struct UsedCountVisitor<'a, 'tcx> {
241 cx: &'a LateContext<'tcx>,
246 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
247 type Map = Map<'tcx>;
249 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
250 if path_to_local_id(expr, self.id) {
253 walk_expr(self, expr);
257 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
258 NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
262 /// Detect the occurrences of calls to `iter` or `into_iter` for the
264 fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> {
265 let mut visitor = IterFunctionVisitor {
270 visitor.visit_block(block);
271 if visitor.seen_other { None } else { Some(visitor.uses) }
274 fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span {
276 if let ExprKind::MethodCall(.., args, _) = &expr.kind;
277 if let ExprKind::MethodCall(_, span, ..) = &args[0].kind;
279 return expr.span.with_lo(span.lo());