Skip to content

Commit 2d3435b

Browse files
committed
Detect calls to .clone() on T: !Clone types on borrowck errors
When encountering a lifetime error on a type that *holds* a type that doesn't implement `Clone`, explore the item's body for potential calls to `.clone()` that are only cloning the reference `&T` instead of `T` because `T: !Clone`. If we find this, suggest `T: Clone`. ``` error[E0502]: cannot borrow `*list` as mutable because it is also borrowed as immutable --> $DIR/clone-on-ref.rs:7:5 | LL | for v in list.iter() { | ---- immutable borrow occurs here LL | cloned_items.push(v.clone()) | ------- this call doesn't do anything, the result is still `&T` because `T` doesn't implement `Clone` LL | } LL | list.push(T::default()); | ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here LL | LL | drop(cloned_items); | ------------ immutable borrow later used here | help: consider further restricting this bound | LL | fn foo<T: Default + Clone>(list: &mut Vec<T>) { | +++++++ ``` ``` error[E0505]: cannot move out of `x` because it is borrowed --> $DIR/clone-on-ref.rs:23:10 | LL | fn qux(x: A) { | - binding `x` declared here LL | let a = &x; | -- borrow of `x` occurs here LL | let b = a.clone(); | ------- this call doesn't do anything, the result is still `&A` because `A` doesn't implement `Clone` LL | drop(x); | ^ move out of `x` occurs here LL | LL | println!("{b:?}"); | ----- borrow later used here | help: consider annotating `A` with `#[derive(Clone)]` | LL + #[derive(Clone)] LL | struct A; | ```
1 parent 7e79dcd commit 2d3435b

File tree

4 files changed

+233
-7
lines changed

4 files changed

+233
-7
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+106-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use rustc_hir::def::{DefKind, Res};
1212
use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
1313
use rustc_hir::{CoroutineDesugaring, PatField};
1414
use rustc_hir::{CoroutineKind, CoroutineSource, LangItem};
15-
use rustc_infer::traits::ObligationCause;
1615
use rustc_middle::hir::nested_filter::OnlyBodies;
1716
use rustc_middle::mir::tcx::PlaceTy;
1817
use rustc_middle::mir::{
@@ -21,16 +20,21 @@ use rustc_middle::mir::{
2120
PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
2221
VarBindingForm,
2322
};
24-
use rustc_middle::ty::{self, suggest_constraining_type_params, PredicateKind, Ty, TyCtxt};
23+
use rustc_middle::ty::{
24+
self, suggest_constraining_type_params, PredicateKind, ToPredicate, Ty, TyCtxt,
25+
TypeSuperVisitable, TypeVisitor,
26+
};
2527
use rustc_middle::util::CallKind;
2628
use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
29+
use rustc_span::def_id::DefId;
2730
use rustc_span::def_id::LocalDefId;
2831
use rustc_span::hygiene::DesugaringKind;
2932
use rustc_span::symbol::{kw, sym, Ident};
3033
use rustc_span::{BytePos, Span, Symbol};
3134
use rustc_trait_selection::infer::InferCtxtExt;
35+
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt;
3236
use rustc_trait_selection::traits::error_reporting::FindExprBySpan;
33-
use rustc_trait_selection::traits::ObligationCtxt;
37+
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
3438
use std::iter;
3539

3640
use crate::borrow_set::TwoPhaseActivation;
@@ -283,7 +287,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
283287
// something that already has `Fn`-like bounds (or is a closure), so we can't
284288
// restrict anyways.
285289
} else {
286-
self.suggest_adding_copy_bounds(&mut err, ty, span);
290+
let copy_did = self.infcx.tcx.require_lang_item(LangItem::Copy, Some(span));
291+
self.suggest_adding_bounds(&mut err, ty, copy_did, span);
287292
}
288293

289294
if needs_note {
@@ -775,7 +780,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
775780
}
776781
}
777782

778-
fn suggest_adding_copy_bounds(&self, err: &mut Diag<'_>, ty: Ty<'tcx>, span: Span) {
783+
fn suggest_adding_bounds(&self, err: &mut Diag<'_>, ty: Ty<'tcx>, def_id: DefId, span: Span) {
779784
let tcx = self.infcx.tcx;
780785
let generics = tcx.generics_of(self.mir_def_id());
781786

@@ -788,10 +793,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
788793
};
789794
// Try to find predicates on *generic params* that would allow copying `ty`
790795
let ocx = ObligationCtxt::new(self.infcx);
791-
let copy_did = tcx.require_lang_item(LangItem::Copy, Some(span));
792796
let cause = ObligationCause::misc(span, self.mir_def_id());
793797

794-
ocx.register_bound(cause, self.param_env, ty, copy_did);
798+
ocx.register_bound(cause, self.param_env, ty, def_id);
795799
let errors = ocx.select_all_or_error();
796800

797801
// Only emit suggestion if all required predicates are on generic
@@ -877,6 +881,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
877881
Some(borrow_span),
878882
None,
879883
);
884+
self.suggest_copy_for_type_in_cloned_ref(&mut err, place);
880885
self.buffer_error(err);
881886
}
882887

