Skip to content

Commit a1a3abb

Browse files
committed
When possible, suggest cloning the result of a call instead of an argument
``` error[E0505]: cannot move out of `a` because it is borrowed --> $DIR/variance-issue-20533.rs:28:14 | LL | let a = AffineU32(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).clone(); | ++++++++ ```
1 parent 7f7f679 commit a1a3abb

File tree

6 files changed

+157
-19
lines changed

6 files changed

+157
-19
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
476476
} else if self.suggest_hoisting_call_outside_loop(err, expr) {
477477
// The place where the the type moves would be misleading to suggest clone.
478478
// #121466
479-
self.suggest_cloning(err, ty, expr);
479+
self.suggest_cloning(err, ty, expr, None);
480480
}
481481
}
482482
if let Some(pat) = finder.pat {
@@ -987,7 +987,78 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
987987
can_suggest_clone
988988
}
989989

990-
pub(crate) fn suggest_cloning(&self, err: &mut Diag<'_>, ty: Ty<'tcx>, expr: &hir::Expr<'_>) {
990+
pub(crate) fn suggest_cloning(
991+
&self,
992+
err: &mut Diag<'_>,
993+
ty: Ty<'tcx>,
994+
expr: &hir::Expr<'_>,
995+
other_expr: Option<&hir::Expr<'_>>,
996+
) {
997+
'outer: {
998+
if let ty::Ref(..) = ty.kind() {
999+
// We check for either `let binding = foo(expr, other_expr);` or
1000+
// `foo(expr, other_expr);` and if so we don't suggest an incorrect
1001+
// `foo(expr, other_expr).clone()`
1002+
if let Some(other_expr) = other_expr
1003+
&& let Some(parent_let) =
1004+
self.infcx.tcx.hir().parent_iter(expr.hir_id).find_map(|n| {
1005+
if let (hir_id, hir::Node::Local(_) | hir::Node::Stmt(_)) = n {
1006+
Some(hir_id)
1007+
} else {
1008+
None
1009+
}
1010+
})
1011+
&& let Some(other_parent_let) =
1012+
self.infcx.tcx.hir().parent_iter(other_expr.hir_id).find_map(|n| {
1013+
if let (hir_id, hir::Node::Local(_) | hir::Node::Stmt(_)) = n {
1014+
Some(hir_id)
1015+
} else {
1016+
None
1017+
}
1018+
})
1019+
&& parent_let == other_parent_let
1020+
{
1021+
// Explicitly check that we don't have `foo(&*expr, other_expr)`, as cloning the
1022+
// result of `foo(...)` won't help.
1023+
break 'outer;
1024+
}
1025+
1026+
// We're suggesting `.clone()` on an borrowed value. See if the expression we have
1027+
// is an argument to a function or method call, and try to suggest cloning the
1028+
// *result* of the call, instead of the argument. This is closest to what people
1029+
// would actually be looking for in most cases, with maybe the exception of things
1030+
// like `fn(T) -> T`, but even then it is reasonable.
1031+
let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());
1032+
let mut prev = expr;
1033+
while let hir::Node::Expr(parent) = self.infcx.tcx.parent_hir_node(prev.hir_id) {
1034+
if let hir::ExprKind::Call(..) | hir::ExprKind::MethodCall(..) = parent.kind
1035+
&& let Some(call_ty) = typeck_results.node_type_opt(parent.hir_id)
1036+
&& let call_ty = call_ty.peel_refs()
1037+
&& (!call_ty
1038+
.walk()
1039+
.any(|t| matches!(t.unpack(), ty::GenericArgKind::Lifetime(_)))
1040+
|| if let ty::Alias(ty::Projection, _) = call_ty.kind() {
1041+
// FIXME: this isn't quite right with lifetimes on assoc types,
1042+
// but ignore for now. We will only suggest cloning if
1043+
// `<Ty as Trait>::Assoc: Clone`, which should keep false positives
1044+
// down to a managable ammount.
1045+
true
1046+
} else {
1047+
false
1048+
})
1049+
&& let Some(clone_trait_def) = self.infcx.tcx.lang_items().clone_trait()
1050+
&& self
1051+
.infcx
1052+
.type_implements_trait(clone_trait_def, [call_ty], self.param_env)
1053+
.must_apply_modulo_regions()
1054+
&& self.suggest_cloning_inner(err, call_ty, parent)
1055+
{
1056+
return;
1057+
}
1058+
prev = parent;
1059+
}
1060+
}
1061+
}
9911062
let ty = ty.peel_refs();
9921063
if let Some(clone_trait_def) = self.infcx.tcx.lang_items().clone_trait()
9931064
&& self
@@ -1014,12 +1085,17 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10141085
}
10151086
}
10161087

