about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2024-02-29 14:33:50 +0100
committerGitHub <noreply@github.com>2024-02-29 14:33:50 +0100
commit5978b6ff8388574559b3c52c6c975f94a34c6ed9 (patch)
tree1d07f193c943a627975425d6a34812966d09f4c7
parent1ee54538a3bc5628b2a26b70fa0420091b2bb7a5 (diff)
parentc8e3f35eb631c35bc7aabf358b7ca5c2a7fbfd1e (diff)
downloadrust-5978b6ff8388574559b3c52c6c975f94a34c6ed9.tar.gz
rust-5978b6ff8388574559b3c52c6c975f94a34c6ed9.zip
Rollup merge of #121654 - compiler-errors:async-fn-for-fn-def, r=oli-obk
Fix `async Fn` confirmation for `FnDef`/`FnPtr`/`Closure` types

Fixes three issues:
1. The code in `extract_tupled_inputs_and_output_from_async_callable` was accidentally getting the *future* type and the *output* type (returned by the future) messed up for fnptr/fndef/closure types. :/
2. We have a (class of) bug(s) in the old solver where we don't really support higher ranked built-in `Future` goals for generators. This is not possible to hit on stable code, but [can be hit with `unboxed_closures`](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=e935de7181e37e13515ad01720bcb899) (#121653).
    * I'm opting not to fix that in this PR. Instead, I just instantiate placeholders when confirming `async Fn` goals.
4. Fixed a bug when generating `FnPtr` shims for `async Fn` trait goals.

r? oli-obk
-rw-r--r--compiler/rustc_mir_transform/src/shim.rs6
-rw-r--r--compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs201
-rw-r--r--compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs81
-rw-r--r--compiler/rustc_trait_selection/src/solve/trait_goals.rs18
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/confirmation.rs22
-rw-r--r--tests/ui/async-await/async-closures/async-fn-once-for-async-fn.rs10
-rw-r--r--tests/ui/async-await/async-closures/is-fn.rs24
-rw-r--r--tests/ui/async-await/async-closures/move-is-async-fn.rs10
-rw-r--r--tests/ui/async-await/async-closures/once.rs6
-rw-r--r--tests/ui/async-await/async-closures/refd.rs13
-rw-r--r--tests/ui/async-await/async-fn/higher-ranked-async-fn.rs31
-rw-r--r--tests/ui/async-await/async-fn/project.rs12
12 files changed, 330 insertions, 104 deletions
diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs
index 75613a2c555..733e2f93b25 100644
--- a/compiler/rustc_mir_transform/src/shim.rs
+++ b/compiler/rustc_mir_transform/src/shim.rs
@@ -37,7 +37,11 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<'
         }
         ty::InstanceDef::FnPtrShim(def_id, ty) => {
             let trait_ = tcx.trait_of_item(def_id).unwrap();
-            let adjustment = match tcx.fn_trait_kind_from_def_id(trait_) {
+            // Supports `Fn` or `async Fn` traits.
+            let adjustment = match tcx
+                .fn_trait_kind_from_def_id(trait_)
+                .or_else(|| tcx.async_fn_trait_kind_from_def_id(trait_))
+            {
                 Some(ty::ClosureKind::FnOnce) => Adjustment::Identity,
                 Some(ty::ClosureKind::Fn) => Adjustment::Deref { source: DerefSource::ImmRef },
                 Some(ty::ClosureKind::FnMut) => Adjustment::Deref { source: DerefSource::MutRef },
diff --git a/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs b/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs
index 3b902dd80f5..af533d8db71 100644
--- a/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs
+++ b/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs
@@ -281,7 +281,58 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
         }
 
         // Coroutine-closures don't implement `Fn` traits the normal way.
-        ty::CoroutineClosure(..) => Err(NoSolution),
+        // Instead, they always implement `FnOnce`, but only implement
+        // `FnMut`/`Fn` if they capture no upvars, since those may borrow
+        // from the closure.
+        ty::CoroutineClosure(def_id, args) => {
+            let args = args.as_coroutine_closure();
+            let kind_ty = args.kind_ty();
+            let sig = args.coroutine_closure_sig().skip_binder();
+
+            let coroutine_ty = if let Some(closure_kind) = kind_ty.to_opt_closure_kind() {
+                if !closure_kind.extends(goal_kind) {
+                    return Err(NoSolution);
+                }
+
+                // If `Fn`/`FnMut`, we only implement this goal if we
+                // have no captures.
+                let no_borrows = match args.tupled_upvars_ty().kind() {
+                    ty::Tuple(tys) => tys.is_empty(),
+                    ty::Error(_) => false,
+                    _ => bug!("tuple_fields called on non-tuple"),
+                };
+                if closure_kind != ty::ClosureKind::FnOnce && !no_borrows {
+                    return Err(NoSolution);
+                }
+
+                coroutine_closure_to_certain_coroutine(
+                    tcx,
+                    goal_kind,
+                    // No captures by ref, so this doesn't matter.
+                    tcx.lifetimes.re_static,
+                    def_id,
+                    args,
+                    sig,
+                )
+            } else {
+                // Closure kind is not yet determined, so we return ambiguity unless
+                // the expected kind is `FnOnce` as that is always implemented.
+                if goal_kind != ty::ClosureKind::FnOnce {
+                    return Ok(None);
+                }
+
+                coroutine_closure_to_ambiguous_coroutine(
+                    tcx,
+                    goal_kind, // No captures by ref, so this doesn't matter.
+                    tcx.lifetimes.re_static,
+                    def_id,
+                    args,
+                    sig,
+                )
+            };
+
+            Ok(Some(args.coroutine_closure_sig().rebind((sig.tupled_inputs_ty, coroutine_ty))))
+        }
 
         ty::Bool
         | ty::Char
@@ -313,6 +364,19 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
     }
 }
 
