Change:
Something is still wrong w/ async call shims. I think I'm not doing the transform correctly or something. Putting this up so I don't forget about it, but I'll fix it.
ohhhhh lol it's drop glue. Huh.
Oh hell yes this is actually totally wrong wtf:

Discussion:
<del> //! A MIR pass which duplicates a coroutine's body and removes any derefs which //! would be present for upvars that are taken by-ref. The result of which will //! be a coroutine body that takes all of its upvars by-move, and which we stash //! into the `CoroutineInfo` for all coroutines returned by coroutine-closures. </del> <ins> //! This pass constructs a second coroutine body sufficient for return from //! `FnOnce`/`AsyncFnOnce` implementations for coroutine-closures (e.g. async closures). //! //! Consider an async closure like: //! ```rust //! #![feature(async_closure)] //! //! let x = vec![1, 2, 3]; //! //! let closure = async move || { //!     println!("{x:#?}"); //! }; //! ``` //! //! This desugars to something like: //! ```rust,ignore (invalid-borrowck) //! let x = vec![1, 2, 3]; //! //! let closure = move || { //!     async { //!         println!("{x:#?}"); //!     } //! }; //! ``` //! //! Important to note here is that while the outer closure *moves* `x: Vec<i32>` //! into its upvars, the inner `async` coroutine simply captures a ref of `x`. //! This is the "magic" of async closures -- the futures that they return are //! allowed to borrow from their parent closure's upvars. //! //! However, what happens when we call `closure` with `AsyncFnOnce` (or `FnOnce`, //! since all async closures implement that too)? Well, recall the signature: //! ``` //! use std::future::Future; //! pub trait AsyncFnOnce<Args> //! { //!     type CallOnceFuture: Future<Output = Self::Output>; //!     type Output; //!     fn async_call_once( //!         self, //!         args: Args //!     ) -> Self::CallOnceFuture; //! } //! ``` //! //! This signature *consumes* the async closure (`self`) and returns a `CallOnceFuture`. //! How do we deal with the fact that the coroutine is supposed to take a reference //! to the captured `x` from the parent closure, when that parent closure has been //! destroyed? //! //! This is the second piece of magic of async closures. We can simply create a //! *second* `async` coroutine body where that `x` that was previously captured //! by reference is now captured by value. This means that we consume the outer //! closure and return a new coroutine that will hold onto all of these captures, //! and drop them when it is finished (i.e. after it has been `.await`ed). //! //! We do this with the analysis below, which detects the captures that come from //! borrowing from the outer closure, and we simply peel off a `deref` projection //! from them. This second body is stored alongside the first body, and optimized //! with it in lockstep. When we need to resolve a body for `FnOnce` or `AsyncFnOnce`, //! we use this "by move" body instead. use itertools::Itertools; </ins> use rustc_data_structures::unord::UnordSet; use rustc_hir as hir;

