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![<](async_span));
196            sig.generics.gt_token.get_or_insert(Token![>](async_span));
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}