@@ -1215,10 +1220,104 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
12151220
);
12161221

12171222
self.suggest_using_local_if_applicable(&mut err, location, issued_borrow, explanation);
1223+
self.suggest_copy_for_type_in_cloned_ref(&mut err, place);
12181224

12191225
err
12201226
}
12211227

1228+
fn suggest_copy_for_type_in_cloned_ref(&self, err: &mut Diag<'tcx>, place: Place<'tcx>) {
1229+
let tcx = self.infcx.tcx;
1230+
let hir = tcx.hir();
1231+
let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return };
1232+
struct FindUselessClone<'hir> {
1233+
pub clones: Vec<&'hir hir::Expr<'hir>>,
1234+
}
1235+
impl<'hir> FindUselessClone<'hir> {
1236+
pub fn new() -> Self {
1237+
Self { clones: vec![] }
1238+
}
1239+
}
1240+
1241+
impl<'v> Visitor<'v> for FindUselessClone<'v> {
1242+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
1243+
// FIXME: use `lookup_method_for_diagnostic`?
1244+
if let hir::ExprKind::MethodCall(segment, _rcvr, args, _span) = ex.kind
1245+
&& segment.ident.name == sym::clone
1246+
&& args.len() == 0
1247+
{
1248+
self.clones.push(ex);
1249+
}
1250+
hir::intravisit::walk_expr(self, ex);
1251+
}
1252+
}
1253+
let mut expr_finder = FindUselessClone::new();
1254+
1255+
let body = hir.body(body_id).value;
1256+
expr_finder.visit_expr(body);
1257+
1258+
pub struct Holds<'tcx> {
1259+
ty: Ty<'tcx>,
1260+
holds: bool,
1261+
}
1262+
1263+
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for Holds<'tcx> {
1264+
type Result = std::ops::ControlFlow<()>;
1265+
1266+
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
1267+
if t == self.ty {
1268+
self.holds = true;
1269+
}
1270+
t.super_visit_with(self)
1271+
}
1272+
}
1273+
1274+
let mut types_to_constrain = FxIndexSet::default();
1275+
1276+
let local_ty = self.body.local_decls[place.local].ty;
1277+
let typeck_results = tcx.typeck(self.mir_def_id());
1278+
let clone = tcx.require_lang_item(LangItem::Clone, Some(body.span));
1279+
for expr in expr_finder.clones {
1280+
if let hir::ExprKind::MethodCall(_, rcvr, _, span) = expr.kind
1281+
&& let Some(rcvr_ty) = typeck_results.node_type_opt(rcvr.hir_id)
1282+
&& let Some(ty) = typeck_results.node_type_opt(expr.hir_id)
1283+
&& rcvr_ty == ty
1284+
&& let ty::Ref(_, inner, _) = rcvr_ty.kind()
1285+
&& let inner = inner.peel_refs()
1286+
&& let mut v = (Holds { ty: inner, holds: false })
1287+
&& let _ = v.visit_ty(local_ty)
1288+
&& v.holds
1289+
&& let None = self.infcx.type_implements_trait_shallow(clone, inner, self.param_env)
1290+
{
1291+
err.span_label(
1292+
span,
1293+
format!(
1294+
"this call doesn't do anything, the result is still `{rcvr_ty}` \
1295+
because `{inner}` doesn't implement `Clone`",
1296+
),
1297+
);
1298+
types_to_constrain.insert(inner);
1299+
}
1300+
}
1301+
for ty in types_to_constrain {
1302+
self.suggest_adding_bounds(err, ty, clone, body.span);
1303+
if let ty::Adt(..) = ty.kind() {
1304+
// The type doesn't implement Clone.
1305+
let trait_ref = ty::Binder::dummy(ty::TraitRef::new(self.infcx.tcx, clone, [ty]));
1306+
let obligation = Obligation::new(
1307+
self.infcx.tcx,
1308+
ObligationCause::dummy(),
1309+
self.param_env,
1310+
trait_ref,
1311+
);
1312+
self.infcx.err_ctxt().suggest_derive(
1313+
&obligation,
1314+
err,
1315+
trait_ref.to_predicate(self.infcx.tcx),
1316+
);
1317+
}
1318+
}
1319+
}
1320+
12221321
#[instrument(level = "debug", skip(self, err))]
12231322
fn suggest_using_local_if_applicable(
12241323
&self,

tests/ui/borrowck/clone-on-ref.fixed

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@ run-rustfix
2+
fn foo<T: Default + Clone>(list: &mut Vec<T>) {
3+
let mut cloned_items = Vec::new();
4+
for v in list.iter() {
5+
cloned_items.push(v.clone())
6+
}
7+
list.push(T::default());
8+
//~^ ERROR cannot borrow `*list` as mutable because it is also borrowed as immutable
9+
drop(cloned_items);
10+
}
11+
fn bar<T: std::fmt::Display + Clone>(x: T) {
12+
let a = &x;
13+
let b = a.clone();
14+
drop(x);
15+
//~^ ERROR cannot move out of `x` because it is borrowed
16+
println!("{b}");
17+
}
18+
#[derive(Debug)]
19+
#[derive(Clone)]
20+
struct A;
21+
fn qux(x: A) {
22+
let a = &x;
23+
let b = a.clone();
24+
drop(x);
25+
//~^ ERROR cannot move out of `x` because it is borrowed
26+
println!("{b:?}");
27+
}
28+
fn main() {
29+
foo(&mut vec![1, 2, 3]);
30+
bar("");
31+
qux(A);
32+
}

tests/ui/borrowck/clone-on-ref.rs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//@ run-rustfix
2+
fn foo<T: Default>(list: &mut Vec<T>) {
3+
let mut cloned_items = Vec::new();
4+
for v in list.iter() {
5+
cloned_items.push(v.clone())
6+
}
7+
list.push(T::default());
8+
//~^ ERROR cannot borrow `*list` as mutable because it is also borrowed as immutable
9+
drop(cloned_items);
10+
}
11+
fn bar<T: std::fmt::Display>(x: T) {
12+
let a = &x;
13+
let b = a.clone();
14+
drop(x);
15+
//~^ ERROR cannot move out of `x` because it is borrowed
16+
println!("{b}");
17+
}
18+
#[derive(Debug)]
19+
struct A;
20+
fn qux(x: A) {
21+
let a = &x;
22+
let b = a.clone();
23+
drop(x);
24+
//~^ ERROR cannot move out of `x` because it is borrowed
25+
println!("{b:?}");
26+
}
27+
fn main() {
28+
foo(&mut vec![1, 2, 3]);
29+
bar("");
30+
qux(A);
31+
}

tests/ui/borrowck/clone-on-ref.stderr

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
error[E0502]: cannot borrow `*list` as mutable because it is also borrowed as immutable
2+
--> $DIR/clone-on-ref.rs:7:5
3+
|
4+
LL | for v in list.iter() {
5+
| ---- immutable borrow occurs here
6+
LL | cloned_items.push(v.clone())
7+
| ------- this call doesn't do anything, the result is still `&T` because `T` doesn't implement `Clone`
8+
LL | }
9+
LL | list.push(T::default());
10+
| ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
11+
LL |
12+
LL | drop(cloned_items);
13+
| ------------ immutable borrow later used here
14+
|
15+
help: consider further restricting this bound
16+
|
17+
LL | fn foo<T: Default + Clone>(list: &mut Vec<T>) {
18+
| +++++++
19+
20+
error[E0505]: cannot move out of `x` because it is borrowed
21+
--> $DIR/clone-on-ref.rs:14:10
22+
|
23+
LL | fn bar<T: std::fmt::Display>(x: T) {
24+
| - binding `x` declared here
25+
LL | let a = &x;
26+
| -- borrow of `x` occurs here
27+
LL | let b = a.clone();
28+
| ------- this call doesn't do anything, the result is still `&T` because `T` doesn't implement `Clone`
29+
LL | drop(x);
30+
| ^ move out of `x` occurs here
31+
LL |
32+
LL | println!("{b}");
33+
| --- borrow later used here
34+
|
35+
help: consider further restricting this bound
36+
|
37+
LL | fn bar<T: std::fmt::Display + Clone>(x: T) {
38+
| +++++++
39+
40+
error[E0505]: cannot move out of `x` because it is borrowed
41+
--> $DIR/clone-on-ref.rs:23:10
42+
|
43+
LL | fn qux(x: A) {
44+
| - binding `x` declared here
45+
LL | let a = &x;
46+
| -- borrow of `x` occurs here
47+
LL | let b = a.clone();
48+
| ------- this call doesn't do anything, the result is still `&A` because `A` doesn't implement `Clone`
49+
LL | drop(x);
50+
| ^ move out of `x` occurs here
51+
LL |
52+
LL | println!("{b:?}");
53+
| ----- borrow later used here
54+
|
55+
help: consider annotating `A` with `#[derive(Clone)]`
56+
|
57+
LL + #[derive(Clone)]
58+
LL | struct A;
59+
|
60+
61+
error: aborting due to 3 previous errors
62+
63+
Some errors have detailed explanations: E0502, E0505.
64+
For more information about an error, try `rustc --explain E0502`.

0 commit comments

Comments
 (0)