Skip to main content

shared_struct/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{format_ident, quote};
4use syn::{Data, DeriveInput, Fields, parse_macro_input};
5
6/// Transforms a struct into a shared, cheaply-cloneable wrapper.
7///
8/// Always generates:
9/// - `XxxInner` with each field wrapped in `Mutex<T>` or stored raw
10/// - `Xxx(Arc<XxxInner>)` with `Clone = Arc::clone`
11/// - `Xxx::new(...)` constructor
12/// - Per-field async accessors
13///
14/// Field attributes:
15/// - (default) `Mutex<T>`, generates `async fn field() -> MutexGuard<T>`
16/// - `#[raw]` stored as-is, generates `fn field() -> &T`
17#[proc_macro_attribute]
18pub fn shared(_attr: TokenStream, item: TokenStream) -> TokenStream {
19    let input = parse_macro_input!(item as DeriveInput);
20    expand(input)
21        .unwrap_or_else(|e| e.to_compile_error())
22        .into()
23}
24
25enum FieldKind {
26    Mutex,
27    Raw,
28}
29
30fn field_kind(field: &syn::Field) -> FieldKind {
31    for attr in &field.attrs {
32        if attr.path().is_ident("raw") {
33            return FieldKind::Raw;
34        }
35    }
36    FieldKind::Mutex
37}
38
39fn strip_field_attrs(field: &syn::Field) -> syn::Field {
40    let mut f = field.clone();
41    f.attrs.retain(|a| !a.path().is_ident("raw"));
42    f
43}
44
45fn expand(input: DeriveInput) -> syn::Result<TokenStream2> {
46    let vis = &input.vis;
47    let name = &input.ident;
48    let inner_name = format_ident!("{}Inner", name);
49
50    let inner_attrs: Vec<_> = input
51        .attrs
52        .iter()
53        .filter(|a| !a.path().is_ident("shared"))
54        .map(|a| quote! { #a })
55        .collect();
56
57    let Data::Struct(data) = &input.data else {
58        return Err(syn::Error::new_spanned(
59            name,
60            "#[shared] only supports structs",
61        ));
62    };
63    let Fields::Named(fields) = &data.fields else {
64        return Err(syn::Error::new_spanned(
65            name,
66            "#[shared] only supports named fields",
67        ));
68    };
69
70    let mut inner_fields = Vec::new();
71    let mut inner_inits = Vec::new();
72    let mut new_params = Vec::new();
73    let mut accessors = Vec::new();
74
75    for field in &fields.named {
76        let fname = field.ident.as_ref().unwrap();
77        let ftype = &field.ty;
78        let fvis = &field.vis;
79        let kind = field_kind(field);
80        let stripped = strip_field_attrs(field);
81        let extra_attrs: Vec<_> = stripped.attrs.iter().collect();
82
83        match kind {
84            FieldKind::Mutex => {
85                inner_fields.push(quote! {
86                    #(#extra_attrs)*
87                    #fvis #fname: ::tokio::sync::Mutex<#ftype>
88                });
89                inner_inits.push(quote! { #fname: ::tokio::sync::Mutex::new(#fname) });
90                new_params.push(quote! { #fname: #ftype });
91                accessors.push(quote! {
92                    #fvis async fn #fname(&self) -> ::tokio::sync::MutexGuard<'_, #ftype> {
93                        self.0.#fname.lock().await
94                    }
95                });
96            }
97            FieldKind::Raw => {
98                inner_fields.push(quote! {
99                    #(#extra_attrs)*
100                    #fvis #fname: #ftype
101                });
102                inner_inits.push(quote! { #fname });
103                new_params.push(quote! { #fname: #ftype });
104                accessors.push(quote! {
105                    #fvis fn #fname(&self) -> &#ftype {
106                        &self.0.#fname
107                    }
108                });
109            }
110        }
111    }
112
113    Ok(quote! {
114        #(#inner_attrs)*
115        #vis struct #inner_name {
116            #(#inner_fields),*
117        }
118
119        #[derive(Clone)]
120        #vis struct #name(::std::sync::Arc<#inner_name>);
121
122        impl #name {
123            #vis fn new(#(#new_params),*) -> Self {
124                Self(::std::sync::Arc::new(#inner_name {
125                    #(#inner_inits),*
126                }))
127            }
128
129            #(#accessors)*
130        }
131    })
132}