Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions clippy_lints/src/loops/while_let_on_iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_hir::{Closure, Expr, ExprKind, HirId, LetStmt, Mutability, UnOp};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_middle::ty;
use rustc_middle::ty::adjustment::Adjust;
use rustc_span::Symbol;
use rustc_span::symbol::sym;
Expand Down Expand Up @@ -43,26 +44,26 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
};

// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
// passed by reference. 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.
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
let iterator_by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|| !iter_expr_struct.can_move
|| !iter_expr_struct.fields.is_empty()
|| needs_mutable_borrow(cx, &iter_expr_struct, expr)
{
".by_ref()"
make_iterator_snippet(cx, iter_expr, iterator)
} else {
""
iterator.to_string()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be into_owned.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you, changed it now.

};

let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
WHILE_LET_ON_ITERATOR,
expr.span.with_hi(let_expr.span.hi()),
"this loop could be written as a `for` loop",
"try",
format!("{loop_label}for {loop_var} in {iterator}{by_ref}"),
format!("{loop_label}for {loop_var} in {iterator_by_ref}"),
applicability,
);
}
Expand Down Expand Up @@ -355,3 +356,19 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
.is_break()
}
}

/// Constructs the transformed iterator expression for the suggestion.
/// Returns `iterator.by_ref()` unless the iterator type is a reference to an unsized type,
/// in which case it returns `&mut *iterator`.
fn make_iterator_snippet<'tcx>(cx: &LateContext<'tcx>, iter_expr: &Expr<'tcx>, iterator: impl Into<String>) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just take &str rather than Into<String>.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorted now.

let iterator = iterator.into();
let ty = cx.typeck_results().expr_ty(iter_expr);

if let ty::Ref(_, inner_ty, _) = ty.kind()
&& !inner_ty.is_sized(cx.tcx, cx.typing_env())
{
return format!("&mut *{iterator}");
}

format!("{iterator}.by_ref()")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work if auto-deref is used for the next call. A simple example is:

struct S<T>(T);
impl<T> core::ops::Deref for S<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target { &self.0 }
}
impl<T> core::ops::DerefMut for S<T> {
    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}

fn f(mut x: S<S<&mut dyn Iterator<Item = u32>>>) {
    while let Some(_) = x.next() {}
    // suggestion: `for _ in &mut **x {}`
}

What you'll need to do is walk the adjustments to find the last deref and use that type for the sized check and suggest applying all the derefs manually.

if let Some((n, adjust)) = cx
    .typeck_results()
    .expr_adjustments(e)
    .iter()
    .take_while(|x| matches!(x.kind, Adjust::Deref(_)))
    .enumerate()
    .last()
    && !adjust.target.is_sized(cx.tcx, cx.typing_env())
{
    format!("&mut {:*<n$}{iterator}", '*', n = n + 1)
} else {
    // by_ref
}

There will always be at least one deref adjustment on the expression since the receiver type on Iterator::next is a reference.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for the help, doing it this way now.

31 changes: 31 additions & 0 deletions tests/ui/while_let_on_iterator.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,37 @@ fn issue13123() {
}
}

fn issue16089() {
trait CertainTrait: Iterator<Item = u8> {
fn iter_over_self(&mut self) {
let mut a = 0;
for r in &mut *self {
//~^ while_let_on_iterator
a = r;
}
self.use_after_iter()
}

fn use_after_iter(&mut self) {}
}
}

fn issue16089_sized_trait_not_reborrowed() {
trait CertainTrait: Iterator<Item = u8> + Sized {
fn iter_over_self(&mut self) {
let mut a = 0;
// Check that the suggestion is just "self", since the trait is sized.
for r in self.by_ref() {
//~^ while_let_on_iterator
a = r;
}
self.use_after_iter()
}

fn use_after_iter(&mut self) {}
}
}

fn main() {
let mut it = 0..20;
for _ in it {
Expand Down
31 changes: 31 additions & 0 deletions tests/ui/while_let_on_iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,37 @@ fn issue13123() {
}
}

fn issue16089() {
trait CertainTrait: Iterator<Item = u8> {
fn iter_over_self(&mut self) {
let mut a = 0;
while let Some(r) = self.next() {
//~^ while_let_on_iterator
a = r;
}
self.use_after_iter()
}

fn use_after_iter(&mut self) {}
}
}

fn issue16089_sized_trait_not_reborrowed() {
trait CertainTrait: Iterator<Item = u8> + Sized {
fn iter_over_self(&mut self) {
let mut a = 0;
// Check that the suggestion is just "self", since the trait is sized.
while let Some(r) = self.next() {
//~^ while_let_on_iterator
a = r;
}
self.use_after_iter()
}

fn use_after_iter(&mut self) {}
}
}

fn main() {
let mut it = 0..20;
while let Some(..) = it.next() {
Expand Down
16 changes: 14 additions & 2 deletions tests/ui/while_let_on_iterator.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,22 @@ LL | 'label: while let Some(n) = it.next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'label: for n in it`

error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:497:5
--> tests/ui/while_let_on_iterator.rs:499:13
|
LL | while let Some(r) = self.next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for r in &mut *self`

error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:515:13
|
LL | while let Some(r) = self.next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for r in self.by_ref()`

error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:528:5
|
LL | while let Some(..) = it.next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it`

error: aborting due to 28 previous errors
error: aborting due to 30 previous errors