use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
use syn::{Attribute, FnArg, ItemFn, Pat, PatIdent, parse_macro_input};
#[proc_macro_attribute]
pub fn sync(_attr: TokenStream, ts: TokenStream) -> TokenStream {
let item = parse_macro_input!(ts as ItemFn);
if item.sig.asyncness.is_none() {
return syn::Error::new(item.sig.span(), "#[sync] requires an async fn")
.to_compile_error()
.into();
}
let vis = &item.vis;
let attrs: Vec<Attribute> = item.attrs.into_iter().filter(not_sync_attr).collect();
let async_name = &item.sig.ident;
let inputs = &item.sig.inputs;
let output = &item.sig.output;
let generics = &item.sig.generics;
let where_clause = &item.sig.generics.where_clause;
let body = &item.block;
let sync_name = {
let tmp = async_name.to_string();
match tmp.strip_suffix("_async") {
None => {
return syn::Error::new(
item.sig.ident.span(),
"#[sync] function name must include the `_async` suffix.\nDefine `async fn \
foo_async(...)` (with suffix); the #[sync] macro will generate a synchronous \
`fn foo(...)` wrapper.",
)
.to_compile_error()
.into();
}
Some(s) => syn::Ident::new(s, item.sig.ident.span()),
}
};
let mut receiver = false;
let mut args = Vec::new();
for input in inputs.iter() {
match input {
FnArg::Receiver(_) => {
receiver = true;
}
FnArg::Typed(t) => {
if let Pat::Ident(PatIdent { ident, .. }) = &*t.pat {
args.push(ident);
} else {
return syn::Error::new(
t.span(),
"#[sync] currently only supports simple identifier arguments like `x: T`.",
)
.to_compile_error()
.into();
}
}
}
}
let caller = match receiver {
true => quote! { self. }, false => quote! {}, };
let async_fn = quote! {
#(#attrs)*
#vis async fn #async_name #generics (#inputs) #output #where_clause {
#body
}
};
let sync_fn = quote! {
#(#attrs)*
#vis fn #sync_name #generics (#inputs) #output #where_clause {
::smol::block_on(async { #caller #async_name ( #(#args),* ).await })
}
};
TokenStream::from(quote! {
#async_fn
#sync_fn
})
}
fn not_sync_attr(attr: &Attribute) -> bool {
!attr.path().to_token_stream().to_string().ends_with("sync")
}