Skip to content

Commit daca50a

Browse files
committed
Improvements to while_let_on_iterator
* Suggest `&mut iter` when the iterator is used after the loop. * Suggest `&mut iter` when the iterator is a field in a struct. * Don't lint when the iterator is a field in a struct, and the struct is used in the loop. * Lint when the loop is nested in another loop, but suggest `&mut iter` unless the iterator is from a local declared inside the loop.
1 parent aa15a54 commit daca50a

File tree

8 files changed

+706
-197
lines changed

8 files changed

+706
-197
lines changed

clippy_lints/src/doc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span:
383383
let mut no_stars = String::with_capacity(doc.len());
384384
for line in doc.lines() {
385385
let mut chars = line.chars();
386-
while let Some(c) = chars.next() {
386+
for c in &mut chars {
387387
if c.is_whitespace() {
388388
no_stars.push(c);
389389
} else {

clippy_lints/src/loops/while_let_on_iterator.rs

Lines changed: 314 additions & 131 deletions
Large diffs are not rendered by default.

clippy_utils/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,24 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
855855
})
856856
}
857857

858+
/// Gets the loop enclosing the given expression, if any.
859+
pub fn get_enclosing_loop(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
860+
let map = tcx.hir();
861+
for (_, node) in map.parent_iter(expr.hir_id) {
862+
match node {
863+
Node::Expr(
864+
e @ Expr {
865+
kind: ExprKind::Loop(..),
866+
..
867+
},
868+
) => return Some(e),
869+
Node::Expr(_) | Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
870+
_ => break,
871+
}
872+
}
873+
None
874+
}
875+
858876
/// Gets the parent node if it's an impl block.
859877
pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
860878
let map = tcx.hir();

clippy_utils/src/sugg.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
289289
let mut chars = sugg.as_ref().chars();
290290
if let Some('(') = chars.next() {
291291
let mut depth = 1;
292-
while let Some(c) = chars.next() {
292+
for c in &mut chars {
293293
if c == '(' {
294294
depth += 1;
295295
} else if c == ')' {

clippy_utils/src/visitors.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::path_to_local_id;
22
use rustc_hir as hir;
33
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
4-
use rustc_hir::{Arm, Block, Body, Destination, Expr, ExprKind, HirId, Stmt};
4+
use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt};
55
use rustc_lint::LateContext;
66
use rustc_middle::hir::map::Map;
77

@@ -218,6 +218,7 @@ impl<'tcx> Visitable<'tcx> for &'tcx Arm<'tcx> {
218218
}
219219
}
220220

221+
/// Calls the given function for each break expression.
221222
pub fn visit_break_exprs<'tcx>(
222223
node: impl Visitable<'tcx>,
223224
f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>),
@@ -239,3 +240,36 @@ pub fn visit_break_exprs<'tcx>(
239240

240241
node.visit(&mut V(f));
241242
}
243+
244+
/// Checks if the given resolved path is used the body.
245+
pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
246+
struct V<'a, 'tcx> {
247+
cx: &'a LateContext<'tcx>,
248+
res: Res,
249+
found: bool,
250+
}
251+
impl Visitor<'tcx> for V<'_, 'tcx> {
252+
type Map = Map<'tcx>;
253+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
254+
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
255+
}
256+
257+
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
258+
if self.found {
259+
return;
260+
}
261+
262+
if let ExprKind::Path(p) = &e.kind {
263+
if self.cx.qpath_res(p, e.hir_id) == self.res {
264+
self.found = true;
265+
}
266+
} else {
267+
walk_expr(self, e)
268+
}
269+
}
270+
}
271+
272+
let mut v = V { cx, res, found: false };
273+
v.visit_expr(&cx.tcx.hir().body(body).value);
274+
v.found
275+
}

tests/ui/while_let_on_iterator.fixed

Lines changed: 135 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// run-rustfix
22

33
#![warn(clippy::while_let_on_iterator)]
4-
#![allow(clippy::never_loop, unreachable_code, unused_mut)]
4+
#![allow(clippy::never_loop, unreachable_code, unused_mut, dead_code)]
55