impl<'tcx> MirPass<'tcx> for ByMoveBody { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) { <ins> // We only need to generate by-move coroutine bodies for coroutines that come // from coroutine-closures. </ins> let Some(coroutine_def_id) = body.source.def_id().as_local() else { return; };

else { return; }; <ins> // Also, let's skip processing any bodies with errors, since there's no guarantee // the MIR body will be constructed well. </ins> let coroutine_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty; if coroutine_ty.references_error() { return; } <del> let ty::Coroutine(_, args) = *coroutine_ty.kind() else { bug!("{body:#?}") }; let coroutine_kind = args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap(); </del> <ins> let ty::Coroutine(_, coroutine_args) = *coroutine_ty.kind() else { bug!("{body:#?}") }; // We don't need to generate a by-move coroutine if the kind of the coroutine is // already `FnOnce` -- that means that any upvars that the closure consumes have // already been taken by-value. let coroutine_kind = coroutine_args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap(); </ins> if coroutine_kind == ty::ClosureKind::FnOnce { return; } <ins> let parent_def_id = tcx.local_parent(coroutine_def_id); let ty::CoroutineClosure(_, parent_args) = *tcx.type_of(parent_def_id).instantiate_identity().kind() else { bug!(); }; let parent_closure_args = parent_args.as_coroutine_closure(); let num_args = parent_closure_args .coroutine_closure_sig() .skip_binder() .tupled_inputs_ty .tuple_fields() .len(); </ins> let mut by_ref_fields = UnordSet::default(); <del> let by_move_upvars = Ty::new_tup_from_iter( tcx, tcx.closure_captures(coroutine_def_id).iter().enumerate().map(|(idx, capture)| { if capture.is_by_ref() { by_ref_fields.insert(FieldIdx::from_usize(idx)); } capture.place.ty() }), ); let by_move_coroutine_ty = Ty::new_coroutine( tcx, coroutine_def_id.to_def_id(), ty::CoroutineArgs::new( </del> <ins> for (idx, (coroutine_capture, parent_capture)) in tcx .closure_captures(coroutine_def_id) .iter() // By construction we capture all the args first. .skip(num_args) .zip_eq(tcx.closure_captures(parent_def_id)) .enumerate() { // This upvar is captured by-move from the parent closure, but by-ref // from the inner async block. That means that it's being borrowed from // the outer closure body -- we need to change the coroutine to take the // upvar by value. if coroutine_capture.is_by_ref() && !parent_capture.is_by_ref() { by_ref_fields.insert(FieldIdx::from_usize(num_args + idx)); } // Make sure we're actually talking about the same capture. // FIXME(async_closures): We could look at the `hir::Upvar` instead? assert_eq!(coroutine_capture.place.ty(), parent_capture.place.ty()); } let by_move_coroutine_ty = tcx .instantiate_bound_regions_with_erased(parent_closure_args.coroutine_closure_sig()) .to_coroutine_given_kind_and_upvars( </ins> tcx, <del> ty::CoroutineArgsParts { parent_args: args.as_coroutine().parent_args(), kind_ty: Ty::from_closure_kind(tcx, ty::ClosureKind::FnOnce), resume_ty: args.as_coroutine().resume_ty(), yield_ty: args.as_coroutine().yield_ty(), return_ty: args.as_coroutine().return_ty(), witness: args.as_coroutine().witness(), tupled_upvars_ty: by_move_upvars, }, ) .args, ); </del> <ins> parent_closure_args.parent_args(), coroutine_def_id.to_def_id(), ty::ClosureKind::FnOnce, tcx.lifetimes.re_erased, parent_closure_args.tupled_upvars_ty(), parent_closure_args.coroutine_captures_by_ref_ty(), ); </ins> let mut by_move_body = body.clone(); MakeByMoveBody { tcx, by_ref_fields, by_move_coroutine_ty }.visit_body(&mut by_move_body);

<ins> // Same as rustc's `tests/ui/async-await/async-closures/captures.rs`, keep in sync #![feature(async_closure, noop_waker)] 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); let ctx = &mut Context::from_waker(Waker::noop()); loop { match fut.as_mut().poll(ctx) { Poll::Pending => {} Poll::Ready(t) => break t, } } } fn main() { block_on(async_main()); } async fn call<T>(f: &impl async Fn() -> T) -> T { f().await } async fn call_once<T>(f: impl async FnOnce() -> T) -> T { f().await } #[derive(Debug)] #[allow(unused)] struct Hello(i32); async fn async_main() { // Capture something by-ref { let x = Hello(0); let c = async || { println!("{x:?}"); }; call(&c).await; call_once(c).await; let x = &Hello(1); let c = async || { println!("{x:?}"); }; call(&c).await; call_once(c).await; } // Capture something and consume it (force to `AsyncFnOnce`) { let x = Hello(2); let c = async || { println!("{x:?}"); drop(x); }; call_once(c).await; } // Capture something with `move`, don't consume it { let x = Hello(3); let c = async move || { println!("{x:?}"); }; call(&c).await; call_once(c).await; let x = &Hello(4); let c = async move || { println!("{x:?}"); }; call(&c).await; call_once(c).await; } // Capture something with `move`, also consume it (so `AsyncFnOnce`) { let x = Hello(5); let c = async move || { println!("{x:?}"); drop(x); }; call_once(c).await; } } </ins>

<ins> //@ aux-build:block-on.rs //@ edition:2021 //@ run-pass //@ check-run-results // Same as miri's `tests/pass/async-closure-captures.rs`, keep in sync #![feature(async_closure)] extern crate block_on; fn main() { block_on::block_on(async_main()); } async fn call<T>(f: &impl async Fn() -> T) -> T { f().await } async fn call_once<T>(f: impl async FnOnce() -> T) -> T { f().await } #[derive(Debug)] #[allow(unused)] struct Hello(i32); async fn async_main() { // Capture something by-ref { let x = Hello(0); let c = async || { println!("{x:?}"); }; call(&c).await; call_once(c).await; let x = &Hello(1); let c = async || { println!("{x:?}"); }; call(&c).await; call_once(c).await; } // Capture something and consume it (force to `AsyncFnOnce`) { let x = Hello(2); let c = async || { println!("{x:?}"); drop(x); }; call_once(c).await; } // Capture something with `move`, don't consume it { let x = Hello(3); let c = async move || { println!("{x:?}"); }; call(&c).await; call_once(c).await; let x = &Hello(4); let c = async move || { println!("{x:?}"); }; call(&c).await; call_once(c).await; } // Capture something with `move`, also consume it (so `AsyncFnOnce`) { let x = Hello(5); let c = async move || { println!("{x:?}"); drop(x); }; call_once(c).await; } } </ins>

<ins> Hello(0) Hello(0) Hello(1) Hello(1) Hello(2) Hello(3) Hello(3) Hello(4) Hello(4) Hello(5) </ins>