Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect borrow checker errors where .clone() would be an appropriate user action #122603

Merged
merged 19 commits into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions compiler/rustc_borrowck/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ borrowck_move_unsized =
borrowck_moved_a_fn_once_in_call =
this value implements `FnOnce`, which causes it to be moved when called

borrowck_moved_a_fn_once_in_call_call =
`FnOnce` closures can only be called once

borrowck_moved_a_fn_once_in_call_def =
`{$ty}` is made to be an `FnOnce` closure here

borrowck_moved_due_to_await =
{$place_name} {$is_partial ->
[true] partially moved
Expand Down
424 changes: 372 additions & 52 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

Large diffs are not rendered by default.

97 changes: 90 additions & 7 deletions compiler/rustc_borrowck/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::session_diagnostics::{
CaptureVarKind, CaptureVarPathUseCause, OnClosureNote,
};
use rustc_errors::{Applicability, Diag};
use rustc_errors::{DiagCtxt, MultiSpan};
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, Namespace};
use rustc_hir::CoroutineKind;
Expand All @@ -29,6 +30,8 @@ use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions;

use crate::fluent_generated as fluent;

use super::borrow_set::BorrowData;
use super::MirBorrowckCtxt;

Expand Down Expand Up @@ -587,7 +590,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn args_subdiag(
self,
dcx: &rustc_errors::DiagCtxt,
dcx: &DiagCtxt,
err: &mut Diag<'_>,
f: impl FnOnce(Span) -> CaptureArgLabel,
) {
Expand All @@ -601,7 +604,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn var_path_only_subdiag(
self,
dcx: &rustc_errors::DiagCtxt,
dcx: &DiagCtxt,
err: &mut Diag<'_>,
action: crate::InitializationRequiringAction,
) {
Expand Down Expand Up @@ -639,7 +642,7 @@ impl UseSpans<'_> {
#[allow(rustc::diagnostic_outside_of_impl)]
pub(super) fn var_subdiag(
self,
dcx: &rustc_errors::DiagCtxt,
dcx: &DiagCtxt,
err: &mut Diag<'_>,
kind: Option<rustc_middle::mir::BorrowKind>,
f: impl FnOnce(hir::ClosureKind, Span) -> CaptureVarCause,
Expand Down Expand Up @@ -1034,7 +1037,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
.map(|n| format!("`{n}`"))
.unwrap_or_else(|| "value".to_owned());
match kind {
CallKind::FnCall { fn_trait_id, .. }
CallKind::FnCall { fn_trait_id, self_ty }
if Some(fn_trait_id) == self.infcx.tcx.lang_items().fn_once_trait() =>
{
err.subdiagnostic(
Expand All @@ -1046,7 +1049,79 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
is_loop_message,
},
);
err.subdiagnostic(self.dcx(), CaptureReasonNote::FnOnceMoveInCall { var_span });
// Check if the move occurs on a value because of a call on a closure that comes
// from a type parameter `F: FnOnce()`. If so, we provide a targeted `note`:
// ```
// 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
// ```
if let ty::Param(param_ty) = self_ty.kind()
estebank marked this conversation as resolved.
Show resolved Hide resolved
&& let generics = self.infcx.tcx.generics_of(self.mir_def_id())
&& let param = generics.type_param(param_ty, self.infcx.tcx)
&& let Some(hir_generics) = self
.infcx
.tcx
.typeck_root_def_id(self.mir_def_id().to_def_id())
.as_local()
.and_then(|def_id| self.infcx.tcx.hir().get_generics(def_id))
&& let spans = hir_generics
.predicates
.iter()
.filter_map(|pred| match pred {
hir::WherePredicate::BoundPredicate(pred) => Some(pred),
_ => None,
})
.filter(|pred| {
if let Some((id, _)) = pred.bounded_ty.as_generic_param() {
id == param.def_id
} else {
false
}
})
.flat_map(|pred| pred.bounds)
.filter_map(|bound| {
if let Some(trait_ref) = bound.trait_ref()
&& let Some(trait_def_id) = trait_ref.trait_def_id()
&& trait_def_id == fn_trait_id
{
Some(bound.span())
} else {
None
}
})
.collect::<Vec<Span>>()
&& !spans.is_empty()
{
let mut span: MultiSpan = spans.clone().into();
for sp in spans {
span.push_span_label(sp, fluent::borrowck_moved_a_fn_once_in_call_def);
}
span.push_span_label(
fn_call_span,
fluent::borrowck_moved_a_fn_once_in_call,
);
err.span_note(span, fluent::borrowck_moved_a_fn_once_in_call_call);
} else {
err.subdiagnostic(
self.dcx(),
CaptureReasonNote::FnOnceMoveInCall { var_span },
);
}
}
CallKind::Operator { self_arg, trait_id, .. } => {
let self_arg = self_arg.unwrap();
Expand Down Expand Up @@ -1212,13 +1287,21 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
.iter_projections()
.any(|(_, elem)| matches!(elem, ProjectionElem::Deref))
{
let (start, end) = if let Some(expr) = self.find_expr(move_span)
&& let Some(_) = self.clone_on_reference(expr)
&& let hir::ExprKind::MethodCall(_, rcvr, _, _) = expr.kind
{
(move_span.shrink_to_lo(), move_span.with_lo(rcvr.span.hi()))
} else {
(move_span.shrink_to_lo(), move_span.shrink_to_hi())
};
vec![
// We use the fully-qualified path because `.clone()` can
// sometimes choose `<&T as Clone>` instead of `<T as Clone>`
// when going through auto-deref, so this ensures that doesn't
// happen, causing suggestions for `.clone().clone()`.
(move_span.shrink_to_lo(), format!("<{ty} as Clone>::clone(&")),
(move_span.shrink_to_hi(), ")".to_string()),
(start, format!("<{ty} as Clone>::clone(&")),
(end, ")".to_string()),
]
} else {
vec![(move_span.shrink_to_hi(), ".clone()".to_string())]
Expand Down
22 changes: 19 additions & 3 deletions compiler/rustc_borrowck/src/diagnostics/move_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {

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

if let Some(expr) = self.find_expr(span) {
self.suggest_cloning(err, place_ty, expr, self.find_expr(other_span));
}

err.subdiagnostic(
self.dcx(),
crate::session_diagnostics::TypeNoCopy::Label {
Expand All @@ -468,19 +474,24 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
}
// No binding. Nothing to suggest.
GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {
let span = use_spans.var_or_use();
let use_span = use_spans.var_or_use();
let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;
let place_desc = match self.describe_place(original_path.as_ref()) {
Some(desc) => format!("`{desc}`"),
None => "value".to_string(),
};

if let Some(expr) = self.find_expr(use_span) {
self.suggest_cloning(err, place_ty, expr, self.find_expr(span));
}

err.subdiagnostic(
self.dcx(),
crate::session_diagnostics::TypeNoCopy::Label {
is_partial_move: false,
ty: place_ty,
place: &place_desc,
span,
span: use_span,
},
);

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

if binds_to.len() == 1 {
let place_desc = &format!("`{}`", self.local_names[*local].unwrap());

if let Some(expr) = self.find_expr(binding_span) {
self.suggest_cloning(err, bind_to.ty, expr, None);
}

err.subdiagnostic(
self.dcx(),
crate::session_diagnostics::TypeNoCopy::Label {
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/associated-types/associated-types-outlives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
// fn body, causing this (invalid) code to be accepted.

pub trait Foo<'a> {
type Bar;
type Bar: Clone;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that the suggestion denormalise(&x).clone() would actually come up, otherwise it would suggest denormalise(x.clone()), and I wanted to verify that logic worked with associated types too (checking that the return type of denormalise could be cloned and side step the longer borrow of x).

}

impl<'a, T:'a> Foo<'a> for T {
impl<'a, T: 'a> Foo<'a> for T {
type Bar = &'a T;
}

Expand Down
5 changes: 5 additions & 0 deletions tests/ui/associated-types/associated-types-outlives.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ LL | drop(x);
| ^ move out of `x` occurs here
LL | return f(y);
| - borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
LL | 's: loop { y = denormalise(&x).clone(); break }
| ++++++++

error: aborting due to 1 previous error

Expand Down
6 changes: 6 additions & 0 deletions tests/ui/associated-types/issue-25700.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ LL | drop(t);
| - value moved here
LL | drop(t);
| ^ value used here after move
|
note: if `S<()>` implemented `Clone`, you could clone the value
--> $DIR/issue-25700.rs:1:1
|
LL | struct S<T: 'static>(#[allow(dead_code)] Option<&'static T>);
| ^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

Expand Down
2 changes: 2 additions & 0 deletions tests/ui/augmented-assignments.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::ops::AddAssign;

#[derive(Clone)]
struct Int(i32);

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

let y = Int(2);
//~^ HELP consider changing this to be mutable
Expand Down
9 changes: 7 additions & 2 deletions tests/ui/augmented-assignments.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0505]: cannot move out of `x` because it is borrowed
--> $DIR/augmented-assignments.rs:16:5
--> $DIR/augmented-assignments.rs:17:5
|
LL | let mut x = Int(1);
| ----- binding `x` declared here
Expand All @@ -8,9 +8,14 @@ LL | x
...
LL | x;
| ^ move out of `x` occurs here
|
help: consider cloning the value if the performance cost is acceptable
|
LL | x.clone();
| ++++++++

error[E0596]: cannot borrow `y` as mutable, as it is not declared as mutable
--> $DIR/augmented-assignments.rs:23:5
--> $DIR/augmented-assignments.rs:25:5
|
LL | y
| ^ cannot borrow as mutable
Expand Down
12 changes: 12 additions & 0 deletions tests/ui/borrowck/borrow-tuple-fields.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ LL | let y = x;
LL |
LL | r.use_ref();
| - borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
LL - let r = &x.0;
LL + let r = x.0.clone();
|

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

error[E0502]: cannot borrow `x.0` as mutable because it is also borrowed as immutable
--> $DIR/borrow-tuple-fields.rs:33:13
Expand Down
12 changes: 12 additions & 0 deletions tests/ui/borrowck/borrowck-bad-nested-calls-move.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ LL | &*a,
| --- borrow of `*a` occurs here
LL | a);
| ^ move out of `a` occurs here
|
help: consider cloning the value if the performance cost is acceptable
|
LL - &*a,
LL + a.clone(),
|

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

error: aborting due to 2 previous errors

Expand Down
11 changes: 11 additions & 0 deletions tests/ui/borrowck/borrowck-closures-slice-patterns.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ LL | let [y, z @ ..] = x;
LL | };
LL | &x;
| ^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
LL | let [y, z @ ..] = x.clone();
| ++++++++

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

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