Skip to main content

zestors_codegen/
lib.rs

1//! Macros for the `zestors` crate.
2
3extern crate proc_macro;
4use darling::FromAttributes;
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};
8
9/// Derives the `Interface` trait for an enum.
10///
11/// This only works un unnamed, single-value enum-fields that are envelopes.
12/// Anything else is rejected.
13///
14/// # Example
15/// ```
16/// # use zestors_interface::*;
17/// #[derive(Interface)]
18/// # #[interface(path = "zestors_interface")]
19/// enum MyInterface {
20///     MessageA(Envelope<u32>),
21///     MessageB(Envelope<String>),
22/// }
23/// ```
24#[proc_macro_derive(Interface, attributes(interface))]
25pub fn derive_interface_polybox(input: TokenStream) -> TokenStream {
26    derive_interface(input, "::zestors::interface")
27}
28
29#[derive(darling::FromAttributes)]
30#[darling(attributes(interface))]
31struct InterfaceAttrs {
32    path: Option<syn::Path>,
33}
34
35fn derive_interface(input: TokenStream, base: &str) -> TokenStream {
36    let input = parse_macro_input!(input as DeriveInput);
37    let attrs = match InterfaceAttrs::from_attributes(&input.attrs) {
38        Ok(attrs) => attrs,
39        Err(err) => return err.write_errors().into(),
40    };
41    let enum_name = &input.ident;
42
43    let base_path: syn::Path = attrs.path.unwrap_or_else(|| syn::parse_str(base).unwrap());
44    let msg_path: syn::Path = base_path.clone();
45
46    // Ensure we are working with an enum
47    let variants = match &input.data {
48        Data::Enum(data_enum) => &data_enum.variants,
49        _ => panic!("Interface derive can only be used on enums"),
50    };
51
52    let mut inner_types = Vec::new();
53    let mut try_from_matches = Vec::new();
54    let mut try_into_matches = Vec::new();
55    let mut into_matches = Vec::new();
56    let mut from_impls = Vec::new();
57
58    for variant in variants {
59        let variant_name = &variant.ident;
60
61        match &variant.fields {
62            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
63                let field_type = &fields.unnamed[0].ty;
64
65                let inner_type = extract_inner_envelope_type(field_type)
66                    .expect("Interface variants must be of type Envelope<T>");
67
68                inner_types.push(inner_type);
69
70                try_from_matches.push(quote! {
71                    let envelope = match envelope.downcast::<#inner_type>() {
72                        Ok(envelope) => return Ok(Self::#variant_name(envelope)),
73                        Err(envelope) => envelope,
74                    };
75                });
76
77                try_into_matches.push(quote! {
78                    if id == std::any::TypeId::of::<#inner_type>() {
79                        if let Self::#variant_name(envelope) = self {
80                            // SAFETY: Verified type matches dynamic I parameter.
81                            let converted = unsafe {
82                                std::mem::transmute_copy::<#base_path::Envelope<#inner_type>, #base_path::Envelope<I>>(&envelope)
83                            };
84                            std::mem::forget(envelope);
85                            return Ok(converted);
86                        }
87                    }
88                });
89
90                into_matches.push(quote! {
91                    Self::#variant_name(envelope) => #msg_path::AnyEnvelope::new::<#inner_type>(envelope),
92                });
93
94                from_impls.push(quote! {
95                    impl From<#msg_path::Envelope<#inner_type>> for #enum_name {
96                        fn from(envelope: #msg_path::Envelope<#inner_type>) -> Self {
97                            Self::#variant_name(envelope)
98                        }
99                    }
100
101                    impl TryInto<#msg_path::Envelope<#inner_type>> for #enum_name {
102                        type Error = Self;
103
104                        fn try_into(self) -> Result<#msg_path::Envelope<#inner_type>, Self> {
105                            if let #enum_name::#variant_name(envelope) = self {
106                                Ok(envelope)
107                            } else {
108                                Err(self)
109                            }
110                        }
111                    }
112                });
113            }
114            _ => panic!("Interface derive only supports variants with a single unnamed field, e.g., A(Envelope<T>)"),
115        }
116    }
117
118    let expanded = quote! {
119        impl #msg_path::Interface for #enum_name {
120            fn try_from_dyn_envelope(envelope: #msg_path::AnyEnvelope) -> Result<Self, #msg_path::AnyEnvelope> {
121                #(#try_from_matches)*
122                Err(envelope)
123            }
124
125            // Could be added to improve performance, but would require unsafe transmute to avoid double downcasting.
126            // fn try_into_envelope<I: #base_path::Message>(self) -> Result<#base_path::Envelope<I>, Self> {
127            //     let id = std::any::TypeId::of::<I>();
128            //     #(#try_into_matches)*
129            //     Err(self)
130            // }
131
132            fn into_dyn_envelope(self) -> #msg_path::AnyEnvelope {
133                match self {
134                    #(#into_matches)*
135                }
136            }
137
138            type Set = (#(#inner_types,)*);
139        }
140
141
142        impl #msg_path::Message for #enum_name {
143            type Output = ();
144            type Resolver = ();
145            type Receipt = ();
146        }
147
148
149        impl From<#msg_path::Envelope<#enum_name>> for #enum_name {
150            fn from(envelope: #msg_path::Envelope<#enum_name>) -> Self {
151                envelope.msg
152            }
153        }
154
155        impl TryInto<#msg_path::Envelope<#enum_name>> for #enum_name {
156            type Error = Self;
157
158            fn try_into(self) -> Result<#msg_path::Envelope<#enum_name>, Self> {
159                Ok(#msg_path::Envelope::new(self, ()))
160            }
161        }
162
163        #(#from_impls)*
164    };
165
166    TokenStream::from(expanded)
167}
168
169/// Derives the `HandlerInterface` trait for an enum, allowing it to act as a
170/// handler interface for an actor. Each variant must contain a single unnamed
171/// field of type `Envelope<T>`.
172#[proc_macro_derive(HandlerInterface, attributes(interface))]
173pub fn derive_actor_interface(input: TokenStream) -> TokenStream {
174    let input = parse_macro_input!(input as DeriveInput);
175    let enum_name = &input.ident;
176    let attrs = match InterfaceAttrs::from_attributes(&input.attrs) {
177        Ok(attrs) => attrs,
178        Err(err) => return err.write_errors().into(),
179    };
180
181    // Ensure we are working with an enum
182    let variants = match &input.data {
183        Data::Enum(data_enum) => &data_enum.variants,
184        _ => panic!("HandlerInterface derive can only be used on enums"),
185    };
186
187    let base_path = attrs
188        .path
189        .unwrap_or_else(|| syn::parse_str("::zestors").unwrap());
190
191    let mut handle_matches = Vec::new();
192    let mut inner_types = Vec::new();
193
194    for variant in variants {
195        let variant_name = &variant.ident;
196
197        match &variant.fields {
198            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
199                let field_type = &fields.unnamed[0].ty;
200
201                let inner_type = extract_inner_envelope_type(field_type)
202                    .expect("HandlerInterface variants must be of type Envelope<T>");
203
204                handle_matches.push(quote! {
205                    Self::#variant_name(envelope) => {
206                        <T as #base_path::actor::Handle<#inner_type>>::handle(actor, state, envelope.msg, envelope.req).await
207                    }
208                });
209                inner_types.push(inner_type);
210            }
211            _ => panic!("HandlerInterface derive only supports variants with a single unnamed field, e.g., A(Envelope<T>)"),
212        }
213    }
214
215    let expanded = quote! {
216        impl<T> #base_path::actor::HandlerInterface<T> for #enum_name
217        where
218            T: #base_path::actor::Handler + #( #base_path::actor::Handle<#inner_types> + )*
219        {
220            async fn handle_with(self, state: #base_path::actor::HandlerContext<'_, T>, actor: &mut T) -> Result<(), ::rootcause::Report> {
221                match self {
222                    #(#handle_matches)*
223                }
224            }
225        }
226    };
227
228    TokenStream::from(expanded)
229}
230
231/// Derives the `Message` trait.
232///
233/// This macro accepts an optional `reply` attribute to specify the reply type for the message.
234/// When `reply` is specified, the receipt becomes a `Response` and the resolver a `Request`.
235///
236///
237/// # Example
238/// ```
239/// # use zestors_interface::*;
240/// #[derive(Message)]
241/// # #[msg(path = "zestors_interface")]
242/// struct SimpleMessage;
243///
244/// #[derive(Message)]
245/// #[msg(reply = u32)]
246/// # #[msg(path = "zestors_interface")]
247/// struct MessageWithOutput;
248/// ```
249#[proc_macro_derive(Message, attributes(msg))]
250pub fn derive_message(input: TokenStream) -> TokenStream {
251    _derive_message(input, "::zestors::interface")
252}
253
254#[derive(darling::FromAttributes)]
255#[darling(attributes(msg))]
256struct MessageAttrs {
257    reply: Option<syn::Type>,
258    path: Option<syn::Path>,
259}
260
261fn _derive_message(input: TokenStream, base: &str) -> TokenStream {
262    let input = parse_macro_input!(input as DeriveInput);
263    let attrs = match MessageAttrs::from_attributes(&input.attrs) {
264        Ok(attrs) => attrs,
265        Err(err) => return err.write_errors().into(),
266    };
267    let name = &input.ident;
268
269    let base_path: syn::Path = attrs.path.unwrap_or_else(|| syn::parse_str(base).unwrap());
270    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
271
272    let expanded = if let Some(reply_type) = attrs.reply {
273        quote!(
274            impl #impl_generics #base_path::Message for #name #ty_generics #where_clause
275            {
276                type Output = #reply_type;
277                type Receipt = #base_path::Reply<#reply_type>;
278                type Resolver = #base_path::Request<#reply_type>;
279            }
280        )
281    } else {
282        quote!(
283            impl #impl_generics #base_path::Message for #name #ty_generics #where_clause
284            {
285                type Output = ();
286                type Resolver = ();
287                type Receipt = ();
288            }
289        )
290    };
291
292    TokenStream::from(expanded)
293}
294
295fn extract_inner_envelope_type(ty: &Type) -> Option<&Type> {
296    if let Type::Path(type_path) = ty {
297        let segment = type_path.path.segments.last()?;
298        if segment.ident == "Envelope" {
299            if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
300                if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
301                    return Some(inner_ty);
302                }
303            }
304        }
305    }
306    None
307}