puppet_derive/
lib.rs

1mod generics_processor;
2mod handler;
3mod utils;
4
5extern crate proc_macro;
6
7use crate::generics_processor::{ArgOrder, Extract, GenericBuilder, ProcessedGenerics};
8use crate::utils::get_segment;
9use proc_macro::TokenStream;
10use proc_macro2::Ident;
11use quote::{format_ident, quote, ToTokens, TokenStreamExt};
12use syn::{parse_macro_input, Error, ItemImpl, Type};
13
14macro_rules! maybe_compiler_error {
15    ($res:expr) => {{
16        match $res {
17            Err(e) => return e.into_compile_error().into(),
18            Ok(r) => r,
19        }
20    }};
21}
22
23#[proc_macro_attribute]
24/// Create an actor for the given struct Impl.
25///
26/// This allows you to mark methods as message handlers with the `#[puppet]` attribute.
27/// Once marked, the first parameter after one of `&self` or `&mut self` will be the message to
28/// receive.
29///
30/// Once the actor has been derived, you can spawn it any of the following:
31///
32/// ### Helper Methods (requires `helper-methods` feature)
33/// - `spawn_actor()`
34/// - `spawn_actor_with_name(name)`
35/// - `spawn_actor_with_queue_size(size)`
36/// - `spawn_actor_with_name_and_size(name, size)`
37///
38/// ### Custom Executor (requires `custom-executor` feature)
39/// - `spawn_actor_with(name, size, <T as puppet::Executor>)`
40///
41/// ### Base Methods
42/// - `run_actor(puppet::Reply<<Msg as Message>::Output>)`
43///
44/// ### Example
45/// ```ignore
46/// use puppet::puppet_actor;
47///
48/// pub struct MyActor;
49///
50/// #[puppet_actor]
51/// impl MyActor {
52///     #[puppet]
53///     async fn on_say_hello(&self, msg: SayHello) -> String {
54///         format!("Hello, {}!", msg.name)
55///     }
56/// }
57///
58/// let actor = MyActor {};
59/// let mailbox = actor.spawn_actor().await;
60/// ```
61pub fn puppet_actor(_attr: TokenStream, item: TokenStream) -> TokenStream {
62    let info = parse_macro_input!(item as ItemImpl);
63    generate(info)
64}
65
66fn generate(mut info: ItemImpl) -> TokenStream {
67    let actor_name = if let Type::Path(path) = &*info.self_ty {
68        get_segment(path).expect("Get struct name").ident
69    } else {
70        return Error::new_spanned(info, "Expected macro to be placed on a struct impl.")
71            .into_compile_error()
72            .into();
73    };
74
75    let extracted_generics = maybe_compiler_error!(generics_processor::process_impl_generics(
76        info.generics.clone()
77    ));
78    let extract_handlers = maybe_compiler_error!(handler::create_message_handlers(
79        &mut info.items,
80        &extracted_generics
81    ));
82
83    let types = extract_handlers.handlers
84        .iter()
85        .map(|(_, msg)| {
86            let ty = msg.ty();
87            let ident = msg.ident();
88            quote! {
89                #ident {
90                    msg: #ty,
91                    tx: puppet::__private::futures::channel::oneshot::Sender<<#ty as puppet::Message>::Output>,
92                }
93            }
94        });
95
96    let enum_name = format_ident!("{}Op", actor_name);
97    let enum_traits = extract_handlers
98        .handlers
99        .iter()
100        .map(|(_handler, msg)| EnumFrom {
101            name: msg.ident(),
102            message: msg.ty(),
103            parent: enum_name.clone(),
104            generics: extract_handlers.generics.clone(),
105        });
106    let mut enum_match_statements = Vec::new();
107    for (method, message) in extract_handlers.handlers.iter() {
108        let callback_name = &method.sig.ident;
109        let tokens = match message {
110            Extract::Message(msg) => {
111                let ident = &msg.ident;
112                quote! {
113                    Self::#ident { msg, tx } => {
114                        let res = actor.#callback_name(msg).await;
115                        let _ = tx.send(res);
116                    }
117                }
118            }
119            Extract::MessageWithReply(msg, ArgOrder::First) => {
120                let ident = &msg.ident;
121                quote! {
122                    Self::#ident { msg, tx } => {
123                        let reply = puppet::Reply::from(tx);
124                        // Do not let methods return a type other than ().
125                        let () = actor.#callback_name(reply, msg).await;
126                    }
127                }
128            }
129            Extract::MessageWithReply(msg, ArgOrder::Second) => {
130                let ident = &msg.ident;
131                quote! {
132                    Self::#ident { msg, tx } => {
133                        let reply = puppet::Reply::from(tx);
134                        // Do not let methods return a type other than ().
135                        let () = actor.#callback_name(msg, reply).await;
136                    }
137                }
138            }
139        };
140
141        enum_match_statements.push(tokens);
142    }
143
144    let remaining_generics = extract_handlers.generics.remaining_generics();
145    let remaining_where = extract_handlers.generics.remaining_where();
146    let enum_where = extract_handlers.generics.enum_where();
147    let generic_builder = extract_handlers.generics.clone();
148    let actor_generics = extracted_generics.types;
149    let actor_where = extracted_generics.where_clause;
150
151    let enum_tokens = quote! {
152        pub enum #enum_name #generic_builder
153        #enum_where
154        {
155            #(#types),*
156        }
157
158        impl #generic_builder #enum_name #generic_builder
159        #enum_where
160        {
161            async fn __run #remaining_generics (self, actor: &mut #actor_name #actor_generics)
162            #remaining_where
163            {
164                match self {
165                    #(#enum_match_statements),*
166                }
167            }
168        }
169
170        #(#enum_traits)*
171    };
172
173    #[cfg(not(feature = "helper-methods"))]
174    let helper_methods = quote! {};
175
176    #[cfg(feature = "helper-methods")]
177    let helper_methods = quote! {
178        pub async fn spawn_actor(mut self) -> puppet::ActorMailbox<#actor_name #actor_generics> {
179            self.spawn_actor_with_queue_size(100).await
180        }
181
182        pub async fn spawn_actor_with_name(mut self, name: impl AsRef<str>) -> puppet::ActorMailbox<#actor_name #actor_generics> {
183            self.spawn_actor_with_name_and_size(name, 100).await
184        }
185
186        pub async fn spawn_actor_with_queue_size(mut self, n: usize) -> puppet::ActorMailbox<#actor_name #actor_generics> {
187            self.spawn_actor_with_name_and_size(stringify!(#actor_name), 100).await
188        }
189
190        pub async fn spawn_actor_with_name_and_size(mut self, name: impl AsRef<str>, n: usize) -> puppet::ActorMailbox<#actor_name #actor_generics> {
191            let (tx, rx) = puppet::__private::flume::bounded::<#enum_name #generic_builder>(n);
192
193            puppet::__private::tokio::spawn(async move {
194                while let Ok(op) = rx.recv_async().await {
195                    op.__run(&mut self).await;
196                }
197            });
198
199            let name = std::borrow::Cow::Owned(name.as_ref().to_string());
200            puppet::ActorMailbox::new(tx, name)
201        }
202    };
203
204    #[cfg(not(feature = "custom-executor"))]
205    let custom_executor = quote! {};
206
207    #[cfg(feature = "custom-executor")]
208    let custom_executor = quote! {
209        pub async fn spawn_actor_with(mut self, name: impl AsRef<str>, n: usize, executor: impl puppet::Executor) -> puppet::ActorMailbox<#actor_name #actor_generics> {
210            use puppet::Executor;
211
212            let (tx, rx) = puppet::__private::flume::bounded::<#enum_name #generic_builder>(n);
213
214            executor.spawn(async move {
215                while let Ok(op) = rx.recv_async().await {
216                    op.__run(&mut self).await;
217                }
218            });
219
220            let name = std::borrow::Cow::Owned(name.as_ref().to_string());
221            puppet::ActorMailbox::new(tx, name)
222        }
223    };
224
225    let tokens = quote! {
226        #info
227
228        impl #actor_generics puppet::Actor for #actor_name #actor_generics #actor_where {
229            type Messages = #enum_name #generic_builder;
230        }
231
232        impl #actor_generics #actor_name #actor_generics #actor_where {
233            pub async fn run_actor(mut self, messages: puppet::__private::flume::Receiver<#enum_name #generic_builder>) {
234                while let Ok(op) = messages.recv_async().await {
235                    op.__run(&mut self).await;
236                }
237            }
238
239            #custom_executor
240
241            #helper_methods
242        }
243
244        #enum_tokens
245    };
246
247    tokens.into()
248}
249
250struct EnumFrom {
251    parent: Ident,
252    name: Ident,
253    message: Type,
254    generics: GenericBuilder,
255}
256
257impl ToTokens for EnumFrom {
258    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
259        let parent = self.parent.clone();
260        let name = self.name.clone();
261        let message = self.message.clone();
262        let generics = self.generics.clone();
263        let where_clause = self.generics.enum_where();
264        let variant = quote! {
265            impl #generics puppet::MessageHandler<#message> for #parent #generics
266            #where_clause
267            {
268                fn create(msg: #message) -> (Self, puppet::__private::futures::channel::oneshot::Receiver<<#message as puppet::Message>::Output>) {
269                    let (tx, rx) = puppet::__private::futures::channel::oneshot::channel();
270
271                    let slf = Self::#name { msg, tx };
272
273                    (slf, rx)
274                }
275            }
276        };
277        tokens.append_all(variant);
278    }
279}