wip
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}