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::{get_enclosing_loop, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used};
5 use rustc_errors::Applicability;
6 use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor};
7 use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, MatchSource, Node, PatKind, QPath, UnOp};
8 use rustc_lint::LateContext;
9 use rustc_span::{symbol::sym, Span, Symbol};
11 pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
12 if let ExprKind::Match(scrutinee_expr, [arm, _], MatchSource::WhileLetDesugar) = expr.kind {
13 let some_pat = match arm.pat.kind {
14 PatKind::TupleStruct(QPath::Resolved(None, path), sub_pats, _) => match path.res {
15 Res::Def(_, id) if match_def_path(cx, id, &paths::OPTION_SOME) => sub_pats.first(),
21 let iter_expr = match scrutinee_expr.kind {
22 ExprKind::MethodCall(name, _, [iter_expr], _)
23 if name.ident.name == sym::next && is_trait_method(cx, scrutinee_expr, sym::Iterator) =>
25 if let Some(iter_expr) = try_parse_iter_expr(cx, iter_expr) {
34 // Needed to find an outer loop, if there are any.
35 let loop_expr = if let Some((_, Node::Expr(e))) = cx.tcx.hir().parent_iter(expr.hir_id).nth(1) {
41 // Refutable patterns don't work with for loops.
42 // The iterator also can't be accessed withing the loop.
43 if some_pat.map_or(true, |p| is_refutable(cx, p)) || uses_iter(cx, &iter_expr, arm.body) {
47 // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
49 // TODO: If the struct can be partially moved from and the struct isn't used afterwards a mutable
50 // borrow of a field isn't necessary.
51 let ref_mut = if !iter_expr.fields.is_empty() || needs_mutable_borrow(cx, &iter_expr, loop_expr) {
56 let mut applicability = Applicability::MachineApplicable;
57 let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
58 let loop_var = some_pat.map_or_else(
60 |pat| snippet_with_applicability(cx, pat.span, "_", &mut applicability).into_owned(),
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 {} in {}{}", loop_var, ref_mut, iterator),
76 /// The span of the whole expression, not just the path and fields stored here.
78 /// The fields used, in order of child to parent.
80 /// The path being used.
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> {
87 let mut fields = Vec::new();
90 ExprKind::Path(ref path) => {
94 path: cx.qpath_res(path, e.hir_id),
97 ExprKind::Field(base, name) => {
98 fields.push(name.name);
101 // Dereferencing a pointer has no side effects and doesn't affect which field is being used.
102 ExprKind::Unary(UnOp::Deref, base) if cx.typeck_results().expr_ty(base).is_ref() => e = base,
104 // Shouldn't have side effects, but there's no way to trace which field is used. So forget which fields have
105 // already been seen.
106 ExprKind::Index(base, idx) if !idx.can_have_side_effects() => {
110 ExprKind::Unary(UnOp::Deref, base) => {
115 // No effect and doesn't affect which field is being used.
116 ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _) => e = base,
122 fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symbol], path_res: Res) -> bool {
124 match (&e.kind, fields) {
125 (&ExprKind::Field(base, name), [head_field, tail_fields @ ..]) if name.name == *head_field => {
127 fields = tail_fields;
129 (ExprKind::Path(path), []) => {
130 break cx.qpath_res(path, e.hir_id) == path_res;
132 (&(ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _)), _) => e = base,
138 /// Checks if the given expression is the same field as, is a child of, of the parent of the given
139 /// field. Used to check if the expression can be used while the given field is borrowed.
140 fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fields: &[Symbol], path_res: Res) -> bool {
142 ExprKind::Field(base, name) => {
143 if let Some((head_field, tail_fields)) = fields.split_first() {
144 if name.name == *head_field && is_expr_same_field(cx, base, fields, path_res) {
147 // Check if the expression is a parent field
148 let mut fields_iter = tail_fields.iter();
149 while let Some(field) = fields_iter.next() {
150 if *field == name.name && is_expr_same_field(cx, base, fields_iter.as_slice(), path_res) {
156 // Check if the expression is a child field.
160 ExprKind::Field(..) if is_expr_same_field(cx, e, fields, path_res) => break true,
161 ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
162 ExprKind::Path(ref path) if fields.is_empty() => {
163 break cx.qpath_res(path, e.hir_id) == path_res;
169 // If the path matches, this is either an exact match, of the expression is a parent of the field.
170 ExprKind::Path(ref path) => cx.qpath_res(path, expr.hir_id) == path_res,
171 ExprKind::DropTemps(base) | ExprKind::Type(base, _) | ExprKind::AddrOf(_, _, base) => {
172 is_expr_same_child_or_parent_field(cx, base, fields, path_res)
178 /// Strips off all field and path expressions. Used to skip them after failing to check for
180 fn skip_fields_and_path(expr: &'tcx Expr<'_>) -> (Option<&'tcx Expr<'tcx>>, bool) {
184 ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
185 ExprKind::Path(_) => return (None, true),
189 (Some(e), e.hir_id != expr.hir_id)
192 /// Checks if the given expression uses the iterator.
193 fn uses_iter(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tcx Expr<'_>) -> bool {
194 struct V<'a, 'b, 'tcx> {
195 cx: &'a LateContext<'tcx>,
196 iter_expr: &'b IterExpr,
199 impl Visitor<'tcx> for V<'_, '_, 'tcx> {
200 type Map = ErasedMap<'tcx>;
201 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
202 NestedVisitorMap::None
205 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
208 } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
209 self.uses_iter = true;
210 } else if let (e, true) = skip_fields_and_path(e) {
214 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
215 if is_res_used(self.cx, self.iter_expr.path, id) {
216 self.uses_iter = true;
229 v.visit_expr(container);
233 #[allow(clippy::too_many_lines)]
234 fn needs_mutable_borrow(cx: &LateContext<'tcx>, iter_expr: &IterExpr, loop_expr: &'tcx Expr<'_>) -> bool {
235 struct AfterLoopVisitor<'a, 'b, 'tcx> {
236 cx: &'a LateContext<'tcx>,
237 iter_expr: &'b IterExpr,
242 impl Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
243 type Map = ErasedMap<'tcx>;
244 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
245 NestedVisitorMap::None
248 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
253 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
254 self.used_iter = true;
255 } else if let (e, true) = skip_fields_and_path(e) {
259 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
260 self.used_iter |= is_res_used(self.cx, self.iter_expr.path, id);
264 } else if self.loop_id == e.hir_id {
265 self.after_loop = true;
272 struct NestedLoopVisitor<'a, 'b, 'tcx> {
273 cx: &'a LateContext<'tcx>,
274 iter_expr: &'b IterExpr,
281 impl Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
282 type Map = ErasedMap<'tcx>;
283 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
284 NestedVisitorMap::None
287 fn visit_local(&mut self, l: &'tcx Local<'_>) {
288 if !self.after_loop {
289 l.pat.each_binding_or_first(&mut |_, id, _, _| {
290 if id == self.local_id {
291 self.found_local = true;
295 if let Some(e) = l.init {
300 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
305 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
306 self.used_after = true;
307 } else if let (e, true) = skip_fields_and_path(e) {
311 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
312 self.used_after |= is_res_used(self.cx, self.iter_expr.path, id);
316 } else if e.hir_id == self.loop_id {
317 self.after_loop = true;
324 if let Some(e) = get_enclosing_loop(cx.tcx, loop_expr) {
325 // The iterator expression will be used on the next iteration unless it is declared within the outer
327 let local_id = match iter_expr.path {
328 Res::Local(id) => id,
331 let mut v = NestedLoopVisitor {
335 loop_id: loop_expr.hir_id,
341 v.used_after || !v.found_local
343 let mut v = AfterLoopVisitor {
346 loop_id: loop_expr.hir_id,
350 v.visit_expr(&cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);