+/// Relevant types for an async callable, including its inputs, output,
+/// and the return type you get from awaiting the output.
+#[derive(Copy, Clone, Debug, TypeVisitable, TypeFoldable)]
+pub(in crate::solve) struct AsyncCallableRelevantTypes<'tcx> {
+    pub tupled_inputs_ty: Ty<'tcx>,
+    /// Type returned by calling the closure
+    /// i.e. `f()`.
+    pub output_coroutine_ty: Ty<'tcx>,
+    /// Type returned by `await`ing the output
+    /// i.e. `f().await`.
+    pub coroutine_return_ty: Ty<'tcx>,
+}
+
 // Returns a binder of the tupled inputs types, output type, and coroutine type
 // from a builtin coroutine-closure type. If we don't yet know the closure kind of
 // the coroutine-closure, emit an additional trait predicate for `AsyncFnKindHelper`
@@ -323,8 +387,10 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
     self_ty: Ty<'tcx>,
     goal_kind: ty::ClosureKind,
     env_region: ty::Region<'tcx>,
-) -> Result<(ty::Binder<'tcx, (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)>, Vec<ty::Predicate<'tcx>>), NoSolution>
-{
+) -> Result<
+    (ty::Binder<'tcx, AsyncCallableRelevantTypes<'tcx>>, Vec<ty::Predicate<'tcx>>),
+    NoSolution,
+> {
     match *self_ty.kind() {
         ty::CoroutineClosure(def_id, args) => {
             let args = args.as_coroutine_closure();
@@ -335,24 +401,11 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
                 if !closure_kind.extends(goal_kind) {
                     return Err(NoSolution);
                 }
-                sig.to_coroutine_given_kind_and_upvars(
-                    tcx,
-                    args.parent_args(),
-                    tcx.coroutine_for_closure(def_id),
-                    goal_kind,
-                    env_region,
-                    args.tupled_upvars_ty(),
-                    args.coroutine_captures_by_ref_ty(),
+
+                coroutine_closure_to_certain_coroutine(
+                    tcx, goal_kind, env_region, def_id, args, sig,
                 )
             } else {
-                let async_fn_kind_trait_def_id =
-                    tcx.require_lang_item(LangItem::AsyncFnKindHelper, None);
-                let upvars_projection_def_id = tcx
-                    .associated_items(async_fn_kind_trait_def_id)
-                    .filter_by_name_unhygienic(sym::Upvars)
-                    .next()
-                    .unwrap()
-                    .def_id;
                 // When we don't know the closure kind (and therefore also the closure's upvars,
                 // which are computed at the same time), we must delay the computation of the
                 // generator's upvars. We do this using the `AsyncFnKindHelper`, which as a trait
@@ -363,38 +416,23 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
                 nested.push(
                     ty::TraitRef::new(
                         tcx,
-                        async_fn_kind_trait_def_id,
+                        tcx.require_lang_item(LangItem::AsyncFnKindHelper, None),
                         [kind_ty, Ty::from_closure_kind(tcx, goal_kind)],
                     )
                     .to_predicate(tcx),
                 );
-                let tupled_upvars_ty = Ty::new_projection(
-                    tcx,
-                    upvars_projection_def_id,
-                    [
-                        ty::GenericArg::from(kind_ty),
-                        Ty::from_closure_kind(tcx, goal_kind).into(),
-                        env_region.into(),
-                        sig.tupled_inputs_ty.into(),
-                        args.tupled_upvars_ty().into(),
-                        args.coroutine_captures_by_ref_ty().into(),
-                    ],
-                );
-                sig.to_coroutine(
-                    tcx,
-                    args.parent_args(),
-                    Ty::from_closure_kind(tcx, goal_kind),
-                    tcx.coroutine_for_closure(def_id),
-                    tupled_upvars_ty,
+
+                coroutine_closure_to_ambiguous_coroutine(
+                    tcx, goal_kind, env_region, def_id, args, sig,
                 )
             };
 
             Ok((
-                args.coroutine_closure_sig().rebind((
-                    sig.tupled_inputs_ty,
-                    sig.return_ty,
-                    coroutine_ty,
-                )),
+                args.coroutine_closure_sig().rebind(AsyncCallableRelevantTypes {
+                    tupled_inputs_ty: sig.tupled_inputs_ty,
+                    output_coroutine_ty: coroutine_ty,
+                    coroutine_return_ty: sig.return_ty,
+                }),
                 nested,
             ))
         }
@@ -418,7 +456,11 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
                 .def_id;
             let future_output_ty = Ty::new_projection(tcx, future_output_def_id, [sig.output()]);
             Ok((
-                bound_sig.rebind((Ty::new_tup(tcx, sig.inputs()), sig.output(), future_output_ty)),
+                bound_sig.rebind(AsyncCallableRelevantTypes {
+                    tupled_inputs_ty: Ty::new_tup(tcx, sig.inputs()),
+                    output_coroutine_ty: sig.output(),
+                    coroutine_return_ty: future_output_ty,
+                }),
                 nested,
             ))
         }
@@ -469,7 +511,14 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
                 .unwrap()
                 .def_id;
             let future_output_ty = Ty::new_projection(tcx, future_output_def_id, [sig.output()]);
-            Ok((bound_sig.rebind((sig.inputs()[0], sig.output(), future_output_ty)), nested))
+            Ok((
+                bound_sig.rebind(AsyncCallableRelevantTypes {
+                    tupled_inputs_ty: sig.inputs()[0],
+                    output_coroutine_ty: sig.output(),
+                    coroutine_return_ty: future_output_ty,
+                }),
+                nested,
+            ))
         }
 
         ty::Bool
@@ -502,6 +551,68 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
     }
 }
 
