1
Fork 0

Prefer AsyncFn* over Fn* for coroutine-closures

This commit is contained in:
Michael Goulet 2024-02-08 15:46:00 +00:00
parent b8c93f1223
commit 3bb384aad6
5 changed files with 79 additions and 29 deletions

View file

@ -260,23 +260,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
adjusted_ty: Ty<'tcx>,
opt_arg_exprs: Option<&'tcx [hir::Expr<'tcx>]>,
) -> Option<(Option<Adjustment<'tcx>>, MethodCallee<'tcx>)> {
// HACK(async_closures): For async closures, prefer `AsyncFn*`
// over `Fn*`, since all async closures implement `FnOnce`, but
// choosing that over `AsyncFn`/`AsyncFnMut` would be more restrictive.
// For other callables, just prefer `Fn*` for perf reasons.
//
// The order of trait choices here is not that big of a deal,
// since it just guides inference (and our choice of autoref).
// Though in the future, I'd like typeck to choose:
// `Fn > AsyncFn > FnMut > AsyncFnMut > FnOnce > AsyncFnOnce`
// ...or *ideally*, we just have `LendingFn`/`LendingFnMut`, which
// would naturally unify these two trait hierarchies in the most
// general way.
let call_trait_choices = if self.shallow_resolve(adjusted_ty).is_coroutine_closure() {
[
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
(self.tcx.lang_items().fn_trait(), sym::call, true),
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
]
} else {
[
(self.tcx.lang_items().fn_trait(), sym::call, true),
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
]
};
// Try the options that are least restrictive on the caller first.
for (opt_trait_def_id, method_name, borrow) in [
(self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true),
(self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true),
(self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false),
(self.tcx.lang_items().async_fn_trait(), Ident::with_dummy_span(sym::async_call), true),
(
self.tcx.lang_items().async_fn_mut_trait(),
Ident::with_dummy_span(sym::async_call_mut),
true,
),
(
self.tcx.lang_items().async_fn_once_trait(),
Ident::with_dummy_span(sym::async_call_once),
false,
),
] {
for (opt_trait_def_id, method_name, borrow) in call_trait_choices {
let Some(trait_def_id) = opt_trait_def_id else { continue };
let opt_input_type = opt_arg_exprs.map(|arg_exprs| {
@ -293,7 +310,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let Some(ok) = self.lookup_method_in_trait(
self.misc(call_expr.span),
method_name,
Ident::with_dummy_span(method_name),
trait_def_id,
adjusted_ty,
opt_input_type.as_ref().map(slice::from_ref),

View file

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