]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs
153f97e4e66c88ffe334d220c0d8425cf6589796
[rust.git] / src / tools / clippy / 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_multi_call_closure, is_refutable, is_res_lang_ctor, is_trait_method, 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, Closure, Expr, ExprKind, HirId, LangItem, Local, Mutability, PatKind, UnOp};
12 use rustc_lint::LateContext;
13 use rustc_middle::hir::nested_filter::OnlyBodies;
14 use rustc_middle::ty::adjustment::Adjust;
15 use rustc_span::{symbol::sym, Symbol};
16
17 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
18     let (scrutinee_expr, iter_expr_struct, iter_expr, some_pat, loop_expr) = if_chain! {
19         if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
20         // check for `Some(..)` pattern
21         if let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind;
22         if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
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 {loop_var} in {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(&Closure { body: 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 #[expect(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         type NestedFilter = OnlyBodies;
253         fn nested_visit_map(&mut self) -> Self::Map {
254             self.cx.tcx.hir()
255         }
256
257         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
258             if self.used_iter {
259                 return;
260             }
261             if self.after_loop {
262                 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
263                     self.used_iter = true;
264                 } else if let (e, true) = skip_fields_and_path(e) {
265                     if let Some(e) = e {
266                         self.visit_expr(e);
267                     }
268                 } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind {
269                     self.used_iter = is_res_used(self.cx, self.iter_expr.path, id);
270                 } else {
271                     walk_expr(self, e);
272                 }
273             } else if self.loop_id == e.hir_id {
274                 self.after_loop = true;
275             } else {
276                 walk_expr(self, e);
277             }
278         }
279     }
280
281     struct NestedLoopVisitor<'a, 'b, 'tcx> {
282         cx: &'a LateContext<'tcx>,
283         iter_expr: &'b IterExpr,
284         local_id: HirId,
285         loop_id: HirId,
286         after_loop: bool,
287         found_local: bool,
288         used_after: bool,
289     }
290     impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
291         type NestedFilter = OnlyBodies;
292         fn nested_visit_map(&mut self) -> Self::Map {
293             self.cx.tcx.hir()
294         }
295
296         fn visit_local(&mut self, l: &'tcx Local<'_>) {
297             if !self.after_loop {
298                 l.pat.each_binding_or_first(&mut |_, id, _, _| {
299                     if id == self.local_id {
300                         self.found_local = true;
301                     }
302                 });
303             }
304             if let Some(e) = l.init {
305                 self.visit_expr(e);
306             }
307         }
308
309         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
310             if self.used_after {
311                 return;
312             }
313             if self.after_loop {
314                 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
315                     self.used_after = true;
316                 } else if let (e, true) = skip_fields_and_path(e) {
317                     if let Some(e) = e {
318                         self.visit_expr(e);
319                     }
320                 } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind {
321                     self.used_after = is_res_used(self.cx, self.iter_expr.path, id);
322                 } else {
323                     walk_expr(self, e);
324                 }
325             } else if e.hir_id == self.loop_id {
326                 self.after_loop = true;
327             } else {
328                 walk_expr(self, e);
329             }
330         }
331     }
332
333     if let Some(e) = get_enclosing_loop_or_multi_call_closure(cx, loop_expr) {
334         let local_id = match iter_expr.path {
335             Res::Local(id) => id,
336             _ => return true,
337         };
338         let mut v = NestedLoopVisitor {
339             cx,
340             iter_expr,
341             local_id,
342             loop_id: loop_expr.hir_id,
343             after_loop: false,
344             found_local: false,
345             used_after: false,
346         };
347         v.visit_expr(e);
348         v.used_after || !v.found_local
349     } else {
350         let mut v = AfterLoopVisitor {
351             cx,
352             iter_expr,
353             loop_id: loop_expr.hir_id,
354             after_loop: false,
355             used_iter: false,
356         };
357         v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);
358         v.used_iter
359     }
360 }