66
fn base() {
77
let mut iter = 1..20;
@@ -38,13 +38,6 @@ fn base() {
3838
println!("next: {:?}", iter.next());
3939
}
4040

41-
// or this
42-
let mut iter = 1u32..20;
43-
while let Some(_) = iter.next() {
44-
break;
45-
}
46-
println!("Remaining iter {:?}", iter);
47-
4841
// or this
4942
let mut iter = 1u32..20;
5043
while let Some(_) = iter.next() {
@@ -135,18 +128,6 @@ fn refutable2() {
135128

136129
fn nested_loops() {
137130
let a = [42, 1337];
138-
let mut y = a.iter();
139-
loop {
140-
// x is reused, so don't lint here
141-
while let Some(_) = y.next() {}
142-
}
143-
144-
let mut y = a.iter();
145-
for _ in 0..2 {
146-
while let Some(_) = y.next() {
147-
// y is reused, don't lint
148-
}
149-
}
150131

151132
loop {
152133
let mut y = a.iter();
@@ -205,13 +186,138 @@ fn issue1654() {
205186
}
206187
}
207188

208-
fn main() {
209-
base();
210-
refutable();
211-
refutable2();
212-
nested_loops();
213-
issue1121();
214-
issue2965();
215-
issue3670();
216-
issue1654();
189+
fn issue6491() {
190+
// Used in outer loop, needs &mut
191+
let mut it = 1..40;
192+
while let Some(n) = it.next() {
193+
for m in &mut it {
194+
if m % 10 == 0 {
195+
break;
196+
}
197+
println!("doing something with m: {}", m);
198+
}
199+
println!("n still is {}", n);
200+
}
201+
202+
// This is fine, inner loop uses a new iterator.
203+
let mut it = 1..40;
204+
for n in it {
205+
let mut it = 1..40;
206+
for m in it {
207+
if m % 10 == 0 {
208+
break;
209+
}
210+
println!("doing something with m: {}", m);
211+
}
212+
213+
// Weird binding shouldn't change anything.
214+
let (mut it, _) = (1..40, 0);
215+
for m in it {
216+
if m % 10 == 0 {
217+
break;
218+
}
219+
println!("doing something with m: {}", m);
220+
}
221+
222+
// Used after the loop, needs &mut.
223+
let mut it = 1..40;
224+
for m in &mut it {
225+
if m % 10 == 0 {
226+
break;
227+
}
228+
println!("doing something with m: {}", m);
229+
}
230+
println!("next item {}", it.next().unwrap());
231+
232+
println!("n still is {}", n);
233+
}
234+
}
235+
236+
fn issue6231() {
237+
// Closure in the outer loop, needs &mut
238+
let mut it = 1..40;
239+
let mut opt = Some(0);
240+
while let Some(n) = opt.take().or_else(|| it.next()) {
241+
for m in &mut it {
242+
if n % 10 == 0 {
243+
break;
244+
}
245+
println!("doing something with m: {}", m);
246+
}
247+
println!("n still is {}", n);
248+
}
217249
}
250+
251+
fn issue1924() {
252+
struct S<T>(T);
253+
impl<T: Iterator<Item = u32>> S<T> {
254+
fn f(&mut self) -> Option<u32> {
255+
// Used as a field.
256+
for i in &mut self.0 {
257+
if !(3..=7).contains(&i) {
258+
return Some(i);
259+
}
260+
}
261+
None
262+
}
263+
264+
fn f2(&mut self) -> Option<u32> {
265+
// Don't lint, self borrowed inside the loop
266+
while let Some(i) = self.0.next() {
267+
if i == 1 {
268+
return self.f();
269+
}
270+
}
271+
None
272+
}
273+
}
274+
impl<T: Iterator<Item = u32>> S<(S<T>, Option<u32>)> {
275+
fn f3(&mut self) -> Option<u32> {
276+
// Don't lint, self borrowed inside the loop
277+
while let Some(i) = self.0.0.0.next() {
278+
if i == 1 {
279+
return self.0.0.f();
280+
}
281+
}
282+
while let Some(i) = self.0.0.0.next() {
283+
if i == 1 {
284+
return self.f3();
285+
}
286+
}
287+
// This one is fine, a different field is borrowed
288+
for i in &mut self.0.0.0 {
289+
if i == 1 {
290+
return self.0.1.take();
291+
}
292+
}
293+
None
294+
}
295+
}
296+
297+
struct S2<T>(T, u32);
298+
impl<T: Iterator<Item = u32>> Iterator for S2<T> {
299+
type Item = u32;
300+
fn next(&mut self) -> Option<u32> {
301+
self.0.next()
302+
}
303+
}
304+
305+
// Don't lint, field of the iterator is accessed in the loop
306+
let mut it = S2(1..40, 0);
307+
while let Some(n) = it.next() {
308+
if n == it.1 {
309+
break;
310+
}
311+
}
312+
313+
// Needs &mut, field of the iterator is accessed after the loop
314+
let mut it = S2(1..40, 0);
315+
for n in &mut it {
316+
if n == 0 {
317+
break;
318+
}
319+
}
320+
println!("iterator field {}", it.1);
321+
}
322+
323+
fn main() {}

0 commit comments

Comments
 (0)