Skip to main content

arc_handle/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse, FnArg, ItemTrait, Pat, Signature, TraitItem, TraitItemFn};
4
5/// Attribute macro that generates an Arc-based handle wrapper for a trait.
6///
7/// This macro renames the original trait to `TraitImpl` and creates a handle struct
8/// with the original trait name that wraps the trait in an `Arc<dyn TraitImpl + Send + Sync>`
9/// and provides methods that delegate to the inner trait implementation.
10///
11/// # Example
12///
13/// ```rust
14/// use arc_handle::arc_handle;
15///
16/// #[arc_handle]
17/// pub trait Greeter {
18///     fn greet(&self, name: &str) -> String;
19/// }
20///
21/// // The macro generates:
22/// //   - `GreeterImpl` trait (renamed from `Greeter`)
23/// //   - `Greeter` struct wrapping `Arc<dyn GreeterImpl + Send + Sync>`
24/// //   - Delegating methods on `Greeter` matching the trait
25///
26/// struct EnglishGreeter;
27///
28/// impl GreeterImpl for EnglishGreeter {
29///     fn greet(&self, name: &str) -> String {
30///         format!("Hello, {name}!")
31///     }
32/// }
33///
34/// let handle = Greeter::new(EnglishGreeter);
35/// assert_eq!(handle.greet("world"), "Hello, world!");
36/// ```
37///
38/// Methods with default implementations are supported. The default body is
39/// kept on the `TraitImpl` trait, and the handle delegates to it through the
40/// trait object, so implementors may override the default and the handle will
41/// dispatch to their override:
42///
43/// ```rust
44/// use arc_handle::arc_handle;
45///
46/// #[arc_handle]
47/// pub trait Worker {
48///     fn name(&self) -> &str;
49///
50///     fn greet(&self) -> String {
51///         format!("Hello from {}", self.name())
52///     }
53/// }
54///
55/// struct Robot;
56///
57/// impl WorkerImpl for Robot {
58///     fn name(&self) -> &str {
59///         "Rob"
60///     }
61/// }
62///
63/// let handle = Worker::new(Robot);
64/// assert_eq!(handle.greet(), "Hello from Rob");
65/// ```
66///
67/// Async methods are also supported:
68///
69/// ```rust,ignore
70/// use arc_handle::arc_handle;
71/// use async_trait::async_trait;
72///
73/// #[arc_handle]
74/// #[async_trait]
75/// pub trait AsyncService {
76///     async fn fetch(&self, url: &str) -> String;
77///     fn name(&self) -> &str;
78/// }
79/// ```
80#[proc_macro_attribute]
81pub fn arc_handle(_args: TokenStream, input: TokenStream) -> TokenStream {
82    match arc_handle_inner(input) {
83        Ok(tokens) => tokens,
84        Err(e) => e.to_compile_error().into(),
85    }
86}
87
88fn arc_handle_inner(input: TokenStream) -> syn::Result<TokenStream> {
89    let mut input = parse::<ItemTrait>(input)?;
90
91    let original_trait_name = &input.ident;
92    let impl_trait_name = syn::Ident::new(
93        &format!("{}Impl", original_trait_name),
94        original_trait_name.span(),
95    );
96    let handle_name = original_trait_name.clone();
97    let vis = &input.vis;
98
99    // Rename the original trait to TraitImpl
100    input.ident = impl_trait_name.clone();
101
102    // Extract methods and validate trait items
103    let mut impl_methods = Vec::new();
104
105    for item in &input.items {
106        match item {
107            TraitItem::Fn(method) => {
108                // Validate receiver
109                validate_receiver(method)?;
110
111                let method_name = &method.sig.ident;
112                let inputs = &method.sig.inputs;
113                let output = &method.sig.output;
114                let is_async = is_async_method(&method.sig);
115
116                let param_names = extract_param_names(&method.sig)?;
117
118                if is_async {
119                    impl_methods.push(quote! {
120                        #[inline]
121                        #vis async fn #method_name(#inputs) #output {
122                            self.inner.#method_name(#(#param_names),*).await
123                        }
124                    });
125                } else {
126                    impl_methods.push(quote! {
127                        #[inline]
128                        #vis fn #method_name(#inputs) #output {
129                            self.inner.#method_name(#(#param_names),*)
130                        }
131                    });
132                }
133            }
134            TraitItem::Const(tc) => {
135                return Err(syn::Error::new_spanned(
136                    tc,
137                    "arc_handle does not support associated constants",
138                ));
139            }
140            TraitItem::Type(tt) => {
141                return Err(syn::Error::new_spanned(
142                    tt,
143                    "arc_handle does not support associated types",
144                ));
145            }
146            _ => {}
147        }
148    }
149
150    let expanded = quote! {
151        #input
152
153        #[doc = concat!("Arc-based handle wrapper for `", stringify!(#impl_trait_name), "`")]
154        #[derive(Clone)]
155        #vis struct #handle_name {
156            inner: std::sync::Arc<dyn #impl_trait_name + Send + Sync>,
157        }
158
159        impl #handle_name {
160            /// Create a new handle from a trait implementation
161            #[inline]
162            #vis fn new(inner: impl #impl_trait_name + Send + Sync + 'static) -> Self {
163                Self {
164                    inner: std::sync::Arc::new(inner),
165                }
166            }
167
168            /// Create a new handle from a boxed trait object
169            #[inline]
170            #vis fn from_boxed(inner: Box<dyn #impl_trait_name + Send + Sync>) -> Self {
171                Self {
172                    inner: std::sync::Arc::from(inner),
173                }
174            }
175
176            /// Create a new handle from an existing Arc
177            #[inline]
178            #vis fn from_arc(inner: std::sync::Arc<dyn #impl_trait_name + Send + Sync>) -> Self {
179                Self { inner }
180            }
181
182            /// Get a reference to the inner Arc
183            #[inline]
184            #vis fn inner(&self) -> &std::sync::Arc<dyn #impl_trait_name + Send + Sync> {
185                &self.inner
186            }
187
188            /// Unwrap the handle into the inner Arc
189            #[inline]
190            #vis fn into_inner(self) -> std::sync::Arc<dyn #impl_trait_name + Send + Sync> {
191                self.inner
192            }
193
194            #(#impl_methods)*
195        }
196    };
197
198    Ok(TokenStream::from(expanded))
199}
200
201fn extract_param_names(sig: &Signature) -> syn::Result<Vec<&syn::Ident>> {
202    sig.inputs
203        .iter()
204        .skip(1)
205        .map(|arg| {
206            if let FnArg::Typed(pat_type) = arg {
207                if let Pat::Ident(ident) = &*pat_type.pat {
208                    Ok(&ident.ident)
209                } else {
210                    Err(syn::Error::new_spanned(
211                        pat_type,
212                        "unsupported parameter pattern; expected a simple identifier",
213                    ))
214                }
215            } else {
216                Err(syn::Error::new_spanned(
217                    sig,
218                    "unexpected receiver in parameter list",
219                ))
220            }
221        })
222        .collect()
223}
224
225fn validate_receiver(method: &TraitItemFn) -> syn::Result<()> {
226    match method.sig.inputs.first() {
227        Some(FnArg::Receiver(r)) => {
228            // &self is fine; &mut self is not (can't get &mut through Arc<dyn>)
229            if r.mutability.is_some() {
230                return Err(syn::Error::new_spanned(
231                    r,
232                    "arc_handle does not support &mut self receivers; \
233                     the handle uses Arc which only provides shared access",
234                ));
235            }
236            Ok(())
237        }
238        Some(FnArg::Typed(pat_type)) => Err(syn::Error::new_spanned(
239            pat_type,
240            "arc_handle requires &self as the first parameter; \
241             by-value self is not supported",
242        )),
243        None => Err(syn::Error::new_spanned(
244            method,
245            "arc_handle requires methods to have a &self receiver",
246        )),
247    }
248}
249
250fn is_async_method(sig: &Signature) -> bool {
251    sig.asyncness.is_some()
252}