1017-
fn suggest_cloning_inner(&self, err: &mut Diag<'_>, ty: Ty<'tcx>, expr: &hir::Expr<'_>) {
1088+
fn suggest_cloning_inner(
1089+
&self,
1090+
err: &mut Diag<'_>,
1091+
ty: Ty<'tcx>,
1092+
expr: &hir::Expr<'_>,
1093+
) -> bool {
10181094
let tcx = self.infcx.tcx;
10191095
if let Some(_) = self.clone_on_reference(expr) {
10201096
// Avoid redundant clone suggestion already suggested in `explain_captures`.
10211097
// See `tests/ui/moves/needs-clone-through-deref.rs`
1022-
return;
1098+
return false;
10231099
}
10241100
// Try to find predicates on *generic params* that would allow copying `ty`
10251101
let suggestion =
@@ -1036,7 +1112,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10361112
if let hir::ExprKind::AddrOf(_, hir::Mutability::Mut, _) = inner_expr.kind {
10371113
// We assume that `&mut` refs are desired for their side-effects, so cloning the
10381114
// value wouldn't do what the user wanted.
1039-
return;
1115+
return false;
10401116
}
10411117
inner_expr = inner;
10421118
}
@@ -1059,6 +1135,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10591135
"consider cloning the value if the performance cost is acceptable"
10601136
};
10611137
err.multipart_suggestion_verbose(msg, sugg, Applicability::MachineApplicable);
1138+
true
10621139
}
10631140

10641141
fn suggest_adding_bounds(&self, err: &mut Diag<'_>, ty: Ty<'tcx>, def_id: DefId, span: Span) {
@@ -1167,7 +1244,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
11671244
if let Some(expr) = self.find_expr(borrow_span)
11681245
&& let Some(ty) = typeck_results.node_type_opt(expr.hir_id)
11691246
{
1170-
self.suggest_cloning(&mut err, ty, expr);
1247+
self.suggest_cloning(&mut err, ty, expr, self.find_expr(span));
11711248
}
11721249
self.buffer_error(err);
11731250
}

compiler/rustc_borrowck/src/diagnostics/move_errors.rs

Lines changed: 9 additions & 7 deletions
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;
@@ -445,7 +447,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
445447
};
446448

447449
if let Some(expr) = self.find_expr(span) {
448-
self.suggest_cloning(err, place_ty, expr);
450+
self.suggest_cloning(err, place_ty, expr, self.find_expr(other_span));
449451
}
450452

451453
err.subdiagnostic(
@@ -472,15 +474,15 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
472474
}
473475
// No binding. Nothing to suggest.
474476
GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {
475-
let span = use_spans.var_or_use();
477+
let use_span = use_spans.var_or_use();
476478
let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;
477479
let place_desc = match self.describe_place(original_path.as_ref()) {
478480
Some(desc) => format!("`{desc}`"),
479481
None => "value".to_string(),
480482
};
481483

482-
if let Some(expr) = self.find_expr(span) {
483-
self.suggest_cloning(err, place_ty, expr);
484+
if let Some(expr) = self.find_expr(use_span) {
485+
self.suggest_cloning(err, place_ty, expr, self.find_expr(span));
484486
}
485487

486488
err.subdiagnostic(
@@ -489,7 +491,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
489491
is_partial_move: false,
490492
ty: place_ty,
491493
place: &place_desc,
492-
span,
494+
span: use_span,
493495
},
494496
);
495497

@@ -593,7 +595,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
593595
let place_desc = &format!("`{}`", self.local_names[*local].unwrap());
594596

595597
if let Some(expr) = self.find_expr(binding_span) {
596-
self.suggest_cloning(err, bind_to.ty, expr);
598+
self.suggest_cloning(err, bind_to.ty, expr, None);
597599
}
598600

599601
err.subdiagnostic(

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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 5 additions & 0 deletions
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/variance/variance-issue-20533.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,15 @@ fn baz<'a, T>(_x: &'a T) -> Baked<'a> {
1919
Baked(PhantomData)
2020
}
2121

