about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2024-01-25 17:43:35 +0000
committerMichael Goulet <michael@errs.io>2024-02-06 02:22:58 +0000
commit37184e86ea58bc90b8cd97f877d52ccce8ea02ab (patch)
treeccdae57eb3b8210c03d14b8e6e7a558c429a63bf
parent881b6b5149e882434a8df80a829bcfde0a2e9d37 (diff)
downloadrust-37184e86ea58bc90b8cd97f877d52ccce8ea02ab.tar.gz
rust-37184e86ea58bc90b8cd97f877d52ccce8ea02ab.zip
Add some tests
-rw-r--r--compiler/rustc_hir_typeck/src/upvar.rs5
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs16
-rw-r--r--tests/ui/async-await/async-closures/arg-mismatch.rs15
-rw-r--r--tests/ui/async-await/async-closures/arg-mismatch.stderr21
-rw-r--r--tests/ui/async-await/async-closures/async-fn-once-for-async-fn.rs23
-rw-r--r--tests/ui/async-await/async-closures/async-fn-once-for-async-fn.stderr2
-rw-r--r--tests/ui/async-await/async-closures/auxiliary/block-on.rs20
-rw-r--r--tests/ui/async-await/async-closures/await-inference-guidance.rs16
-rw-r--r--tests/ui/async-await/async-closures/brand.rs26
-rw-r--r--tests/ui/async-await/async-closures/higher-ranked-return.rs18
-rw-r--r--tests/ui/async-await/async-closures/higher-ranked-return.stderr14
-rw-r--r--tests/ui/async-await/async-closures/higher-ranked.rs14
-rw-r--r--tests/ui/async-await/async-closures/is-not-fn.rs12
-rw-r--r--tests/ui/async-await/async-closures/is-not-fn.stderr19
-rw-r--r--tests/ui/async-await/async-closures/move-consuming-capture.rs20
-rw-r--r--tests/ui/async-await/async-closures/move-consuming-capture.stderr17
-rw-r--r--tests/ui/async-await/async-closures/move-is-async-fn.rs21
-rw-r--r--tests/ui/async-await/async-closures/mutate.rs19
-rw-r--r--tests/ui/async-await/async-closures/not-lending.rs21
-rw-r--r--tests/ui/async-await/async-closures/not-lending.stderr24
-rw-r--r--tests/ui/async-await/async-closures/return-type-mismatch.rs14
-rw-r--r--tests/ui/async-await/async-closures/return-type-mismatch.stderr14
-rw-r--r--tests/ui/async-await/async-closures/wrong-fn-kind.rs18
-rw-r--r--tests/ui/async-await/async-closures/wrong-fn-kind.stderr22
24 files changed, 402 insertions, 9 deletions
diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs
index d4e072976fa..e8bdf283c4f 100644
--- a/compiler/rustc_hir_typeck/src/upvar.rs
+++ b/compiler/rustc_hir_typeck/src/upvar.rs
@@ -394,13 +394,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 coroutine_captures_by_ref_ty,
             );
 
