]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/while_let_on_iterator.rs
Merge commit 'dc5423ad448877e33cca28db2f1445c9c4473c75' into clippyup
[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, 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<'tcx>(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<'tcx>(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<'tcx>(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<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> {
214         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
215             if self.uses_iter {
216                 // return
217             } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
218                 self.uses_iter = true;
219             } else if let (e, true) = skip_fields_and_path(e) {
220                 if let Some(e) = e {
221                     self.visit_expr(e);
222                 }
223             } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
224                 if is_res_used(self.cx, self.iter_expr.path, id) {
225                     self.uses_iter = true;
226                 }
227             } else {
228                 walk_expr(self, e);
229             }
230         }
231     }
232
233     let mut v = V {
234         cx,
235         iter_expr,
236         uses_iter: false,
237     };
238     v.visit_expr(container);
239     v.uses_iter
240 }
241
242 #[allow(clippy::too_many_lines)]
243 fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &Expr<'_>) -> bool {
244     struct AfterLoopVisitor<'a, 'b, 'tcx> {
245         cx: &'a LateContext<'tcx>,
246         iter_expr: &'b IterExpr,
247         loop_id: HirId,
248         after_loop: bool,
249         used_iter: bool,
250     }
251     impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
252         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
253             if self.used_iter {
254                 return;
255             }
256             if self.after_loop {
257                 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
258                     self.used_iter = true;
259                 } else if let (e, true) = skip_fields_and_path(e) {
260                     if let Some(e) = e {
261                         self.visit_expr(e);
262                     }
263                 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
264                     self.used_iter = is_res_used(self.cx, self.iter_expr.path, id);
265                 } else {
266                     walk_expr(self, e);
267                 }
268             } else if self.loop_id == e.hir_id {
269                 self.after_loop = true;
270             } else {
271                 walk_expr(self, e);
272             }
273         }
274     }
275
276     struct NestedLoopVisitor<'a, 'b, 'tcx> {
277         cx: &'a LateContext<'tcx>,
278         iter_expr: &'b IterExpr,
279         local_id: HirId,
280         loop_id: HirId,
281         after_loop: bool,
282         found_local: bool,
283         used_after: bool,
284     }
285     impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
286         fn visit_local(&mut self, l: &'tcx Local<'_>) {
287             if !self.after_loop {
288                 l.pat.each_binding_or_first(&mut |_, id, _, _| {
289                     if id == self.local_id {
290                         self.found_local = true;
291                     }
292                 });
293             }
294             if let Some(e) = l.init {
295                 self.visit_expr(e);
296             }
297         }
298
299         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
300             if self.used_after {
301                 return;
302             }
303             if self.after_loop {
304                 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
305                     self.used_after = true;
306                 } else if let (e, true) = skip_fields_and_path(e) {
307                     if let Some(e) = e {
308                         self.visit_expr(e);
309                     }
310                 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
311                     self.used_after = is_res_used(self.cx, self.iter_expr.path, id);
312                 } else {
313                     walk_expr(self, e);
314                 }
315             } else if e.hir_id == self.loop_id {
316                 self.after_loop = true;
317             } else {
318                 walk_expr(self, e);
319             }
320         }
321     }
322
323     if let Some(e) = get_enclosing_loop_or_closure(cx.tcx, loop_expr) {
324         // The iterator expression will be used on the next iteration (for loops), or on the next call (for
325         // closures) unless it is declared within the enclosing expression. TODO: Check for closures
326         // used where an `FnOnce` type is expected.
327         let local_id = match iter_expr.path {
328             Res::Local(id) => id,
329             _ => return true,
330         };
331         let mut v = NestedLoopVisitor {
332             cx,
333             iter_expr,
334             local_id,
335             loop_id: loop_expr.hir_id,
336             after_loop: false,
337             found_local: false,
338             used_after: false,
339         };
340         v.visit_expr(e);
341         v.used_after || !v.found_local
342     } else {
343         let mut v = AfterLoopVisitor {
344             cx,
345             iter_expr,
346             loop_id: loop_expr.hir_id,
347             after_loop: false,
348             used_iter: false,
349         };
350         v.visit_expr(&cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);
351         v.used_iter
352     }
353 }