Skip to content
Open
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
96 changes: 83 additions & 13 deletions compiler/rustc_borrowck/src/diagnostics/move_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,11 +514,62 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
format!("captured by this `{closure_kind}` closure"),
)
.with_span_help(
self.get_closure_bound_clause_span(*def_id),
self.get_closure_bound_clause_span(*def_id, false),
"`Fn` and `FnMut` closures require captured values to be able to be \
consumed multiple times, but `FnOnce` closures may consume them only once",
)
}
ty::CoroutineClosure(def_id, closure_args)
if def_id.as_local() == Some(self.mir_def_id())
&& let Some(upvar_field) = upvar_field
&& let bound_span = self.get_closure_bound_clause_span(*def_id, true)
&& bound_span != DUMMY_SP =>
{
let closure_kind_ty = closure_args.as_coroutine_closure().kind_ty();
let closure_kind = match closure_kind_ty.to_opt_closure_kind() {
Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind,
Some(ty::ClosureKind::FnOnce) => {
bug!("coroutine closure kind does not match first argument type")
}
None => bug!("coroutine closure kind not inferred by borrowck"),
};
let capture_description =
format!("captured variable in an `Async{closure_kind}` closure");

let upvar = &self.upvars[upvar_field.index()];
let upvar_hir_id = upvar.get_root_variable();
let upvar_name = upvar.to_string(tcx);
let upvar_span = tcx.hir_span(upvar_hir_id);

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

let place_description =
if self.is_upvar_field_projection(move_place.as_ref()).is_some() {
format!("{place_name}, a {capture_description}")
} else {
format!("{place_name}, as `{upvar_name}` is a {capture_description}")
};

debug!(
"report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}",
closure_kind_ty, closure_kind, place_description,
);

let closure_span = tcx.def_span(def_id);

self.cannot_move_out_of(span, &place_description)
.with_span_label(upvar_span, "captured outer variable")
.with_span_label(
closure_span,
format!("captured by this `Async{closure_kind}` closure"),
)
.with_span_help(
bound_span,
"`AsyncFn` and `AsyncFnMut` closures require captured values to be able \
to be consumed multiple times, but `AsyncFnOnce` closures may consume \
them only once",
)
}
_ => {
let source = self.borrowed_content_source(deref_base);
let move_place_ref = move_place.as_ref();
Expand Down Expand Up @@ -566,7 +617,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
err
}

fn get_closure_bound_clause_span(&self, def_id: DefId) -> Span {
fn get_closure_bound_clause_span(&self, def_id: DefId, is_async: bool) -> Span {
let tcx = self.infcx.tcx;
let typeck_result = tcx.typeck(self.mir_def_id());
// Check whether the closure is an argument to a call, if so,
Expand All @@ -590,18 +641,37 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
_ => return DUMMY_SP,
};

// Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`.
// Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`
// or `AsyncFn[Mut]`.
for (pred, span) in predicates.predicates.iter().zip(predicates.spans.iter()) {
if let Some(clause) = pred.as_trait_clause()
&& let ty::Closure(clause_closure_def_id, _) = clause.self_ty().skip_binder().kind()
&& *clause_closure_def_id == def_id
&& (tcx.lang_items().fn_mut_trait() == Some(clause.def_id())
|| tcx.lang_items().fn_trait() == Some(clause.def_id()))
{
// Found `<TyOfCapturingClosure as FnMut>`
// We point at the `Fn()` or `FnMut()` bound that coerced the closure, which
// could be changed to `FnOnce()` to avoid the move error.
return *span;
if let Some(clause) = pred.as_trait_clause() {
let dominated_by_fn_trait = if is_async {
matches!(
tcx.async_fn_trait_kind_from_def_id(clause.def_id()),
Some(ty::ClosureKind::Fn | ty::ClosureKind::FnMut)
)
} else {
matches!(
tcx.fn_trait_kind_from_def_id(clause.def_id()),
Some(ty::ClosureKind::Fn | ty::ClosureKind::FnMut)
)
};
let clause_closure_def_id = match clause.self_ty().skip_binder().kind() {
ty::Closure(closure_def_id, _) | ty::CoroutineClosure(closure_def_id, _) => {
Some(closure_def_id)
}
_ => None,
};
if let Some(clause_closure_def_id) = clause_closure_def_id
&& *clause_closure_def_id == def_id
&& dominated_by_fn_trait
{
// Found `<TyOfCapturingClosure as FnMut>` or
// `<TyOfCapturingClosure as AsyncFnMut>`.
// We point at the bound that coerced the closure, which could be changed
// to `FnOnce()` or `AsyncFnOnce()` to avoid the move error.
return *span;
}
}
}
DUMMY_SP
Expand Down
13 changes: 13 additions & 0 deletions tests/ui/async-await/async-closures/move-from-async-fn-bound.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//@ edition:2021
// Test that a by-ref `AsyncFn` closure gets an error when it tries to
// consume a value, with a helpful diagnostic pointing to the bound.

fn call<F>(_: F) where F: AsyncFn() {}

fn main() {
let y = vec![format!("World")];
call(async || {
//~^ ERROR cannot move out of `y`, a captured variable in an `AsyncFn` closure
y.into_iter();
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
error[E0507]: cannot move out of `y`, a captured variable in an `AsyncFn` closure
--> $DIR/move-from-async-fn-bound.rs:9:10
|
LL | let y = vec![format!("World")];
| - captured outer variable
LL | call(async || {
| ^^^^^^^^
| |
| captured by this `AsyncFn` closure
| `y` is moved here
LL |
LL | y.into_iter();
| -
| |
| variable moved due to use in coroutine
| move occurs because `y` has type `Vec<String>`, which does not implement the `Copy` trait
|
help: `AsyncFn` and `AsyncFnMut` closures require captured values to be able to be consumed multiple times, but `AsyncFnOnce` closures may consume them only once
--> $DIR/move-from-async-fn-bound.rs:5:27
|
LL | fn call<F>(_: F) where F: AsyncFn() {}
| ^^^^^^^^^
help: consider cloning the value if the performance cost is acceptable
|
LL | y.clone().into_iter();
| ++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0507`.
Loading