Skip to content

Commit bbadb72

Browse files
committed
while_let_on_iterator: consider all deref adjustments for by_ref
1 parent f4496bf commit bbadb72

File tree

4 files changed

+182
-16
lines changed

4 files changed

+182
-16
lines changed

clippy_lints/src/loops/while_let_on_iterator.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use rustc_hir::intravisit::{Visitor, walk_expr};
1212
use rustc_hir::{Closure, Expr, ExprKind, HirId, LetStmt, Mutability, UnOp};
1313
use rustc_lint::LateContext;
1414
use rustc_middle::hir::nested_filter::OnlyBodies;
15-
use rustc_middle::ty;
1615
use rustc_middle::ty::adjustment::Adjust;
1716
use rustc_span::Symbol;
1817
use rustc_span::symbol::sym;
@@ -52,9 +51,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
5251
|| !iter_expr_struct.fields.is_empty()
5352
|| needs_mutable_borrow(cx, &iter_expr_struct, expr)
5453
{
55-
make_iterator_snippet(cx, iter_expr, iterator)
54+
make_iterator_snippet(cx, iter_expr, &iterator)
5655
} else {
57-
iterator.to_string()
56+
iterator.into_owned()
5857
};
5958

6059
span_lint_and_sugg(
@@ -358,17 +357,20 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
358357
}
359358

360359
/// Constructs the transformed iterator expression for the suggestion.
361-
/// Returns `iterator.by_ref()` unless the iterator type is a reference to an unsized type,
362-
/// in which case it returns `&mut *iterator`.
363-
fn make_iterator_snippet<'tcx>(cx: &LateContext<'tcx>, iter_expr: &Expr<'tcx>, iterator: impl Into<String>) -> String {
364-
let iterator = iterator.into();
365-
let ty = cx.typeck_results().expr_ty(iter_expr);
366-
367-
if let ty::Ref(_, inner_ty, _) = ty.kind()
368-
&& !inner_ty.is_sized(cx.tcx, cx.typing_env())
360+
/// Returns `iterator.by_ref()` unless the last deref adjustment targets an unsized type,
361+
/// in which case it applies all derefs (e.g., `&mut **iterator` or `&mut ***iterator`).
362+
fn make_iterator_snippet<'tcx>(cx: &LateContext<'tcx>, iter_expr: &Expr<'tcx>, iterator: &str) -> String {
363+
if let Some((n, adjust)) = cx
364+
.typeck_results()
365+
.expr_adjustments(iter_expr)
366+
.iter()
367+
.take_while(|x| matches!(x.kind, Adjust::Deref(_)))
368+
.enumerate()
369+
.last()
370+
&& !adjust.target.is_sized(cx.tcx, cx.typing_env())
369371
{
370-
return format!("&mut *{iterator}");
372+
format!("&mut {:*<n$}{iterator}", '*', n = n + 1)
373+
} else {
374+
format!("{iterator}.by_ref()")
371375
}
372-
373-
format!("{iterator}.by_ref()")
374376
}

tests/ui/while_let_on_iterator.fixed

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,79 @@ fn issue16089_sized_trait_not_reborrowed() {
523523
}
524524
}
525525

