Prefer AsyncFn* over Fn* for coroutine-closures
This commit is contained in:
parent
b8c93f1223
commit
3bb384aad6
5 changed files with 79 additions and 29 deletions
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue