Skip to content

Commit 0bbe193

Browse files
authored
Rollup merge of rust-lang#124136 - estebank:clone-o-rama-2, r=nnethercote
Provide more context and suggestions in borrowck errors involving closures Start pointing to where bindings where declared when they are captured in closures: ``` error[E0597]: `x` does not live long enough --> $DIR/suggest-return-closure.rs:23:9 | LL | let x = String::new(); | - binding `x` declared here ... LL | |c| { | --- value captured here LL | x.push(c); | ^ borrowed value does not live long enough ... LL | } | -- borrow later used here | | | `x` dropped here while still borrowed ``` Suggest cloning in more cases involving closures: ``` error[E0507]: cannot move out of `foo` in pattern guard --> $DIR/issue-27282-move-ref-mut-into-guard.rs:11:19 | LL | if { (|| { let mut bar = foo; bar.take() })(); false } => {}, | ^^ --- move occurs because `foo` has type `&mut Option<&i32>`, which does not implement the `Copy` trait | | | `foo` is moved here | = note: variables bound in patterns cannot be moved from until after the end of the pattern guard help: consider cloning the value if the performance cost is acceptable | LL | if { (|| { let mut bar = foo.clone(); bar.take() })(); false } => {}, | ++++++++ ``` Mention when type parameter could be Clone ``` error[E0382]: use of moved value: `t` --> $DIR/use_of_moved_value_copy_suggestions.rs:7:9 | LL | fn duplicate_t<T>(t: T) -> (T, T) { | - move occurs because `t` has type `T`, which does not implement the `Copy` trait ... LL | (t, t) | - ^ value used here after move | | | value moved here | help: if `T` implemented `Clone`, you could clone the value --> $DIR/use_of_moved_value_copy_suggestions.rs:4:16 | LL | fn duplicate_t<T>(t: T) -> (T, T) { | ^ consider constraining this type parameter with `Clone` ... LL | (t, t) | - you could clone this value help: consider restricting type parameter `T` | LL | fn duplicate_t<T: Copy>(t: T) -> (T, T) { | ++++++ ``` The `help` is new. On ADTs, we also extend the output with span labels: ``` error[E0507]: cannot move out of static item `FOO` --> $DIR/issue-17718-static-move.rs:6:14 | LL | let _a = FOO; | ^^^ move occurs because `FOO` has type `Foo`, which does not implement the `Copy` trait | note: if `Foo` implemented `Clone`, you could clone the value --> $DIR/issue-17718-static-move.rs:1:1 | LL | struct Foo; | ^^^^^^^^^^ consider implementing `Clone` for this type ... LL | let _a = FOO; | --- you could clone this value help: consider borrowing here | LL | let _a = &FOO; | + ``` Suggest cloning captured binding in move closure ``` error[E0507]: cannot move out of `bar`, a captured variable in an `FnMut` closure --> $DIR/borrowck-move-by-capture.rs:9:29 | LL | let bar: Box<_> = Box::new(3); | --- captured outer variable LL | let _g = to_fn_mut(|| { | -- captured by this `FnMut` closure LL | let _h = to_fn_once(move || -> isize { *bar }); | ^^^^^^^^^^^^^^^^ ---- | | | | | variable moved due to use in closure | | move occurs because `bar` has type `Box<isize>`, which does not implement the `Copy` trait | `bar` is moved here | help: clone the value before moving it into the closure 1 | LL ~ let value = bar.clone(); LL ~ let _h = to_fn_once(move || -> isize { value }); | ```
2 parents 7b5165c + 2f29f78 commit 0bbe193

File tree

84 files changed

+1098
-128
lines changed

Some content is hidden

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

84 files changed

+1098
-128
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+46-9
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
347347
mpi: MovePathIndex,
348348
err: &mut Diag<'tcx>,
349349
in_pattern: &mut bool,
350-
move_spans: UseSpans<'_>,
350+
move_spans: UseSpans<'tcx>,
351351
) {
352352
let move_span = match move_spans {
353353
UseSpans::ClosureUse { capture_kind_span, .. } => capture_kind_span,
@@ -491,11 +491,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
491491
..
492492
} = move_spans
493493
{
494-
self.suggest_cloning(err, ty, expr, None);
494+
self.suggest_cloning(err, ty, expr, None, Some(move_spans));
495495
} else if self.suggest_hoisting_call_outside_loop(err, expr) {
496496
// The place where the the type moves would be misleading to suggest clone.
497497
// #121466
498-
self.suggest_cloning(err, ty, expr, None);
498+
self.suggest_cloning(err, ty, expr, None, Some(move_spans));
499499
}
500500
}
501501
if let Some(pat) = finder.pat {
@@ -1085,6 +1085,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10851085
ty: Ty<'tcx>,
10861086
mut expr: &'cx hir::Expr<'cx>,
10871087
mut other_expr: Option<&'cx hir::Expr<'cx>>,
1088+
use_spans: Option<UseSpans<'tcx>>,
10881089
) {
10891090
if let hir::ExprKind::Struct(_, _, Some(_)) = expr.kind {
10901091
// We have `S { foo: val, ..base }`. In `check_aggregate_rvalue` we have a single
@@ -1197,14 +1198,50 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
11971198
.all(|field| self.implements_clone(field.ty(self.infcx.tcx, args)))
11981199
})
11991200
{
1201+
let ty_span = self.infcx.tcx.def_span(def.did());
1202+
let mut span: MultiSpan = ty_span.into();
1203+
span.push_span_label(ty_span, "consider implementing `Clone` for this type");
1204+
span.push_span_label(expr.span, "you could clone this value");
12001205
err.span_note(
1201-
self.infcx.tcx.def_span(def.did()),
1206+
span,
1207+
format!("if `{ty}` implemented `Clone`, you could clone the value"),
1208+
);
1209+
} else if let ty::Param(param) = ty.kind()
1210+
&& let Some(_clone_trait_def) = self.infcx.tcx.lang_items().clone_trait()
1211+
&& let generics = self.infcx.tcx.generics_of(self.mir_def_id())
1212+
&& let generic_param = generics.type_param(*param, self.infcx.tcx)
1213+
&& let param_span = self.infcx.tcx.def_span(generic_param.def_id)
1214+
&& if let Some(UseSpans::FnSelfUse { kind, .. }) = use_spans
1215+
&& let CallKind::FnCall { fn_trait_id, self_ty } = kind
1216+
&& let ty::Param(_) = self_ty.kind()
1217+
&& ty == self_ty
1218+
&& [
1219+
self.infcx.tcx.lang_items().fn_once_trait(),
1220+
self.infcx.tcx.lang_items().fn_mut_trait(),
1221+
self.infcx.tcx.lang_items().fn_trait(),
1222+
]
1223+
.contains(&Some(fn_trait_id))
1224+
{
1225+
// Do not suggest `F: FnOnce() + Clone`.
1226+
false
1227+
} else {
1228+
true
1229+
}
1230+
{
1231+
let mut span: MultiSpan = param_span.into();
1232+
span.push_span_label(
1233+
param_span,
1234+
"consider constraining this type parameter with `Clone`",
1235+
);
1236+
span.push_span_label(expr.span, "you could clone this value");
1237+
err.span_help(
1238+
span,
12021239
format!("if `{ty}` implemented `Clone`, you could clone the value"),
12031240
);
12041241
}
12051242
}
12061243

1207-
fn implements_clone(&self, ty: Ty<'tcx>) -> bool {
1244+
pub(crate) fn implements_clone(&self, ty: Ty<'tcx>) -> bool {
12081245
let Some(clone_trait_def) = self.infcx.tcx.lang_items().clone_trait() else { return false };
12091246
self.infcx
12101247
.type_implements_trait(clone_trait_def, [ty], self.param_env)
@@ -1403,7 +1440,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
14031440
if let Some(expr) = self.find_expr(borrow_span)
14041441
&& let Some(ty) = typeck_results.node_type_opt(expr.hir_id)
14051442
{
1406-
self.suggest_cloning(&mut err, ty, expr, self.find_expr(span));
1443+
self.suggest_cloning(&mut err, ty, expr, self.find_expr(span), Some(move_spans));
14071444
}
14081445
self.buffer_error(err);
14091446
}
@@ -1964,7 +2001,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
19642001
pub(crate) fn find_expr(&self, span: Span) -> Option<&hir::Expr<'_>> {
19652002
let tcx = self.infcx.tcx;
19662003
let body_id = tcx.hir_node(self.mir_hir_id()).body_id()?;
1967-
let mut expr_finder = FindExprBySpan::new(span);
2004+
let mut expr_finder = FindExprBySpan::new(span, tcx);
19682005
expr_finder.visit_expr(tcx.hir().body(body_id).value);
19692006
expr_finder.result
19702007
}
@@ -1998,14 +2035,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
19982035
};
19992036