526+
fn issue16089_nested_derefs() {
527+
struct S<T>(T);
528+
impl<T> core::ops::Deref for S<T> {
529+
type Target = T;
530+
fn deref(&self) -> &Self::Target {
531+
&self.0
532+
}
533+
}
534+
impl<T> core::ops::DerefMut for S<T> {
535+
fn deref_mut(&mut self) -> &mut Self::Target {
536+
&mut self.0
537+
}
538+
}
539+
540+
fn f(mut x: S<S<&mut dyn Iterator<Item = u32>>>) {
541+
for _ in &mut ***x {}
542+
//~^ while_let_on_iterator
543+
}
544+
}
545+
546+
fn issue16089_nested_derefs_last_not_sized() {
547+
struct WithSize<T>(T);
548+
impl<T> core::ops::Deref for WithSize<T> {
549+
type Target = T;
550+
fn deref(&self) -> &Self::Target {
551+
&self.0
552+
}
553+
}
554+
impl<T> core::ops::DerefMut for WithSize<T> {
555+
fn deref_mut(&mut self) -> &mut Self::Target {
556+
&mut self.0
557+
}
558+
}
559+
// The suggestion must use `&mut **x`. Using `x.by_ref()` doesn't work in this
560+
// case, since the last type adjustment for `x` in the expression `x.next()` is
561+
// to dereference a `?Sized` trait.
562+
fn f(mut x: WithSize<&mut dyn Iterator<Item = u32>>) {
563+
for _ in &mut **x {}
564+
//~^ while_let_on_iterator
565+
}
566+
}
567+
568+
fn issue16089_nested_derefs_last_sized() {
569+
struct NoSize<T: ?Sized>(T);
570+
impl<T: ?Sized> core::ops::Deref for NoSize<T> {
571+
type Target = T;
572+
fn deref(&self) -> &Self::Target {
573+
&self.0
574+
}
575+
}
576+
impl<T: ?Sized> core::ops::DerefMut for NoSize<T> {
577+
fn deref_mut(&mut self) -> &mut Self::Target {
578+
&mut self.0
579+
}
580+
}
581+
582+
struct SizedIter {}
583+
584+
impl Iterator for SizedIter {
585+
type Item = u32;
586+
fn next(&mut self) -> Option<u32> {
587+
Some(0)
588+
}
589+
}
590+
591+
// We want the suggestion to be `x.by_ref()`. It works in this case since the last type
592+
// adjustment for `x` in the expression `x.next()` is to dereference a Sized type.
593+
fn f(mut x: NoSize<NoSize<SizedIter>>) {
594+
for _ in x.by_ref() {}
595+
//~^ while_let_on_iterator
596+
}
597+
}
598+
526599
fn main() {
527600
let mut it = 0..20;
528601
for _ in it {

tests/ui/while_let_on_iterator.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,79 @@ fn issue16089_sized_trait_not_reborrowed() {
523523
}
524524
}
525525

526+
fn issue16089_nested_derefs() {
527+
struct S<T>(T);
528+
impl<T> core::ops::Deref for S<T> {
529+
type Target = T;
530+
fn deref(&self) -> &Self::Target {
531+
&self.0
532+
}
533+
}
534+
impl<T> core::ops::DerefMut for S<T> {
535+
fn deref_mut(&mut self) -> &mut Self::Target {
536+
&mut self.0
537+
}
538+
}
539+
540+
fn f(mut x: S<S<&mut dyn Iterator<Item = u32>>>) {
541+
while let Some(_) = x.next() {}
542+
//~^ while_let_on_iterator
543+
}
544+
}
545+
546+
fn issue16089_nested_derefs_last_not_sized() {
547+
struct WithSize<T>(T);
548+
impl<T> core::ops::Deref for WithSize<T> {
549+
type Target = T;
550+
fn deref(&self) -> &Self::Target {
551+
&self.0
552+
}
553+
}
554+
impl<T> core::ops::DerefMut for WithSize<T> {
555+
fn deref_mut(&mut self) -> &mut Self::Target {
556+
&mut self.0
557+
}
558+
}
559+
// The suggestion must use `&mut **x`. Using `x.by_ref()` doesn't work in this
560+
// case, since the last type adjustment for `x` in the expression `x.next()` is
561+
// to dereference a `?Sized` trait.
562+
fn f(mut x: WithSize<&mut dyn Iterator<Item = u32>>) {
563+
while let Some(_) = x.next() {}
564+
//~^ while_let_on_iterator
565+
}
566+
}
567+
568+
fn issue16089_nested_derefs_last_sized() {
569+
struct NoSize<T: ?Sized>(T);
570+
impl<T: ?Sized> core::ops::Deref for NoSize<T> {
571+
type Target = T;
572+
fn deref(&self) -> &Self::Target {
573+
&self.0
574+
}
575+
}
576+
impl<T: ?Sized> core::ops::DerefMut for NoSize<T> {
577+
fn deref_mut(&mut self) -> &mut Self::Target {
578+
&mut self.0
579+
}
580+
}
581+
582+
struct SizedIter {}
583+
584+
impl Iterator for SizedIter {
585+
type Item = u32;
586+
fn next(&mut self) -> Option<u32> {
587+
Some(0)
588+
}
589+
}
590+
591+
// We want the suggestion to be `x.by_ref()`. It works in this case since the last type
592+
// adjustment for `x` in the expression `x.next()` is to dereference a Sized type.
593+
fn f(mut x: NoSize<NoSize<SizedIter>>) {
594+
while let Some(_) = x.next() {}
595+
//~^ while_let_on_iterator
596+
}
597+
}
598+
526599
fn main() {
527600
let mut it = 0..20;
528601
while let Some(..) = it.next() {

tests/ui/while_let_on_iterator.stderr

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,28 @@ LL | while let Some(r) = self.next() {
176176
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for r in self.by_ref()`
177177

178178
error: this loop could be written as a `for` loop
179-
--> tests/ui/while_let_on_iterator.rs:528:5
179+
--> tests/ui/while_let_on_iterator.rs:541:9
180+
|
181+
LL | while let Some(_) = x.next() {}
182+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in &mut ***x`
183+
184+
error: this loop could be written as a `for` loop
185+
--> tests/ui/while_let_on_iterator.rs:563:9
186+
|
187+
LL | while let Some(_) = x.next() {}
188+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in &mut **x`
189+
190+
error: this loop could be written as a `for` loop
191+
--> tests/ui/while_let_on_iterator.rs:594:9
192+
|
193+
LL | while let Some(_) = x.next() {}
194+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in x.by_ref()`
195+
196+
error: this loop could be written as a `for` loop
197+
--> tests/ui/while_let_on_iterator.rs:601:5
180198
|
181199
LL | while let Some(..) = it.next() {
182200
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it`
183201

184-
error: aborting due to 30 previous errors
202+
error: aborting due to 33 previous errors
185203

0 commit comments

Comments
 (0)