diff options
| author | Michael Goulet <michael@errs.io> | 2024-02-08 15:46:00 +0000 |
|---|---|---|
| committer | Michael Goulet <michael@errs.io> | 2024-02-08 15:46:00 +0000 |
| commit | 3bb384aad6e7f61a0b4b8c604206e78ffa418df4 (patch) | |
| tree | 28228de771624b1f144cf3fb4aaf9f68c54d1c8b | |
| parent | b8c93f1223695217cbabc1f3f1e428c358bb4e7a (diff) | |
| download | rust-3bb384aad6e7f61a0b4b8c604206e78ffa418df4.tar.gz rust-3bb384aad6e7f61a0b4b8c604206e78ffa418df4.zip | |
Prefer AsyncFn* over Fn* for coroutine-closures
5 files changed, 79 insertions, 29 deletions
diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index fbe6f454dbc..bed0d80787f 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -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), diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index a82acc3ba05..eb4b3b7a62e 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -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 { diff --git a/tests/ui/async-await/async-closures/is-not-fn.rs b/tests/ui/async-await/async-closures/is-not-fn.rs index 94c8e8563bd..40b0febbf06 100644 --- a/tests/ui/async-await/async-closures/is-not-fn.rs +++ b/tests/ui/async-await/async-closures/is-not-fn.rs @@ -5,8 +5,5 @@ fn main() { fn needs_fn(x: impl FnOnce()) {} needs_fn(async || {}); - //~^ ERROR expected a `FnOnce()` closure, found `{coroutine-closure@ - // FIXME(async_closures): This should explain in more detail how async fns don't - // implement the regular `Fn` traits. Or maybe we should just fix it and make them - // when there are no upvars or whatever. + //~^ ERROR expected `{coroutine-closure@is-not-fn.rs:7:14}` to be a closure that returns `()` } diff --git a/tests/ui/async-await/async-closures/is-not-fn.stderr b/tests/ui/async-await/async-closures/is-not-fn.stderr index 12da4b1fc6f..6169cee85fd 100644 --- a/tests/ui/async-await/async-closures/is-not-fn.stderr +++ b/tests/ui/async-await/async-closures/is-not-fn.stderr @@ -1,13 +1,13 @@ -error[E0277]: expected a `FnOnce()` closure, found `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}` +error[E0271]: expected `{coroutine-closure@is-not-fn.rs:7:14}` to be a closure that returns `()`, but it returns `{async closure body@$DIR/is-not-fn.rs:7:23: 7:25}` --> $DIR/is-not-fn.rs:7:14 | LL | needs_fn(async || {}); - | -------- ^^^^^^^^^^^ expected an `FnOnce()` closure, found `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}` + | -------- ^^^^^^^^^^^ expected `()`, found `async` closure body | | | required by a bound introduced by this call | - = help: the trait `FnOnce<()>` is not implemented for `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}` - = note: wrap the `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}` in a closure with no arguments: `|| { /* code */ }` + = note: expected unit type `()` + found `async` closure body `{async closure body@$DIR/is-not-fn.rs:7:23: 7:25}` note: required by a bound in `needs_fn` --> $DIR/is-not-fn.rs:6:25 | @@ -16,4 +16,4 @@ LL | fn needs_fn(x: impl FnOnce()) {} error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0277`. +For more information about this error, try `rustc --explain E0271`. diff --git a/tests/ui/async-await/async-fn/dyn-pos.stderr b/tests/ui/async-await/async-fn/dyn-pos.stderr index c9323526516..488c5d06938 100644 --- a/tests/ui/async-await/async-fn/dyn-pos.stderr +++ b/tests/ui/async-await/async-fn/dyn-pos.stderr @@ -8,6 +8,9 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL | = note: the trait cannot be made into an object because it contains the generic associated type `CallFuture` + = 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: + &F + std::boxed::Box<F, A> error[E0038]: the trait `AsyncFnMut` cannot be made into an object --> $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 --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL | = note: the trait cannot be made into an object because it contains the generic associated type `CallMutFuture` + = 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: + &F + &mut F + std::boxed::Box<F, A> error[E0038]: the trait `AsyncFn` cannot be made into an object --> $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 --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL | = note: the trait cannot be made into an object because it contains the generic associated type `CallFuture` + = 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: + &F + std::boxed::Box<F, A> = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` 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 --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL | = note: the trait cannot be made into an object because it contains the generic associated type `CallMutFuture` + = 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: + &F + &mut F + std::boxed::Box<F, A> = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` 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 --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL | = note: the trait cannot be made into an object because it contains the generic associated type `CallFuture` + = 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: + &F + std::boxed::Box<F, A> = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` 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 --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL | = note: the trait cannot be made into an object because it contains the generic associated type `CallMutFuture` + = 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: + &F + &mut F + std::boxed::Box<F, A> = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` 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 ::: $SRC_DIR/core/src/ops/async_function.rs:LL:COL | = note: the trait cannot be made into an object because it contains the generic associated type `CallMutFuture` + = 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: + &F + std::boxed::Box<F, A> error: aborting due to 7 previous errors |
