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;
6 get_enclosing_loop_or_multi_call_closure, is_refutable, is_res_lang_ctor, is_trait_method, visitors::is_res_used,
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};
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);
31 (let_expr, iter_expr_struct, iter_expr, some_pat, expr)
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.
43 snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
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)
61 let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
64 WHILE_LET_ON_ITERATOR,
65 expr.span.with_hi(scrutinee_expr.span.hi()),
66 "this loop could be written as a `for` loop",
68 format!("for {loop_var} in {iterator}{by_ref}"),
75 /// The fields used, in order of child to parent.
77 /// The path being used.
79 /// Whether or not the iterator can be moved.
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;
93 .any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
95 // Custom deref impls need to borrow the whole value as it's captured by reference
100 ExprKind::Path(ref path) => {
101 break Some(IterExpr {
103 path: cx.qpath_res(path, e.hir_id),
107 ExprKind::Field(base, name) => {
108 fields.push(name.name);
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,
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() => {
121 ExprKind::Unary(UnOp::Deref, base) => {
127 // No effect and doesn't affect which field is being used.
128 ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _) => e = base,
134 fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symbol], path_res: Res) -> bool {
136 match (&e.kind, fields) {
137 (&ExprKind::Field(base, name), [head_field, tail_fields @ ..]) if name.name == *head_field => {
139 fields = tail_fields;
141 (ExprKind::Path(path), []) => {
142 break cx.qpath_res(path, e.hir_id) == path_res;
144 (&(ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _)), _) => e = base,
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 {
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) {
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) {
170 // Check if the expression is a child field.
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;
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)
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) {
198 ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
199 ExprKind::Path(_) => return (None, true),
203 (Some(e), e.hir_id != expr.hir_id)
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,
213 impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> {
214 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
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) {
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;
238 v.visit_expr(container);
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,
251 impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
252 type NestedFilter = OnlyBodies;
253 fn nested_visit_map(&mut self) -> Self::Map {
257 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
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) {
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);
273 } else if self.loop_id == e.hir_id {
274 self.after_loop = true;
281 struct NestedLoopVisitor<'a, 'b, 'tcx> {
282 cx: &'a LateContext<'tcx>,
283 iter_expr: &'b IterExpr,
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 {
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;
304 if let Some(e) = l.init {
309 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
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) {
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);
325 } else if e.hir_id == self.loop_id {
326 self.after_loop = true;
333 if let Some(e) = get_enclosing_loop_or_multi_call_closure(cx, loop_expr) {
334 let Res::Local(local_id) = iter_expr.path else {
337 let mut v = NestedLoopVisitor {
341 loop_id: loop_expr.hir_id,
347 v.used_after || !v.found_local
349 let mut v = AfterLoopVisitor {
352 loop_id: loop_expr.hir_id,
356 v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);