Skip to content

Commit 6eaa7fb

Browse files
committed
Auto merge of rust-lang#122603 - estebank:clone-o-rama, r=lcnr
Detect borrow checker errors where `.clone()` would be an appropriate user action When a value is moved twice, suggest cloning the earlier move: ``` error[E0509]: cannot move out of type `U2`, which implements the `Drop` trait --> $DIR/union-move.rs:49:18 | LL | move_out(x.f1_nocopy); | ^^^^^^^^^^^ | | | cannot move out of here | move occurs because `x.f1_nocopy` has type `ManuallyDrop<RefCell<i32>>`, which does not implement the `Copy` trait | help: consider cloning the value if the performance cost is acceptable | LL | move_out(x.f1_nocopy.clone()); | ++++++++ ``` When a value is borrowed by an `fn` call, consider if cloning the result of the call would be reasonable, and suggest cloning that, instead of the argument: ``` error[E0505]: cannot move out of `a` because it is borrowed --> $DIR/variance-issue-20533.rs:53:14 | LL | let a = AffineU32(1); | - binding `a` declared here LL | let x = bat(&a); | -- borrow of `a` occurs here LL | drop(a); | ^ move out of `a` occurs here LL | drop(x); | - borrow later used here | help: consider cloning the value if the performance cost is acceptable | LL | let x = bat(&a).clone(); | ++++++++ ``` otherwise, suggest cloning the argument: ``` error[E0505]: cannot move out of `a` because it is borrowed --> $DIR/variance-issue-20533.rs:59:14 | LL | let a = ClonableAffineU32(1); | - binding `a` declared here LL | let x = foo(&a); | -- borrow of `a` occurs here LL | drop(a); | ^ move out of `a` occurs here LL | drop(x); | - borrow later used here | help: consider cloning the value if the performance cost is acceptable | LL - let x = foo(&a); LL + let x = foo(a.clone()); | ``` This suggestion doesn't attempt to square out the types between what's cloned and what the `fn` expects, to allow the user to make a determination on whether to change the `fn` call or `fn` definition themselves. Special case move errors caused by `FnOnce`: ``` error[E0382]: use of moved value: `blk` --> $DIR/once-cant-call-twice-on-heap.rs:8:5 | LL | fn foo<F:FnOnce()>(blk: F) { | --- move occurs because `blk` has type `F`, which does not implement the `Copy` trait LL | blk(); | ----- `blk` moved due to this call LL | blk(); | ^^^ value used here after move | note: `FnOnce` closures can only be called once --> $DIR/once-cant-call-twice-on-heap.rs:6:10 | LL | fn foo<F:FnOnce()>(blk: F) { | ^^^^^^^^ `F` is made to be an `FnOnce` closure here LL | blk(); | ----- this value implements `FnOnce`, which causes it to be moved when called ``` Account for redundant `.clone()` calls in resulting suggestions: ``` error[E0507]: cannot move out of dereference of `S` --> $DIR/needs-clone-through-deref.rs:15:18 | LL | for _ in self.clone().into_iter() {} | ^^^^^^^^^^^^ ----------- value moved due to this method call | | | move occurs because value has type `Vec<usize>`, which does not implement the `Copy` trait | note: `into_iter` takes ownership of the receiver `self`, which moves value --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL help: you can `clone` the value and consume it, but this might not be your desired behavior | LL | for _ in <Vec<usize> as Clone>::clone(&self).into_iter() {} | ++++++++++++++++++++++++++++++ ~ ``` We use the presence of `&mut` values in a move error as a proxy for the user caring about side effects, so we don't emit a clone suggestion in that case: ``` error[E0505]: cannot move out of `s` because it is borrowed --> $DIR/borrowck-overloaded-index-move-index.rs:53:7 | LL | let mut s = "hello".to_string(); | ----- binding `s` declared here LL | let rs = &mut s; | ------ borrow of `s` occurs here ... LL | f[s] = 10; | ^ move out of `s` occurs here ... LL | use_mut(rs); | -- borrow later used here ``` We properly account for `foo += foo;` errors where we *don't* suggest `foo.clone() += foo;`, instead suggesting `foo += foo.clone();`. --- Each commit can be reviewed in isolation. There are some "cleanup" commits, but kept them separate in order to show *why* specific changes were being made, and their effect on tests' output. Fix rust-lang#49693, CC rust-lang#64167.
2 parents f96442b + 4c7213c commit 6eaa7fb

