Skip to content

Commit 907076c

Browse files
authored
Rollup merge of rust-lang#144558 - estebank:issue-68119, r=lcnr
Point at the `Fn()` or `FnMut()` bound that coerced a closure, which caused a move error When encountering a move error involving a closure because the captured value isn't `Copy`, and the obligation comes from a bound on a type parameter that requires `Fn` or `FnMut`, we point at it and explain that an `FnOnce` wouldn't cause the move error. ``` error[E0507]: cannot move out of `foo`, a captured variable in an `Fn` closure --> f111.rs:15:25 | 14 | fn do_stuff(foo: Option<Foo>) { | --- ----------- move occurs because `foo` has type `Option<Foo>`, which does not implement the `Copy` trait | | | captured outer variable 15 | require_fn_trait(|| async { | -- ^^^^^ `foo` is moved here | | | captured by this `Fn` closure 16 | if foo.map_or(false, |f| f.foo()) { | --- variable moved due to use in coroutine | help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once --> f111.rs:12:53 | 12 | fn require_fn_trait<F: Future<Output = ()>>(_: impl Fn() -> F) {} | ^^^^^^^^^ help: consider cloning the value if the performance cost is acceptable | 16 | if foo.clone().map_or(false, |f| f.foo()) { | ++++++++ ``` Fix rust-lang#68119, by pointing at `Fn` and `FnMut` bounds involved in move errors.
2 parents 3724e13 + 74496bc commit 907076c

16 files changed

+941
-26
lines changed

compiler/rustc_borrowck/src/diagnostics/move_errors.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use rustc_middle::bug;
99
use rustc_middle::mir::*;
1010
use rustc_middle::ty::{self, Ty, TyCtxt};
1111
use rustc_mir_dataflow::move_paths::{LookupResult, MovePathIndex};
12-
use rustc_span::{BytePos, ExpnKind, MacroKind, Span};
12+
use rustc_span::def_id::DefId;
13+
use rustc_span::{BytePos, DUMMY_SP, ExpnKind, MacroKind, Span};
1314
use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
1415
use rustc_trait_selection::infer::InferCtxtExt;
1516
use tracing::debug;
@@ -507,12 +508,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
507508
);
508509

509510
let closure_span = tcx.def_span(def_id);
511+
510512
self.cannot_move_out_of(span, &place_description)
511513
.with_span_label(upvar_span, "captured outer variable")
512514
.with_span_label(
513515
closure_span,
514516
format!("captured by this `{closure_kind}` closure"),
515517
)
518+
.with_span_help(
519+
self.get_closure_bound_clause_span(*def_id),
520+
"`Fn` and `FnMut` closures require captured values to be able to be \
521+
consumed multiple times, but an `FnOnce` consume them only once",
522+
)
516523
}
517524
_ => {
518525
let source = self.borrowed_content_source(deref_base);
@@ -561,6 +568,47 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
561568
err
562569
}
563570

