Skip to content

Commit 90fb7c4

Browse files
committed
Auto merge of #6966 - Jarcho:while_let_on_iterator_fp, r=xFrednet
`while_let_on_iterator` Improvements fixes: #6491 fixes: #6231 fixes: #5844 fixes: #1924 fixes: #1033 The check for whether a field can be borrowed should probably be moved to utils at some point, but it would require some cleanup work and knowing what parts can actually be shared. changelog: Suggest `&mut iter` when the iterator is used after the loop. changelog: Suggest `&mut iter` when the iterator is a field in a struct. changelog: Don't lint when the iterator is a field in a struct, and the struct is used in the loop. changelog: Lint when the loop is nested in another loop, but suggest `&mut iter` unless the iterator is from a local declared inside the loop.
2 parents aa15a54 + cd0db8a commit 90fb7c4

File tree

8 files changed

+725
-206
lines changed

8 files changed

+725
-206
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

+311-134
Large diffs are not rendered by default.

clippy_utils/src/lib.rs

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

+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

+35-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, 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 in the given 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

+143-32
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();
@@ -167,10 +148,8 @@ fn issue1121() {
167148
}
168149

169150
fn issue2965() {
170-
// This should not cause an ICE and suggest:
171-
//
172-
// for _ in values.iter() {}
173-
//
151+
// This should not cause an ICE
152+
174153
use std::collections::HashSet;
175154
let mut values = HashSet::new();
176155
values.insert(1);
@@ -205,13 +184,145 @@ fn issue1654() {
205184
}
206185
}
207186

187+
fn issue6491() {
188+
// Used in outer loop, needs &mut
189+
let mut it = 1..40;
190+
while let Some(n) = it.next() {
191+
for m in &mut it {
192+
if m % 10 == 0 {
193+
break;
194+
}
195+
println!("doing something with m: {}", m);
196+
}
197+
println!("n still is {}", n);
198+
}
199+
200+
// This is fine, inner loop uses a new iterator.
201+
let mut it = 1..40;
202+
for n in it {
203+
let mut it = 1..40;
204+
for m in it {
205+
if m % 10 == 0 {
206+
break;
207+
}
208+
println!("doing something with m: {}", m);
209+
}
210+
211+
// Weird binding shouldn't change anything.
212+
let (mut it, _) = (1..40, 0);
213+
for m in it {
214+
if m % 10 == 0 {
215+
break;
216+
}
217+
println!("doing something with m: {}", m);
218+
}
219+
220+
// Used after the loop, needs &mut.
221+
let mut it = 1..40;
222+
for m in &mut it {
223+
if m % 10 == 0 {
224+
break;
225+
}
226+
println!("doing something with m: {}", m);
227+
}
228+
println!("next item {}", it.next().unwrap());
229+
230+
println!("n still is {}", n);
231+
}
232+
}
233+
234+
fn issue6231() {
235+
// Closure in the outer loop, needs &mut
236+
let mut it = 1..40;
237+
let mut opt = Some(0);
238+
while let Some(n) = opt.take().or_else(|| it.next()) {
239+
for m in &mut it {
240+
if n % 10 == 0 {
241+
break;
242+
}
243+
println!("doing something with m: {}", m);
244+
}
245+
println!("n still is {}", n);
246+
}
247+
}
248+
249+
fn issue1924() {
250+
struct S<T>(T);
251+
impl<T: Iterator<Item = u32>> S<T> {
252+
fn f(&mut self) -> Option<u32> {
253+
// Used as a field.
254+
for i in &mut self.0 {
255+
if !(3..=7).contains(&i) {
256+
return Some(i);
257+
}
258+
}
259+
None
260+
}
261+
262+
fn f2(&mut self) -> Option<u32> {
263+
// Don't lint, self borrowed inside the loop
264+
while let Some(i) = self.0.next() {
265+
if i == 1 {
266+
return self.f();
267+
}
268+
}
269+
None
270+
}
271+
}
272+
impl<T: Iterator<Item = u32>> S<(S<T>, Option<u32>)> {
273+
fn f3(&mut self) -> Option<u32> {
274+
// Don't lint, self borrowed inside the loop
275+
while let Some(i) = self.0.0.0.next() {
276+
if i == 1 {
277+
return self.0.0.f();
278+
}
279+
}
280+
while let Some(i) = self.0.0.0.next() {
281+
if i == 1 {
282+
return self.f3();
283+
}
284+
}
285+
// This one is fine, a different field is borrowed
286+
for i in &mut self.0.0.0 {
287+
if i == 1 {
288+
return self.0.1.take();
289+
} else {
290+
self.0.1 = Some(i);
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+
208323
fn main() {
209-
base();
210-
refutable();
211-
refutable2();
212-
nested_loops();
213-
issue1121();
214-
issue2965();
215-
issue3670();
216-
issue1654();
324+
let mut it = 0..20;
325+
for _ in it {
326+
println!("test");
327+
}
217328
}

0 commit comments

Comments
 (0)