Skip to content

Commit 92048f4

Browse files
committed
Auto merge of #8221 - Jarcho:while_let_on_iterator_8113, r=llogiq
Better detect when a field can be moved from in `while_let_on_iterator` fixes #8113 changelog: Better detect when a field can be moved from in `while_let_on_iterator`
2 parents 20f2a89 + ff58efb commit 92048f4

File tree

4 files changed

+100
-11
lines changed

4 files changed

+100
-11
lines changed

clippy_lints/src/loops/while_let_on_iterator.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ use clippy_utils::{
88
use if_chain::if_chain;
99
use rustc_errors::Applicability;
1010
use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor};
11-
use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, PatKind, QPath, UnOp};
11+
use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp};
1212
use rustc_lint::LateContext;
13-
use rustc_span::{symbol::sym, Span, Symbol};
13+
use rustc_middle::ty::adjustment::Adjust;
14+
use rustc_span::{symbol::sym, Symbol};
1415

1516
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
16-
let (scrutinee_expr, iter_expr, some_pat, loop_expr) = if_chain! {
17+
let (scrutinee_expr, iter_expr_struct, iter_expr, some_pat, loop_expr) = if_chain! {
1718
if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
1819
// check for `Some(..)` pattern
1920
if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = let_pat.kind;
@@ -27,7 +28,7 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
2728
// get the loop containing the match expression
2829
if !uses_iter(cx, &iter_expr_struct, if_then);
2930
then {
30-
(let_expr, iter_expr_struct, some_pat, expr)
31+
(let_expr, iter_expr_struct, iter_expr, some_pat, expr)
3132
} else {
3233
return;
3334
}
@@ -47,7 +48,11 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
4748
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
4849
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
4950
// afterwards a mutable borrow of a field isn't necessary.
50-
let by_ref = if !iter_expr.fields.is_empty() || needs_mutable_borrow(cx, &iter_expr, loop_expr) {
51+
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
52+
|| !iter_expr_struct.can_move
53+
|| !iter_expr_struct.fields.is_empty()
54+
|| needs_mutable_borrow(cx, &iter_expr_struct, loop_expr)
55+
{
5156
".by_ref()"
5257
} else {
5358
""
@@ -67,26 +72,36 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
6772

6873
#[derive(Debug)]
6974
struct IterExpr {
70-
/// The span of the whole expression, not just the path and fields stored here.
71-
span: Span,
7275
/// The fields used, in order of child to parent.
7376
fields: Vec<Symbol>,
7477
/// The path being used.
7578
path: Res,
79+
/// Whether or not the iterator can be moved.
80+
can_move: bool,
7681
}
7782

7883
/// Parses any expression to find out which field of which variable is used. Will return `None` if
7984
/// the expression might have side effects.
8085
fn try_parse_iter_expr(cx: &LateContext<'_>, mut e: &Expr<'_>) -> Option<IterExpr> {
81-
let span = e.span;
8286
let mut fields = Vec::new();
87+
let mut can_move = true;
8388
loop {
89+
if cx
90+
.typeck_results()
91+
.expr_adjustments(e)
92+
.iter()
93+
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
94+
{
95+
// Custom deref impls need to borrow the whole value as it's captured by reference
96+
can_move = false;
97+
fields.clear();
98+
}
8499
match e.kind {
85100
ExprKind::Path(ref path) => {
86101
break Some(IterExpr {
87-
span,
88102
fields,
89103
path: cx.qpath_res(path, e.hir_id),
104+
can_move,
90105
});
91106
},
92107
ExprKind::Field(base, name) => {
@@ -99,10 +114,12 @@ fn try_parse_iter_expr(cx: &LateContext<'_>, mut e: &Expr<'_>) -> Option<IterExp
99114
// Shouldn't have side effects, but there's no way to trace which field is used. So forget which fields have
100115
// already been seen.
101116
ExprKind::Index(base, idx) if !idx.can_have_side_effects() => {
117+
can_move = false;
102118
fields.clear();
103119
e = base;
104120
},
105121
ExprKind::Unary(UnOp::Deref, base) => {
122+
can_move = false;
106123
fields.clear();
107124
e = base;
108125
},

tests/ui/while_let_on_iterator.fixed

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,36 @@ fn exact_match_with_single_field() {
372372
}
373373
}
374374

375+
fn custom_deref() {
376+
struct S1<T> {
377+
x: T,
378+
}
379+
struct S2<T>(S1<T>);
380+
impl<T> core::ops::Deref for S2<T> {
381+
type Target = S1<T>;
382+
fn deref(&self) -> &Self::Target {
383+
&self.0
384+
}
385+
}
386+
impl<T> core::ops::DerefMut for S2<T> {
387+
fn deref_mut(&mut self) -> &mut Self::Target {
388+
&mut self.0
389+
}
390+
}
391+
392+
let mut s = S2(S1 { x: 0..10 });
393+
for x in s.x.by_ref() {
394+
println!("{}", x);
395+
}
396+
}
397+
398+
fn issue_8113() {
399+
let mut x = [0..10];
400+
for x in x[0].by_ref() {
401+
println!("{}", x);
402+
}
403+
}
404+
375405
fn main() {
376406
let mut it = 0..20;
377407
for _ in it {

tests/ui/while_let_on_iterator.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,36 @@ fn exact_match_with_single_field() {
372372
}
373373
}
374374

375+
fn custom_deref() {
376+
struct S1<T> {
377+
x: T,
378+
}
379+
struct S2<T>(S1<T>);
380+
impl<T> core::ops::Deref for S2<T> {
381+
type Target = S1<T>;
382+
fn deref(&self) -> &Self::Target {
383+
&self.0
384+
}
385+
}
386+
impl<T> core::ops::DerefMut for S2<T> {
387+
fn deref_mut(&mut self) -> &mut Self::Target {
388+
&mut self.0
389+
}
390+
}
391+
392+
let mut s = S2(S1 { x: 0..10 });
393+
while let Some(x) = s.x.next() {
394+
println!("{}", x);
395+
}
396+
}
397+
398+
fn issue_8113() {
399+
let mut x = [0..10];
400+
while let Some(x) = x[0].next() {
401+
println!("{}", x);
402+
}
403+
}
404+
375405
fn main() {
376406
let mut it = 0..20;
377407
while let Some(..) = it.next() {

tests/ui/while_let_on_iterator.stderr

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,22 @@ LL | while let Some(x) = it.0.next() {
123123
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.0.by_ref()`
124124

125125
error: this loop could be written as a `for` loop
126-
--> $DIR/while_let_on_iterator.rs:377:5
126+
--> $DIR/while_let_on_iterator.rs:393:5
127+
|
128+
LL | while let Some(x) = s.x.next() {
129+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in s.x.by_ref()`
130+
131+
error: this loop could be written as a `for` loop
132+
--> $DIR/while_let_on_iterator.rs:400:5
133+
|
134+
LL | while let Some(x) = x[0].next() {
135+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in x[0].by_ref()`
136+
137+
error: this loop could be written as a `for` loop
138+
--> $DIR/while_let_on_iterator.rs:407:5
127139
|
128140
LL | while let Some(..) = it.next() {
129141
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it`
130142

131-
error: aborting due to 21 previous errors
143+
error: aborting due to 23 previous errors
132144

0 commit comments

Comments
 (0)