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