about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2024-02-08 15:46:00 +0000
committerMichael Goulet <michael@errs.io>2024-02-08 15:46:00 +0000
commit3bb384aad6e7f61a0b4b8c604206e78ffa418df4 (patch)
tree28228de771624b1f144cf3fb4aaf9f68c54d1c8b
parentb8c93f1223695217cbabc1f3f1e428c358bb4e7a (diff)
downloadrust-3bb384aad6e7f61a0b4b8c604206e78ffa418df4.tar.gz
rust-3bb384aad6e7f61a0b4b8c604206e78ffa418df4.zip
Prefer AsyncFn* over Fn* for coroutine-closures
-rw-r--r--compiler/rustc_hir_typeck/src/callee.rs51
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs18
-rw-r--r--tests/ui/async-await/async-closures/is-not-fn.rs5
-rw-r--r--tests/ui/async-await/async-closures/is-not-fn.stderr10
-rw-r--r--tests/ui/async-await/async-fn/dyn-pos.stderr24
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