571+
fn get_closure_bound_clause_span(&self, def_id: DefId) -> Span {
572+
let tcx = self.infcx.tcx;
573+
let typeck_result = tcx.typeck(self.mir_def_id());
574+
// Check whether the closure is an argument to a call, if so,
575+
// get the instantiated where-bounds of that call.
576+
let closure_hir_id = tcx.local_def_id_to_hir_id(def_id.expect_local());
577+
let hir::Node::Expr(parent) = tcx.parent_hir_node(closure_hir_id) else { return DUMMY_SP };
578+
579+
let predicates = match parent.kind {
580+
hir::ExprKind::Call(callee, _) => {
581+
let Some(ty) = typeck_result.node_type_opt(callee.hir_id) else { return DUMMY_SP };
582+
let ty::FnDef(fn_def_id, args) = ty.kind() else { return DUMMY_SP };
583+
tcx.predicates_of(fn_def_id).instantiate(tcx, args)
584+
}
585+
hir::ExprKind::MethodCall(..) => {
586+
let Some((_, method)) = typeck_result.type_dependent_def(parent.hir_id) else {
587+
return DUMMY_SP;
588+
};
589+
let args = typeck_result.node_args(parent.hir_id);
590+
tcx.predicates_of(method).instantiate(tcx, args)
591+
}
592+
_ => return DUMMY_SP,
593+
};
594+
595+
// Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`.
596+
for (pred, span) in predicates.predicates.iter().zip(predicates.spans.iter()) {
597+
if let Some(clause) = pred.as_trait_clause()
598+
&& let ty::Closure(clause_closure_def_id, _) = clause.self_ty().skip_binder().kind()
599+
&& *clause_closure_def_id == def_id
600+
&& (tcx.lang_items().fn_mut_trait() == Some(clause.def_id())
601+
|| tcx.lang_items().fn_trait() == Some(clause.def_id()))
602+
{
603+
// Found `<TyOfCapturingClosure as FnMut>`
604+
// We point at the `Fn()` or `FnMut()` bound that coerced the closure, which
605+
// could be changed to `FnOnce()` to avoid the move error.
606+
return *span;
607+
}
608+
}
609+
DUMMY_SP
610+
}
611+
564612
fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diag<'_>, span: Span) {
565613
match error {
566614
GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {

compiler/rustc_errors/src/diagnostic.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -847,17 +847,18 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
847847
self
848848
}
849849

850+
with_fn! { with_span_help,
850851
/// Prints the span with some help above it.
851852
/// This is like [`Diag::help()`], but it gets its own span.
852853
#[rustc_lint_diagnostics]
853-
pub fn span_help<S: Into<MultiSpan>>(
854+
pub fn span_help(
854855
&mut self,
855-
sp: S,
856+
sp: impl Into<MultiSpan>,
856857
msg: impl Into<SubdiagMessage>,
857858
) -> &mut Self {
858859
self.sub(Level::Help, msg, sp.into());
859860
self
860-
}
861+
} }
861862

862863
/// Disallow attaching suggestions to this diagnostic.
863864
/// Any suggestions attached e.g. with the `span_suggestion_*` methods

tests/ui/borrowck/borrowck-in-static.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ LL | Box::new(|| x)
1010
| |
1111
| captured by this `Fn` closure
1212
|
13+
= help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once
1314
help: consider cloning the value if the performance cost is acceptable
1415
|
1516
LL | Box::new(|| x.clone())

tests/ui/borrowck/borrowck-move-by-capture.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ LL | let _h = to_fn_once(move || -> isize { *bar });
1212
| |
1313
| `bar` is moved here
1414
|
15+
help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once
16+
--> $DIR/borrowck-move-by-capture.rs:3:37
17+
|
18+
LL | fn to_fn_mut<A:std::marker::Tuple,F:FnMut<A>>(f: F) -> F { f }
19+
| ^^^^^^^^
1520
help: consider cloning the value before moving it into the closure
1621
|
1722
LL ~ let value = bar.clone();

tests/ui/borrowck/issue-103624.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ LL |
1313
LL | self.b;
1414
| ^^^^^^ `self.b` is moved here
1515
|
16+
help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once
17+
--> $DIR/issue-103624.rs:7:36
18+
|
19+
LL | async fn spawn_blocking<T>(f: impl (Fn() -> T) + Send + Sync + 'static) -> T {
20+
| ^^^^^^^^^^^
1621
note: if `StructB` implemented `Clone`, you could clone the value
1722
--> $DIR/issue-103624.rs:23:1
1823
|

tests/ui/borrowck/issue-87456-point-to-closure.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ LL |
1010
LL | let _foo: String = val;
1111
| ^^^ move occurs because `val` has type `String`, which does not implement the `Copy` trait
1212
|
13+
help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once
14+
--> $DIR/issue-87456-point-to-closure.rs:3:24
15+
|
16+
LL | fn take_mut(_val: impl FnMut()) {}
17+
| ^^^^^^^
1318
help: consider borrowing here
1419
|
1520
LL | let _foo: String = &val;

tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ LL | y.into_iter();
1010
| |
1111
| move occurs because `y` has type `Vec<String>`, which does not implement the `Copy` trait
1212
|
13+
help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once
14+
--> $DIR/unboxed-closures-move-upvar-from-non-once-ref-closure.rs:5:28
15+
|
16+
LL | fn call<F>(f: F) where F : Fn() {
17+
| ^^^^
1318
note: `into_iter` takes ownership of the receiver `self`, which moves `y`
1419
--> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
1520
help: you can `clone` the value and consume it, but this might not be your desired behavior

tests/ui/issues/issue-4335.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ LL | id(Box::new(|| *v))
1010
| |
1111
| captured by this `FnMut` closure
1212
|
13+
= help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once
1314
help: if `T` implemented `Clone`, you could clone the value
1415
--> $DIR/issue-4335.rs:5:10
1516
|

tests/ui/moves/moves-based-on-type-move-out-of-closure-env-issue-1965.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ LL | let _f = to_fn(|| test(i));
1010
| |
1111
| captured by this `Fn` closure
1212
|
13+
help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once
14+
--> $DIR/moves-based-on-type-move-out-of-closure-env-issue-1965.rs:3:33
15+
|
16+
LL | fn to_fn<A:std::marker::Tuple,F:Fn<A>>(f: F) -> F { f }
17+
| ^^^^^
1318
help: consider cloning the value if the performance cost is acceptable
1419
|
1520
LL | let _f = to_fn(|| test(i.clone()));

tests/ui/nll/issue-52663-span-decl-captured-variable.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ LL | expect_fn(|| drop(x.0));
1010
| |
1111
| captured by this `Fn` closure
1212
|
13+
help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but an `FnOnce` consume them only once
14+
--> $DIR/issue-52663-span-decl-captured-variable.rs:1:33
15+
|
16+
LL | fn expect_fn<F>(f: F) where F : Fn() {
17+
| ^^^^
1318
help: consider cloning the value if the performance cost is acceptable
1419
|
1520
LL | expect_fn(|| drop(x.0.clone()));

0 commit comments

Comments
 (0)