Skip to main content

zestors_codegen/
lib.rs

1//! The derive macros of `zestors`, re-exported in `zestors::prelude`:
2//!
3//! - [`Message`](derive@Message): a message, fire-and-forget or with a reply.
4//! - [`Interface`](derive@Interface): the set of messages an actor accepts.
5//! - [`HandlerInterface`](derive@HandlerInterface): dispatch from an
6//!   interface to a `Handler`'s `Handle<M>` implementations.
7//! - [`StableId`](derive@StableId): a message's id on the wire, for sending
8//!   it to other nodes.
9//!
10//! # Attributes
11//!
12//! All derives read `#[msg(...)]` and `#[zestors(...)]`; the two are
13//! interchangeable.
14//!
15//! | Key | Used by | Meaning |
16//! |---|---|---|
17//! | `reply = T` | `Message` | The message expects a reply of type `T`. Quote types with generics: `reply = "Option<u32>"`. |
18//! | `id = "<uuid>"` | `StableId` | The message's id. Required. |
19//! | `no_auto_register` | `StableId` | Leave the type out of `ClusterConfig::auto_register`. |
20//! | `interface_path = "path"` | `Message`, `Interface` | Where to find `zestors-interface`. Default `::zestors::interface`. |
21//! | `actor_path = "path"` | `HandlerInterface` | Where to find `zestors-actor`. Default `::zestors::actor`. |
22//! | `distr_path = "path"` | `StableId` | Where to find `zestors-distr`. Default `::zestors::distr`. |
23//!
24//! The path keys are only needed when depending on the `zestors-*` crates
25//! directly instead of on `zestors`.
26
27extern crate proc_macro;
28use darling::FromAttributes;
29use proc_macro::TokenStream;
30use proc_macro2::Span;
31use quote::quote;
32use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};
33
34/// Derives the `Interface` trait for an enum: the set of messages an actor
35/// accepts.
36///
37/// Every variant must be a tuple variant holding exactly one `Envelope<M>`,
38/// with each message type `M` appearing once; anything else is rejected.
39/// Generic enums aren't supported. Besides `Interface`, it generates the
40/// conversions between each `Envelope<M>` and the enum.
41///
42/// # Example
43/// ```
44/// # use zestors_interface::*;
45/// #[derive(Interface)]
46/// # #[zestors(interface_path = "zestors_interface")]
47/// enum MyInterface {
48///     MessageA(Envelope<u32>),
49///     MessageB(Envelope<String>),
50/// }
51/// ```
52#[proc_macro_derive(Interface, attributes(zestors))]
53pub fn derive_interface_polybox(input: TokenStream) -> TokenStream {
54    derive_interface(input)
55}
56
57/// The attributes shared by all derives, read from both `#[zestors(..)]` and
58/// `#[msg(..)]`; see the crate docs for what each key means.
59#[derive(darling::FromAttributes)]
60#[darling(attributes(zestors, msg))]
61struct Attrs {
62    interface_path: Option<syn::Path>,
63    distr_path: Option<syn::Path>,
64    actor_path: Option<syn::Path>,
65    reply: Option<syn::Type>,
66    id: Option<syn::LitStr>,
67    no_auto_register: darling::util::Flag,
68}
69
70impl Attrs {
71    fn interface_path(&self) -> syn::Path {
72        self.interface_path
73            .clone()
74            .unwrap_or_else(|| syn::parse_str("::zestors::interface").unwrap())
75    }
76
77    fn distr_path(&self) -> syn::Path {
78        self.distr_path
79            .clone()
80            .unwrap_or_else(|| syn::parse_str("::zestors::distr").unwrap())
81    }
82
83    fn actor_path(&self) -> syn::Path {
84        self.actor_path
85            .clone()
86            .unwrap_or_else(|| syn::parse_str("::zestors::actor").unwrap())
87    }
88}
89
90fn derive_interface(input: TokenStream) -> TokenStream {
91    let input = parse_macro_input!(input as DeriveInput);
92    let attrs = match Attrs::from_attributes(&input.attrs) {
93        Ok(attrs) => attrs,
94        Err(err) => return err.write_errors().into(),
95    };
96    let enum_name = &input.ident;
97
98    let base_path: syn::Path = attrs.interface_path();
99    let msg_path: syn::Path = base_path.clone();
100
101    // Ensure we are working with an enum
102    let variants = match &input.data {
103        Data::Enum(data_enum) => &data_enum.variants,
104        _ => panic!("Interface derive can only be used on enums"),
105    };
106
107    let mut inner_types = Vec::new();
108    let mut try_from_matches = Vec::new();
109    let mut try_into_matches = Vec::new();
110    let mut into_matches = Vec::new();
111    let mut from_impls = Vec::new();
112
113    for variant in variants {
114        let variant_name = &variant.ident;
115
116        match &variant.fields {
117            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
118                let field_type = &fields.unnamed[0].ty;
119
120                let inner_type = extract_inner_envelope_type(field_type)
121                    .expect("Interface variants must be of type Envelope<T>");
122
123                inner_types.push(inner_type);
124
125                try_from_matches.push(quote! {
126                    let envelope = match envelope.downcast::<#inner_type>() {
127                        Ok(envelope) => return Ok(Self::#variant_name(envelope)),
128                        Err(envelope) => envelope,
129                    };
130                });
131
132                try_into_matches.push(quote! {
133                    if id == std::any::TypeId::of::<#inner_type>() {
134                        if let Self::#variant_name(envelope) = self {
135                            // SAFETY: Verified type matches dynamic I parameter.
136                            let converted = unsafe {
137                                std::mem::transmute_copy::<#base_path::Envelope<#inner_type>, #base_path::Envelope<I>>(&envelope)
138                            };
139                            std::mem::forget(envelope);
140                            return Ok(converted);
141                        }
142                    }
143                });
144
145                into_matches.push(quote! {
146                    Self::#variant_name(envelope) => #msg_path::AnyEnvelope::new::<#inner_type>(envelope),
147                });
148
149                from_impls.push(quote! {
150                    impl From<#msg_path::Envelope<#inner_type>> for #enum_name {
151                        fn from(envelope: #msg_path::Envelope<#inner_type>) -> Self {
152                            Self::#variant_name(envelope)
153                        }
154                    }
155
156                    impl TryInto<#msg_path::Envelope<#inner_type>> for #enum_name {
157                        type Error = Self;
158
159                        fn try_into(self) -> Result<#msg_path::Envelope<#inner_type>, Self> {
160                            if let #enum_name::#variant_name(envelope) = self {
161                                Ok(envelope)
162                            } else {
163                                Err(self)
164                            }
165                        }
166                    }
167                });
168            }
169            _ => panic!("Interface derive only supports variants with a single unnamed field, e.g., A(Envelope<T>)"),
170        }
171    }
172
173    let expanded = quote! {
174        impl #msg_path::Interface for #enum_name {
175            fn try_from_dyn_envelope(envelope: #msg_path::AnyEnvelope) -> Result<Self, #msg_path::AnyEnvelope> {
176                #(#try_from_matches)*
177                Err(envelope)
178            }
179
180            // Could be added to improve performance, but would require unsafe transmute to avoid double downcasting.
181            // fn try_into_envelope<I: #base_path::Message>(self) -> Result<#base_path::Envelope<I>, Self> {
182            //     let id = std::any::TypeId::of::<I>();
183            //     #(#try_into_matches)*
184            //     Err(self)
185            // }
186
187            fn into_dyn_envelope(self) -> #msg_path::AnyEnvelope {
188                match self {
189                    #(#into_matches)*
190                }
191            }
192
193            type Set = (#(#inner_types,)*);
194        }
195
196
197        impl #msg_path::Message for #enum_name {
198            type Output = ();
199            type Kind = #msg_path::Cast;
200        }
201
202
203        impl From<#msg_path::Envelope<#enum_name>> for #enum_name {
204            fn from(envelope: #msg_path::Envelope<#enum_name>) -> Self {
205                envelope.msg
206            }
207        }
208
209        impl TryInto<#msg_path::Envelope<#enum_name>> for #enum_name {
210            type Error = Self;
211
212            fn try_into(self) -> Result<#msg_path::Envelope<#enum_name>, Self> {
213                Ok(#msg_path::Envelope::new(self, ()))
214            }
215        }
216
217        #(#from_impls)*
218    };
219
220    TokenStream::from(expanded)
221}
222
223/// Derives the `HandlerInterface` trait for an enum, which dispatches each
224/// variant to the `Handle<M>` implementation of a `Handler`. Use it next to
225/// `#[derive(Interface)]` on the enum that is the handler's `Interface`.
226///
227/// The generated code names `rootcause::Report`, so the crate using it must
228/// depend on `rootcause`.
229///
230/// # Example
231/// ```
232/// use zestors::interface::{Envelope, Interface, Message};
233/// use zestors::prelude::*;
234///
235/// #[derive(Message, Debug)]
236/// struct Ping;
237///
238/// #[derive(Interface, HandlerInterface, Debug)]
239/// enum PingInterface {
240///     Ping(Envelope<Ping>),
241/// }
242///
243/// #[derive(Debug)]
244/// struct Pinger;
245///
246/// impl Handler for Pinger {
247///     type Interface = PingInterface;
248/// }
249///
250/// impl Handle<Ping> for Pinger {
251///     async fn handle(
252///         &mut self,
253///         _ctx: HandlerContext<'_, Self>,
254///         _msg: Ping,
255///         _req: (),
256///     ) -> Result<(), rootcause::Report> {
257///         Ok(())
258///     }
259/// }
260/// ```
261#[proc_macro_derive(HandlerInterface, attributes(zestors))]
262pub fn derive_actor_interface(input: TokenStream) -> TokenStream {
263    let input = parse_macro_input!(input as DeriveInput);
264    let enum_name = &input.ident;
265    let attrs = match Attrs::from_attributes(&input.attrs) {
266        Ok(attrs) => attrs,
267        Err(err) => return err.write_errors().into(),
268    };
269
270    // Ensure we are working with an enum
271    let variants = match &input.data {
272        Data::Enum(data_enum) => &data_enum.variants,
273        _ => panic!("HandlerInterface derive can only be used on enums"),
274    };
275
276    let actor_path = attrs.actor_path();
277
278    let mut handle_matches = Vec::new();
279    let mut inner_types = Vec::new();
280
281    for variant in variants {
282        let variant_name = &variant.ident;
283
284        match &variant.fields {
285            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
286                let field_type = &fields.unnamed[0].ty;
287
288                let inner_type = extract_inner_envelope_type(field_type)
289                    .expect("HandlerInterface variants must be of type Envelope<T>");
290
291                handle_matches.push(quote! {
292                    Self::#variant_name(envelope) => {
293                        <T as #actor_path::Handle<#inner_type>>::handle(actor, state, envelope.msg, envelope.req).await
294                    }
295                });
296                inner_types.push(inner_type);
297            }
298            _ => panic!("HandlerInterface derive only supports variants with a single unnamed field, e.g., A(Envelope<T>)"),
299        }
300    }
301
302    let expanded = quote! {
303        impl<T> #actor_path::HandlerInterface<T> for #enum_name
304        where
305            T: #actor_path::Handler + #( #actor_path::Handle<#inner_types> + )*
306        {
307            async fn handle_with(self, state: #actor_path::HandlerContext<'_, T>, actor: &mut T) -> Result<(), ::rootcause::Report> {
308                match self {
309                    #(#handle_matches)*
310                }
311            }
312        }
313    };
314
315    TokenStream::from(expanded)
316}
317
318/// Derives the `Message` trait.
319///
320/// Without attributes the message is fire-and-forget (`Kind = Cast`). With
321/// `#[msg(reply = T)]` it expects a reply of type `T` (`Kind = Call`): it is
322/// sent with a `Request<T>`, and the sender waits on a `Reply<T>`. Quote a type
323/// with generics: `#[msg(reply = "Option<u32>")]`.
324///
325/// To send the message to other nodes, also derive
326/// [`StableId`](derive@StableId) and serde's `Serialize`/`Deserialize`; the
327/// id goes in the same attribute, `#[msg(reply = u32, id = "<uuid>")]`.
328///
329/// # Example
330/// ```
331/// # use zestors_interface::*;
332/// #[derive(Message)]
333/// # #[zestors(interface_path = "zestors_interface")]
334/// struct SimpleMessage;
335///
336/// #[derive(Message)]
337/// #[msg(reply = u32)]
338/// # #[zestors(interface_path = "zestors_interface")]
339/// struct MessageWithOutput;
340/// ```
341#[proc_macro_derive(Message, attributes(msg, zestors))]
342pub fn derive_message(input: TokenStream) -> TokenStream {
343    let input = parse_macro_input!(input as DeriveInput);
344    let attrs = match Attrs::from_attributes(&input.attrs) {
345        Ok(attrs) => attrs,
346        Err(err) => return err.write_errors().into(),
347    };
348    let name = &input.ident;
349
350    let base_path: syn::Path = attrs.interface_path();
351    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
352
353    let expanded = if let Some(reply_type) = attrs.reply {
354        quote!(
355            impl #impl_generics #base_path::Message for #name #ty_generics #where_clause
356            {
357                type Output = #reply_type;
358                type Kind = #base_path::Call;
359            }
360        )
361    } else {
362        quote!(
363            impl #impl_generics #base_path::Message for #name #ty_generics #where_clause
364            {
365                type Output = ();
366                type Kind = #base_path::Cast;
367            }
368        )
369    };
370
371    TokenStream::from(expanded)
372}
373
374/// Derives the `StableId` trait, giving the message a stable, globally unique
375/// [`MessageId`](https://docs.rs/zestors-distr/latest/zestors_distr/struct.MessageId.html)
376/// that identifies it across nodes.
377///
378/// The id is set with `#[msg(id = "<uuid>")]`. If it is missing, compilation
379/// fails with a freshly generated random id that can be pasted in. Once nodes
380/// running different builds may talk to each other, never change an id, and
381/// never give two types the same one: registering both on a node panics.
382///
383/// With the `auto-register` feature of `zestors-distr`, a type that isn't
384/// generic is also collected for `ClusterConfig::auto_register`, which
385/// registers it if it is a `RemoteMessage`. `#[msg(no_auto_register)]` leaves
386/// it out.
387///
388/// # Example
389/// ```
390/// # use zestors_distr::*;
391/// #[derive(StableId)]
392/// # #[zestors(distr_path = "zestors_distr")]
393/// #[msg(id = "0b0f7e4e-3f3a-4d5b-9d7e-6a1c2b3d4e92")]
394/// struct Ping;
395///
396/// assert_eq!(Ping::Id, MessageId::from_u128(0x0b0f7e4e_3f3a_4d5b_9d7e_6a1c2b3d4e92));
397/// ```
398#[proc_macro_derive(StableId, attributes(msg, zestors))]
399pub fn derive_message_id(input: TokenStream) -> TokenStream {
400    let input = parse_macro_input!(input as DeriveInput);
401    let attrs = match Attrs::from_attributes(&input.attrs) {
402        Ok(attrs) => attrs,
403        Err(err) => return err.write_errors().into(),
404    };
405    let name = &input.ident;
406    let distr_path = attrs.distr_path();
407
408    let Some(id) = attrs.id else {
409        let suggestion = uuid::Uuid::new_v4();
410        return syn::Error::new_spanned(
411            name,
412            format!(
413                "`StableId` requires a unique id; add this attribute to the type: #[msg(id = \"{suggestion}\")]"
414            ),
415        )
416        .to_compile_error()
417        .into();
418    };
419
420    let uuid = match uuid::Uuid::parse_str(&id.value()) {
421        Ok(uuid) => uuid.as_u128(),
422        Err(err) => {
423            return syn::Error::new(Span::call_site(), format!("invalid message id: {err}"))
424                .to_compile_error()
425                .into();
426        }
427    };
428
429    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
430
431    // A generic type has no one type to register.
432    let auto_register = (input.generics.params.is_empty() && !attrs.no_auto_register.is_present())
433        .then(|| quote!(#distr_path::__auto_register!(#name);));
434
435    TokenStream::from(quote! {
436        impl #impl_generics #distr_path::StableId for #name #ty_generics #where_clause {
437            const Id: #distr_path::MessageId = #distr_path::MessageId::from_u128(#uuid);
438        }
439        #auto_register
440    })
441}
442
443fn extract_inner_envelope_type(ty: &Type) -> Option<&Type> {
444    if let Type::Path(type_path) = ty {
445        let segment = type_path.path.segments.last()?;
446        if segment.ident == "Envelope" {
447            if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
448                if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
449                    return Some(inner_ty);
450                }
451            }
452        }
453    }
454    None
455}