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