Skip to content

Commit 314af8f

Browse files
committed
Cleanup of while_let_on_iterator
1 parent 3d6ffe5 commit 314af8f

File tree

2 files changed

+53
-62
lines changed

2 files changed

+53
-62
lines changed

clippy_lints/src/loops/while_let_on_iterator.rs

Lines changed: 52 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::WHILE_LET_ON_ITERATOR;
2+
use if_chain::if_chain;
23
use clippy_utils::diagnostics::span_lint_and_sugg;
34
use clippy_utils::source::snippet_with_applicability;
45
use clippy_utils::{get_enclosing_loop, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used};
@@ -9,66 +10,56 @@ use rustc_lint::LateContext;
910
use rustc_span::{symbol::sym, Span, Symbol};
1011

1112
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(),
16-
_ => return,
17-
},
18-
_ => return,
19-
};
20-
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) =>
24-
{
25-
if let Some(iter_expr) = try_parse_iter_expr(cx, iter_expr) {
26-
iter_expr
27-
} else {
28-
return;
29-
}
30-
}
31-
_ => return,
32-
};
33-
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) {
36-
e
13+
let (scrutinee_expr, iter_expr, some_pat, loop_expr) = if_chain!{
14+
if let ExprKind::Match(scrutinee_expr, [arm, _], MatchSource::WhileLetDesugar) = expr.kind;
15+
// check for `Some(..)` pattern
16+
if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = arm.pat.kind;
17+
if let Res::Def(_, pat_did) = pat_path.res;
18+
if match_def_path(cx, pat_did, &paths::OPTION_SOME);
19+
// check for call to `Iterator::next`
20+
if let ExprKind::MethodCall(method_name, _, [iter_expr], _) = scrutinee_expr.kind;
21+
if method_name.ident.name == sym::next;
22+
if is_trait_method(cx, scrutinee_expr, sym::Iterator);
23+
if let Some(iter_expr) = try_parse_iter_expr(cx, iter_expr);
24+
// get the loop containing the match expression
25+
if let Some((_, Node::Expr(loop_expr))) = cx.tcx.hir().parent_iter(expr.hir_id).nth(1);
26+
if !uses_iter(cx, &iter_expr, arm.body);
27+
then {
28+
(scrutinee_expr, iter_expr, some_pat, loop_expr)
3729
} else {
3830
return;
39-
};
31+
}
32+
};
4033

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) {
34+
let mut applicability = Applicability::MachineApplicable;
35+
let loop_var = if let Some(some_pat) = some_pat.first() {
36+
if is_refutable(cx, some_pat) {
37+
// Refutable patterns don't work with for loops.
4438
return;
4539
}
40+
snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
41+
} else {
42+
"_".into()
43+
};
4644

47-
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
48-
// borrowed mutably.
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) {
52-
"&mut "
53-
} else {
54-
""
55-
};
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(
59-
|| "_".into(),
60-
|pat| snippet_with_applicability(cx, pat.span, "_", &mut applicability).into_owned(),
61-
);
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, ref_mut, iterator),
69-
applicability,
70-
);
71-
}
45+
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be borrowed mutably.
46+
// TODO: If the struct can be partially moved from and the struct isn't used afterwards a mutable borrow of a field isn't necessary.
47+
let ref_mut = if !iter_expr.fields.is_empty() || needs_mutable_borrow(cx, &iter_expr, loop_expr) {
48+
"&mut "
49+
} else {
50+
""
51+
};
52+
53+
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
54+
span_lint_and_sugg(
55+
cx,
56+
WHILE_LET_ON_ITERATOR,
57+
expr.span.with_hi(scrutinee_expr.span.hi()),
58+
"this loop could be written as a `for` loop",
59+
"try",
60+
format!("for {} in {}{}", loop_var, ref_mut, iterator),
61+
applicability,
62+
);
7263
}
7364

7465
#[derive(Debug)]
@@ -135,8 +126,9 @@ fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symb
135126
}
136127
}
137128

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.
129+
/// Checks if the given expression is the same field as, is a child of, or is the parent of the given
130+
/// field. Used to check if the expression can be used while the given field is borrowed mutably.
131+
/// e.g. if checking for `x.y`, then `x.y`, `x.y.z`, and `x` will all return true, but `x.z`, and `y` will return false.
140132
fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fields: &[Symbol], path_res: Res) -> bool {
141133
match expr.kind {
142134
ExprKind::Field(base, name) => {
@@ -166,7 +158,7 @@ fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fie
166158
}
167159
}
168160
},
169-
// If the path matches, this is either an exact match, of the expression is a parent of the field.
161+
// If the path matches, this is either an exact match, or the expression is a parent of the field.
170162
ExprKind::Path(ref path) => cx.qpath_res(path, expr.hir_id) == path_res,
171163
ExprKind::DropTemps(base) | ExprKind::Type(base, _) | ExprKind::AddrOf(_, _, base) => {
172164
is_expr_same_child_or_parent_field(cx, base, fields, path_res)
@@ -175,8 +167,7 @@ fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fie
175167
}
176168
}
177169

178-
/// Strips off all field and path expressions. Used to skip them after failing to check for
179-
/// equality.
170+
/// Strips off all field and path expressions. This will return true if a field or path has been skipped. Used to skip them after failing to check for equality.
180171
fn skip_fields_and_path(expr: &'tcx Expr<'_>) -> (Option<&'tcx Expr<'tcx>>, bool) {
181172
let mut e = expr;
182173
let e = loop {
@@ -257,7 +248,7 @@ fn needs_mutable_borrow(cx: &LateContext<'tcx>, iter_expr: &IterExpr, loop_expr:
257248
self.visit_expr(e);
258249
}
259250
} else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
260-
self.used_iter |= is_res_used(self.cx, self.iter_expr.path, id);
251+
self.used_iter = is_res_used(self.cx, self.iter_expr.path, id);
261252
} else {
262253
walk_expr(self, e);
263254
}
@@ -309,7 +300,7 @@ fn needs_mutable_borrow(cx: &LateContext<'tcx>, iter_expr: &IterExpr, loop_expr:
309300
self.visit_expr(e);
310301
}
311302
} else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
312-
self.used_after |= is_res_used(self.cx, self.iter_expr.path, id);
303+
self.used_after = is_res_used(self.cx, self.iter_expr.path, id);
313304
} else {
314305
walk_expr(self, e);
315306
}

clippy_utils/src/visitors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
189189
}
190190
}
191191

192-
/// Checks if the given resolved path is used the body.
192+
/// Checks if the given resolved path is used in the given body.
193193
pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
194194
struct V<'a, 'tcx> {
195195
cx: &'a LateContext<'tcx>,

0 commit comments

Comments
 (0)