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}