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::{can_move_expr_to_closure, is_trait_method, path_to_local, path_to_local_id, CaptureKind};
7 use if_chain::if_chain;
8 use rustc_data_structures::fx::FxHashMap;
9 use rustc_errors::Applicability;
10 use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
11 use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind};
12 use rustc_lint::LateContext;
13 use rustc_middle::hir::map::Map;
14 use rustc_middle::ty::subst::GenericArgKind;
15 use rustc_middle::ty::{self, TyS};
17 use rustc_span::{MultiSpan, Span};
19 const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
21 pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
22 check_needless_collect_direct_usage(expr, cx);
23 check_needless_collect_indirect_usage(expr, cx);
25 fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
27 if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
28 if let ExprKind::MethodCall(chain_method, method0_span, _, _) = args[0].kind;
29 if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
31 let ty = cx.typeck_results().expr_ty(&args[0]);
32 let mut applicability = Applicability::MaybeIncorrect;
33 let is_empty_sugg = "next().is_none()".to_string();
34 let method_name = method.ident.name.as_str();
35 let sugg = if is_type_diagnostic_item(cx, ty, sym::Vec) ||
36 is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
37 is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
38 is_type_diagnostic_item(cx, ty, sym::BinaryHeap) {
40 "len" => "count()".to_string(),
41 "is_empty" => is_empty_sugg,
43 let contains_arg = snippet_with_applicability(cx, args[1].span, "??", &mut applicability);
44 let (arg, pred) = contains_arg
46 .map_or(("&x", &*contains_arg), |s| ("x", s));
47 format!("any(|{}| x == {})", arg, pred)
52 else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
53 is_type_diagnostic_item(cx, ty, sym::HashMap) {
55 "is_empty" => is_empty_sugg,
65 method0_span.with_hi(expr.span.hi()),
75 fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
76 if let ExprKind::Block(block, _) = expr.kind {
77 for stmt in block.stmts {
79 if let StmtKind::Local(local) = stmt.kind;
80 if let PatKind::Binding(_, id, ..) = local.pat.kind;
81 if let Some(init_expr) = local.init;
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 let ty = cx.typeck_results().expr_ty(init_expr);
85 if is_type_diagnostic_item(cx, ty, sym::Vec) ||
86 is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
87 is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
88 is_type_diagnostic_item(cx, ty, sym::LinkedList);
89 let iter_ty = cx.typeck_results().expr_ty(iter_source);
90 if let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty));
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());
106 span_lint_hir_and_then(
108 super::NEEDLESS_COLLECT,
111 NEEDLESS_COLLECT_MSG,
113 let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
114 diag.multipart_suggestion(
115 iter_call.get_suggestion_text(),
117 (stmt.span, String::new()),
118 (iter_call.span, iter_replacement)
120 Applicability::MaybeIncorrect,
130 struct IterFunction {
131 func: IterFunctionKind,
135 fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
137 IterFunctionKind::IntoIter => String::new(),
138 IterFunctionKind::Len => String::from(".count()"),
139 IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
140 IterFunctionKind::Contains(span) => {
141 let s = snippet(cx, *span, "..");
142 if let Some(stripped) = s.strip_prefix('&') {
143 format!(".any(|x| x == {})", stripped)
145 format!(".any(|x| x == *{})", s)
150 fn get_suggestion_text(&self) -> &'static str {
152 IterFunctionKind::IntoIter => {
153 "use the original Iterator instead of collecting it and then producing a new one"
155 IterFunctionKind::Len => {
156 "take the original Iterator's count instead of collecting it and finding the length"
158 IterFunctionKind::IsEmpty => {
159 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
161 IterFunctionKind::Contains(_) => {
162 "check if the original Iterator contains an element instead of collecting then checking"
167 enum IterFunctionKind {
174 struct IterFunctionVisitor<'a, 'tcx> {
175 illegal_mutable_capture_ids: HirIdSet,
176 current_mutably_captured_ids: HirIdSet,
177 cx: &'a LateContext<'tcx>,
178 uses: Vec<Option<IterFunction>>,
179 hir_id_uses_map: FxHashMap<HirId, usize>,
180 current_statement_hir_id: Option<HirId>,
184 impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
185 fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
186 for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) {
187 self.visit_block_expr(expr, hir_id);
189 if let Some(expr) = block.expr {
190 self.visit_block_expr(expr, None);
194 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
195 // Check function calls on our collection
196 if let ExprKind::MethodCall(method_name, _, [recv, args @ ..], _) = &expr.kind {
197 if method_name.ident.name == sym!(collect) && is_trait_method(self.cx, expr, sym::Iterator) {
198 self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));
199 self.visit_expr(recv);
203 if path_to_local_id(recv, self.target) {
205 .illegal_mutable_capture_ids
206 .intersection(&self.current_mutably_captured_ids)
210 if let Some(hir_id) = self.current_statement_hir_id {
211 self.hir_id_uses_map.insert(hir_id, self.uses.len());
213 match method_name.ident.name.as_str() {
214 "into_iter" => self.uses.push(Some(IterFunction {
215 func: IterFunctionKind::IntoIter,
218 "len" => self.uses.push(Some(IterFunction {
219 func: IterFunctionKind::Len,
222 "is_empty" => self.uses.push(Some(IterFunction {
223 func: IterFunctionKind::IsEmpty,
226 "contains" => self.uses.push(Some(IterFunction {
227 func: IterFunctionKind::Contains(args[0].span),
231 self.seen_other = true;
232 if let Some(hir_id) = self.current_statement_hir_id {
233 self.hir_id_uses_map.remove(&hir_id);
241 if let Some(hir_id) = path_to_local(recv) {
242 if let Some(index) = self.hir_id_uses_map.remove(&hir_id) {
244 .illegal_mutable_capture_ids
245 .intersection(&self.current_mutably_captured_ids)
249 if let Some(hir_id) = self.current_statement_hir_id {
250 self.hir_id_uses_map.insert(hir_id, index);
253 self.uses[index] = None;
258 // Check if the collection is used for anything else
259 if path_to_local_id(expr, self.target) {
260 self.seen_other = true;
262 walk_expr(self, expr);
266 type Map = Map<'tcx>;
267 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
268 NestedVisitorMap::None
272 impl<'tcx> IterFunctionVisitor<'_, 'tcx> {
273 fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option<HirId>) {
274 self.current_statement_hir_id = hir_id;
275 self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(expr));
276 self.visit_expr(expr);
280 fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>, Option<HirId>)> {
282 StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)),
283 StmtKind::Item(..) => None,
284 StmtKind::Local(Local { init, pat, .. }) => {
285 if let PatKind::Binding(_, hir_id, ..) = pat.kind {
286 init.map(|init_expr| (init_expr, Some(hir_id)))
288 init.map(|init_expr| (init_expr, None))
294 struct UsedCountVisitor<'a, 'tcx> {
295 cx: &'a LateContext<'tcx>,
300 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
301 type Map = Map<'tcx>;
303 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
304 if path_to_local_id(expr, self.id) {
307 walk_expr(self, expr);
311 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
312 NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
316 /// Detect the occurrences of calls to `iter` or `into_iter` for the
318 fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
319 block: &'tcx Block<'tcx>,
321 cx: &'a LateContext<'tcx>,
322 captured_ids: HirIdSet,
323 ) -> Option<Vec<IterFunction>> {
324 let mut visitor = IterFunctionVisitor {
329 current_mutably_captured_ids: HirIdSet::default(),
330 illegal_mutable_capture_ids: captured_ids,
331 hir_id_uses_map: FxHashMap::default(),
332 current_statement_hir_id: None,
334 visitor.visit_block(block);
335 if visitor.seen_other {
338 Some(visitor.uses.into_iter().flatten().collect())
342 fn get_captured_ids(cx: &LateContext<'_>, ty: &'_ TyS<'_>) -> HirIdSet {
343 fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: &'_ TyS<'_>, set: &mut HirIdSet) {
345 ty::Adt(_, generics) => {
346 for generic in *generics {
347 if let GenericArgKind::Type(ty) = generic.unpack() {
348 get_captured_ids_recursive(cx, ty, set);
352 ty::Closure(def_id, _) => {
353 let closure_hir_node = cx.tcx.hir().get_if_local(*def_id).unwrap();
354 if let Node::Expr(closure_expr) = closure_hir_node {
355 can_move_expr_to_closure(cx, closure_expr)
358 .for_each(|(hir_id, capture_kind)| {
359 if matches!(capture_kind, CaptureKind::Ref(Mutability::Mut)) {
369 let mut set = HirIdSet::default();
371 get_captured_ids_recursive(cx, ty, &mut set);