20002037
let mut expr_finder =
2001-
FindExprBySpan::new(self.body.local_decls[*index1].source_info.span);
2038+
FindExprBySpan::new(self.body.local_decls[*index1].source_info.span, tcx);
20022039
expr_finder.visit_expr(hir.body(body_id).value);
20032040
let Some(index1) = expr_finder.result else {
20042041
note_default_suggestion();
20052042
return;
20062043
};
20072044

2008-
expr_finder = FindExprBySpan::new(self.body.local_decls[*index2].source_info.span);
2045+
expr_finder = FindExprBySpan::new(self.body.local_decls[*index2].source_info.span, tcx);
20092046
expr_finder.visit_expr(hir.body(body_id).value);
20102047
let Some(index2) = expr_finder.result else {
20112048
note_default_suggestion();

compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl<'tcx> BorrowExplanation<'tcx> {
7676
&& let Some(body_id) = node.body_id()
7777
{
7878
let body = tcx.hir().body(body_id);
79-
let mut expr_finder = FindExprBySpan::new(span);
79+
let mut expr_finder = FindExprBySpan::new(span, tcx);
8080
expr_finder.visit_expr(body.value);
8181
if let Some(mut expr) = expr_finder.result {
8282
while let hir::ExprKind::AddrOf(_, _, inner)

compiler/rustc_borrowck/src/diagnostics/move_errors.rs

+144-10
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
#![allow(rustc::untranslatable_diagnostic)]
33

44
use rustc_errors::{Applicability, Diag};
5+
use rustc_hir::intravisit::Visitor;
6+
use rustc_hir::{CaptureBy, ExprKind, HirId, Node};
57
use rustc_middle::mir::*;
68
use rustc_middle::ty::{self, Ty};
79
use rustc_mir_dataflow::move_paths::{LookupResult, MovePathIndex};
810
use rustc_span::{BytePos, ExpnKind, MacroKind, Span};
11+
use rustc_trait_selection::traits::error_reporting::FindExprBySpan;
912

1013
use crate::diagnostics::CapturedMessageOpt;
1114
use crate::diagnostics::{DescribePlaceOpt, UseSpans};
@@ -303,17 +306,133 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
303306
self.cannot_move_out_of(span, &description)
304307
}
305308

309+
fn suggest_clone_of_captured_var_in_move_closure(
310+
&self,
311+
err: &mut Diag<'_>,
312+
upvar_hir_id: HirId,
313+
upvar_name: &str,
314+
use_spans: Option<UseSpans<'tcx>>,
315+
) {
316+
let tcx = self.infcx.tcx;
317+
let typeck_results = tcx.typeck(self.mir_def_id());
318+
let Some(use_spans) = use_spans else { return };
319+
// We only care about the case where a closure captured a binding.
320+
let UseSpans::ClosureUse { args_span, .. } = use_spans else { return };
321+
let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return };
322+
// Fetch the type of the expression corresponding to the closure-captured binding.
323+
let Some(captured_ty) = typeck_results.node_type_opt(upvar_hir_id) else { return };
324+
if !self.implements_clone(captured_ty) {
325+
// We only suggest cloning the captured binding if the type can actually be cloned.
326+
return;
327+
};
328+
// Find the closure that captured the binding.
329+
let mut expr_finder = FindExprBySpan::new(args_span, tcx);
330+
expr_finder.include_closures = true;
331+
expr_finder.visit_expr(tcx.hir().body(body_id).value);
332+
let Some(closure_expr) = expr_finder.result else { return };
333+
let ExprKind::Closure(closure) = closure_expr.kind else { return };
334+
// We'll only suggest cloning the binding if it's a `move` closure.
335+
let CaptureBy::Value { .. } = closure.capture_clause else { return };
336+
// Find the expression within the closure where the binding is consumed.
337+
let mut suggested = false;
338+
let use_span = use_spans.var_or_use();
339+
let mut expr_finder = FindExprBySpan::new(use_span, tcx);
340+
expr_finder.include_closures = true;
341+
expr_finder.visit_expr(tcx.hir().body(body_id).value);
342+
let Some(use_expr) = expr_finder.result else { return };
343+
let parent = tcx.parent_hir_node(use_expr.hir_id);
344+
if let Node::Expr(expr) = parent
345+
&& let ExprKind::Assign(lhs, ..) = expr.kind
346+
&& lhs.hir_id == use_expr.hir_id
347+
{
348+
// Cloning the value being assigned makes no sense:
349+
//
350+
// error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure
351+
// --> $DIR/option-content-move2.rs:11:9
352+
// |
353+
// LL | let mut var = None;
354+
// | ------- captured outer variable
355+
// LL | func(|| {
356+
// | -- captured by this `FnMut` closure
357+
// LL | // Shouldn't suggest `move ||.as_ref()` here
358+
// LL | move || {
359+
// | ^^^^^^^ `var` is moved here
360+
// LL |
361+
// LL | var = Some(NotCopyable);
362+
// | ---
363+
// | |
364+
// | variable moved due to use in closure
365+
// | move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait
366+
// |
367+
return;
368+
}
369+
370+
// Search for an appropriate place for the structured `.clone()` suggestion to be applied.
371+
// If we encounter a statement before the borrow error, we insert a statement there.
372+
for (_, node) in tcx.hir().parent_iter(closure_expr.hir_id) {
373+
if let Node::Stmt(stmt) = node {
374+
let padding = tcx
375+
.sess
376+
.source_map()
377+
.indentation_before(stmt.span)
378+
.unwrap_or_else(|| " ".to_string());
379+
err.multipart_suggestion_verbose(
380+
"clone the value before moving it into the closure",
381+
vec![
382+
(
383+
stmt.span.shrink_to_lo(),
384+
format!("let value = {upvar_name}.clone();\n{padding}"),
385+
),
386+
(use_span, "value".to_string()),
387+
],
388+
Applicability::MachineApplicable,
389+
);
390+
suggested = true;
391+
break;
392+
} else if let Node::Expr(expr) = node
393+
&& let ExprKind::Closure(_) = expr.kind
394+
{
395+
// We want to suggest cloning only on the first closure, not
396+
// subsequent ones (like `ui/suggestions/option-content-move2.rs`).
397+
break;
398+
}
399+
}
400+
if !suggested {
401+
// If we couldn't find a statement for us to insert a new `.clone()` statement before,
402+
// we have a bare expression, so we suggest the creation of a new block inline to go
403+
// from `move || val` to `{ let value = val.clone(); move || value }`.
404+
let padding = tcx
405+
.sess
406+
.source_map()
407+
.indentation_before(closure_expr.span)
408+
.unwrap_or_else(|| " ".to_string());
409+
err.multipart_suggestion_verbose(
410+
"clone the value before moving it into the closure",
411+
vec![
412+
(
413+
closure_expr.span.shrink_to_lo(),
414+
format!("{{\n{padding}let value = {upvar_name}.clone();\n{padding}"),
415+
),
416+
(use_spans.var_or_use(), "value".to_string()),
417+
(closure_expr.span.shrink_to_hi(), format!("\n{padding}}}")),
418+
],
419+
Applicability::MachineApplicable,
420+
);
421+
}
422+
}
423+
306424
fn report_cannot_move_from_borrowed_content(
307425
&mut self,
308426
move_place: Place<'tcx>,
309427
deref_target_place: Place<'tcx>,
310428
span: Span,
311429
use_spans: Option<UseSpans<'tcx>>,
312430
) -> Diag<'tcx> {
431+
let tcx = self.infcx.tcx;
313432
// Inspect the type of the content behind the
314433
// borrow to provide feedback about why this
315434
// was a move rather than a copy.
316-
let ty = deref_target_place.ty(self.body, self.infcx.tcx).ty;
435+
let ty = deref_target_place.ty(self.body, tcx).ty;
317436
let upvar_field = self
318437
.prefixes(move_place.as_ref(), PrefixSet::All)
319438
.find_map(|p| self.is_upvar_field_projection(p));
@@ -363,8 +482,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
363482

364483
let upvar = &self.upvars[upvar_field.unwrap().index()];
365484
let upvar_hir_id = upvar.get_root_variable();
366-
let upvar_name = upvar.to_string(self.infcx.tcx);
367-
let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id);
485+
let upvar_name = upvar.to_string(tcx);
486+
let upvar_span = tcx.hir().span(upvar_hir_id);
368487

369488
let place_name = self.describe_any_place(move_place.as_ref());
370489

@@ -380,12 +499,21 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
380499
closure_kind_ty, closure_kind, place_description,
381500
);
382501