File tree

124 files changed

+1742
-143
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+1742
-143
lines changed

compiler/rustc_borrowck/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ borrowck_move_unsized =
8787
borrowck_moved_a_fn_once_in_call =
8888
this value implements `FnOnce`, which causes it to be moved when called
8989
90+
borrowck_moved_a_fn_once_in_call_call =
91+
`FnOnce` closures can only be called once
92+
93+
borrowck_moved_a_fn_once_in_call_def =
94+
`{$ty}` is made to be an `FnOnce` closure here
95+
9096
borrowck_moved_due_to_await =
9197
{$place_name} {$is_partial ->
9298
[true] partially moved

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+372-52
Large diffs are not rendered by default.

compiler/rustc_borrowck/src/diagnostics/mod.rs

+90-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::session_diagnostics::{
55
CaptureVarKind, CaptureVarPathUseCause, OnClosureNote,
66
};
77
use rustc_errors::{Applicability, Diag};
8+
use rustc_errors::{DiagCtxt, MultiSpan};
89
use rustc_hir as hir;
910
use rustc_hir::def::{CtorKind, Namespace};
1011
use rustc_hir::CoroutineKind;
@@ -29,6 +30,8 @@ use rustc_trait_selection::infer::InferCtxtExt;
2930
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _;
3031
use rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions;
3132

33+
use crate::fluent_generated as fluent;
34+
3235
use super::borrow_set::BorrowData;
3336
use super::MirBorrowckCtxt;
3437

@@ -587,7 +590,7 @@ impl UseSpans<'_> {
587590
#[allow(rustc::diagnostic_outside_of_impl)]
588591
pub(super) fn args_subdiag(
589592
self,
590-
dcx: &rustc_errors::DiagCtxt,
593+
dcx: &DiagCtxt,
591594
err: &mut Diag<'_>,
592595
f: impl FnOnce(Span) -> CaptureArgLabel,
593596
) {
@@ -601,7 +604,7 @@ impl UseSpans<'_> {
601604
#[allow(rustc::diagnostic_outside_of_impl)]
602605
pub(super) fn var_path_only_subdiag(
603606
self,
604-
dcx: &rustc_errors::DiagCtxt,
607+
dcx: &DiagCtxt,
605608
err: &mut Diag<'_>,
606609
action: crate::InitializationRequiringAction,
607610
) {
@@ -639,7 +642,7 @@ impl UseSpans<'_> {
639642
#[allow(rustc::diagnostic_outside_of_impl)]
640643
pub(super) fn var_subdiag(
641644
self,
642-
dcx: &rustc_errors::DiagCtxt,
645+
dcx: &DiagCtxt,
643646
err: &mut Diag<'_>,
644647
kind: Option<rustc_middle::mir::BorrowKind>,
645648
f: impl FnOnce(hir::ClosureKind, Span) -> CaptureVarCause,
@@ -1034,7 +1037,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10341037
.map(|n| format!("`{n}`"))
10351038
.unwrap_or_else(|| "value".to_owned());
10361039
match kind {
1037-
CallKind::FnCall { fn_trait_id, .. }
1040+
CallKind::FnCall { fn_trait_id, self_ty }
10381041
if Some(fn_trait_id) == self.infcx.tcx.lang_items().fn_once_trait() =>
10391042
{
10401043
err.subdiagnostic(
@@ -1046,7 +1049,79 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10461049
is_loop_message,
10471050
},
10481051
);
1049-
err.subdiagnostic(self.dcx(), CaptureReasonNote::FnOnceMoveInCall { var_span });
1052+
// Check if the move occurs on a value because of a call on a closure that comes
1053+
// from a type parameter `F: FnOnce()`. If so, we provide a targeted `note`:
1054+
// ```
1055+
// error[E0382]: use of moved value: `blk`
1056+
// --> $DIR/once-cant-call-twice-on-heap.rs:8:5
1057+
// |
1058+
// LL | fn foo<F:FnOnce()>(blk: F) {
1059+
// | --- move occurs because `blk` has type `F`, which does not implement the `Copy` trait
1060+
// LL | blk();
1061+
// | ----- `blk` moved due to this call
1062+
// LL | blk();
1063+
// | ^^^ value used here after move
1064+
// |
1065+
// note: `FnOnce` closures can only be called once
1066+
// --> $DIR/once-cant-call-twice-on-heap.rs:6:10
1067+
// |
1068+
// LL | fn foo<F:FnOnce()>(blk: F) {
1069+
// | ^^^^^^^^ `F` is made to be an `FnOnce` closure here
1070+
// LL | blk();
1071+
// | ----- this value implements `FnOnce`, which causes it to be moved when called
1072+
// ```
1073+
if let ty::Param(param_ty) = self_ty.kind()
1074+
&& let generics = self.infcx.tcx.generics_of(self.mir_def_id())
1075+
&& let param = generics.type_param(param_ty, self.infcx.tcx)
1076+
&& let Some(hir_generics) = self
1077+
.infcx
1078+
.tcx
1079+
.typeck_root_def_id(self.mir_def_id().to_def_id())
1080+
.as_local()
1081+
.and_then(|def_id| self.infcx.tcx.hir().get_generics(def_id))
1082+
&& let spans = hir_generics
1083+
.predicates
1084+
.iter()
1085+
.filter_map(|pred| match pred {
1086+
hir::WherePredicate::BoundPredicate(pred) => Some(pred),
1087+
_ => None,
1088+
})
1089+
.filter(|pred| {
1090+
if let Some((id, _)) = pred.bounded_ty.as_generic_param() {
1091+
id == param.def_id
1092+
} else {
1093+
false
1094+
}
1095+
})
1096+
.flat_map(|pred| pred.bounds)
1097+
.filter_map(|bound| {
1098+
if let Some(trait_ref) = bound.trait_ref()
1099+
&& let Some(trait_def_id) = trait_ref.trait_def_id()
1100+
&& trait_def_id == fn_trait_id
1101+
{
1102+
Some(bound.span())
1103+
} else {
1104+
None
1105+
}
1106+
})
1107+
.collect::<Vec<Span>>()
1108+
&& !spans.is_empty()
1109+
{
1110+
let mut span: MultiSpan = spans.clone().into();
1111+
for sp in spans {
1112+
span.push_span_label(sp, fluent::borrowck_moved_a_fn_once_in_call_def);
1113+
}
1114+
span.push_span_label(
1115+
fn_call_span,
1116+
fluent::borrowck_moved_a_fn_once_in_call,
1117+
);
1118+
err.span_note(span, fluent::borrowck_moved_a_fn_once_in_call_call);
1119+
} else {
1120+
err.subdiagnostic(
1121+
self.dcx(),
1122+
CaptureReasonNote::FnOnceMoveInCall { var_span },
1123+
);
1124+
}
10501125
}
10511126
CallKind::Operator { self_arg, trait_id, .. } => {
10521127
let self_arg = self_arg.unwrap();
@@ -1212,13 +1287,21 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
12121287
.iter_projections()
12131288
.any(|(_, elem)| matches!(elem, ProjectionElem::Deref))
12141289
{
1290+
let (start, end) = if let Some(expr) = self.find_expr(move_span)
1291+
&& let Some(_) = self.clone_on_reference(expr)
1292+
&& let hir::ExprKind::MethodCall(_, rcvr, _, _) = expr.kind
1293+
{
1294+
(move_span.shrink_to_lo(), move_span.with_lo(rcvr.span.hi()))
1295+
} else {
1296+
(move_span.shrink_to_lo(), move_span.shrink_to_hi())
1297+
};
12151298
vec![
12161299
// We use the fully-qualified path because `.clone()` can
12171300
// sometimes choose `<&T as Clone>` instead of `<T as Clone>`
12181301
// when going through auto-deref, so this ensures that doesn't
12191302
// happen, causing suggestions for `.clone().clone()`.
1220-
(move_span.shrink_to_lo(), format!("<{ty} as Clone>::clone(&")),
1221-
(move_span.shrink_to_hi(), ")".to_string()),
1303+
(start, format!("<{ty} as Clone>::clone(&")),
1304+
(end, ")".to_string()),
12221305
]
12231306
} else {
12241307
vec![(move_span.shrink_to_hi(), ".clone()".to_string())]

compiler/rustc_borrowck/src/diagnostics/move_errors.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
435435

436436
fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diag<'_>, span: Span) {
437437
match error {
438-
GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {
438+
GroupedMoveError::MovesFromPlace {
439+
mut binds_to, move_from, span: other_span, ..
440+
} => {
439441
self.add_borrow_suggestions(err, span);
440442
if binds_to.is_empty() {
441443
let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;
@@ -444,6 +446,10 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
444446
None => "value".to_string(),
445447
};
446448

449+
if let Some(expr) = self.find_expr(span) {
450+
self.suggest_cloning(err, place_ty, expr, self.find_expr(other_span));
451+
}
452+
447453
err.subdiagnostic(
448454
self.dcx(),
449455
crate::session_diagnostics::TypeNoCopy::Label {
@@ -468,19 +474,24 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
468474
}
469475
// No binding. Nothing to suggest.
470476
GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {
471-
let span = use_spans.var_or_use();
477+
let use_span = use_spans.var_or_use();
472478
let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;
473479
let place_desc = match self.describe_place(original_path.as_ref()) {
474480
Some(desc) => format!("`{desc}`"),
475481
None => "value".to_string(),
476482
};
483+
484+
if let Some(expr) = self.find_expr(use_span) {
485+
self.suggest_cloning(err, place_ty, expr, self.find_expr(span));
486+
}
487+
477488
err.subdiagnostic(
478489
self.dcx(),
479490
crate::session_diagnostics::TypeNoCopy::Label {
480491
is_partial_move: false,
481492
ty: place_ty,
482493
place: &place_desc,
483-
span,
494+
span: use_span,
484495
},
485496
);
486497

@@ -582,6 +593,11 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
582593

583594
if binds_to.len() == 1 {
584595
let place_desc = &format!("`{}`", self.local_names[*local].unwrap());
596+
597+
if let Some(expr) = self.find_expr(binding_span) {
598+
self.suggest_cloning(err, bind_to.ty, expr, None);
599+
}
600+
585601
err.subdiagnostic(
586602
self.dcx(),
587603
crate::session_diagnostics::TypeNoCopy::Label {

tests/ui/associated-types/associated-types-outlives.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
// fn body, causing this (invalid) code to be accepted.
44

55
pub trait Foo<'a> {
6-
type Bar;
6+
type Bar: Clone;
77
}
88

9-
impl<'a, T:'a> Foo<'a> for T {
9+
impl<'a, T: 'a> Foo<'a> for T {
1010
type Bar = &'a T;
1111
}
1212

tests/ui/associated-types/associated-types-outlives.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ LL | drop(x);
1010
| ^ move out of `x` occurs here
1111
LL | return f(y);
1212
| - borrow later used here
13+
|
14+
help: consider cloning the value if the performance cost is acceptable
15+
|
16+
LL | 's: loop { y = denormalise(&x).clone(); break }
17+
| ++++++++
1318

1419
error: aborting due to 1 previous error
1520

tests/ui/associated-types/issue-25700.stderr

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ LL | drop(t);
77
| - value moved here
88
LL | drop(t);
99
| ^ value used here after move
10+
|
11+
note: if `S<()>` implemented `Clone`, you could clone the value
12+
--> $DIR/issue-25700.rs:1:1
13+
|
14+
LL | struct S<T: 'static>(#[allow(dead_code)] Option<&'static T>);
15+
| ^^^^^^^^^^^^^^^^^^^^
1016

1117
error: aborting due to 1 previous error
1218

tests/ui/augmented-assignments.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::ops::AddAssign;
22

3+
#[derive(Clone)]
34
struct Int(i32);
45

56
impl AddAssign for Int {
@@ -16,6 +17,7 @@ fn main() {
1617
x;
1718
//~^ ERROR cannot move out of `x` because it is borrowed
1819
//~| move out of `x` occurs here
20+
//~| HELP consider cloning
1921

2022
let y = Int(2);
2123
//~^ HELP consider changing this to be mutable

tests/ui/augmented-assignments.stderr

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0505]: cannot move out of `x` because it is borrowed
2-
--> $DIR/augmented-assignments.rs:16:5
2+
--> $DIR/augmented-assignments.rs:17:5
33
|
44
LL | let mut x = Int(1);
55
| ----- binding `x` declared here
@@ -8,9 +8,14 @@ LL | x
88
...
99
LL | x;
1010
| ^ move out of `x` occurs here
11+
|
12+
help: consider cloning the value if the performance cost is acceptable
13+
|
14+
LL | x.clone();
15+
| ++++++++
1116

1217
error[E0596]: cannot borrow `y` as mutable, as it is not declared as mutable
13-
--> $DIR/augmented-assignments.rs:23:5
18+
--> $DIR/augmented-assignments.rs:25:5
1419
|
1520
LL | y
1621
| ^ cannot borrow as mutable

tests/ui/borrowck/borrow-tuple-fields.stderr

+12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ LL | let y = x;
1010
LL |
1111
LL | r.use_ref();
1212
| - borrow later used here
13+
|
14+
help: consider cloning the value if the performance cost is acceptable
15+
|
16+
LL - let r = &x.0;
17+
LL + let r = x.0.clone();
18+
|
1319

1420
error[E0502]: cannot borrow `x.0` as mutable because it is also borrowed as immutable
1521
--> $DIR/borrow-tuple-fields.rs:18:13
@@ -42,6 +48,12 @@ LL | let y = x;
4248
| ^ move out of `x` occurs here
4349
LL | r.use_ref();
4450
| - borrow later used here
51+
|
52+
help: consider cloning the value if the performance cost is acceptable
53+
|
54+
LL - let r = &x.0;
55+
LL + let r = x.0.clone();
56+
|
4557

4658
error[E0502]: cannot borrow `x.0` as mutable because it is also borrowed as immutable
4759
--> $DIR/borrow-tuple-fields.rs:33:13

tests/ui/borrowck/borrowck-bad-nested-calls-move.stderr

+12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ LL | &*a,
1010
| --- borrow of `*a` occurs here
1111
LL | a);
1212
| ^ move out of `a` occurs here
13+
|
14+
help: consider cloning the value if the performance cost is acceptable
15+
|
16+
LL - &*a,
17+
LL + a.clone(),
18+
|
1319

1420
error[E0505]: cannot move out of `a` because it is borrowed
1521
--> $DIR/borrowck-bad-nested-calls-move.rs:32:9
@@ -22,6 +28,12 @@ LL | &*a,
2228
| --- borrow of `*a` occurs here
2329
LL | a);
2430
| ^ move out of `a` occurs here
31+
|
32+
help: consider cloning the value if the performance cost is acceptable
33+
|
34+
LL - &*a,
35+
LL + a.clone(),
36+
|
2537

2638
error: aborting due to 2 previous errors
2739

tests/ui/borrowck/borrowck-closures-slice-patterns.stderr

+11
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ LL | let [y, z @ ..] = x;
3838
LL | };
3939
LL | &x;
4040
| ^^ value borrowed here after move
41+
|
42+
help: consider cloning the value if the performance cost is acceptable
43+
|
44+
LL | let [y, z @ ..] = x.clone();
45+
| ++++++++
4146

4247
error[E0502]: cannot borrow `*x` as mutable because it is also borrowed as immutable
4348
--> $DIR/borrowck-closures-slice-patterns.rs:33:13
@@ -79,6 +84,12 @@ LL | let [y, z @ ..] = *x;
7984
LL | };
8085
LL | &x;
8186
| ^^ value borrowed here after move
87+
|
88+
help: consider cloning the value if the performance cost is acceptable
89+
|
90+
LL - let [y, z @ ..] = *x;
91+
LL + let [y, z @ ..] = x.clone();
92+
|
8293

8394
error[E0502]: cannot borrow `*x` as mutable because it is also borrowed as immutable
8495
--> $DIR/borrowck-closures-slice-patterns.rs:59:13

0 commit comments

Comments
 (0)