#![forbid(unsafe_code)]
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, ItemFn};
fn has_derive(input: &DeriveInput, trait_name: &str) -> bool {
input.attrs.iter().any(|attr| {
if attr.path().is_ident("derive") {
let mut found = false;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident(trait_name) {
found = true;
}
Ok(())
});
found
} else {
false
}
})
}
#[derive(Default)]
struct MessageConfig {
ipc: bool,
}
impl MessageConfig {
fn parse(attr: &TokenStream) -> Self {
let mut config = Self::default();
let attr_string = attr.to_string();
for part in attr_string.split(',') {
let trimmed = part.trim();
if trimmed == "ipc" {
config.ipc = true;
}
}
config
}
}
#[derive(Default)]
struct ActorConfig {
no_default: bool,
}
impl ActorConfig {
fn parse(attr: &TokenStream) -> Self {
let mut config = Self::default();
let attr_string = attr.to_string();
for part in attr_string.split(',') {
let trimmed = part.trim();
if trimmed == "no_default" {
config.no_default = true;
}
}
config
}
}
#[proc_macro_attribute]
pub fn acton_message(attr: TokenStream, item: TokenStream) -> TokenStream {
let config = MessageConfig::parse(&attr);
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
let generics = &input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let need_clone = !has_derive(&input, "Clone");
let need_debug = !has_derive(&input, "Debug");
let derives = {
let mut traits = Vec::new();
if need_clone {
traits.push(quote!(Clone));
}
if need_debug {
traits.push(quote!(Debug));
}
if config.ipc {
if !has_derive(&input, "Serialize") {
traits.push(quote!(serde::Serialize));
}
if !has_derive(&input, "Deserialize") {
traits.push(quote!(serde::Deserialize));
}
}
if traits.is_empty() {
quote!()
} else {
quote!(#[derive(#(#traits),*)])
}
};
let assert_ident = quote::format_ident!("_AssertActonMessage_{}", name);
let expanded = quote! {
#derives
#input
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case, clippy::needless_lifetimes)]
const _: () = {
fn #assert_ident #impl_generics () #where_clause {
fn assert_bounds<T: Send + Sync + 'static>() {}
assert_bounds::<#name #ty_generics>();
}
};
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn acton_actor(attr: TokenStream, item: TokenStream) -> TokenStream {
let config = ActorConfig::parse(&attr);
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
let generics = &input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let need_default = !config.no_default && !has_derive(&input, "Default");
let need_debug = !has_derive(&input, "Debug");
let derives = {
let mut traits = Vec::new();
if need_default {
traits.push(quote!(Default));
}
if need_debug {
traits.push(quote!(Debug));
}
if traits.is_empty() {
quote!()
} else {
quote!(#[derive(#(#traits),*)])
}
};
let assert_ident = quote::format_ident!("_AssertActonActor_{}", name);
let expanded = quote! {
#derives
#input
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case, clippy::needless_lifetimes)]
const _: () = {
fn #assert_ident #impl_generics () #where_clause {
fn assert_bounds<T: Send + 'static>() {}
assert_bounds::<#name #ty_generics>();
}
};
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn acton_main(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let attrs = &input.attrs;
let vis = &input.vis;
let sig = &input.sig;
let body = &input.block;
if sig.asyncness.is_none() {
return syn::Error::new_spanned(
sig.fn_token,
"the async keyword is missing from the function declaration",
)
.to_compile_error()
.into();
}
if sig.ident != "main" {
return syn::Error::new_spanned(
&sig.ident, "acton_main can only be applied to the main function",
)
.to_compile_error()
.into();
}
let attr_string = attr.to_string();
let use_current_thread = attr_string.contains("current_thread");
let worker_threads: Option<usize> = attr_string
.split(',')
.find(|s| s.contains("worker_threads"))
.and_then(|s| s.split('=').nth(1).and_then(|v| v.trim().parse().ok()));
let runtime_builder = if use_current_thread {
quote! {
::acton_reactive::prelude::tokio::runtime::Builder::new_current_thread()
}
} else if let Some(threads) = worker_threads {
quote! {
::acton_reactive::prelude::tokio::runtime::Builder::new_multi_thread()
.worker_threads(#threads)
}
} else {
quote! {
::acton_reactive::prelude::tokio::runtime::Builder::new_multi_thread()
}
};
let fn_name = &sig.ident;
let fn_inputs = &sig.inputs;
let fn_output = &sig.output;
let expanded = quote! {
#(#attrs)*
#vis fn #fn_name(#fn_inputs) #fn_output {
#runtime_builder
.enable_all()
.build()
.expect("Failed to build Acton runtime")
.block_on(async #body)
}
};
TokenStream::from(expanded)
}