]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/loops/needless_collect.rs
Rollup merge of #89528 - FabianWolff:issue-89497, r=jackh726
[rust.git] / src / tools / clippy / clippy_lints / src / loops / needless_collect.rs
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;
13 use rustc_span::sym;
14 use rustc_span::{MultiSpan, Span};
15
16 const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
17
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);
21 }
22 fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
23     if_chain! {
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);
27         then {
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) {
36                 match method_name {
37                     "len" => "count()".to_string(),
38                     "is_empty" => is_empty_sugg,
39                     "contains" => {
40                         let contains_arg = snippet_with_applicability(cx, args[1].span, "??", &mut applicability);
41                         let (arg, pred) = contains_arg
42                             .strip_prefix('&')
43                             .map_or(("&x", &*contains_arg), |s| ("x", s));
44                         format!("any(|{}| x == {})", arg, pred)
45                     }
46                     _ => return,
47                 }
48             }
49             else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
50                 is_type_diagnostic_item(cx, ty, sym::HashMap) {
51                 match method_name {
52                     "is_empty" => is_empty_sugg,
53                     _ => return,
54                 }
55             }
56             else {
57                 return;
58             };
59             span_lint_and_sugg(
60                 cx,
61                 NEEDLESS_COLLECT,
62                 method0_span.with_hi(expr.span.hi()),
63                 NEEDLESS_COLLECT_MSG,
64                 "replace with",
65                 sugg,
66                 applicability,
67             );
68         }
69     }
70 }
71
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 {
75             if_chain! {
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;
88                 then {
89                     let mut used_count_visitor = UsedCountVisitor {
90                         cx,
91                         id,
92                         count: 0,
93                     };
94                     walk_block(&mut used_count_visitor, block);
95                     if used_count_visitor.count > 1 {
96                         return;
97                     }
98
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(
103                         cx,
104                         super::NEEDLESS_COLLECT,
105                         init_expr.hir_id,
106                         span,
107                         NEEDLESS_COLLECT_MSG,
108                         |diag| {
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(),
112                                 vec![
113                                     (stmt.span, String::new()),
114                                     (iter_call.span, iter_replacement)
115                                 ],
116                                 Applicability::MaybeIncorrect,
117                             );
118                         },
119                     );
120                 }
121             }
122         }
123     }
124 }
125
126 struct IterFunction {
127     func: IterFunctionKind,
128     span: Span,
129 }
130 impl IterFunction {
131     fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
132         match &self.func {
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)
140                 } else {
141                     format!(".any(|x| x == *{})", s)
142                 }
143             },
144         }
145     }
146     fn get_suggestion_text(&self) -> &'static str {
147         match &self.func {
148             IterFunctionKind::IntoIter => {
149                 "use the original Iterator instead of collecting it and then producing a new one"
150             },
151             IterFunctionKind::Len => {
152                 "take the original Iterator's count instead of collecting it and finding the length"
153             },
154             IterFunctionKind::IsEmpty => {
155                 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
156             },
157             IterFunctionKind::Contains(_) => {
158                 "check if the original Iterator contains an element instead of collecting then checking"
159             },
160         }
161     }
162 }
163 enum IterFunctionKind {
164     IntoIter,
165     Len,
166     IsEmpty,
167     Contains(Span),
168 }
169
170 struct IterFunctionVisitor {
171     uses: Vec<IterFunction>,
172     seen_other: bool,
173     target: HirId,
174 }
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,
183                         span: expr.span,
184                     }),
185                     "len" => self.uses.push(IterFunction {
186                         func: IterFunctionKind::Len,
187                         span: expr.span,
188                     }),
189                     "is_empty" => self.uses.push(IterFunction {
190                         func: IterFunctionKind::IsEmpty,
191                         span: expr.span,
192                     }),
193                     "contains" => self.uses.push(IterFunction {
194                         func: IterFunctionKind::Contains(args[0].span),
195                         span: expr.span,
196                     }),
197                     _ => self.seen_other = true,
198                 }
199                 return;
200             }
201         }
202         // Check if the collection is used for anything else
203         if path_to_local_id(expr, self.target) {
204             self.seen_other = true;
205         } else {
206             walk_expr(self, expr);
207         }
208     }
209
210     type Map = Map<'tcx>;
211     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
212         NestedVisitorMap::None
213     }
214 }
215
216 struct UsedCountVisitor<'a, 'tcx> {
217     cx: &'a LateContext<'tcx>,
218     id: HirId,
219     count: usize,
220 }
221
222 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
223     type Map = Map<'tcx>;
224
225     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
226         if path_to_local_id(expr, self.id) {
227             self.count += 1;
228         } else {
229             walk_expr(self, expr);
230         }
231     }
232
233     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
234         NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
235     }
236 }
237
238 /// Detect the occurrences of calls to `iter` or `into_iter` for the
239 /// given identifier
240 fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, id: HirId) -> Option<Vec<IterFunction>> {
241     let mut visitor = IterFunctionVisitor {
242         uses: Vec::new(),
243         target: id,
244         seen_other: false,
245     };
246     visitor.visit_block(block);
247     if visitor.seen_other { None } else { Some(visitor.uses) }
248 }