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