use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Fields, parse_macro_input};
#[proc_macro_attribute]
pub fn shared(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
expand(input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
enum FieldKind {
Mutex,
Raw,
}
fn field_kind(field: &syn::Field) -> FieldKind {
for attr in &field.attrs {
if attr.path().is_ident("raw") {
return FieldKind::Raw;
}
}
FieldKind::Mutex
}
fn strip_field_attrs(field: &syn::Field) -> syn::Field {
let mut f = field.clone();
f.attrs.retain(|a| !a.path().is_ident("raw"));
f
}
fn expand(input: DeriveInput) -> syn::Result<TokenStream2> {
let vis = &input.vis;
let name = &input.ident;
let inner_name = format_ident!("{}Inner", name);
let inner_attrs: Vec<_> = input
.attrs
.iter()
.filter(|a| !a.path().is_ident("shared"))
.map(|a| quote! { #a })
.collect();
let Data::Struct(data) = &input.data else {
return Err(syn::Error::new_spanned(
name,
"#[shared] only supports structs",
));
};
let Fields::Named(fields) = &data.fields else {
return Err(syn::Error::new_spanned(
name,
"#[shared] only supports named fields",
));
};
let mut inner_fields = Vec::new();
let mut inner_inits = Vec::new();
let mut new_params = Vec::new();
let mut accessors = Vec::new();
for field in &fields.named {
let fname = field.ident.as_ref().unwrap();
let ftype = &field.ty;
let fvis = &field.vis;
let kind = field_kind(field);
let stripped = strip_field_attrs(field);
let extra_attrs: Vec<_> = stripped.attrs.iter().collect();
match kind {
FieldKind::Mutex => {
inner_fields.push(quote! {
#(#extra_attrs)*
#fvis #fname: ::tokio::sync::Mutex<#ftype>
});
inner_inits.push(quote! { #fname: ::tokio::sync::Mutex::new(#fname) });
new_params.push(quote! { #fname: #ftype });
accessors.push(quote! {
#fvis async fn #fname(&self) -> ::tokio::sync::MutexGuard<'_, #ftype> {
self.0.#fname.lock().await
}
});
}
FieldKind::Raw => {
inner_fields.push(quote! {
#(#extra_attrs)*
#fvis #fname: #ftype
});
inner_inits.push(quote! { #fname });
new_params.push(quote! { #fname: #ftype });
accessors.push(quote! {
#fvis fn #fname(&self) -> &#ftype {
&self.0.#fname
}
});
}
}
}
Ok(quote! {
#(#inner_attrs)*
#vis struct #inner_name {
#(#inner_fields),*
}
#[derive(Clone)]
#vis struct #name(::std::sync::Arc<#inner_name>);
impl #name {
#vis fn new(#(#new_params),*) -> Self {
Self(::std::sync::Arc::new(#inner_name {
#(#inner_inits),*
}))
}
#(#accessors)*
}
})
}