Skip to content

Commit 3bb384a

Browse files
Prefer AsyncFn* over Fn* for coroutine-closures
1 parent b8c93f1 commit 3bb384a

File tree

5 files changed

+79
-29
lines changed

5 files changed

+79
-29
lines changed

compiler/rustc_hir_typeck/src/callee.rs

+34-17
Original file line numberDiff line numberDiff line change
@@ -260,23 +260,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
260260
adjusted_ty: Ty<'tcx>,
261261
opt_arg_exprs: Option<&'tcx [hir::Expr<'tcx>]>,
262262
) -> Option<(Option<Adjustment<'tcx>>, MethodCallee<'tcx>)> {
263+
// HACK(async_closures): For async closures, prefer `AsyncFn*`
264+
// over `Fn*`, since all async closures implement `FnOnce`, but
265+
// choosing that over `AsyncFn`/`AsyncFnMut` would be more restrictive.
266+
// For other callables, just prefer `Fn*` for perf reasons.
267+
//
268+
// The order of trait choices here is not that big of a deal,
269+
// since it just guides inference (and our choice of autoref).
270+
// Though in the future, I'd like typeck to choose:
271+
// `Fn > AsyncFn > FnMut > AsyncFnMut > FnOnce > AsyncFnOnce`
272+
// ...or *ideally*, we just have `LendingFn`/`LendingFnMut`, which
273+
// would naturally unify these two trait hierarchies in the most
274+
// general way.
275+
let call_trait_choices = if self.shallow_resolve(adjusted_ty).is_coroutine_closure() {
276+
[
277+
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
278+
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
279+
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
280+
(self.tcx.lang_items().fn_trait(), sym::call, true),
281+
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
282+
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
283+
]
284+
} else {
285+
[
286+
(self.tcx.lang_items().fn_trait(), sym::call, true),
287+
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
288+
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
289+
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
290+
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
291+
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
292+
]
293+
};
294+
263295
// Try the options that are least restrictive on the caller first.
264-
for (opt_trait_def_id, method_name, borrow) in [
265-
(self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true),
266-
(self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true),
267-
(self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false),
268-
(self.tcx.lang_items().async_fn_trait(), Ident::with_dummy_span(sym::async_call), true),
269-
(
270-
self.tcx.lang_items().async_fn_mut_trait(),
271-
Ident::with_dummy_span(sym::async_call_mut),
272-
true,
273-
),
274-
(
275-
self.tcx.lang_items().async_fn_once_trait(),
276-
Ident::with_dummy_span(sym::async_call_once),
277-
false,
278-
),
279-
] {
296+
for (opt_trait_def_id, method_name, borrow) in call_trait_choices {
280297
let Some(trait_def_id) = opt_trait_def_id else { continue };
281298

282299
let opt_input_type = opt_arg_exprs.map(|arg_exprs| {
@@ -293,7 +310,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
293310

294311
if let Some(ok) = self.lookup_method_in_trait(
295312
self.misc(call_expr.span),
296-
method_name,
313+
Ident::with_dummy_span(method_name),
297314
trait_def_id,
298315
adjusted_ty,
299316
opt_input_type.as_ref().map(slice::from_ref),

compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,23 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
336336
let is_const = self.tcx().is_const_fn_raw(def_id);
337337
match self.infcx.closure_kind(self_ty) {
338338
Some(closure_kind) => {
339-
let no_borrows = self
339+
let no_borrows = match self
340340
.infcx
341341
.shallow_resolve(args.as_coroutine_closure().tupled_upvars_ty())
342-
.tuple_fields()
343-
.is_empty();
342+
.kind()
343+
{
344+
ty::Tuple(tys) => tys.is_empty(),
345+
ty::Error(_) => false,
346+
_ => bug!("tuple_fields called on non-tuple"),
347+
};
348+
// A coroutine-closure implements `FnOnce` *always*, since it may
349+
// always be called once. It additionally implements `Fn`/`FnMut`
350+
// only if it has no upvars (therefore no borrows from the closure
351+
// that would need to be represented with a lifetime) and if the
352+
// closure kind permits it.
353+
// FIXME(async_closures): Actually, it could also implement `Fn`/`FnMut`
354+
// if it takes all of its upvars by copy, and none by ref. This would
355+
// require us to record a bit more information during upvar analysis.
344356
if no_borrows && closure_kind.extends(kind) {
345357
candidates.vec.push(ClosureCandidate { is_const });
346358
} else if kind == ty::ClosureKind::FnOnce {

tests/ui/async-await/async-closures/is-not-fn.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,5 @@
55
fn main() {
66
fn needs_fn(x: impl FnOnce()) {}
77
needs_fn(async || {});
8-
//~^ ERROR expected a `FnOnce()` closure, found `{coroutine-closure@
9-
// FIXME(async_closures): This should explain in more detail how async fns don't
10-
// implement the regular `Fn` traits. Or maybe we should just fix it and make them
11-
// when there are no upvars or whatever.
8+
//~^ ERROR expected `{[email protected]:7:14}` to be a closure that returns `()`
129
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
error[E0277]: expected a `FnOnce()` closure, found `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}`
1+
error[E0271]: expected `{[email protected]:7:14}` to be a closure that returns `()`, but it returns `{async closure body@$DIR/is-not-fn.rs:7:23: 7:25}`
22
--> $DIR/is-not-fn.rs:7:14
33
|
44
LL | needs_fn(async || {});
5-
| -------- ^^^^^^^^^^^ expected an `FnOnce()` closure, found `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}`
5+
| -------- ^^^^^^^^^^^ expected `()`, found `async` closure body
66
| |
77
| required by a bound introduced by this call
88
|
9-
= help: the trait `FnOnce<()>` is not implemented for `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}`
10-
= note: wrap the `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}` in a closure with no arguments: `|| { /* code */ }`
9+
= note: expected unit type `()`
10+
found `async` closure body `{async closure body@$DIR/is-not-fn.rs:7:23: 7:25}`
1111
note: required by a bound in `needs_fn`
1212
--> $DIR/is-not-fn.rs:6:25
1313
|
@@ -16,4 +16,4 @@ LL | fn needs_fn(x: impl FnOnce()) {}
1616

1717
error: aborting due to 1 previous error
1818

19-
For more information about this error, try `rustc --explain E0277`.
19+
For more information about this error, try `rustc --explain E0271`.

tests/ui/async-await/async-fn/dyn-pos.stderr

+24
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
88
--> $SRC_DIR/core/src/ops/async_function.rs:LL:COL
99
|
1010
= note: the trait cannot be made into an object because it contains the generic associated type `CallFuture`
11+
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `AsyncFn` for this new enum and using it instead:
12+
&F
13+
std::boxed::Box<F, A>
1114

1215
error[E0038]: the trait `AsyncFnMut` cannot be made into an object
1316
--> $DIR/dyn-pos.rs:5:16
@@ -19,6 +22,10 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
1922
--> $SRC_DIR/core/src/ops/async_function.rs:LL:COL
2023
|
2124
= note: the trait cannot be made into an object because it contains the generic associated type `CallMutFuture`
25+
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `AsyncFnMut` for this new enum and using it instead:
26+
&F
27+
&mut F
28+
std::boxed::Box<F, A>
2229

2330
error[E0038]: the trait `AsyncFn` cannot be made into an object
2431
--> $DIR/dyn-pos.rs:5:16
@@ -30,6 +37,9 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
3037
--> $SRC_DIR/core/src/ops/async_function.rs:LL:COL
3138
|
3239
= note: the trait cannot be made into an object because it contains the generic associated type `CallFuture`
40+
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `AsyncFn` for this new enum and using it instead:
41+
&F
42+
std::boxed::Box<F, A>
3343
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
3444

3545
error[E0038]: the trait `AsyncFnMut` cannot be made into an object
@@ -42,6 +52,10 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
4252
--> $SRC_DIR/core/src/ops/async_function.rs:LL:COL
4353
|
4454
= note: the trait cannot be made into an object because it contains the generic associated type `CallMutFuture`
55+
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `AsyncFnMut` for this new enum and using it instead:
56+
&F
57+
&mut F
58+
std::boxed::Box<F, A>
4559
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
4660

4761
error[E0038]: the trait `AsyncFn` cannot be made into an object
@@ -54,6 +68,9 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
5468
--> $SRC_DIR/core/src/ops/async_function.rs:LL:COL
5569
|
5670
= note: the trait cannot be made into an object because it contains the generic associated type `CallFuture`
71+
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `AsyncFn` for this new enum and using it instead:
72+
&F
73+
std::boxed::Box<F, A>
5774
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
5875

5976
error[E0038]: the trait `AsyncFnMut` cannot be made into an object
@@ -66,6 +83,10 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
6683
--> $SRC_DIR/core/src/ops/async_function.rs:LL:COL
6784
|
6885
= note: the trait cannot be made into an object because it contains the generic associated type `CallMutFuture`
86+
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `AsyncFnMut` for this new enum and using it instead:
87+
&F
88+
&mut F
89+
std::boxed::Box<F, A>
6990
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
7091

7192
error[E0038]: the trait `AsyncFn` cannot be made into an object
@@ -81,6 +102,9 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
81102
::: $SRC_DIR/core/src/ops/async_function.rs:LL:COL
82103
|
83104
= note: the trait cannot be made into an object because it contains the generic associated type `CallMutFuture`
105+
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `AsyncFn` for this new enum and using it instead:
106+
&F
107+
std::boxed::Box<F, A>
84108

85109
error: aborting due to 7 previous errors
86110

0 commit comments

Comments
 (0)