1 use super::NEEDLESS_COLLECT;
2 use crate::utils::sugg::Sugg;
3 use crate::utils::{is_trait_method, path_to_local_id, paths, snippet, span_lint_and_sugg, span_lint_and_then};
4 use clippy_utils::ty::{is_type_diagnostic_item, match_type};
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
8 use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind};
9 use rustc_lint::LateContext;
10 use rustc_middle::hir::map::Map;
11 use rustc_span::source_map::Span;
12 use rustc_span::symbol::{sym, Ident};
14 const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
16 pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
17 check_needless_collect_direct_usage(expr, cx);
18 check_needless_collect_indirect_usage(expr, cx);
20 fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
22 if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind;
23 if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind;
24 if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
25 if let Some(ref generic_args) = chain_method.args;
26 if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
28 let ty = cx.typeck_results().node_type(ty.hir_id);
29 if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
30 is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
31 match_type(cx, ty, &paths::BTREEMAP) ||
32 is_type_diagnostic_item(cx, ty, sym::hashmap_type) {
33 if method.ident.name == sym!(len) {
34 let span = shorten_needless_collect_span(expr);
41 "count()".to_string(),
42 Applicability::MachineApplicable,
45 if method.ident.name == sym!(is_empty) {
46 let span = shorten_needless_collect_span(expr);
53 "next().is_none()".to_string(),
54 Applicability::MachineApplicable,
57 if method.ident.name == sym!(contains) {
58 let contains_arg = snippet(cx, args[1].span, "??");
59 let span = shorten_needless_collect_span(expr);
66 let (arg, pred) = contains_arg
68 .map_or(("&x", &*contains_arg), |s| ("x", s));
76 Applicability::MachineApplicable,
86 fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
87 if let ExprKind::Block(ref block, _) = expr.kind {
88 for ref stmt in block.stmts {
90 if let StmtKind::Local(
91 Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
92 init: Some(ref init_expr), .. }
94 if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind;
95 if method_name.ident.name == sym!(collect) && is_trait_method(cx, &init_expr, sym::Iterator);
96 if let Some(ref generic_args) = method_name.args;
97 if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
98 if let ty = cx.typeck_results().node_type(ty.hir_id);
99 if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
100 is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
101 match_type(cx, ty, &paths::LINKED_LIST);
102 if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
103 if iter_calls.len() == 1;
105 let mut used_count_visitor = UsedCountVisitor {
110 walk_block(&mut used_count_visitor, block);
111 if used_count_visitor.count > 1 {
115 // Suggest replacing iter_call with iter_replacement, and removing stmt
116 let iter_call = &iter_calls[0];
119 super::NEEDLESS_COLLECT,
120 stmt.span.until(iter_call.span),
121 NEEDLESS_COLLECT_MSG,
123 let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
124 diag.multipart_suggestion(
125 iter_call.get_suggestion_text(),
127 (stmt.span, String::new()),
128 (iter_call.span, iter_replacement)
130 Applicability::MachineApplicable,// MaybeIncorrect,
140 struct IterFunction {
141 func: IterFunctionKind,
145 fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
147 IterFunctionKind::IntoIter => String::new(),
148 IterFunctionKind::Len => String::from(".count()"),
149 IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
150 IterFunctionKind::Contains(span) => {
151 let s = snippet(cx, *span, "..");
152 if let Some(stripped) = s.strip_prefix('&') {
153 format!(".any(|x| x == {})", stripped)
155 format!(".any(|x| x == *{})", s)
160 fn get_suggestion_text(&self) -> &'static str {
162 IterFunctionKind::IntoIter => {
163 "use the original Iterator instead of collecting it and then producing a new one"
165 IterFunctionKind::Len => {
166 "take the original Iterator's count instead of collecting it and finding the length"
168 IterFunctionKind::IsEmpty => {
169 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
171 IterFunctionKind::Contains(_) => {
172 "check if the original Iterator contains an element instead of collecting then checking"
177 enum IterFunctionKind {
184 struct IterFunctionVisitor {
185 uses: Vec<IterFunction>,
189 impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
190 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
191 // Check function calls on our collection
193 if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind;
194 if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0);
195 if let &[name] = &path.segments;
196 if name.ident == self.target;
199 let is_empty = sym!(is_empty);
200 let contains = sym!(contains);
201 match method_name.ident.name {
202 sym::into_iter => self.uses.push(
203 IterFunction { func: IterFunctionKind::IntoIter, span: expr.span }
205 name if name == len => self.uses.push(
206 IterFunction { func: IterFunctionKind::Len, span: expr.span }
208 name if name == is_empty => self.uses.push(
209 IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
211 name if name == contains => self.uses.push(
212 IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
214 _ => self.seen_other = true,
219 // Check if the collection is used for anything else
221 if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr;
222 if let &[name] = &path.segments;
223 if name.ident == self.target;
225 self.seen_other = true;
227 walk_expr(self, expr);
232 type Map = Map<'tcx>;
233 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
234 NestedVisitorMap::None
238 struct UsedCountVisitor<'a, 'tcx> {
239 cx: &'a LateContext<'tcx>,
244 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
245 type Map = Map<'tcx>;
247 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
248 if path_to_local_id(expr, self.id) {
251 walk_expr(self, expr);
255 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
256 NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
260 /// Detect the occurrences of calls to `iter` or `into_iter` for the
262 fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> {
263 let mut visitor = IterFunctionVisitor {
268 visitor.visit_block(block);
269 if visitor.seen_other { None } else { Some(visitor.uses) }
272 fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span {
274 if let ExprKind::MethodCall(.., args, _) = &expr.kind;
275 if let ExprKind::MethodCall(_, span, ..) = &args[0].kind;
277 return expr.span.with_lo(span.lo());