22+
fn bat(x: &AffineU32) -> &u32 {
23+
&x.0
24+
}
25+
2226
struct AffineU32(u32);
2327

28+
#[derive(Clone)]
29+
struct ClonableAffineU32(u32);
30+
2431
fn main() {
2532
{
2633
let a = AffineU32(1);
@@ -40,4 +47,16 @@ fn main() {
4047
drop(a); //~ ERROR cannot move out of `a`
4148
drop(x);
4249
}
50+
{
51+
let a = AffineU32(1);
52+
let x = bat(&a);
53+
drop(a); //~ ERROR cannot move out of `a`
54+
drop(x);
55+
}
56+
{
57+
let a = ClonableAffineU32(1);
58+
let x = foo(&a);
59+
drop(a); //~ ERROR cannot move out of `a`
60+
drop(x);
61+
}
4362
}

tests/ui/variance/variance-issue-20533.stderr

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0505]: cannot move out of `a` because it is borrowed
2-
--> $DIR/variance-issue-20533.rs:28:14
2+
--> $DIR/variance-issue-20533.rs:35:14
33
|
44
LL | let a = AffineU32(1);
55
| - binding `a` declared here
@@ -11,7 +11,7 @@ LL | drop(x);
1111
| - borrow later used here
1212

1313
error[E0505]: cannot move out of `a` because it is borrowed
14-
--> $DIR/variance-issue-20533.rs:34:14
14+
--> $DIR/variance-issue-20533.rs:41:14
1515
|
1616
LL | let a = AffineU32(1);
1717
| - binding `a` declared here
@@ -23,7 +23,7 @@ LL | drop(x);
2323
| - borrow later used here
2424

2525
error[E0505]: cannot move out of `a` because it is borrowed
26-
--> $DIR/variance-issue-20533.rs:40:14
26+
--> $DIR/variance-issue-20533.rs:47:14
2727
|
2828
LL | let a = AffineU32(1);
2929
| - binding `a` declared here
@@ -34,6 +34,41 @@ LL | drop(a);
3434
LL | drop(x);
3535
| - borrow later used here
3636

37-
error: aborting due to 3 previous errors
37+
error[E0505]: cannot move out of `a` because it is borrowed
38+
--> $DIR/variance-issue-20533.rs:53:14
39+
|
40+
LL | let a = AffineU32(1);
41+
| - binding `a` declared here
42+
LL | let x = bat(&a);
43+
| -- borrow of `a` occurs here
44+
LL | drop(a);
45+
| ^ move out of `a` occurs here
46+
LL | drop(x);
47+
| - borrow later used here
48+
|
49+
help: consider cloning the value if the performance cost is acceptable
50+
|
51+
LL | let x = bat(&a).clone();
52+
| ++++++++
53+
54+
error[E0505]: cannot move out of `a` because it is borrowed
55+
--> $DIR/variance-issue-20533.rs:59:14
56+
|
57+
LL | let a = ClonableAffineU32(1);
58+
| - binding `a` declared here
59+
LL | let x = foo(&a);
60+
| -- borrow of `a` occurs here
61+
LL | drop(a);
62+
| ^ move out of `a` occurs here
63+
LL | drop(x);
64+
| - borrow later used here
65+
|
66+
help: consider cloning the value if the performance cost is acceptable
67+
|
68+
LL - let x = foo(&a);
69+
LL + let x = foo(a.clone());
70+
|
71+
72+
error: aborting due to 5 previous errors
3873

3974
For more information about this error, try `rustc --explain E0505`.

0 commit comments

Comments
 (0)