Account for move error in the spread operator on struct literals

We attempt to suggest an appropriate clone for move errors on expressions
like `S { ..s }` where a field isn't `Copy`. If we can't suggest, we still don't
emit the incorrect suggestion of `S { ..s }.clone()`.

```
error[E0509]: cannot move out of type `S<K>`, which implements the `Drop` trait
  --> $DIR/borrowck-struct-update-with-dtor.rs:28:19
   |
LL |         let _s2 = S { a: 2, ..s0 };
   |                   ^^^^^^^^^^^^^^^^
   |                   |
   |                   cannot move out of here
   |                   move occurs because `s0.c` has type `K`, which does not implement the `Copy` trait
   |
help: clone the value from the field instead of using the spread operator syntax
   |
LL |         let _s2 = S { a: 2, c: s0.c.clone(), ..s0 };
   |                           +++++++++++++++++
```
```
error[E0509]: cannot move out of type `S<()>`, which implements the `Drop` trait
  --> $DIR/borrowck-struct-update-with-dtor.rs:20:19
   |
LL |         let _s2 = S { a: 2, ..s0 };
   |                   ^^^^^^^^^^^^^^^^
   |                   |
   |                   cannot move out of here
   |                   move occurs because `s0.b` has type `B`, which does not implement the `Copy` trait
   |
note: `B` doesn't implement `Copy` or `Clone`
  --> $DIR/borrowck-struct-update-with-dtor.rs:4:1
   |
LL | struct B;
   | ^^^^^^^^
help: if `B` implemented `Clone`, you could clone the value from the field instead of using the spread operator syntax
   |
LL |         let _s2 = S { a: 2, b: s0.b.clone(), ..s0 };
   |                           +++++++++++++++++
```
This commit is contained in:
Esteban Küber 2024-03-16 18:19:17 +00:00
parent 4ca876b7a4
commit d578ac9e47
4 changed files with 344 additions and 40 deletions

View file