383-
self.cannot_move_out_of(span, &place_description)
502+
let closure_span = tcx.def_span(def_id);
503+
let mut err = self
504+
.cannot_move_out_of(span, &place_description)
384505
.with_span_label(upvar_span, "captured outer variable")
385506
.with_span_label(
386-
self.infcx.tcx.def_span(def_id),
507+
closure_span,
387508
format!("captured by this `{closure_kind}` closure"),
388-
)
509+
);
510+
self.suggest_clone_of_captured_var_in_move_closure(
511+
&mut err,
512+
upvar_hir_id,
513+
&upvar_name,
514+
use_spans,
515+
);
516+
err
389517
}
390518
_ => {
391519
let source = self.borrowed_content_source(deref_base);
@@ -415,7 +543,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
415543
),
416544
(_, _, _) => self.cannot_move_out_of(
417545
span,
418-
&source.describe_for_unnamed_place(self.infcx.tcx),
546+
&source.describe_for_unnamed_place(tcx),
419547
),
420548
}
421549
}
@@ -447,7 +575,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
447575
};
448576

449577
if let Some(expr) = self.find_expr(span) {
450-
self.suggest_cloning(err, place_ty, expr, self.find_expr(other_span));
578+
self.suggest_cloning(err, place_ty, expr, self.find_expr(other_span), None);
451579
}
452580

