async_ffi_macros/lib.rs
1//! Helper macros for `async_ffi::FfiFuture`.
2use std::mem;
3
4use proc_macro::TokenStream as RawTokenStream;
5use proc_macro2::{Ident, Span, TokenStream, TokenTree};
6use quote::{quote, quote_spanned, ToTokens};
7use syn::parse::{Parse, ParseStream, Result};
8use syn::spanned::Spanned;
9use syn::{
10 parse_quote_spanned, Attribute, Block, Error, FnArg, ForeignItemFn, GenericParam, ItemFn,
11 Lifetime, LifetimeParam, Pat, PatIdent, Signature, Token,
12};
13
14/// A helper macro attribute to converts an `async fn` into a ordinary `fn` returning `FfiFuture`.
15///
16/// Note that this crate doesn't automatically pulls in `async_ffi` dependency. You must manually
17/// add `async_ffi` dependency to your `Cargo.toml`. Or alternatively, using `macros` feature of
18/// `async_ffi` which re-export the macro from this crate, instead of pulling in this crate
19/// explicitly.
20///
21/// # Usages
22///
23/// The typical usage is to apply this macro to an `async fn`.
24/// ```
25/// # async fn do_work(_: i32) {}
26/// use async_ffi_macros::async_ffi;
27/// // Or if you have `macros` feature of `async_ffi` enabled,
28/// // use async_ffi::async_ffi;
29///
30/// #[async_ffi]
31/// #[no_mangle]
32/// async fn func(x: i32) -> i32 {
33/// do_work(x).await;
34/// x + 42
35/// }
36/// ```
37///
38/// It would be converted into roughly:
39/// ```
40/// # async fn do_work(_: i32) {}
41/// #[no_mangle]
42/// extern "C" fn func(x: i32) -> ::async_ffi::FfiFuture<i32> {
43/// ::async_ffi::FfiFuture::new(async move {
44/// // NB. Arguments are always moved into the result Future, no matter if it's used.
45/// // This is the same behavior as original `async fn`s.
46/// let x = x;
47///
48/// do_work(x).await;
49/// x + 42
50/// })
51/// }
52/// ```
53///
54/// You can also apply `#[async_ffi]` to external functions.
55/// ```
56/// # use async_ffi_macros::async_ffi;
57/// extern "C" {
58/// #[async_ffi]
59/// async fn extern_fn(arg: i32) -> i32;
60/// // => fn extern_fn(arg: i32) -> ::async_ffi::FfiFuture<i32>;
61/// }
62/// ```
63///
64/// ## Non-`Send` futures
65/// Call the macro with arguments `?Send` to wrap the result into `LocalFfiFuture` instead of
66/// `FfiFuture`.
67///
68/// ```
69/// # use async_ffi_macros::async_ffi;
70/// #[async_ffi(?Send)]
71/// async fn func() {}
72/// // => fn func() -> ::async_ffi::LocalFfiFuture<()> { ... }
73/// ```
74///
75/// ## References in parameters
76/// When parameters of your `async fn` contain references, you need to capture their lifetimes in
77/// the result `FfiFuture`. Currently, we don't expand lifetime elisions. You must explicitly give
78/// the result lifetime a name in macro arguments and specify all bounds if necessary.
79///
80/// ```
81/// # use async_ffi_macros::async_ffi;
82/// #[async_ffi('fut)]
83/// async fn borrow(x: &'fut i32, y: &'fut i32) -> i32 { *x + *y }
84/// // => fn borrow<'fut>(x: &'fut i32) -> ::async_ffi::BorrowingFfiFuture<'fut, i32> { ... }
85///
86/// // In complex cases, explicit bounds are necessary.
87/// #[async_ffi('fut)]
88/// async fn complex<'a: 'fut, 'b: 'fut>(x: &'a mut i32, y: &'b mut i32) -> i32 { *x + *y }
89/// // => fn complex<'a: 'fut, 'b: 'fut, 'fut>(x: &'a mut i32, y: &'b mut i32) -> BorrowingFfiFuture<'fut, i32> { ... }
90///
91/// // Non Send async fn can also work together.
92/// #[async_ffi('fut, ?Send)]
93/// async fn non_send(x: &'fut i32, y: &'fut i32) -> i32 { *x }
94/// // => fn non_send<'fut>(x: &'fut i32) -> ::async_ffi::LocalBorrowingFfiFuture<'fut, i32> { ... }
95/// ```
96#[proc_macro_attribute]
97pub fn async_ffi(args: RawTokenStream, input: RawTokenStream) -> RawTokenStream {
98 async_ffi_inner(args.into(), input.into()).into()
99}
100
101fn async_ffi_inner(args: TokenStream, mut input: TokenStream) -> TokenStream {
102 let mut errors = Vec::new();
103 let args = syn::parse2::<Args>(args)
104 .map_err(|err| errors.push(err))
105 .unwrap_or_default();
106 if matches!(input.clone().into_iter().last(), Some(TokenTree::Punct(p)) if p.as_char() == ';') {
107 match syn::parse2::<ForeignItemFn>(input.clone()) {
108 Ok(mut item) => {
109 expand(&mut item.attrs, &mut item.sig, None, args, &mut errors);
110 input = item.to_token_stream();
111 }
112 Err(err) => errors.push(err),
113 }
114 } else {
115 match syn::parse2::<ItemFn>(input.clone()) {
116 Ok(mut item) => {
117 expand(
118 &mut item.attrs,
119 &mut item.sig,
120 Some(&mut item.block),
121 args,
122 &mut errors,
123 );
124 input = item.to_token_stream();
125 }
126 Err(err) => errors.push(err),
127 }
128 }
129 for err in errors {
130 input.extend(err.into_compile_error());
131 }
132 input
133}
134
135mod kw {
136 syn::custom_keyword!(Send);
137}
138
139#[derive(Default)]
140struct Args {
141 pub lifetime: Option<Lifetime>,
142 pub local: bool,
143}
144
145impl Parse for Args {
146 fn parse(input: ParseStream) -> Result<Self> {
147 let mut this = Self::default();
148 if input.peek(Lifetime) {
149 this.lifetime = Some(input.parse::<Lifetime>()?);
150 if input.peek(Token![,]) {
151 input.parse::<Token![,]>()?;
152 }
153 }
154 if input.peek(Token![?]) {
155 input.parse::<Token![?]>()?;
156 input.parse::<kw::Send>()?;
157 this.local = true;
158 }
159 if !input.is_empty() {
160 return Err(Error::new(
161 Span::call_site(),
162 "invalid arguments for #[async_ffi]",
163 ));
164 }
165 Ok(this)
166 }
167}
168
169fn expand(
170 attrs: &mut Vec<Attribute>,
171 sig: &mut Signature,
172 body: Option<&mut Block>,
173 args: Args,
174 errors: &mut Vec<Error>,
175) {
176 let mut emit_err =
177 |span: Span, msg: &str| errors.push(Error::new(span, format!("#[async_ffi] {}", msg)));
178
179 let async_span = if let Some(tok) = sig.asyncness.take() {
180 tok.span
181 } else {
182 if body.is_some() {
183 emit_err(sig.fn_token.span, "expects an `async fn`");
184 }
185 Span::call_site()
186 };
187
188 attrs.push(parse_quote_spanned!(async_span=> #[allow(clippy::needless_lifetimes)]));
189 attrs.push(parse_quote_spanned!(async_span=> #[must_use]));
190
191 let lifetime = match args.lifetime {
192 None => Lifetime::new("'static", Span::call_site()),
193 Some(lifetime) => {
194 // Add the lifetime into generic parameters, at the end of existing lifetimes.
195 sig.generics.lt_token.get_or_insert(Token);
196 sig.generics.gt_token.get_or_insert(Token);
197 let lifetime_cnt = sig.generics.lifetimes_mut().count();
198 sig.generics.params.insert(
199 lifetime_cnt,
200 GenericParam::Lifetime(LifetimeParam::new(lifetime.clone())),
201 );
202
203 lifetime
204 }
205 };
206
207 let ffi_future = if args.local {
208 quote_spanned!(async_span=> ::async_ffi::LocalBorrowingFfiFuture)
209 } else {
210 quote_spanned!(async_span=> ::async_ffi::BorrowingFfiFuture)
211 };
212
213 match &mut sig.output {
214 syn::ReturnType::Default => {
215 sig.output = parse_quote_spanned!(async_span=> -> #ffi_future<#lifetime, ()>);
216 }
217 syn::ReturnType::Type(_r_arrow, ret_ty) => {
218 *ret_ty = parse_quote_spanned!(async_span=> #ffi_future<#lifetime, #ret_ty>);
219 }
220 }
221
222 if let Some(va) = &sig.variadic {
223 emit_err(va.span(), "does not support variadic parameters");
224 }
225
226 // Force capturing all arguments in the returned Future.
227 // This is the behavior of `async fn`.
228 let mut param_bindings = TokenStream::new();
229 for (param, i) in sig.inputs.iter_mut().zip(1..) {
230 let pat_ty = match param {
231 FnArg::Receiver(receiver) => {
232 emit_err(receiver.span(), "does not support `self` parameter");
233 continue;
234 }
235 FnArg::Typed(pat_ty) => pat_ty,
236 };
237
238 let attributes = &pat_ty.attrs;
239 let param_ident = match &*pat_ty.pat {
240 Pat::Ident(pat_ident) => {
241 if pat_ident.ident == "self" {
242 emit_err(pat_ident.span(), "does not support `self` parameter");
243 continue;
244 }
245 pat_ident.ident.clone()
246 }
247 _ => Ident::new(&format!("__param{}", i), pat_ty.span()),
248 };
249
250 // If this is a declaration, only check but not transform.
251 if body.is_none() {
252 continue;
253 }
254
255 let old_pat = mem::replace(
256 &mut *pat_ty.pat,
257 Pat::Ident(PatIdent {
258 attrs: Vec::new(),
259 by_ref: None,
260 mutability: None,
261 ident: param_ident.clone(),
262 subpat: None,
263 }),
264 );
265
266 // NB.
267 // - Rebind the parameter once, to ensure their drop order not broken
268 // by non-moving patterns containing `_`.
269 // - `mut` is required when the old pattern has `ref mut` inside.
270 // - Use external (macro) spans, so they won't trigger lints.
271 param_bindings.extend(quote! {
272 #(#attributes)*
273 #[allow(clippy::used_underscore_binding)]
274 let mut #param_ident = #param_ident;
275 #(#attributes)*
276 #[allow(clippy::used_underscore_binding)]
277 let #old_pat = #param_ident;
278 });
279 }
280
281 if let Some(body) = body {
282 let stmts = mem::take(&mut body.stmts);
283 body.stmts = parse_quote_spanned! {async_span=>
284 #ffi_future::new(async move {
285 #param_bindings
286 #(#stmts)*
287 })
288 };
289 }
290}
291
292#[cfg(doctest)]
293mod tests {
294 /// ```compile_fail
295 /// #[async_ffi_macros::async_ffi]
296 /// pub fn not_async() {}
297 /// ```
298 fn not_async() {}
299
300 /// ```compile_fail
301 /// pub trait Trait {
302 /// #[async_ffi_macros::async_ffi]
303 /// async fn method(&self);
304 /// }
305 /// ```
306 fn receiver_trait_method() {}
307
308 /// ```compile_fail
309 /// struct Struct;
310 /// impl Struct {
311 /// #[async_ffi_macros::async_ffi]
312 /// async fn method(&self) {}
313 /// }
314 /// ```
315 fn receiver_impl_method() {}
316
317 /// ```compile_fail
318 /// struct Struct;
319 /// impl Struct {
320 /// #[async_ffi_macros::async_ffi]
321 /// async fn method(self: &mut Self) {}
322 /// }
323 /// ```
324 fn typed_receiver() {}
325
326 /// ```compile_fail
327 /// extern "C" {
328 /// #[async_ffi_macros::async_ffi]
329 /// async fn foo(ref mut x: i32);
330 /// }
331 /// ```
332 fn declaration_pattern() {}
333}