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