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