Skip to main content

acton_macro/
lib.rs

1/*
2 * Copyright (c) 2024. Govcraft
3 *
4 * Licensed under either of
5 *   * Apache License, Version 2.0 (the "License");
6 *     you may not use this file except in compliance with the License.
7 *     You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8 *   * MIT license: http://opensource.org/licenses/MIT
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the applicable License for the specific language governing permissions and
14 * limitations under that License.
15 */
16#![forbid(unsafe_code)]
17
18//! Acton Macro Library
19//!
20//! This library provides procedural macros for the Acton actor framework.
21//! It includes macros to derive common traits and boilerplate code for Acton messages
22//! and actors.
23//!
24//! # Message Macro
25//!
26//! The [`acton_message`] macro simplifies creating message types for actor communication:
27//!
28//! ```ignore
29//! // Basic message for internal actor communication
30//! #[acton_message]
31//! pub struct Ping;
32//!
33//! // IPC-enabled message with serialization support
34//! #[acton_message(ipc)]
35//! pub struct Request {
36//!     pub query: String,
37//! }
38//! ```
39//!
40//! # Actor Macro
41//!
42//! The [`acton_actor`] macro simplifies creating actor state types:
43//!
44//! ```ignore
45//! #[acton_actor]
46//! pub struct Counter {
47//!     count: i32,
48//! }
49//! ```
50//!
51//! # Main Entry Point
52//!
53//! The [`acton_main`] macro provides a convenient entry point for Acton applications:
54//!
55//! ```ignore
56//! use acton_reactive::prelude::*;
57//!
58//! #[acton_main]
59//! async fn main() {
60//!     let mut app = ActonApp::launch_async().await;
61//!     // ... your application logic
62//!     app.shutdown_all().await;
63//! }
64//! ```
65
66use proc_macro::TokenStream;
67
68use quote::quote;
69use syn::{parse_macro_input, DeriveInput, ItemFn};
70
71fn has_derive(input: &DeriveInput, trait_name: &str) -> bool {
72    input.attrs.iter().any(|attr| {
73        if attr.path().is_ident("derive") {
74            let mut found = false;
75            let _ = attr.parse_nested_meta(|meta| {
76                if meta.path.is_ident(trait_name) {
77                    found = true;
78                }
79                Ok(())
80            });
81            found
82        } else {
83            false
84        }
85    })
86}
87
88
89/// Configuration options parsed from `#[acton_message(...)]` attributes.
90#[derive(Default)]
91struct MessageConfig {
92    /// Enable serde serialization for IPC support.
93    ipc: bool,
94}
95
96impl MessageConfig {
97    /// Parse configuration from attribute tokens.
98    fn parse(attr: &TokenStream) -> Self {
99        let mut config = Self::default();
100
101        // Parse the attribute stream to look for known options
102        let attr_string = attr.to_string();
103        for part in attr_string.split(',') {
104            let trimmed = part.trim();
105            if trimmed == "ipc" {
106                config.ipc = true;
107            }
108        }
109
110        config
111    }
112}
113
114/// Configuration options parsed from `#[acton_actor(...)]` attributes.
115#[derive(Default)]
116struct ActorConfig {
117    /// Skip deriving Default (user will implement it manually).
118    no_default: bool,
119}
120
121impl ActorConfig {
122    /// Parse configuration from attribute tokens.
123    fn parse(attr: &TokenStream) -> Self {
124        let mut config = Self::default();
125
126        // Parse the attribute stream to look for known options
127        let attr_string = attr.to_string();
128        for part in attr_string.split(',') {
129            let trimmed = part.trim();
130            if trimmed == "no_default" {
131                config.no_default = true;
132            }
133        }
134
135        config
136    }
137}
138
139/// A procedural macro to derive the necessary traits for an Acton message.
140///
141/// This macro automatically implements the traits required for a type to be used
142/// as a message in the Acton actor framework. It ensures compile-time verification
143/// that the message type satisfies `Send + Sync` bounds.
144///
145/// # Basic Usage
146///
147/// ```ignore
148/// use acton_macro::acton_message;
149///
150/// #[acton_message]
151/// pub struct Ping;
152///
153/// #[acton_message]
154/// pub struct Increment {
155///     pub amount: u32,
156/// }
157/// ```
158///
159/// This expands to:
160/// - `#[derive(Clone, Debug)]` (if not already present)
161/// - A compile-time assertion that the type is `Send + Sync + 'static`
162///
163/// # IPC Support
164///
165/// For messages that need to cross process boundaries via IPC, use the `ipc` option:
166///
167/// ```ignore
168/// use acton_macro::acton_message;
169///
170/// #[acton_message(ipc)]
171/// pub struct Request {
172///     pub query: String,
173/// }
174/// ```
175///
176/// This additionally derives `serde::Serialize` and `serde::Deserialize`.
177///
178/// **Note:** The `ipc` option requires `serde` to be available in scope. When using
179/// `acton-reactive` with the `ipc` feature enabled, serde is automatically available.
180#[proc_macro_attribute]
181pub fn acton_message(attr: TokenStream, item: TokenStream) -> TokenStream {
182    // Parse configuration from attributes
183    let config = MessageConfig::parse(&attr);
184
185    // Parse the input tokens into a syntax tree.
186    let input = parse_macro_input!(item as DeriveInput);
187
188    // Get the name and generics of the struct.
189    let name = &input.ident;
190    let generics = &input.generics;
191    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
192
193    // Determine which traits need to be derived
194    let need_clone = !has_derive(&input, "Clone");
195    let need_debug = !has_derive(&input, "Debug");
196
197    // Build the list of traits to derive
198    let derives = {
199        let mut traits = Vec::new();
200        if need_clone {
201            traits.push(quote!(Clone));
202        }
203        if need_debug {
204            traits.push(quote!(Debug));
205        }
206        if config.ipc {
207            // Only add serde derives if not already present
208            if !has_derive(&input, "Serialize") {
209                traits.push(quote!(serde::Serialize));
210            }
211            if !has_derive(&input, "Deserialize") {
212                traits.push(quote!(serde::Deserialize));
213            }
214        }
215        if traits.is_empty() {
216            quote!()
217        } else {
218            quote!(#[derive(#(#traits),*)])
219        }
220    };
221
222    // Generate a unique identifier for the static assertion to avoid conflicts
223    let assert_ident = quote::format_ident!("_AssertActonMessage_{}", name);
224
225    let expanded = quote! {
226        #derives
227        #input
228
229        // Compile-time assertion that the message type satisfies Send + Sync + 'static.
230        // This catches invalid message types early with clear error messages.
231        #[doc(hidden)]
232        #[allow(dead_code, non_camel_case_types, non_snake_case, clippy::needless_lifetimes)]
233        const _: () = {
234            fn #assert_ident #impl_generics () #where_clause {
235                fn assert_bounds<T: Send + Sync + 'static>() {}
236                assert_bounds::<#name #ty_generics>();
237            }
238        };
239    };
240
241    // Return the generated tokens.
242    TokenStream::from(expanded)
243}
244
245/// A procedural macro to derive boilerplate traits for Acton actors.
246///
247/// This macro automatically implements the traits required for a type to be used
248/// as an actor's state (model) in the Acton framework. It provides compile-time
249/// verification that the actor type satisfies necessary bounds.
250///
251/// # Usage
252///
253/// ```ignore
254/// use acton_macro::acton_actor;
255///
256/// #[acton_actor]
257/// pub struct Counter {
258///     count: i32,
259/// }
260///
261/// #[acton_actor]
262/// pub struct ChatRoom {
263///     messages: Vec<String>,
264///     participants: Vec<String>,
265/// }
266/// ```
267///
268/// This expands to:
269/// - `#[derive(Default, Debug)]` (only traits not already present)
270/// - A compile-time assertion that the type is `Send + 'static`
271///
272/// # Options
273///
274/// ## `no_default`
275///
276/// Skip deriving `Default` when you need to implement it manually (e.g., when
277/// a field's type doesn't implement `Default`):
278///
279/// ```ignore
280/// use std::io::{stdout, Stdout};
281///
282/// #[acton_actor(no_default)]
283/// struct Printer {
284///     out: Stdout,
285/// }
286///
287/// impl Default for Printer {
288///     fn default() -> Self {
289///         Self { out: stdout() }
290///     }
291/// }
292/// ```
293///
294/// # Note
295///
296/// Actor state types must implement `Default` because actors are initialized
297/// with their default state before handlers are registered. When using
298/// `no_default`, you must provide your own `Default` implementation.
299#[proc_macro_attribute]
300pub fn acton_actor(attr: TokenStream, item: TokenStream) -> TokenStream {
301    // Parse configuration from attributes
302    let config = ActorConfig::parse(&attr);
303
304    // Parse the input tokens into a syntax tree.
305    let input = parse_macro_input!(item as DeriveInput);
306
307    // Get the name and generics of the struct.
308    let name = &input.ident;
309    let generics = &input.generics;
310    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
311
312    // Determine which traits need to be derived
313    let need_default = !config.no_default && !has_derive(&input, "Default");
314    let need_debug = !has_derive(&input, "Debug");
315
316    // Build the list of traits to derive
317    let derives = {
318        let mut traits = Vec::new();
319        if need_default {
320            traits.push(quote!(Default));
321        }
322        if need_debug {
323            traits.push(quote!(Debug));
324        }
325        if traits.is_empty() {
326            quote!()
327        } else {
328            quote!(#[derive(#(#traits),*)])
329        }
330    };
331
332    // Generate a unique identifier for the static assertion to avoid conflicts
333    let assert_ident = quote::format_ident!("_AssertActonActor_{}", name);
334
335    let expanded = quote! {
336        #derives
337        #input
338
339        // Compile-time assertion that the actor type satisfies Send + 'static.
340        // This catches invalid actor types early with clear error messages.
341        #[doc(hidden)]
342        #[allow(dead_code, non_camel_case_types, non_snake_case, clippy::needless_lifetimes)]
343        const _: () = {
344            fn #assert_ident #impl_generics () #where_clause {
345                fn assert_bounds<T: Send + 'static>() {}
346                assert_bounds::<#name #ty_generics>();
347            }
348        };
349    };
350
351    // Return the generated tokens.
352    TokenStream::from(expanded)
353}
354
355/// Entry point macro for Acton applications.
356///
357/// This macro marks an async function as the entry point for an Acton application,
358/// setting up the async runtime automatically. It is a convenience wrapper that
359/// eliminates the need to directly reference the underlying async runtime.
360///
361/// # Usage
362///
363/// ```ignore
364/// use acton_reactive::prelude::*;
365///
366/// #[acton_main]
367/// async fn main() {
368///     let mut app = ActonApp::launch_async().await;
369///     // ... your application logic
370///     app.shutdown_all().await;
371/// }
372/// ```
373///
374/// # Configuration
375///
376/// The macro supports optional configuration for the runtime:
377///
378/// - `flavor`: The runtime flavor (`"multi_thread"` or `"current_thread"`)
379/// - `worker_threads`: Number of worker threads (only for multi-threaded runtime)
380///
381/// ```ignore
382/// // Use single-threaded runtime
383/// #[acton_main(flavor = "current_thread")]
384/// async fn main() { }
385///
386/// // Specify worker thread count
387/// #[acton_main(worker_threads = 4)]
388/// async fn main() { }
389/// ```
390///
391/// The default is a multi-threaded runtime with the default number of worker threads.
392#[proc_macro_attribute]
393pub fn acton_main(attr: TokenStream, item: TokenStream) -> TokenStream {
394    let input = parse_macro_input!(item as ItemFn);
395
396    let attrs = &input.attrs;
397    let vis = &input.vis;
398    let sig = &input.sig;
399    let body = &input.block;
400
401    // Validate that the function is async
402    if sig.asyncness.is_none() {
403        return syn::Error::new_spanned(
404            sig.fn_token,
405            "the async keyword is missing from the function declaration",
406        )
407        .to_compile_error()
408        .into();
409    }
410
411    // Validate function name is main
412    if sig.ident != "main" {
413        return syn::Error::new_spanned(
414            &sig.ident, // Keep reference to avoid moving sig.ident which is used later
415            "acton_main can only be applied to the main function",
416        )
417        .to_compile_error()
418        .into();
419    }
420
421    // Parse configuration attributes
422    let attr_string = attr.to_string();
423    let use_current_thread = attr_string.contains("current_thread");
424
425    // Extract worker_threads if specified
426    let worker_threads: Option<usize> = attr_string
427        .split(',')
428        .find(|s| s.contains("worker_threads"))
429        .and_then(|s| s.split('=').nth(1).and_then(|v| v.trim().parse().ok()));
430
431    // Generate the runtime builder based on configuration
432    let runtime_builder = if use_current_thread {
433        quote! {
434            ::acton_reactive::prelude::tokio::runtime::Builder::new_current_thread()
435        }
436    } else if let Some(threads) = worker_threads {
437        quote! {
438            ::acton_reactive::prelude::tokio::runtime::Builder::new_multi_thread()
439                .worker_threads(#threads)
440        }
441    } else {
442        quote! {
443            ::acton_reactive::prelude::tokio::runtime::Builder::new_multi_thread()
444        }
445    };
446
447    // Create the sync function signature (remove async)
448    let fn_name = &sig.ident;
449    let fn_inputs = &sig.inputs;
450    let fn_output = &sig.output;
451
452    let expanded = quote! {
453        #(#attrs)*
454        #vis fn #fn_name(#fn_inputs) #fn_output {
455            #runtime_builder
456                .enable_all()
457                .build()
458                .expect("Failed to build Acton runtime")
459                .block_on(async #body)
460        }
461    };
462
463    TokenStream::from(expanded)
464}