-            let ty::Coroutine(_, args) = *self.typeck_results.borrow().expr_ty(body.value).kind()
+            let ty::Coroutine(_, coroutine_args) =
+                *self.typeck_results.borrow().expr_ty(body.value).kind()
             else {
                 bug!();
             };
             self.demand_eqtype(
                 span,
-                args.as_coroutine().kind_ty(),
+                coroutine_args.as_coroutine().kind_ty(),
                 Ty::from_closure_kind(self.tcx, closure_kind),
             );
         }
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 75aedd5cd22..dda68fd4244 100644
--- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
@@ -371,9 +371,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
         obligation: &PolyTraitObligation<'tcx>,
         candidates: &mut SelectionCandidateSet<'tcx>,
     ) {
-        if let Some(closure_kind) = obligation.self_ty().skip_binder().to_opt_closure_kind()
-            && let Some(goal_kind) =
-                obligation.predicate.skip_binder().trait_ref.args.type_at(1).to_opt_closure_kind()
+        let self_ty = obligation.self_ty().skip_binder();
+        let target_kind_ty = obligation.predicate.skip_binder().trait_ref.args.type_at(1);
+
+        // `to_opt_closure_kind` is kind of ICEy when it sees non-int types.
+        if !(self_ty.is_integral() || self_ty.is_ty_var()) {
+            return;
+        }
+        if !(target_kind_ty.is_integral() || self_ty.is_ty_var()) {
+            return;
+        }
+
+        if let Some(closure_kind) = self_ty.to_opt_closure_kind()
+            && let Some(goal_kind) = target_kind_ty.to_opt_closure_kind()
         {
             if closure_kind.extends(goal_kind) {
                 candidates.vec.push(AsyncFnKindHelperCandidate);
diff --git a/tests/ui/async-await/async-closures/arg-mismatch.rs b/tests/ui/async-await/async-closures/arg-mismatch.rs
new file mode 100644
index 00000000000..650e13677bc
--- /dev/null
+++ b/tests/ui/async-await/async-closures/arg-mismatch.rs
@@ -0,0 +1,15 @@
+// aux-build:block-on.rs
+// edition:2021
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+fn main() {
+    block_on::block_on(async {
+        let c = async |x| {};
+        c(1i32).await;
+        c(2usize).await;
+        //~^ ERROR mismatched types
+    });
+}
diff --git a/tests/ui/async-await/async-closures/arg-mismatch.stderr b/tests/ui/async-await/async-closures/arg-mismatch.stderr
new file mode 100644
index 00000000000..70853ae2815
--- /dev/null
+++ b/tests/ui/async-await/async-closures/arg-mismatch.stderr
@@ -0,0 +1,21 @@
+error[E0308]: mismatched types
+  --> $DIR/arg-mismatch.rs:12:11
+   |
+LL |         c(2usize).await;
+   |         - ^^^^^^ expected `i32`, found `usize`
+   |         |
+   |         arguments to this function are incorrect
+   |
+note: closure parameter defined here
+  --> $DIR/arg-mismatch.rs:10:24
+   |
+LL |         let c = async |x| {};
+   |                        ^
+help: change the type of the numeric literal from `usize` to `i32`
+   |
+LL |         c(2i32).await;
+   |            ~~~
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0308`.
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
new file mode 100644
index 00000000000..4afc43fe6bd
--- /dev/null
+++ b/tests/ui/async-await/async-closures/async-fn-once-for-async-fn.rs
@@ -0,0 +1,23 @@
+// aux-build:block-on.rs
+// edition:2021
+// run-pass
+
+// FIXME(async_closures): When `fn_sig_for_fn_abi` is fixed, remove this.
+// ignore-pass (test emits codegen-time warnings)
+
+#![feature(async_closure, async_fn_traits)]
+
+extern crate block_on;
+
+use std::ops::AsyncFnOnce;
+
+fn main() {
+    block_on::block_on(async {
+        let x = async || {};
+
+        async fn needs_async_fn_once(x: impl AsyncFnOnce()) {
+            x().await;
+        }
+        needs_async_fn_once(x).await;
+    });
+}
diff --git a/tests/ui/async-await/async-closures/async-fn-once-for-async-fn.stderr b/tests/ui/async-await/async-closures/async-fn-once-for-async-fn.stderr
new file mode 100644
index 00000000000..9ae4692f003
--- /dev/null
+++ b/tests/ui/async-await/async-closures/async-fn-once-for-async-fn.stderr
@@ -0,0 +1,2 @@
+WARN rustc_codegen_ssa::mir::locals Unexpected initial operand type: expected std::pin::Pin<&ReErased mut Coroutine(DefId(0:8 ~ async_fn_once_for_async_fn[6cdf]::main::{closure#0}::{closure#1}), [i32, std::future::ResumeTy, (), (), CoroutineWitness(DefId(0:8 ~ async_fn_once_for_async_fn[6cdf]::main::{closure#0}::{closure#1}), []), ()])>, found std::pin::Pin<&ReErased mut Coroutine(DefId(0:8 ~ async_fn_once_for_async_fn[6cdf]::main::{closure#0}::{closure#1}), [i8, std::future::ResumeTy, (), (), CoroutineWitness(DefId(0:8 ~ async_fn_once_for_async_fn[6cdf]::main::{closure#0}::{closure#1}), []), ()])>.See <https://github.com/rust-lang/rust/issues/114858>.
+WARN rustc_codegen_ssa::mir::locals Unexpected initial operand type: expected *mut Coroutine(DefId(0:8 ~ async_fn_once_for_async_fn[6cdf]::main::{closure#0}::{closure#1}), [i8, std::future::ResumeTy, (), (), CoroutineWitness(DefId(0:8 ~ async_fn_once_for_async_fn[6cdf]::main::{closure#0}::{closure#1}), []), ()]), found *mut Coroutine(DefId(0:8 ~ async_fn_once_for_async_fn[6cdf]::main::{closure#0}::{closure#1}), [i32, std::future::ResumeTy, (), (), CoroutineWitness(DefId(0:8 ~ async_fn_once_for_async_fn[6cdf]::main::{closure#0}::{closure#1}), []), ()]).See <https://github.com/rust-lang/rust/issues/114858>.
diff --git a/tests/ui/async-await/async-closures/auxiliary/block-on.rs b/tests/ui/async-await/async-closures/auxiliary/block-on.rs
new file mode 100644
index 00000000000..3c27548b865
--- /dev/null
+++ b/tests/ui/async-await/async-closures/auxiliary/block-on.rs
@@ -0,0 +1,20 @@
+// edition: 2021
+
+#![feature(async_closure, noop_waker, async_fn_traits)]
+
+use std::future::Future;
+use std::pin::pin;
+use std::task::*;
+
+pub fn block_on<T>(fut: impl Future<Output = T>) -> T {
+    let mut fut = pin!(fut);
+    // Poll loop, just to test the future...
+    let ctx = &mut Context::from_waker(Waker::noop());
+
+    loop {
+        match unsafe { fut.as_mut().poll(ctx) } {
+            Poll::Pending => {}
+            Poll::Ready(t) => break t,
+        }
+    }
+}
diff --git a/tests/ui/async-await/async-closures/await-inference-guidance.rs b/tests/ui/async-await/async-closures/await-inference-guidance.rs
new file mode 100644
index 00000000000..3702915cbad
--- /dev/null
+++ b/tests/ui/async-await/async-closures/await-inference-guidance.rs
@@ -0,0 +1,16 @@
+// aux-build:block-on.rs
+// edition:2021
+// run-pass
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+fn main() {
+    block_on::block_on(async {
+        let x = async |x: &str| -> String { x.to_owned() };
+        let mut s = x("hello, world").await;
+        s.truncate(4);
+        println!("{s}");
+    });
+}
diff --git a/tests/ui/async-await/async-closures/brand.rs b/tests/ui/async-await/async-closures/brand.rs
new file mode 100644
index 00000000000..3bda7737bb4
--- /dev/null
+++ b/tests/ui/async-await/async-closures/brand.rs
@@ -0,0 +1,26 @@
+// aux-build:block-on.rs
+// edition:2021
+// build-pass
+
+#![feature(async_closure, async_fn_traits)]
+
+extern crate block_on;
+
+use std::future::Future;
+use std::marker::PhantomData;
+use std::ops::AsyncFn;
+
+struct S;
+struct B<'b>(PhantomData<&'b mut &'b mut ()>);
+
+impl S {
+    async fn q<F: AsyncFn(B<'_>)>(self, f: F) {
+        f(B(PhantomData)).await;
+    }
+}
+
+fn main() {
+    block_on::block_on(async {
+        S.q(async |b: B<'_>| { println!("...") }).await;
+    });
+}
diff --git a/tests/ui/async-await/async-closures/higher-ranked-return.rs b/tests/ui/async-await/async-closures/higher-ranked-return.rs
new file mode 100644
index 00000000000..d98779c6ea3
--- /dev/null
+++ b/tests/ui/async-await/async-closures/higher-ranked-return.rs
@@ -0,0 +1,18 @@
+// aux-build:block-on.rs
+// edition:2021
+
+// known-bug: unknown
+// Borrow checking doesn't like that higher-ranked output...
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+fn main() {
+    block_on::block_on(async {
+        let x = async move |x: &str| -> &str {
+            x
+        };
+        let s = x("hello!").await;
+    });
+}
diff --git a/tests/ui/async-await/async-closures/higher-ranked-return.stderr b/tests/ui/async-await/async-closures/higher-ranked-return.stderr
new file mode 100644
index 00000000000..268631f67cd
--- /dev/null
+++ b/tests/ui/async-await/async-closures/higher-ranked-return.stderr
@@ -0,0 +1,14 @@
+error: lifetime may not live long enough
+  --> $DIR/higher-ranked-return.rs:13:46
+   |
+LL |           let x = async move |x: &str| -> &str {
+   |  ________________________________-________----_^
+   | |                                |        |
+   | |                                |        return type of async closure `{async closure body@$DIR/higher-ranked-return.rs:13:46: 15:10}` contains a lifetime `'2`
+   | |                                let's call the lifetime of this reference `'1`
+LL | |             x
+LL | |         };
+   | |_________^ returning this value requires that `'1` must outlive `'2`
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/async-await/async-closures/higher-ranked.rs b/tests/ui/async-await/async-closures/higher-ranked.rs
index 5bbcc7041a8..17b5116cceb 100644
--- a/tests/ui/async-await/async-closures/higher-ranked.rs
+++ b/tests/ui/async-await/async-closures/higher-ranked.rs
@@ -1,10 +1,16 @@
+// aux-build:block-on.rs
 // edition:2021
-// check-pass
+// build-pass
 
 #![feature(async_closure)]
 
+extern crate block_on;
+
 fn main() {
-    let x = async move |x: &str| {
-        println!("{x}");
-    };
+    block_on::block_on(async {
+        let x = async move |x: &str| {
+            println!("{x}");
+        };
+        x("hello!").await;
+    });
 }
diff --git a/tests/ui/async-await/async-closures/is-not-fn.rs b/tests/ui/async-await/async-closures/is-not-fn.rs
new file mode 100644
index 00000000000..94c8e8563bd
--- /dev/null
+++ b/tests/ui/async-await/async-closures/is-not-fn.rs
@@ -0,0 +1,12 @@
+// edition:2021
+
+#![feature(async_closure)]
+
+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.
+}
diff --git a/tests/ui/async-await/async-closures/is-not-fn.stderr b/tests/ui/async-await/async-closures/is-not-fn.stderr
new file mode 100644
index 00000000000..12da4b1fc6f
--- /dev/null
+++ b/tests/ui/async-await/async-closures/is-not-fn.stderr
@@ -0,0 +1,19 @@
+error[E0277]: expected a `FnOnce()` closure, found `{coroutine-closure@$DIR/is-not-fn.rs:7:14: 7:22}`
+  --> $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}`
+   |     |
+   |     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: required by a bound in `needs_fn`
+  --> $DIR/is-not-fn.rs:6:25
+   |
+LL |     fn needs_fn(x: impl FnOnce()) {}
+   |                         ^^^^^^^^ required by this bound in `needs_fn`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0277`.
diff --git a/tests/ui/async-await/async-closures/move-consuming-capture.rs b/tests/ui/async-await/async-closures/move-consuming-capture.rs
new file mode 100644
index 00000000000..b8964c571f9
--- /dev/null
+++ b/tests/ui/async-await/async-closures/move-consuming-capture.rs
@@ -0,0 +1,20 @@
+// aux-build:block-on.rs
+// edition:2021
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+struct NoCopy;
+
+fn main() {
+    block_on::block_on(async {
+        let s = NoCopy;
+        let x = async move || {
+            drop(s);
+        };
+        x().await;
+        x().await;
+        //~^ ERROR use of moved value: `x`
+    });
+}
diff --git a/tests/ui/async-await/async-closures/move-consuming-capture.stderr b/tests/ui/async-await/async-closures/move-consuming-capture.stderr
new file mode 100644
index 00000000000..2c2a0d1162d
--- /dev/null
+++ b/tests/ui/async-await/async-closures/move-consuming-capture.stderr
@@ -0,0 +1,17 @@
+error[E0382]: use of moved value: `x`
+  --> $DIR/move-consuming-capture.rs:17:9
+   |
+LL |         let x = async move || {
+   |             - move occurs because `x` has type `{coroutine-closure@$DIR/move-consuming-capture.rs:13:17: 13:30}`, which does not implement the `Copy` trait
+...
+LL |         x().await;
+   |         --- `x` moved due to this method call
+LL |         x().await;
+   |         ^ value used here after move
+   |
+note: `async_call_once` takes ownership of the receiver `self`, which moves `x`
+  --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0382`.
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
new file mode 100644
index 00000000000..943c0629541
--- /dev/null
+++ b/tests/ui/async-await/async-closures/move-is-async-fn.rs
@@ -0,0 +1,21 @@
+// aux-build:block-on.rs
+// edition:2021
+// build-pass
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+fn main() {
+    block_on::block_on(async {
+        let s = String::from("hello, world");
+        let c = async move || {
+            println!("{s}");
+        };
+        c().await;
+        c().await;
+
+        fn is_static<T: 'static>(_: T) {}
+        is_static(c);
+    });
+}
diff --git a/tests/ui/async-await/async-closures/mutate.rs b/tests/ui/async-await/async-closures/mutate.rs
new file mode 100644
index 00000000000..cc1df5f034f
--- /dev/null
+++ b/tests/ui/async-await/async-closures/mutate.rs
@@ -0,0 +1,19 @@
+// aux-build:block-on.rs
+// edition:2021
+// run-pass
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+fn main() {
+    block_on::block_on(async {
+        let mut prefix = String::from("Hello");
+        let mut c = async move |x: &str| {
+            prefix.push(',');
+            println!("{prefix} {x}!")
+        };
+        c("world").await;
+        c("rust").await;
+    });
+}
diff --git a/tests/ui/async-await/async-closures/not-lending.rs b/tests/ui/async-await/async-closures/not-lending.rs
new file mode 100644
index 00000000000..90832e1a074
--- /dev/null
+++ b/tests/ui/async-await/async-closures/not-lending.rs
@@ -0,0 +1,21 @@
+// aux-build:block-on.rs
+// edition:2021
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+// Make sure that we can't make an async closure that evaluates to a self-borrow.
+// i.e. that the generator may reference captures, but the future's return type can't.
+
+fn main() {
+    block_on::block_on(async {
+        let s = String::new();
+        let x = async move || -> &String { &s };
+        //~^ ERROR lifetime may not live long enough
+
+        let s = String::new();
+        let x = async move || { &s };
+        //~^ ERROR lifetime may not live long enough
+    });
+}
diff --git a/tests/ui/async-await/async-closures/not-lending.stderr b/tests/ui/async-await/async-closures/not-lending.stderr
new file mode 100644
index 00000000000..1713e49b551
--- /dev/null
+++ b/tests/ui/async-await/async-closures/not-lending.stderr
@@ -0,0 +1,24 @@
+error: lifetime may not live long enough
+  --> $DIR/not-lending.rs:14:42
+   |
+LL |         let x = async move || -> &String { &s };
+   |                 ------------------------ ^^^^^^ returning this value requires that `'1` must outlive `'2`
+   |                 |                |
+   |                 |                return type of async closure `{async closure body@$DIR/not-lending.rs:14:42: 14:48}` contains a lifetime `'2`
+   |                 lifetime `'1` represents this closure's body
+   |
+   = note: closure implements `Fn`, so references to captured variables can't escape the closure
+
+error: lifetime may not live long enough
+  --> $DIR/not-lending.rs:18:31
+   |
+LL |         let x = async move || { &s };
+   |                 ------------- ^^^^^^ returning this value requires that `'1` must outlive `'2`
+   |                 |           |
+   |                 |           return type of async closure `{async closure body@$DIR/not-lending.rs:18:31: 18:37}` contains a lifetime `'2`
+   |                 lifetime `'1` represents this closure's body
+   |
+   = note: closure implements `Fn`, so references to captured variables can't escape the closure
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/ui/async-await/async-closures/return-type-mismatch.rs b/tests/ui/async-await/async-closures/return-type-mismatch.rs
new file mode 100644
index 00000000000..9ad6be0b6e6
--- /dev/null
+++ b/tests/ui/async-await/async-closures/return-type-mismatch.rs
@@ -0,0 +1,14 @@
+// aux-build:block-on.rs
+// edition:2021
+
+#![feature(async_closure)]
+
+extern crate block_on;
+
+fn main() {
+    block_on::block_on(async {
+        let x = async || -> i32 { 0 };
+        let y: usize = x().await;
+        //~^ ERROR mismatched types
+    });
+}
diff --git a/tests/ui/async-await/async-closures/return-type-mismatch.stderr b/tests/ui/async-await/async-closures/return-type-mismatch.stderr
new file mode 100644
index 00000000000..53841f62777
--- /dev/null
+++ b/tests/ui/async-await/async-closures/return-type-mismatch.stderr
@@ -0,0 +1,14 @@
+error[E0308]: mismatched types
+  --> $DIR/return-type-mismatch.rs:11:24
+   |
+LL |         let y: usize = x().await;
+   |                        ^^^^^^^^^ expected `usize`, found `i32`
+   |
+help: you can convert an `i32` to a `usize` and panic if the converted value doesn't fit
+   |
+LL |         let y: usize = x().await.try_into().unwrap();
+   |                                 ++++++++++++++++++++
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/async-await/async-closures/wrong-fn-kind.rs b/tests/ui/async-await/async-closures/wrong-fn-kind.rs
new file mode 100644
index 00000000000..24828832531
--- /dev/null
+++ b/tests/ui/async-await/async-closures/wrong-fn-kind.rs
@@ -0,0 +1,18 @@
+// edition:2021
+
+// FIXME(async_closures): This needs a better error message!
+
+#![feature(async_closure, async_fn_traits)]
+
+use std::ops::AsyncFn;
+
+fn main() {
+    fn needs_async_fn(_: impl AsyncFn()) {}
+
+    let mut x = 1;
+    needs_async_fn(async || {
+        //~^ ERROR i16: ops::async_function::internal_implementation_detail::AsyncFnKindHelper<i8>
+        // FIXME: Should say "closure is AsyncFnMut but it needs AsyncFn" or sth.
+        x += 1;
+    });
+}
diff --git a/tests/ui/async-await/async-closures/wrong-fn-kind.stderr b/tests/ui/async-await/async-closures/wrong-fn-kind.stderr
new file mode 100644
index 00000000000..ef95e6a211c
--- /dev/null
+++ b/tests/ui/async-await/async-closures/wrong-fn-kind.stderr
@@ -0,0 +1,22 @@
+error[E0277]: the trait bound `i16: ops::async_function::internal_implementation_detail::AsyncFnKindHelper<i8>` is not satisfied
+  --> $DIR/wrong-fn-kind.rs:13:20
+   |
+LL |       needs_async_fn(async || {
+   |  _____--------------_^
+   | |     |
+   | |     required by a bound introduced by this call
+LL | |
+LL | |         // FIXME: Should say "closure is AsyncFnMut but it needs AsyncFn" or sth.
+LL | |         x += 1;
+LL | |     });
+   | |_____^ the trait `ops::async_function::internal_implementation_detail::AsyncFnKindHelper<i8>` is not implemented for `i16`
+   |
+note: required by a bound in `needs_async_fn`
+  --> $DIR/wrong-fn-kind.rs:10:31
+   |
+LL |     fn needs_async_fn(_: impl AsyncFn()) {}
+   |                               ^^^^^^^^^ required by this bound in `needs_async_fn`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0277`.