+/// Given a coroutine-closure, project to its returned coroutine when we are *certain*
+/// that the closure's kind is compatible with the goal.
+fn coroutine_closure_to_certain_coroutine<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    goal_kind: ty::ClosureKind,
+    goal_region: ty::Region<'tcx>,
+    def_id: DefId,
+    args: ty::CoroutineClosureArgs<'tcx>,
+    sig: ty::CoroutineClosureSignature<'tcx>,
+) -> Ty<'tcx> {
+    sig.to_coroutine_given_kind_and_upvars(
+        tcx,
+        args.parent_args(),
+        tcx.coroutine_for_closure(def_id),
+        goal_kind,
+        goal_region,
+        args.tupled_upvars_ty(),
+        args.coroutine_captures_by_ref_ty(),
+    )
+}
+
+/// Given a coroutine-closure, project to its returned coroutine when we are *not certain*
+/// that the closure's kind is compatible with the goal, and therefore also don't know
+/// yet what the closure's upvars are.
+///
+/// Note that we do not also push a `AsyncFnKindHelper` goal here.
+fn coroutine_closure_to_ambiguous_coroutine<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    goal_kind: ty::ClosureKind,
+    goal_region: ty::Region<'tcx>,
+    def_id: DefId,
+    args: ty::CoroutineClosureArgs<'tcx>,
+    sig: ty::CoroutineClosureSignature<'tcx>,
+) -> Ty<'tcx> {
+    let async_fn_kind_trait_def_id = tcx.require_lang_item(LangItem::AsyncFnKindHelper, None);
+    let upvars_projection_def_id = tcx
+        .associated_items(async_fn_kind_trait_def_id)
+        .filter_by_name_unhygienic(sym::Upvars)
+        .next()
+        .unwrap()
+        .def_id;
+    let tupled_upvars_ty = Ty::new_projection(
+        tcx,
+        upvars_projection_def_id,
+        [
+            ty::GenericArg::from(args.kind_ty()),
+            Ty::from_closure_kind(tcx, goal_kind).into(),
+            goal_region.into(),
+            sig.tupled_inputs_ty.into(),
+            args.tupled_upvars_ty().into(),
+            args.coroutine_captures_by_ref_ty().into(),
+        ],
+    );
+    sig.to_coroutine(
+        tcx,
+        args.parent_args(),
+        Ty::from_closure_kind(tcx, goal_kind),
+        tcx.coroutine_for_closure(def_id),
+        tupled_upvars_ty,
+    )
+}
+
 /// Assemble a list of predicates that would be present on a theoretical
 /// user impl for an object type. These predicates must be checked any time
 /// we assemble a built-in object candidate for an object type, since they
diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
index aa8cc3667cd..3aba5c85abc 100644
--- a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
@@ -1,5 +1,6 @@
 use crate::traits::{check_args_compatible, specialization_graph};
 
+use super::assembly::structural_traits::AsyncCallableRelevantTypes;
 use super::assembly::{self, structural_traits, Candidate};
 use super::{EvalCtxt, GoalSource};
 use rustc_hir::def::DefKind;
@@ -392,46 +393,56 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
                 goal_kind,
                 env_region,
             )?;
-        let output_is_sized_pred =
-            tupled_inputs_and_output_and_coroutine.map_bound(|(_, output, _)| {
-                ty::TraitRef::from_lang_item(tcx, LangItem::Sized, DUMMY_SP, [output])
-            });
+        let output_is_sized_pred = tupled_inputs_and_output_and_coroutine.map_bound(
+            |AsyncCallableRelevantTypes { output_coroutine_ty: output_ty, .. }| {
+                ty::TraitRef::from_lang_item(tcx, LangItem::Sized, DUMMY_SP, [output_ty])
+            },
+        );
 
         let pred = tupled_inputs_and_output_and_coroutine
-            .map_bound(|(inputs, output, coroutine)| {
-                let (projection_ty, term) = match tcx.item_name(goal.predicate.def_id()) {
-                    sym::CallOnceFuture => (
-                        ty::AliasTy::new(
-                            tcx,
-                            goal.predicate.def_id(),
-                            [goal.predicate.self_ty(), inputs],
+            .map_bound(
+                |AsyncCallableRelevantTypes {
+                     tupled_inputs_ty,
+                     output_coroutine_ty,
+                     coroutine_return_ty,
+                 }| {
+                    let (projection_ty, term) = match tcx.item_name(goal.predicate.def_id()) {
+                        sym::CallOnceFuture => (
+                            ty::AliasTy::new(
+                                tcx,
+                                goal.predicate.def_id(),
+                                [goal.predicate.self_ty(), tupled_inputs_ty],
+                            ),
+                            output_coroutine_ty.into(),
                         ),
-                        coroutine.into(),
-                    ),
-                    sym::CallMutFuture | sym::CallFuture => (
-                        ty::AliasTy::new(
-                            tcx,
-                            goal.predicate.def_id(),
-                            [
-                                ty::GenericArg::from(goal.predicate.self_ty()),
-                                inputs.into(),
-                                env_region.into(),
-                            ],
+                        sym::CallMutFuture | sym::CallFuture => (
+                            ty::AliasTy::new(
+                                tcx,
+                                goal.predicate.def_id(),
+                                [
+                                    ty::GenericArg::from(goal.predicate.self_ty()),
+                                    tupled_inputs_ty.into(),
+                                    env_region.into(),
+                                ],
+                            ),
+                            output_coroutine_ty.into(),
                         ),
-                        coroutine.into(),
-                    ),
-                    sym::Output => (
-                        ty::AliasTy::new(
-                            tcx,
-                            goal.predicate.def_id(),
-                            [ty::GenericArg::from(goal.predicate.self_ty()), inputs.into()],
+                        sym::Output => (
+                            ty::AliasTy::new(
+                                tcx,
+                                goal.predicate.def_id(),
+                                [
+                                    ty::GenericArg::from(goal.predicate.self_ty()),
+                                    tupled_inputs_ty.into(),
+                                ],
+                            ),
+                            coroutine_return_ty.into(),
                         ),
-                        output.into(),
-                    ),
-                    name => bug!("no such associated type: {name}"),
-                };
-                ty::ProjectionPredicate { projection_ty, term }
-            })
+                        name => bug!("no such associated type: {name}"),
+                    };
+                    ty::ProjectionPredicate { projection_ty, term }
+                },
+            )
             .to_predicate(tcx);
 
         // A built-in `AsyncFn` impl only holds if the output is sized.
diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
index 73bf66f6689..eba6ba3f7b0 100644
--- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs
+++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
@@ -2,6 +2,7 @@
 
 use crate::traits::supertrait_def_ids;
 
+use super::assembly::structural_traits::AsyncCallableRelevantTypes;
 use super::assembly::{self, structural_traits, Candidate};
 use super::{EvalCtxt, GoalSource, SolverMode};
 use rustc_data_structures::fx::FxIndexSet;
@@ -327,14 +328,19 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
                 // This region doesn't matter because we're throwing away the coroutine type
                 tcx.lifetimes.re_static,
             )?;
-        let output_is_sized_pred =
-            tupled_inputs_and_output_and_coroutine.map_bound(|(_, output, _)| {
-                ty::TraitRef::from_lang_item(tcx, LangItem::Sized, DUMMY_SP, [output])
-            });
+        let output_is_sized_pred = tupled_inputs_and_output_and_coroutine.map_bound(
+            |AsyncCallableRelevantTypes { output_coroutine_ty, .. }| {
+                ty::TraitRef::from_lang_item(tcx, LangItem::Sized, DUMMY_SP, [output_coroutine_ty])
+            },
+        );
 
         let pred = tupled_inputs_and_output_and_coroutine
-            .map_bound(|(inputs, _, _)| {
-                ty::TraitRef::new(tcx, goal.predicate.def_id(), [goal.predicate.self_ty(), inputs])
+            .map_bound(|AsyncCallableRelevantTypes { tupled_inputs_ty, .. }| {
+                ty::TraitRef::new(
+                    tcx,
+                    goal.predicate.def_id(),
+                    [goal.predicate.self_ty(), tupled_inputs_ty],
+                )
             })
             .to_predicate(tcx);
         // A built-in `AsyncFn` impl only holds if the output is sized.
diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
index d316149731e..b8733bab27b 100644
--- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs
@@ -923,14 +923,22 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                         [self_ty, Ty::new_tup(tcx, sig.inputs())],
                     )
                 });
+
                 // We must additionally check that the return type impls `Future`.
+
+                // FIXME(async_closures): Investigate this before stabilization.
+                // We instantiate this binder eagerly because the `confirm_future_candidate`
+                // method doesn't support higher-ranked futures, which the `AsyncFn`
+                // traits expressly allow the user to write. To fix this correctly,
+                // we'd need to instantiate trait bounds before we get to selection,
+                // like the new trait solver does.
                 let future_trait_def_id = tcx.require_lang_item(LangItem::Future, None);
+                let placeholder_output_ty = self.infcx.enter_forall_and_leak_universe(sig.output());
                 nested.push(obligation.with(
                     tcx,
-                    sig.map_bound(|sig| {
-                        ty::TraitRef::new(tcx, future_trait_def_id, [sig.output()])
-                    }),
+                    ty::TraitRef::new(tcx, future_trait_def_id, [placeholder_output_ty]),
                 ));
+
                 (trait_ref, Ty::from_closure_kind(tcx, ty::ClosureKind::Fn))
             }
             ty::Closure(_, args) => {
@@ -943,14 +951,16 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                         [self_ty, sig.inputs()[0]],
                     )
                 });
