]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/needless_collect.rs
Auto merge of #7039 - phansch:melt-ice, r=flip1995
[rust.git] / clippy_lints / src / loops / needless_collect.rs
1 use super::NEEDLESS_COLLECT;
2 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
3 use clippy_utils::source::snippet;
4 use clippy_utils::sugg::Sugg;
5 use clippy_utils::ty::{is_type_diagnostic_item, match_type};
6 use clippy_utils::{is_trait_method, path_to_local_id, paths};
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::symbol::{sym, Ident};
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         if let Some(generic_args) = chain_method.args;
28         if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
29         let ty = cx.typeck_results().node_type(ty.hir_id);
30         if is_type_diagnostic_item(cx, ty, sym::vec_type)
31             || is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
32             || match_type(cx, ty, &paths::BTREEMAP)
33             || is_type_diagnostic_item(cx, ty, sym::hashmap_type);
34         if let Some(sugg) = match &*method.ident.name.as_str() {
35             "len" => Some("count()".to_string()),
36             "is_empty" => Some("next().is_none()".to_string()),
37             "contains" => {
38                 let contains_arg = snippet(cx, args[1].span, "??");
39                 let (arg, pred) = contains_arg
40                     .strip_prefix('&')
41                     .map_or(("&x", &*contains_arg), |s| ("x", s));
42                 Some(format!("any(|{}| x == {})", arg, pred))
43             }
44             _ => None,
45         };
46         then {
47             span_lint_and_sugg(
48                 cx,
49                 NEEDLESS_COLLECT,
50                 method0_span.with_hi(expr.span.hi()),
51                 NEEDLESS_COLLECT_MSG,
52                 "replace with",
53                 sugg,
54                 Applicability::MachineApplicable,
55             );
56         }
57     }
58 }
59
60 fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
61     if let ExprKind::Block(block, _) = expr.kind {
62         for stmt in block.stmts {
63             if_chain! {
64                 if let StmtKind::Local(
65                     Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
66                     init: Some(init_expr), .. }
67                 ) = stmt.kind;
68                 if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
69                 if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
70                 if let Some(generic_args) = method_name.args;
71                 if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
72                 if let ty = cx.typeck_results().node_type(ty.hir_id);
73                 if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
74                     is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
75                     match_type(cx, ty, &paths::LINKED_LIST);
76                 if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
77                 if let [iter_call] = &*iter_calls;
78                 then {
79                     let mut used_count_visitor = UsedCountVisitor {
80                         cx,
81                         id: *pat_id,
82                         count: 0,
83                     };
84                     walk_block(&mut used_count_visitor, block);
85                     if used_count_visitor.count > 1 {
86                         return;
87                     }
88
89                     // Suggest replacing iter_call with iter_replacement, and removing stmt
90                     let mut span = MultiSpan::from_span(collect_span);
91                     span.push_span_label(iter_call.span, "the iterator could be used here instead".into());
92                     span_lint_and_then(
93                         cx,
94                         super::NEEDLESS_COLLECT,
95                         span,
96                         NEEDLESS_COLLECT_MSG,
97                         |diag| {
98                             let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
99                             diag.multipart_suggestion(
100                                 iter_call.get_suggestion_text(),
101                                 vec![
102                                     (stmt.span, String::new()),
103                                     (iter_call.span, iter_replacement)
104                                 ],
105                                 Applicability::MachineApplicable,// MaybeIncorrect,
106                             );
107                         },
108                     );
109                 }
110             }
111         }
112     }
113 }
114
115 struct IterFunction {
116     func: IterFunctionKind,
117     span: Span,
118 }
119 impl IterFunction {
120     fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
121         match &self.func {
122             IterFunctionKind::IntoIter => String::new(),
123             IterFunctionKind::Len => String::from(".count()"),
124             IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
125             IterFunctionKind::Contains(span) => {
126                 let s = snippet(cx, *span, "..");
127                 if let Some(stripped) = s.strip_prefix('&') {
128                     format!(".any(|x| x == {})", stripped)
129                 } else {
130                     format!(".any(|x| x == *{})", s)
131                 }
132             },
133         }
134     }
135     fn get_suggestion_text(&self) -> &'static str {
136         match &self.func {
137             IterFunctionKind::IntoIter => {
138                 "use the original Iterator instead of collecting it and then producing a new one"
139             },
140             IterFunctionKind::Len => {
141                 "take the original Iterator's count instead of collecting it and finding the length"
142             },
143             IterFunctionKind::IsEmpty => {
144                 "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
145             },
146             IterFunctionKind::Contains(_) => {
147                 "check if the original Iterator contains an element instead of collecting then checking"
148             },
149         }
150     }
151 }
152 enum IterFunctionKind {
153     IntoIter,
154     Len,
155     IsEmpty,
156     Contains(Span),
157 }
158
159 struct IterFunctionVisitor {
160     uses: Vec<IterFunction>,
161     seen_other: bool,
162     target: Ident,
163 }
164 impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
165     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
166         // Check function calls on our collection
167         if_chain! {
168             if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
169             if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }) = args.get(0);
170             if let &[name] = &path.segments;
171             if name.ident == self.target;
172             then {
173                 let len = sym!(len);
174                 let is_empty = sym!(is_empty);
175                 let contains = sym!(contains);
176                 match method_name.ident.name {
177                     sym::into_iter => self.uses.push(
178                         IterFunction { func: IterFunctionKind::IntoIter, span: expr.span }
179                     ),
180                     name if name == len => self.uses.push(
181                         IterFunction { func: IterFunctionKind::Len, span: expr.span }
182                     ),
183                     name if name == is_empty => self.uses.push(
184                         IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
185                     ),
186                     name if name == contains => self.uses.push(
187                         IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
188                     ),
189                     _ => self.seen_other = true,
190                 }
191                 return
192             }
193         }
194         // Check if the collection is used for anything else
195         if_chain! {
196             if let Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. } = expr;
197             if let &[name] = &path.segments;
198             if name.ident == self.target;
199             then {
200                 self.seen_other = true;
201             } else {
202                 walk_expr(self, expr);
203             }
204         }
205     }
206
207     type Map = Map<'tcx>;
208     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
209         NestedVisitorMap::None
210     }
211 }
212
213 struct UsedCountVisitor<'a, 'tcx> {
214     cx: &'a LateContext<'tcx>,
215     id: HirId,
216     count: usize,
217 }
218
219 impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
220     type Map = Map<'tcx>;
221
222     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
223         if path_to_local_id(expr, self.id) {
224             self.count += 1;
225         } else {
226             walk_expr(self, expr);
227         }
228     }
229
230     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
231         NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
232     }
233 }
234
235 /// Detect the occurrences of calls to `iter` or `into_iter` for the
236 /// given identifier
237 fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> {
238     let mut visitor = IterFunctionVisitor {
239         uses: Vec::new(),
240         target: identifier,
241         seen_other: false,
242     };
243     visitor.visit_block(block);
244     if visitor.seen_other { None } else { Some(visitor.uses) }
245 }