at main 8.4 kB view raw
1use proc_macro::TokenStream; 2use quote::{ToTokens, quote}; 3use syn::{ 4 Expr, ExprAwait, ItemFn, ReturnType, parse_macro_input, parse_quote, 5 parse2, visit_mut::VisitMut, 6}; 7 8/// Takes async fn that returns anonymous `Future` impl. 9/// Generates fn that returns `UnscopedFutureWrapper` wrapper for the the anonymous `Future` impl. 10/// 11/// ```rust,ignore 12/// fn my_func<'a, 'b>(a: &'a A, b: &'b B) -> impl ScopedFuture<LifetimeGuard, Output = Output> { 13/// let output = async move { [body] } // compilers turns this into -> impl Future<Output = T> + 'a + 'b 14/// unsafe { futures_compat::std_future_to_bespoke(output) } 15/// } 16/// ``` 17/// 18/// see https://rust-lang.github.io/rfcs/2394-async_await.html#lifetime-capture-in-the-anonymous-future 19/// for more context on lifetime capture 20/// - resulting ScopedFuture needs to be constrained to not outlive the lifetimes of any references 21/// 22/// to actually implement this (capture all lifetimes) we use `ScopedFuture<'_> + '_` so the compiler can infer 23/// lifetimes from the anonymous future impl returned by the actual inner async fn 24#[proc_macro_attribute] 25pub fn async_function(_: TokenStream, item: TokenStream) -> TokenStream { 26 let mut item_fn = parse_macro_input!(item as ItemFn); 27 // Wraps *every* async expression within the function block with 28 // `BespokeFutureWrapper`, allowing them to be treated as regular `Future` 29 // impls. 30 // 31 // This will cause a compiler error if any expression being awaited is not 32 // a `ScopedFuture`, which is intentional because the `Future` and 33 // `ScopedFuture` systems are incompatible. 34 BespokeFutureWrappingVisitor.visit_item_fn_mut(&mut item_fn); 35 36 // disable async since it is moved to the block 37 item_fn.sig.asyncness = None; 38 39 // wrap block with UnscopedFutureWrapper 40 let block = *item_fn.block; 41 *item_fn.block = parse_quote! { 42 { 43 let future = async move #block; 44 unsafe { futures_compat::std_future_to_bespoke(future) } 45 } 46 }; 47 48 let output_type = match &item_fn.sig.output { 49 ReturnType::Default => quote! { () }, 50 ReturnType::Type(_, ty) => quote! { #ty }, 51 }; 52 53 item_fn.sig.output = parse_quote! { -> impl futures_core::Future<LocalWaker, Output = #output_type> }; 54 55 // let has_lifetime_dependency = 56 // item_fn.sig.inputs.iter().any(|param| match param { 57 // FnArg::Receiver(receiver) => receiver.reference.is_some(), 58 // FnArg::Typed(pat) => has_lifetime_dependency(&pat.ty), 59 // }); 60 61 // // set outer fn output to ScopedFuture<'_/'static, Output = #output> 62 // item_fn.sig.output = if has_lifetime_dependency { 63 // parse_quote! { -> impl futures_core::ScopedFuture<'_, Output = #output> + '_ } 64 // } else { 65 // parse_quote! { -> impl futures_core::ScopedFuture<'static, Output = #output> } 66 // }; 67 68 item_fn.to_token_stream().into() 69} 70 71/// This currently is impossible to do the `futures_compat` workarounds not 72/// being compatible with closures. 73/// 74/// Takes async fn that returns anonymous `Future` impl. 75/// Generates fn that returns `UnscopedFutureWrapper` wrapper for the the anonymous `Future` impl. 76/// 77/// ```rust,ignore 78/// fn [original name]<'a, 'b>(a: &'a A, b: &'b B) -> impl ScopedFuture<'a + 'b, Output = T> + 'a + 'b { 79/// async fn [__inner]<'a, 'b>(a: &'a A, b: &'b B) -> T { [body] } // compilers turns this into -> impl Future<Output = T> + 'a + 'b 80/// unsafe { UnscopedFutureWrapper::from_future(__inner()) } 81/// } 82/// ``` 83/// 84/// see https://rust-lang.github.io/rfcs/2394-async_await.html#lifetime-capture-in-the-anonymous-future 85/// for more context on lifetime capture 86/// - resulting ScopedFuture needs to be constrained to not outlive the lifetimes of any references 87/// 88/// to actually implement this (capture all lifetimes) we use `ScopedFuture<'_> + '_` so the compiler can infer 89/// lifetimes from the anonymous future impl returned by the actual inner async fn 90// #[proc_macro] 91// pub fn closure(input: TokenStream) -> TokenStream { 92// // let ExprClosure { 93// // attrs, 94// // lifetimes, 95// // constness, 96// // movability, 97// // capture, 98// // inputs, 99// // output, 100// // body, 101// // .. 102// // } = parse_macro_input!(input as ExprClosure); 103// let mut closure = parse_macro_input!(input as ExprClosure); 104// // disable async because we move it to inner 105// closure.asyncness = None; 106// let body = closure.body; 107 108// // let output = match closure.output { 109// // ReturnType::Default => parse_quote! { () }, 110// // ReturnType::Type(_, ty) => parse_quote! { #ty }, 111// // }; 112 113// // let outer_output = 114// // parse_quote! { futures_core::ScopedFuture<'_, Output = #output> + '_ }; 115 116// closure.body = parse_quote! {{ 117// let output = async move { #body }; 118// unsafe { futures_compat::UnscopedFutureWrapper::from_future(output) } 119// }}; 120// // closure.output = outer_output; 121// closure.to_token_stream().into() 122// } 123 124/// Wraps a block of optionally async statements and expressions in an anonymous `ScopedFuture` impl. 125/// 126/// This generates a modified block of the form: 127/// 128/// ```rust,ignore 129/// { 130/// let output = async { <original block, mapped to convert all `ScopedFuture` to `Future`> }; 131/// unsafe { futures_compat::UnscopedFutureWrapper::from_future(output) } 132/// } 133/// ``` 134#[proc_macro] 135pub fn async_block(input: TokenStream) -> TokenStream { 136 // block is formed { **expr/stmt }, so we need to surround the inputs in {} 137 let input = proc_macro2::TokenStream::from(input); 138 let block_input = quote! { { #input } }; 139 140 let mut block = parse2(block_input).expect("Failed to parse as block."); 141 142 BespokeFutureWrappingVisitor.visit_block_mut(&mut block); 143 144 quote! { 145 { 146 let output = async #block; 147 unsafe { futures_compat::std_future_to_bespoke(output) } 148 } 149 } 150 .into() 151} 152 153/// Determines if typed pattern contains a reference or dependency on a 154/// lifetime (used for deciding between '_ and 'static ScopedFuture). 155// fn has_lifetime_dependency(ty: &syn::Type) -> bool { 156// match ty { 157// syn::Type::Reference(_) => true, 158// syn::Type::Path(type_path) => { 159// type_path.path.segments.iter().any(|segment| { 160// if let syn::PathArguments::AngleBracketed(args) = 161// &segment.arguments 162// { 163// args.args.iter().any(|arg| match arg { 164// GenericArgument::Type(ty) => { 165// has_lifetime_dependency(ty) 166// } 167// syn::GenericArgument::Lifetime(_) => true, 168// _ => false, 169// }) 170// } else { 171// false 172// } 173// }) 174// } 175// syn::Type::Tuple(tuple) => { 176// tuple.elems.iter().any(has_lifetime_dependency) 177// } 178// syn::Type::Slice(slice) => has_lifetime_dependency(&slice.elem), 179// syn::Type::Array(array) => has_lifetime_dependency(&array.elem), 180// syn::Type::Ptr(ptr) => has_lifetime_dependency(&ptr.elem), 181// syn::Type::Group(group) => has_lifetime_dependency(&group.elem), 182// syn::Type::Paren(paren) => has_lifetime_dependency(&paren.elem), 183// syn::Type::BareFn(bare_fn) => { 184// bare_fn 185// .inputs 186// .iter() 187// .any(|input| has_lifetime_dependency(&input.ty)) 188// || match &bare_fn.output { 189// ReturnType::Default => false, 190// ReturnType::Type(_, ty) => has_lifetime_dependency(ty), 191// } 192// } 193 194// _ => false, 195// } 196// } 197 198/// Uses the `syn::visit_mut` api to wrap every `.await` expression in 199/// `ScopedFutureWrapper`. 200struct BespokeFutureWrappingVisitor; 201 202impl VisitMut for BespokeFutureWrappingVisitor { 203 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { 204 if let Expr::Await(ExprAwait { attrs, base, .. }) = expr { 205 *expr = syn::parse_quote! { 206 unsafe { futures_compat::bespoke_future_to_std(#(#attrs)* #base) }.await 207 }; 208 } 209 210 syn::visit_mut::visit_expr_mut(self, expr); 211 } 212}