@ -999,6 +999,93 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
can_suggest_clone can_suggest_clone
} }
fn suggest_cloning_on_spread_operator(
&self,
err: &mut Diag<'_>,
ty: Ty<'tcx>,
expr: &'cx hir::Expr<'cx>,
) {
let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());
let hir::ExprKind::Struct(struct_qpath, fields, Some(base)) = expr.kind else { return };
let hir::QPath::Resolved(_, path) = struct_qpath else { return };
let hir::def::Res::Def(_, def_id) = path.res else { return };
let Some(expr_ty) = typeck_results.node_type_opt(expr.hir_id) else { return };
let ty::Adt(def, args) = expr_ty.kind() else { return };
let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = base.kind else { return };
let (hir::def::Res::Local(_)
| hir::def::Res::Def(
DefKind::Const | DefKind::ConstParam | DefKind::Static { .. } | DefKind::AssocConst,
_,
)) = path.res
else {
return;
};
let Ok(base_str) = self.infcx.tcx.sess.source_map().span_to_snippet(base.span) else {
return;
};
// 1. look for the fields of type `ty`.
// 2. check if they are clone and add them to suggestion
// 3. check if there are any values left to `..` and remove it if not
// 4. emit suggestion to clone the field directly as `bar: base.bar.clone()`
let mut final_field_count = fields.len();
let Some(variant) = def.variants().iter().find(|variant| variant.def_id == def_id) else {
// When we have an enum, look for the variant that corresponds to the variant the user
// wrote.
return;
};
let mut sugg = vec![];
for field in &variant.fields {
// In practice unless there are more than one field with the same type, we'll be
// suggesting a single field at a type, because we don't aggregate multiple borrow
// checker errors involving the spread operator into a single one.
let field_ty = field.ty(self.infcx.tcx, args);
let ident = field.ident(self.infcx.tcx);
if field_ty == ty && fields.iter().all(|field| field.ident.name != ident.name) {
// Suggest adding field and cloning it.
sugg.push(format!("{ident}: {base_str}.{ident}.clone()"));
final_field_count += 1;
}
}
let (span, sugg) = match fields {
[.., last] => (
if final_field_count == variant.fields.len() {
// We'll remove the `..base` as there aren't any fields left.
last.span.shrink_to_hi().with_hi(base.span.hi())
} else {
last.span.shrink_to_hi()
},
format!(", {}", sugg.join(", ")),
),
// Account for no fields in suggestion span.
[] => (
expr.span.with_lo(struct_qpath.span().hi()),
if final_field_count == variant.fields.len() {
// We'll remove the `..base` as there aren't any fields left.
format!(" {{ {} }}", sugg.join(", "))
} else {
format!(" {{ {}, ..{base_str} }}", sugg.join(", "))
},
),
};
let prefix = if !self.implements_clone(ty) {
let msg = format!("`{ty}` doesn't implement `Copy` or `Clone`");
if let ty::Adt(def, _) = ty.kind() {
err.span_note(self.infcx.tcx.def_span(def.did()), msg);
} else {
err.note(msg);
}
format!("if `{ty}` implemented `Clone`, you could ")
} else {
String::new()
};
let msg = format!(
"{prefix}clone the value from the field instead of using the spread operator syntax",
);
err.span_suggestion_verbose(span, msg, sugg, Applicability::MachineApplicable);
}
pub(crate) fn suggest_cloning( pub(crate) fn suggest_cloning(
&self, &self,
err: &mut Diag<'_>, err: &mut Diag<'_>,
@ -1006,6 +1093,15 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
mut expr: &'cx hir::Expr<'cx>, mut expr: &'cx hir::Expr<'cx>,
mut other_expr: Option<&'cx hir::Expr<'cx>>, mut other_expr: Option<&'cx hir::Expr<'cx>>,
) { ) {
if let hir::ExprKind::Struct(_, _, Some(_)) = expr.kind {
// We have `S { foo: val, ..base }`. In `check_aggregate_rvalue` we have a single
// `Location` that covers both the `S { ... }` literal, all of its fields and the
// `base`. If the move happens because of `S { foo: val, bar: base.bar }` the `expr`
// will already be correct. Instead, we see if we can suggest writing.
self.suggest_cloning_on_spread_operator(err, ty, expr);
return;
}
if let Some(some_other_expr) = other_expr if let Some(some_other_expr) = other_expr
&& let Some(parent_binop) = && let Some(parent_binop) =
self.infcx.tcx.hir().parent_iter(expr.hir_id).find_map(|n| { self.infcx.tcx.hir().parent_iter(expr.hir_id).find_map(|n| {
@ -1087,11 +1183,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
} else { } else {
false false
}) })
&& let Some(clone_trait_def) = self.infcx.tcx.lang_items().clone_trait() && self.implements_clone(call_ty)
&& self
.infcx
.type_implements_trait(clone_trait_def, [call_ty], self.param_env)
.must_apply_modulo_regions()
&& self.suggest_cloning_inner(err, call_ty, parent) && self.suggest_cloning_inner(err, call_ty, parent)
{ {
return; return;
@ -1101,14 +1193,18 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
} }
} }
let ty = ty.peel_refs(); let ty = ty.peel_refs();
if let Some(clone_trait_def) = self.infcx.tcx.lang_items().clone_trait() if self.implements_clone(ty) {
&& self self.suggest_cloning_inner(err, ty, expr);
.infcx // } else {
// err.note(format!("if `{ty}` implemented `Clone`, you could clone the value"));
}
}
fn implements_clone(&self, ty: Ty<'tcx>) -> bool {
let Some(clone_trait_def) = self.infcx.tcx.lang_items().clone_trait() else { return false };
self.infcx
.type_implements_trait(clone_trait_def, [ty], self.param_env) .type_implements_trait(clone_trait_def, [ty], self.param_env)
.must_apply_modulo_regions() .must_apply_modulo_regions()
{
self.suggest_cloning_inner(err, ty, expr);
}
} }
pub(crate) fn clone_on_reference(&self, expr: &hir::Expr<'_>) -> Option<Span> { pub(crate) fn clone_on_reference(&self, expr: &hir::Expr<'_>) -> Option<Span> {

View file

@ -2,20 +2,63 @@
// move, when the struct implements Drop. // move, when the struct implements Drop.
struct B; struct B;
struct S { a: isize, b: B } struct S<K> { a: isize, b: B, c: K }
impl Drop for S { fn drop(&mut self) { } } impl<K> Drop for S<K> { fn drop(&mut self) { } }
struct T { a: isize, mv: Box<isize> } struct T { a: isize, b: Box<isize> }
impl Drop for T { fn drop(&mut self) { } } impl Drop for T { fn drop(&mut self) { } }
fn f(s0:S) { struct V<K> { a: isize, b: Box<isize>, c: K }
impl<K> Drop for V<K> { fn drop(&mut self) { } }
#[derive(Clone)]
struct Clonable;
mod not_all_clone {
use super::*;
fn a(s0: S<()>) {
let _s2 = S { a: 2, ..s0 }; let _s2 = S { a: 2, ..s0 };
//~^ ERROR [E0509] //~^ ERROR [E0509]
} }
fn b(s0: S<B>) {
fn g(s0:T) { let _s2 = S { a: 2, ..s0 };
//~^ ERROR [E0509]
//~| ERROR [E0509]
}
fn c<K: Clone>(s0: S<K>) {
let _s2 = S { a: 2, ..s0 };
//~^ ERROR [E0509]
//~| ERROR [E0509]
}
}
mod all_clone {
use super::*;
fn a(s0: T) {
let _s2 = T { a: 2, ..s0 }; let _s2 = T { a: 2, ..s0 };
//~^ ERROR [E0509] //~^ ERROR [E0509]
} }
fn b(s0: T) {
let _s2 = T { ..s0 };
//~^ ERROR [E0509]
}
fn c(s0: T) {
let _s2 = T { a: 2, b: s0.b };
//~^ ERROR [E0509]
}
fn d<K: Clone>(s0: V<K>) {
let _s2 = V { a: 2, ..s0 };
//~^ ERROR [E0509]
//~| ERROR [E0509]
}
fn e(s0: V<Clonable>) {
let _s2 = V { a: 2, ..s0 };
//~^ ERROR [E0509]
//~| ERROR [E0509]
}
}
fn main() { } fn main() { }

View file

@ -1,26 +1,191 @@
error[E0509]: cannot move out of type `S`, which implements the `Drop` trait error[E0509]: cannot move out of type `S<()>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:12:15 --> $DIR/borrowck-struct-update-with-dtor.rs:20:19
| |
LL | let _s2 = S { a: 2, ..s0 }; LL | let _s2 = S { a: 2, ..s0 };
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
| | | |
| cannot move out of here | cannot move out of here
| move occurs because `s0.b` has type `B`, which does not implement the `Copy` trait | move occurs because `s0.b` has type `B`, which does not implement the `Copy` trait
error[E0509]: cannot move out of type `T`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:17:15
| |
LL | let _s2 = T{a: 2, ..s0}; note: `B` doesn't implement `Copy` or `Clone`
| ^^^^^^^^^^^^^ --> $DIR/borrowck-struct-update-with-dtor.rs:4:1
|
LL | struct B;
| ^^^^^^^^
help: if `B` implemented `Clone`, you could clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = S { a: 2, b: s0.b.clone(), ..s0 };
| +++++++++++++++++
error[E0509]: cannot move out of type `S<B>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:24:19
|
LL | let _s2 = S { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| | | |
| cannot move out of here | cannot move out of here
| move occurs because `s0.mv` has type `Box<isize>`, which does not implement the `Copy` trait | move occurs because `s0.b` has type `B`, which does not implement the `Copy` trait
|
note: `B` doesn't implement `Copy` or `Clone`
--> $DIR/borrowck-struct-update-with-dtor.rs:4:1
|
LL | struct B;
| ^^^^^^^^
help: if `B` implemented `Clone`, you could clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = S { a: 2, b: s0.b.clone(), c: s0.c.clone() };
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error[E0509]: cannot move out of type `S<B>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:24:19
|
LL | let _s2 = S { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.c` has type `B`, which does not implement the `Copy` trait
|
note: `B` doesn't implement `Copy` or `Clone`
--> $DIR/borrowck-struct-update-with-dtor.rs:4:1
|
LL | struct B;
| ^^^^^^^^
help: if `B` implemented `Clone`, you could clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = S { a: 2, b: s0.b.clone(), c: s0.c.clone() };
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error[E0509]: cannot move out of type `S<K>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:29:19
|
LL | let _s2 = S { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.b` has type `B`, which does not implement the `Copy` trait
|
note: `B` doesn't implement `Copy` or `Clone`
--> $DIR/borrowck-struct-update-with-dtor.rs:4:1
|
LL | struct B;
| ^^^^^^^^
help: if `B` implemented `Clone`, you could clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = S { a: 2, b: s0.b.clone(), ..s0 };
| +++++++++++++++++
error[E0509]: cannot move out of type `S<K>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:29:19
|
LL | let _s2 = S { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.c` has type `K`, which does not implement the `Copy` trait
|
help: clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = S { a: 2, c: s0.c.clone(), ..s0 };
| +++++++++++++++++
error[E0509]: cannot move out of type `T`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:37:19
|
LL | let _s2 = T { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.b` has type `Box<isize>`, which does not implement the `Copy` trait
|
help: clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = T { a: 2, b: s0.b.clone() };
| ~~~~~~~~~~~~~~~~~
error[E0509]: cannot move out of type `T`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:42:19
|
LL | let _s2 = T { ..s0 };
| ^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.b` has type `Box<isize>`, which does not implement the `Copy` trait
|
help: clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = T { b: s0.b.clone(), ..s0 };
| ~~~~~~~~~~~~~~~~~~~~~~~~~
error[E0509]: cannot move out of type `T`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:47:32
|
LL | let _s2 = T { a: 2, b: s0.b };
| ^^^^
| |
| cannot move out of here
| move occurs because `s0.b` has type `Box<isize>`, which does not implement the `Copy` trait
| |
help: consider cloning the value if the performance cost is acceptable help: consider cloning the value if the performance cost is acceptable
| |
LL | let _s2 = T{a: 2, ..s0}.clone(); LL | let _s2 = T { a: 2, b: s0.b.clone() };
| ++++++++ | ++++++++
error: aborting due to 2 previous errors error[E0509]: cannot move out of type `V<K>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:52:19
|
LL | let _s2 = V { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.b` has type `Box<isize>`, which does not implement the `Copy` trait
|
help: clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = V { a: 2, b: s0.b.clone(), ..s0 };
| +++++++++++++++++
error[E0509]: cannot move out of type `V<K>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:52:19
|
LL | let _s2 = V { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.c` has type `K`, which does not implement the `Copy` trait
|
help: clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = V { a: 2, c: s0.c.clone(), ..s0 };
| +++++++++++++++++
error[E0509]: cannot move out of type `V<Clonable>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:58:19
|
LL | let _s2 = V { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.b` has type `Box<isize>`, which does not implement the `Copy` trait
|
help: clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = V { a: 2, b: s0.b.clone(), ..s0 };
| +++++++++++++++++
error[E0509]: cannot move out of type `V<Clonable>`, which implements the `Drop` trait
--> $DIR/borrowck-struct-update-with-dtor.rs:58:19
|
LL | let _s2 = V { a: 2, ..s0 };
| ^^^^^^^^^^^^^^^^
| |
| cannot move out of here
| move occurs because `s0.c` has type `Clonable`, which does not implement the `Copy` trait
|
help: clone the value from the field instead of using the spread operator syntax
|
LL | let _s2 = V { a: 2, c: s0.c.clone(), ..s0 };
| +++++++++++++++++
error: aborting due to 12 previous errors
For more information about this error, try `rustc --explain E0509`. For more information about this error, try `rustc --explain E0509`.

View file

@ -7,10 +7,10 @@ LL | let _b = A { y: Arc::new(3), ..a };
| cannot move out of here | cannot move out of here
| move occurs because `a.x` has type `Arc<isize>`, which does not implement the `Copy` trait | move occurs because `a.x` has type `Arc<isize>`, which does not implement the `Copy` trait
| |
help: clone the value to increment its reference count help: clone the value from the field instead of using the spread operator syntax
| |
LL | let _b = A { y: Arc::new(3), ..a }.clone(); LL | let _b = A { y: Arc::new(3), x: a.x.clone() };
| ++++++++ | ~~~~~~~~~~~~~~~~
error: aborting due to 1 previous error error: aborting due to 1 previous error