Skip to content

Commit 3d6ffe5

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 6ae0835 commit 3d6ffe5

File tree

8 files changed

+705
-197
lines changed

8 files changed

+705
-197
lines changed

clippy_lints/src/doc.rs

+1-1
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

+314-131
Large diffs are not rendered by default.

clippy_utils/src/lib.rs

+18
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,24 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
782782
})
783783
}
784784

785+
/// Gets the loop enclosing the given expression, if any.
786+
pub fn get_enclosing_loop(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
787+
let map = tcx.hir();
788+
for (_, node) in map.parent_iter(expr.hir_id) {
789+
match node {
790+
Node::Expr(
791+
e @ Expr {
792+
kind: ExprKind::Loop(..),
793+
..
794+
},
795+
) => return Some(e),
796+
Node::Expr(_) | Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
797+
_ => break,
798+
}
799+
}
800+
None
801+
}
802+
785803
/// Gets the parent node if it's an impl block.
786804
pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
787805
let map = tcx.hir();

clippy_utils/src/sugg.rs

+1-1
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

+34-1
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, NestedVisitorMap, Visitor};
4-
use rustc_hir::{Arm, Body, Expr, HirId, Stmt};
4+
use rustc_hir::{def::Res, Arm, Body, BodyId, Expr, ExprKind, HirId, Stmt};
55
use rustc_lint::LateContext;
66
use rustc_middle::hir::map::Map;
77

@@ -188,3 +188,36 @@ impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
188188
NestedVisitorMap::OnlyBodies(self.hir)
189189
}
190190
}
191+
192+
/// Checks if the given resolved path is used the body.
193+
pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
194+
struct V<'a, 'tcx> {
195+
cx: &'a LateContext<'tcx>,
196+
res: Res,
197+
found: bool,
198+
}
199+
impl Visitor<'tcx> for V<'_, 'tcx> {
200+
type Map = Map<'tcx>;
201+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
202+
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
203+
}
204+
205+
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
206+
if self.found {
207+
return;
208+
}
209+
210+
if let ExprKind::Path(p) = &e.kind {
211+
if self.cx.qpath_res(p, e.hir_id) == self.res {
212+
self.found = true;
213+
}
214+
} else {
215+
walk_expr(self, e)
216+
}
217+
}
218+
}
219+
220+
let mut v = V { cx, res, found: false };
221+
v.visit_expr(&cx.tcx.hir().body(body).value);
222+
v.found
223+
}

tests/ui/while_let_on_iterator.fixed

+135-29
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)