]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/while_let_on_iterator.rs
Auto merge of #7089 - Jarcho:multiple_impls_generic, r=Jarcho
[rust.git] / clippy_lints / src / loops / while_let_on_iterator.rs
1 use super::WHILE_LET_ON_ITERATOR;
2 use clippy_utils::diagnostics::span_lint_and_sugg;
3 use clippy_utils::source::snippet_with_applicability;
4 use clippy_utils::{get_enclosing_loop, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used};
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor};
8 use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, MatchSource, Node, PatKind, QPath, UnOp};
9 use rustc_lint::LateContext;
10 use rustc_span::{symbol::sym, Span, Symbol};
11
12 pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
13     let (scrutinee_expr, iter_expr, some_pat, loop_expr) = if_chain! {
14         if let ExprKind::Match(scrutinee_expr, [arm, _], MatchSource::WhileLetDesugar) = expr.kind;
15         // check for `Some(..)` pattern
16         if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = arm.pat.kind;
17         if let Res::Def(_, pat_did) = pat_path.res;
18         if match_def_path(cx, pat_did, &paths::OPTION_SOME);
19         // check for call to `Iterator::next`
20         if let ExprKind::MethodCall(method_name, _, [iter_expr], _) = scrutinee_expr.kind;
21         if method_name.ident.name == sym::next;
22         if is_trait_method(cx, scrutinee_expr, sym::Iterator);
23         if let Some(iter_expr) = try_parse_iter_expr(cx, iter_expr);
24         // get the loop containing the match expression
25         if let Some((_, Node::Expr(loop_expr))) = cx.tcx.hir().parent_iter(expr.hir_id).nth(1);
26         if !uses_iter(cx, &iter_expr, arm.body);
27         then {
28             (scrutinee_expr, iter_expr, some_pat, loop_expr)
29         } else {
30             return;
31         }
32     };
33
34     let mut applicability = Applicability::MachineApplicable;
35     let loop_var = if let Some(some_pat) = some_pat.first() {
36         if is_refutable(cx, some_pat) {
37             // Refutable patterns don't work with for loops.
38             return;
39         }
40         snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
41     } else {
42         "_".into()
43     };
44
45     // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
46     // borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
47     // afterwards a mutable borrow of a field isn't necessary.
48     let ref_mut = if !iter_expr.fields.is_empty() || needs_mutable_borrow(cx, &iter_expr, loop_expr) {
49         "&mut "
50     } else {
51         ""
52     };
53
54     let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
55     span_lint_and_sugg(
56         cx,
57         WHILE_LET_ON_ITERATOR,
58         expr.span.with_hi(scrutinee_expr.span.hi()),
59         "this loop could be written as a `for` loop",
60         "try",
61         format!("for {} in {}{}", loop_var, ref_mut, iterator),
62         applicability,
63     );
64 }
65
66 #[derive(Debug)]
67 struct IterExpr {
68     /// The span of the whole expression, not just the path and fields stored here.
69     span: Span,
70     /// The fields used, in order of child to parent.
71     fields: Vec<Symbol>,
72     /// The path being used.
73     path: Res,
74 }
75 /// Parses any expression to find out which field of which variable is used. Will return `None` if
76 /// the expression might have side effects.
77 fn try_parse_iter_expr(cx: &LateContext<'_>, mut e: &Expr<'_>) -> Option<IterExpr> {
78     let span = e.span;
79     let mut fields = Vec::new();
80     loop {
81         match e.kind {
82             ExprKind::Path(ref path) => {
83                 break Some(IterExpr {
84                     span,
85                     fields,
86                     path: cx.qpath_res(path, e.hir_id),
87                 });
88             },
89             ExprKind::Field(base, name) => {
90                 fields.push(name.name);
91                 e = base;
92             },
93             // Dereferencing a pointer has no side effects and doesn't affect which field is being used.
94             ExprKind::Unary(UnOp::Deref, base) if cx.typeck_results().expr_ty(base).is_ref() => e = base,
95
96             // Shouldn't have side effects, but there's no way to trace which field is used. So forget which fields have
97             // already been seen.
98             ExprKind::Index(base, idx) if !idx.can_have_side_effects() => {
99                 fields.clear();
100                 e = base;
101             },
102             ExprKind::Unary(UnOp::Deref, base) => {
103                 fields.clear();
104                 e = base;
105             },
106
107             // No effect and doesn't affect which field is being used.
108             ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _) => e = base,
109             _ => break None,
110         }
111     }
112 }
113
114 fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symbol], path_res: Res) -> bool {
115     loop {
116         match (&e.kind, fields) {
117             (&ExprKind::Field(base, name), [head_field, tail_fields @ ..]) if name.name == *head_field => {
118                 e = base;
119                 fields = tail_fields;
120             },
121             (ExprKind::Path(path), []) => {
122                 break cx.qpath_res(path, e.hir_id) == path_res;
123             },
124             (&(ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _)), _) => e = base,
125             _ => break false,
126         }
127     }
128 }
129
130 /// Checks if the given expression is the same field as, is a child of, or is the parent of the
131 /// given field. Used to check if the expression can be used while the given field is borrowed
132 /// mutably. e.g. if checking for `x.y`, then `x.y`, `x.y.z`, and `x` will all return true, but
133 /// `x.z`, and `y` will return false.
134 fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fields: &[Symbol], path_res: Res) -> bool {
135     match expr.kind {
136         ExprKind::Field(base, name) => {
137             if let Some((head_field, tail_fields)) = fields.split_first() {
138                 if name.name == *head_field && is_expr_same_field(cx, base, fields, path_res) {
139                     return true;
140                 }
141                 // Check if the expression is a parent field
142                 let mut fields_iter = tail_fields.iter();
143                 while let Some(field) = fields_iter.next() {
144                     if *field == name.name && is_expr_same_field(cx, base, fields_iter.as_slice(), path_res) {
145                         return true;
146                     }
147                 }
148             }
149
150             // Check if the expression is a child field.
151             let mut e = base;
152             loop {
153                 match e.kind {
154                     ExprKind::Field(..) if is_expr_same_field(cx, e, fields, path_res) => break true,
155                     ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
156                     ExprKind::Path(ref path) if fields.is_empty() => {
157                         break cx.qpath_res(path, e.hir_id) == path_res;
158                     },
159                     _ => break false,
160                 }
161             }
162         },
163         // If the path matches, this is either an exact match, or the expression is a parent of the field.
164         ExprKind::Path(ref path) => cx.qpath_res(path, expr.hir_id) == path_res,
165         ExprKind::DropTemps(base) | ExprKind::Type(base, _) | ExprKind::AddrOf(_, _, base) => {
166             is_expr_same_child_or_parent_field(cx, base, fields, path_res)
167         },
168         _ => false,
169     }
170 }
171
172 /// Strips off all field and path expressions. This will return true if a field or path has been
173 /// skipped. Used to skip them after failing to check for equality.
174 fn skip_fields_and_path(expr: &'tcx Expr<'_>) -> (Option<&'tcx Expr<'tcx>>, bool) {
175     let mut e = expr;
176     let e = loop {
177         match e.kind {
178             ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
179             ExprKind::Path(_) => return (None, true),
180             _ => break e,
181         }
182     };
183     (Some(e), e.hir_id != expr.hir_id)
184 }
185
186 /// Checks if the given expression uses the iterator.
187 fn uses_iter(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tcx Expr<'_>) -> bool {
188     struct V<'a, 'b, 'tcx> {
189         cx: &'a LateContext<'tcx>,
190         iter_expr: &'b IterExpr,
191         uses_iter: bool,
192     }
193     impl Visitor<'tcx> for V<'_, '_, 'tcx> {
194         type Map = ErasedMap<'tcx>;
195         fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
196             NestedVisitorMap::None
197         }
198
199         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
200             if self.uses_iter {
201                 // return
202             } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
203                 self.uses_iter = true;
204             } else if let (e, true) = skip_fields_and_path(e) {
205                 if let Some(e) = e {
206                     self.visit_expr(e);
207                 }
208             } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
209                 if is_res_used(self.cx, self.iter_expr.path, id) {
210                     self.uses_iter = true;
211                 }
212             } else {
213                 walk_expr(self, e);
214             }
215         }
216     }
217
218     let mut v = V {
219         cx,
220         iter_expr,
221         uses_iter: false,
222     };
223     v.visit_expr(container);
224     v.uses_iter
225 }
226
227 #[allow(clippy::too_many_lines)]
228 fn needs_mutable_borrow(cx: &LateContext<'tcx>, iter_expr: &IterExpr, loop_expr: &'tcx Expr<'_>) -> bool {
229     struct AfterLoopVisitor<'a, 'b, 'tcx> {
230         cx: &'a LateContext<'tcx>,
231         iter_expr: &'b IterExpr,
232         loop_id: HirId,
233         after_loop: bool,
234         used_iter: bool,
235     }
236     impl Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
237         type Map = ErasedMap<'tcx>;
238         fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
239             NestedVisitorMap::None
240         }
241
242         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
243             if self.used_iter {
244                 return;
245             }
246             if self.after_loop {
247                 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
248                     self.used_iter = true;
249                 } else if let (e, true) = skip_fields_and_path(e) {
250                     if let Some(e) = e {
251                         self.visit_expr(e);
252                     }
253                 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
254                     self.used_iter = is_res_used(self.cx, self.iter_expr.path, id);
255                 } else {
256                     walk_expr(self, e);
257                 }
258             } else if self.loop_id == e.hir_id {
259                 self.after_loop = true;
260             } else {
261                 walk_expr(self, e);
262             }
263         }
264     }
265
266     struct NestedLoopVisitor<'a, 'b, 'tcx> {
267         cx: &'a LateContext<'tcx>,
268         iter_expr: &'b IterExpr,
269         local_id: HirId,
270         loop_id: HirId,
271         after_loop: bool,
272         found_local: bool,
273         used_after: bool,
274     }
275     impl Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
276         type Map = ErasedMap<'tcx>;
277         fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
278             NestedVisitorMap::None
279         }
280
281         fn visit_local(&mut self, l: &'tcx Local<'_>) {
282             if !self.after_loop {
283                 l.pat.each_binding_or_first(&mut |_, id, _, _| {
284                     if id == self.local_id {
285                         self.found_local = true;
286                     }
287                 });
288             }
289             if let Some(e) = l.init {
290                 self.visit_expr(e);
291             }
292         }
293
294         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
295             if self.used_after {
296                 return;
297             }
298             if self.after_loop {
299                 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
300                     self.used_after = true;
301                 } else if let (e, true) = skip_fields_and_path(e) {
302                     if let Some(e) = e {
303                         self.visit_expr(e);
304                     }
305                 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
306                     self.used_after = is_res_used(self.cx, self.iter_expr.path, id);
307                 } else {
308                     walk_expr(self, e);
309                 }
310             } else if e.hir_id == self.loop_id {
311                 self.after_loop = true;
312             } else {
313                 walk_expr(self, e);
314             }
315         }
316     }
317
318     if let Some(e) = get_enclosing_loop(cx.tcx, loop_expr) {
319         // The iterator expression will be used on the next iteration unless it is declared within the outer
320         // loop.
321         let local_id = match iter_expr.path {
322             Res::Local(id) => id,
323             _ => return true,
324         };
325         let mut v = NestedLoopVisitor {
326             cx,
327             iter_expr,
328             local_id,
329             loop_id: loop_expr.hir_id,
330             after_loop: false,
331             found_local: false,
332             used_after: false,
333         };
334         v.visit_expr(e);
335         v.used_after || !v.found_local
336     } else {
337         let mut v = AfterLoopVisitor {
338             cx,
339             iter_expr,
340             loop_id: loop_expr.hir_id,
341             after_loop: false,
342             used_iter: false,
343         };
344         v.visit_expr(&cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);
345         v.used_iter
346     }
347 }