Skip to content

Commit 1aa5668

Browse files
committed
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()) { | ++++++++ ```
1 parent 18eeac0 commit 1aa5668

16 files changed

+268
-25
lines changed

compiler/rustc_borrowck/src/diagnostics/move_errors.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ 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::{BytePos, DUMMY_SP, ExpnKind, MacroKind, Span};
1313
use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
1414
use rustc_trait_selection::infer::InferCtxtExt;
1515
use tracing::debug;
@@ -507,12 +507,50 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
507507
);
508508

509509
let closure_span = tcx.def_span(def_id);
510+
let mut clause_span = DUMMY_SP;
511+
let typck_result = self.infcx.tcx.typeck(self.mir_def_id());
512+
if let Some(closure_def_id) = def_id.as_local()
513+
&& let hir::Node::Expr(expr) = tcx.hir_node_by_def_id(closure_def_id)
514+
&& let hir::Node::Expr(parent) = tcx.parent_hir_node(expr.hir_id)
515+
&& let hir::ExprKind::Call(callee, _) = parent.kind
516+
&& let Some(ty) = typck_result.node_type_opt(callee.hir_id)
517+
&& let ty::FnDef(fn_def_id, args) = ty.kind()
518+
&& let predicates = tcx.predicates_of(fn_def_id).instantiate(tcx, args)
519+
&& let Some((_, span)) =
520+
predicates.predicates.iter().zip(predicates.spans.iter()).find(
521+
|(pred, _)| match pred.as_trait_clause() {
522+
Some(clause)
523+
if let ty::Closure(clause_closure_def_id, _) =
524+
clause.self_ty().skip_binder().kind()
525+
&& clause_closure_def_id == def_id
526+
&& (tcx.lang_items().fn_mut_trait()
527+
== Some(clause.def_id())
528+
|| tcx.lang_items().fn_trait()
529+
== Some(clause.def_id())) =>
530+
{
531+
// Found `<TyOfCapturingClosure as FnMut>`
532+
true
533+
}
534+
_ => false,
535+
},
536+
)
537+
{
538+
// We point at the `Fn()` or `FnMut()` bound that coerced the closure, which
539+
// could be changed to `FnOnce()` to avoid the move error.
540+
clause_span = *span;
541+
}
542+
510543
self.cannot_move_out_of(span, &place_description)
511544
.with_span_label(upvar_span, "captured outer variable")
512545
.with_span_label(
513546
closure_span,
514547
format!("captured by this `{closure_kind}` closure"),
515548
)
549+
.with_span_help(
550+
clause_span,
551+
"`Fn` and `FnMut` closures require captured values to be able to be \
552+
consumed multiple times, but an `FnOnce` consume them only once",
553+
)
516554
}
517555
_ => {
518556
let source = self.borrowed_content_source(deref_base);

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)