453581
err.subdiagnostic(
@@ -482,7 +610,13 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
482610
};
483611

484612
if let Some(expr) = self.find_expr(use_span) {
485-
self.suggest_cloning(err, place_ty, expr, self.find_expr(span));
613+
self.suggest_cloning(
614+
err,
615+
place_ty,
616+
expr,
617+
self.find_expr(span),
618+
Some(use_spans),
619+
);
486620
}
487621

488622
err.subdiagnostic(
@@ -595,7 +729,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
595729
let place_desc = &format!("`{}`", self.local_names[*local].unwrap());
596730

597731
if let Some(expr) = self.find_expr(binding_span) {
598-
self.suggest_cloning(err, bind_to.ty, expr, None);
732+
self.suggest_cloning(err, bind_to.ty, expr, None, None);
599733
}
600734

601735
err.subdiagnostic(

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,38 @@ pub struct FindExprBySpan<'hir> {
5050
pub span: Span,
5151
pub result: Option<&'hir hir::Expr<'hir>>,
5252
pub ty_result: Option<&'hir hir::Ty<'hir>>,
53+
pub include_closures: bool,
54+
pub tcx: TyCtxt<'hir>,
5355
}
5456

5557
impl<'hir> FindExprBySpan<'hir> {
56-
pub fn new(span: Span) -> Self {
57-
Self { span, result: None, ty_result: None }
58+
pub fn new(span: Span, tcx: TyCtxt<'hir>) -> Self {
59+
Self { span, result: None, ty_result: None, tcx, include_closures: false }
5860
}
5961
}
6062

6163
impl<'v> Visitor<'v> for FindExprBySpan<'v> {
64+
type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;
65+
66+
fn nested_visit_map(&mut self) -> Self::Map {
67+
self.tcx.hir()
68+
}
69+
6270
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
6371
if self.span == ex.span {
6472
self.result = Some(ex);
6573
} else {
74+
if let hir::ExprKind::Closure(..) = ex.kind
75+
&& self.include_closures
76+
&& let closure_header_sp = self.span.with_hi(ex.span.hi())
77+
&& closure_header_sp == ex.span
78+
{
79+
self.result = Some(ex);
80+
}
6681
hir::intravisit::walk_expr(self, ex);
6782
}
6883
}
84+
6985
fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
7086
if self.span == ty.span {
7187
self.ty_result = Some(ty);

0 commit comments

Comments
 (0)