+
                 // We must additionally check that the return type impls `Future`.
+                // See FIXME in last branch for why we instantiate the binder eagerly.
                 let future_trait_def_id = tcx.require_lang_item(LangItem::Future, None);
+                let placeholder_output_ty = self.infcx.enter_forall_and_leak_universe(sig.output());
                 nested.push(obligation.with(
                     tcx,
-                    sig.map_bound(|sig| {
-                        ty::TraitRef::new(tcx, future_trait_def_id, [sig.output()])
-                    }),
+                    ty::TraitRef::new(tcx, future_trait_def_id, [placeholder_output_ty]),
                 ));
+
                 (trait_ref, args.kind_ty())
             }
             _ => bug!("expected callable type for AsyncFn candidate"),
diff --git a/tests/ui/async-await/async-closures/async-fn-once-for-async-fn.rs b/tests/ui/async-await/async-closures/async-fn-once-for-async-fn.rs
index 9c3b458cd3a..be436465315 100644
--- a/tests/ui/async-await/async-closures/async-fn-once-for-async-fn.rs
+++ b/tests/ui/async-await/async-closures/async-fn-once-for-async-fn.rs
@@ -8,11 +8,15 @@ extern crate block_on;
 
 fn main() {
     block_on::block_on(async {
-        let x = async || {};
-
         async fn needs_async_fn_once(x: impl async FnOnce()) {
             x().await;
         }
-        needs_async_fn_once(x).await;
+
+        needs_async_fn_once(async || {}).await;
+
+        needs_async_fn_once(|| async {}).await;
+
+        async fn foo() {}
+        needs_async_fn_once(foo).await;
     });
 }
