]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/needless_collect.rs
Auto merge of #6828 - mgacek8:issue_6758_enhance_wrong_self_convention, r=flip1995
[rust.git] / clippy_lints / src / loops / needless_collect.rs
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};
13
14 const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
15
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);
19 }
20 fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
21     if_chain! {
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);
27         then {
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);
35                     span_lint_and_sugg(
36                         cx,
37                         NEEDLESS_COLLECT,
38                         span,
39                         NEEDLESS_COLLECT_MSG,
40                         "replace with",
41                         "count()".to_string(),
42                         Applicability::MachineApplicable,
43                     );
44                 }
45                 if method.ident.name == sym!(is_empty) {
46                     let span = shorten_needless_collect_span(expr);
47                     span_lint_and_sugg(
48                         cx,
49                         NEEDLESS_COLLECT,
50                         span,
51                         NEEDLESS_COLLECT_MSG,
52                         "replace with",
53                         "next().is_none()".to_string(),
54                         Applicability::MachineApplicable,
55                     );
56                 }
57                 if method.ident.name == sym!(contains) {
58                     let contains_arg = snippet(cx, args[1].span, "??");
59                     let span = shorten_needless_collect_span(expr);
60                     span_lint_and_then(
61                         cx,
62                         NEEDLESS_COLLECT,
63                         span,
64                         NEEDLESS_COLLECT_MSG,
65                         |diag| {
66                             let (arg, pred) = contains_arg
67                                     .strip_prefix('&')
68                                     .map_or(("&x", &*contains_arg), |s| ("x", s));
69                             diag.span_suggestion(
70                                 span,
71                                 "replace with",
72                                 format!(
73                                     "any(|{}| x == {})",
74                                     arg, pred
75                                 ),
76                                 Applicability::MachineApplicable,
77                             );
78                         }
79                     );
80                 }
81             }
82         }
83     }
84 }
85
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 {
89             if_chain! {
90                 if let StmtKind::Local(
91                     Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
92                     init: Some(ref init_expr), .. }
93                 ) = stmt.kind;
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;
104                 then {
105                     let mut used_count_visitor = UsedCountVisitor {
106                         cx,
107                         id: *pat_id,
108                         count: 0,
109                     };
110                     walk_block(&mut used_count_visitor, block);
111                     if used_count_visitor.count > 1 {
112                         return;
113                     }
114
115                     // Suggest replacing iter_call with iter_replacement, and removing stmt
116                     let iter_call = &iter_calls[0];
117                     span_lint_and_then(
118                         cx,
119                         super::NEEDLESS_COLLECT,
120                         stmt.span.until(iter_call.span),
121                         NEEDLESS_COLLECT_MSG,
122                         |diag| {
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(),
126                                 vec![
127                                     (stmt.span, String::new()),
128                                     (iter_call.span, iter_replacement)
129                                 ],
130                                 Applicability::MachineApplicable,// MaybeIncorrect,
131                             ).emit();
132                         },
133                     );
134                 }
135             }
136         }
137     }
138 }
139
140 struct IterFunction {
141     func: IterFunctionKind,
142     span: Span,
143 }
144 impl IterFunction {
145     fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
146         match &self.func {
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)
154                 } else {
155                     format!(".any(|x| x == *{})", s)
156                 }
157             },
158         }
159     }
160     fn get_suggestion_text(&self) -> &'static str {
161         match &self.func {
162             IterFunctionKind::IntoIter => {
163                 "use the original Iterator instead of collecting it and then producing a new one"
164             },
165             IterFunctionKind::Len => {
166                 "take the original Iterator's count instead of collecting it and finding the length"
167             },
168             IterFunctionKind::IsEmpty => {
169                 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
170             },
171             IterFunctionKind::Contains(_) => {
172                 "check if the original Iterator contains an element instead of collecting then checking"
173             },
174         }
175     }
176 }
177 enum IterFunctionKind {
178     IntoIter,
179     Len,
180     IsEmpty,
181     Contains(Span),
182 }
183
184 struct IterFunctionVisitor {
185     uses: Vec<IterFunction>,
186     seen_other: bool,
187     target: Ident,
188 }
189 impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
190     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
191         // Check function calls on our collection
192         if_chain! {
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;
197             then {
198                 let len = sym!(len);
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 }
204                     ),
205                     name if name == len => self.uses.push(
206                         IterFunction { func: IterFunctionKind::Len, span: expr.span }
207                     ),
208                     name if name == is_empty => self.uses.push(
209                         IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
210                     ),
211                     name if name == contains => self.uses.push(
212                         IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
213                     ),
214                     _ => self.seen_other = true,
215                 }
216                 return
217             }
218         }
219         // Check if the collection is used for anything else
220         if_chain! {
221             if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr;
222             if let &[name] = &path.segments;
223             if name.ident == self.target;
224             then {
225                 self.seen_other = true;
226             } else {
227                 walk_expr(self, expr);
228             }
229         }
230     }
231
232     type Map = Map<'tcx>;
233     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
234         NestedVisitorMap::None
235     }
236 }
237
238 struct UsedCountVisitor<'a, 'tcx> {
239     cx: &'a LateContext<'tcx>,
240     id: HirId,
241     count: usize,
242 }
243
244 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
245     type Map = Map<'tcx>;
246
247     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
248         if path_to_local_id(expr, self.id) {
249             self.count += 1;
250         } else {
251             walk_expr(self, expr);
252         }
253     }
254
255     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
256         NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
257     }
258 }
259
260 /// Detect the occurrences of calls to `iter` or `into_iter` for the
261 /// given identifier
262 fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> {
263     let mut visitor = IterFunctionVisitor {
264         uses: Vec::new(),
265         target: identifier,
266         seen_other: false,
267     };
268     visitor.visit_block(block);
269     if visitor.seen_other { None } else { Some(visitor.uses) }
270 }
271
272 fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span {
273     if_chain! {
274         if let ExprKind::MethodCall(.., args, _) = &expr.kind;
275         if let ExprKind::MethodCall(_, span, ..) = &args[0].kind;
276         then {
277             return expr.span.with_lo(span.lo());
278         }
279     }
280     unreachable!();
281 }