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