diff --git a/tests/ui/async-await/async-closures/is-fn.rs b/tests/ui/async-await/async-closures/is-fn.rs
new file mode 100644
index 00000000000..64cc28e425f
--- /dev/null
+++ b/tests/ui/async-await/async-closures/is-fn.rs
@@ -0,0 +1,24 @@
+//@ aux-build:block-on.rs
+//@ edition:2021
+//@ build-pass
+//@ revisions: current next
+//@[next] compile-flags: -Znext-solver
+
+#![feature(async_closure)]
+
+use std::future::Future;
+
+extern crate block_on;
+
+// Check that closures that don't capture any state may implement `Fn`.
+
+fn main() {
+    block_on::block_on(async {
+        async fn call_once<F: Future>(x: impl FnOnce(&'static str) -> F) -> F::Output {
+            x("hello, world").await
+        }
+        call_once(async |x: &'static str| {
+            println!("hello, {x}");
+        }).await
+    });
+}
diff --git a/tests/ui/async-await/async-closures/move-is-async-fn.rs b/tests/ui/async-await/async-closures/move-is-async-fn.rs
index 0ccd137266d..79e2298f609 100644
--- a/tests/ui/async-await/async-closures/move-is-async-fn.rs
+++ b/tests/ui/async-await/async-closures/move-is-async-fn.rs
@@ -2,7 +2,7 @@
 //@ edition:2021
 //@ build-pass
 
-#![feature(async_closure)]
+#![feature(async_closure, async_fn_traits)]
 
 extern crate block_on;
 
