Skip to content

Commit ab39b0f

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 f8e355c commit ab39b0f

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;
@@ -508,12 +508,50 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
508508
);
509509

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