]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/loops/needless_collect.rs
New upstream version 1.54.0+dfsg1
[rustc.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, 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 mut applicability = Applicability::MachineApplicable;
32 let is_empty_sugg = "next().is_none()".to_string();
33 let method_name = &*method.ident.name.as_str();
34 let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
35 is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
36 is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
37 is_type_diagnostic_item(cx, ty, sym::BinaryHeap) {
38 match method_name {
39 "len" => "count()".to_string(),
40 "is_empty" => is_empty_sugg,
41 "contains" => {
42 let contains_arg = snippet_with_applicability(cx, args[1].span, "??", &mut applicability);
43 let (arg, pred) = contains_arg
44 .strip_prefix('&')
45 .map_or(("&x", &*contains_arg), |s| ("x", s));
46 format!("any(|{}| x == {})", arg, pred)
47 }
48 _ => return,
49 }
50 }
51 else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
52 is_type_diagnostic_item(cx, ty, sym::hashmap_type) {
53 match method_name {
54 "is_empty" => is_empty_sugg,
55 _ => return,
56 }
57 }
58 else {
59 return;
60 };
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,
69 );
70 }
71 }
72 }
73
74 fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
75 fn get_hir_id<'tcx>(ty: Option<&Ty<'tcx>>, method_args: Option<&GenericArgs<'tcx>>) -> Option<HirId> {
76 if let Some(ty) = ty {
77 return Some(ty.hir_id);
78 }
79
80 if let Some(generic_args) = method_args {
81 if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0) {
82 return Some(ty.hir_id);
83 }
84 }
85
86 None
87 }
88 if let ExprKind::Block(block, _) = expr.kind {
89 for stmt in block.stmts {
90 if_chain! {
91 if let StmtKind::Local(
92 Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
93 init: Some(init_expr), ty, .. }
94 ) = stmt.kind;
95 if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
96 if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
97 if let Some(hir_id) = get_hir_id(*ty, method_name.args);
98 if let Some(ty) = cx.typeck_results().node_type_opt(hir_id);
99 if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
100 is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
101 is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
102 is_type_diagnostic_item(cx, ty, sym::LinkedList);
103 if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
104 if let [iter_call] = &*iter_calls;
105 then {
106 let mut used_count_visitor = UsedCountVisitor {
107 cx,
108 id: *pat_id,
109 count: 0,
110 };
111 walk_block(&mut used_count_visitor, block);
112 if used_count_visitor.count > 1 {
113 return;
114 }
115
116 // Suggest replacing iter_call with iter_replacement, and removing stmt
117 let mut span = MultiSpan::from_span(collect_span);
118 span.push_span_label(iter_call.span, "the iterator could be used here instead".into());
119 span_lint_hir_and_then(
120 cx,
121 super::NEEDLESS_COLLECT,
122 init_expr.hir_id,
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 }