@@ -15,7 +15,11 @@ fn main() {
         c().await;
         c().await;
 
-        fn is_static<T: 'static>(_: T) {}
-        is_static(c);
+        fn is_static<T: 'static>(_: &T) {}
+        is_static(&c);
+
+        // Check that `<{async fn} as AsyncFnOnce>::CallOnceFuture` owns its captures.
+        fn call_once<F: async FnOnce()>(f: F) -> F::CallOnceFuture { f() }
+        is_static(&call_once(c));
     });
 }
diff --git a/tests/ui/async-await/async-closures/once.rs b/tests/ui/async-await/async-closures/once.rs
index 55e4cedb267..761df3de444 100644
--- a/tests/ui/async-await/async-closures/once.rs
+++ b/tests/ui/async-await/async-closures/once.rs
@@ -1,6 +1,8 @@
 //@ aux-build:block-on.rs
 //@ edition:2021
 //@ build-pass
+//@ revisions: current next
+//@[next] compile-flags: -Znext-solver
 
 #![feature(async_closure)]
 
@@ -8,11 +10,11 @@ use std::future::Future;
 
 extern crate block_on;
 
-struct NoCopy;
+// Check that async closures always implement `FnOnce`
 
 fn main() {
     block_on::block_on(async {
-        async fn call_once<F: Future>(x: impl Fn(&'static str) -> F) -> F::Output {
+        async fn call_once<F: Future>(x: impl FnOnce(&'static str) -> F) -> F::Output {
             x("hello, world").await
         }
         call_once(async |x: &'static str| {
diff --git a/tests/ui/async-await/async-closures/refd.rs b/tests/ui/async-await/async-closures/refd.rs
index 1d9bc1a601b..0b8d3d7aff5 100644
--- a/tests/ui/async-await/async-closures/refd.rs
+++ b/tests/ui/async-await/async-closures/refd.rs
@@ -2,8 +2,6 @@
 //@ edition:2021
 //@ build-pass
 
-// check that `&{async-closure}` implements `AsyncFn`.
-
 #![feature(async_closure)]
 
 extern crate block_on;
@@ -13,6 +11,15 @@ struct NoCopy;
 fn main() {
     block_on::block_on(async {
         async fn call_once(x: impl async Fn()) { x().await }
-        call_once(&async || {}).await
+
+        // check that `&{async-closure}` implements `async Fn`.
+        call_once(&async || {}).await;
+
+        // check that `&{closure}` implements `async Fn`.
+        call_once(&|| async {}).await;
+
+        // check that `&fndef` implements `async Fn`.
+        async fn foo() {}
+        call_once(&foo).await;
     });
 }
diff --git a/tests/ui/async-await/async-fn/higher-ranked-async-fn.rs b/tests/ui/async-await/async-fn/higher-ranked-async-fn.rs
new file mode 100644
index 00000000000..5680c057737
--- /dev/null
+++ b/tests/ui/async-await/async-fn/higher-ranked-async-fn.rs
@@ -0,0 +1,31 @@
+//@ aux-build:block-on.rs
+//@ edition:2018
+//@ revisions: current next
+//@[next] compile-flags: -Znext-solver
+//@ build-pass (since it ICEs during mono)
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+use std::future::Future;
+
+async fn f(arg: &i32) {}
+
+async fn func<F>(f: F)
+where
+    F: async for<'a> Fn(&'a i32),
+{
+    let x: i32 = 0;
+    f(&x).await;
+}
+
+fn main() {
+    block_on::block_on(async {
+        // Function
+        func(f).await;
+
+        // Regular closure (doesn't capture)
+        func(|x: &i32| async {});
+    });
+}
diff --git a/tests/ui/async-await/async-fn/project.rs b/tests/ui/async-await/async-fn/project.rs
new file mode 100644
index 00000000000..302564bb951
--- /dev/null
+++ b/tests/ui/async-await/async-fn/project.rs
@@ -0,0 +1,12 @@
+//@ edition:2018
+//@ revisions: current next
+//@[next] compile-flags: -Znext-solver
+//@ check-pass
+
+#![feature(async_closure, unboxed_closures, async_fn_traits)]
+
+fn project<F: async Fn<()>>(_: F) -> Option<F::Output> { None }
+
+fn main() {
+    let x: Option<i32> = project(|| async { 1i32 });
+}