use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
parse_macro_input, Data, DeriveInput, FnArg, ImplItem, ImplItemFn, ItemImpl, PatType,
ReturnType, Type,
};
#[proc_macro_derive(Actor)]
pub fn derive_actor(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let expanded = match derive_actor_impl(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
};
TokenStream::from(expanded)
}
fn derive_actor_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
let name = &input.ident;
let generics = &input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
match &input.data {
Data::Struct(_) | Data::Enum(_) => {
let expanded = quote! {
impl #impl_generics rsactor::Actor for #name #ty_generics #where_clause {
type Args = Self;
type Error = std::convert::Infallible;
type IdleEvent = ();
async fn on_start(
args: Self::Args,
_actor_ref: &rsactor::ActorRef<Self>,
) -> std::result::Result<Self, Self::Error> {
Ok(args)
}
}
};
Ok(expanded)
}
_ => Err(syn::Error::new_spanned(
name,
"Actor derive macro can only be used on structs and enums",
)),
}
}
#[proc_macro_attribute]
pub fn message_handlers(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemImpl);
let expanded = match message_impl(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
};
TokenStream::from(expanded)
}
fn message_impl(mut input: ItemImpl) -> syn::Result<TokenStream2> {
let actor_type = &input.self_ty;
let generics = &input.generics;
let message_impls = process_handler_methods(&input.items, actor_type, generics)?;
clean_handler_attributes(&mut input.items);
let result = quote! {
#input
#(#message_impls)*
};
Ok(result)
}
#[derive(Default)]
struct HandlerOptions {
force_result: bool,
no_log: bool,
}
fn parse_handler_options(attr: &syn::Attribute) -> syn::Result<HandlerOptions> {
let mut options = HandlerOptions::default();
match &attr.meta {
syn::Meta::Path(_) => {}
syn::Meta::List(_) => {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("result") {
options.force_result = true;
Ok(())
} else if meta.path.is_ident("no_log") {
options.no_log = true;
Ok(())
} else {
Err(meta.error("unknown handler option; expected `result` or `no_log`"))
}
})?;
}
_ => {
return Err(syn::Error::new_spanned(
attr,
"expected `#[handler]`, `#[handler(result)]`, or `#[handler(no_log)]`",
));
}
}
if options.force_result && options.no_log {
return Err(syn::Error::new_spanned(
attr,
"`result` and `no_log` are mutually exclusive",
));
}
Ok(options)
}
fn is_result_type(ty: &syn::Type) -> bool {
match ty {
syn::Type::Path(type_path) => type_path
.path
.segments
.last()
.map(|seg| seg.ident == "Result")
.unwrap_or(false),
_ => false,
}
}
fn process_handler_methods(
items: &[ImplItem],
actor_type: &Type,
generics: &syn::Generics,
) -> syn::Result<Vec<TokenStream2>> {
let mut message_impls = Vec::new();
for item in items {
if let ImplItem::Fn(method) = item {
let handler_attr = method
.attrs
.iter()
.find(|attr| attr.path().is_ident("handler"));
if let Some(attr) = handler_attr {
let options = parse_handler_options(attr)?;
let impl_tokens = generate_message_impl(method, actor_type, generics, &options)?;
message_impls.push(impl_tokens);
}
}
}
Ok(message_impls)
}
fn clean_handler_attributes(items: &mut [ImplItem]) {
for item in items {
if let ImplItem::Fn(method) = item {
method.attrs.retain(|attr| !attr.path().is_ident("handler"));
}
}
}
fn generate_message_impl(
method: &ImplItemFn,
actor_type: &Type,
generics: &syn::Generics,
options: &HandlerOptions,
) -> syn::Result<TokenStream2> {
let inputs = &method.sig.inputs;
if method.sig.asyncness.is_none() {
return Err(syn::Error::new_spanned(
&method.sig,
format!("Handler method '{}' must be async", method.sig.ident),
));
}
if inputs.len() != 3 {
return Err(syn::Error::new_spanned(
&method.sig,
format!(
"Message handler method '{}' must have exactly 3 parameters: &mut self, message, &ActorRef<Self>. Found {} parameters.",
method.sig.ident,
inputs.len()
)
));
}
if !matches!(&inputs[0], FnArg::Receiver(receiver) if receiver.mutability.is_some()) {
return Err(syn::Error::new_spanned(
&inputs[0],
"First parameter must be '&mut self'",
));
}
let message_type = match &inputs[1] {
FnArg::Typed(PatType { ty, .. }) => ty,
_ => {
return Err(syn::Error::new_spanned(
&inputs[1],
"Second parameter must be a typed message parameter (e.g., 'msg: MessageType')",
))
}
};
let third_param_valid = match &inputs[2] {
FnArg::Typed(PatType { ty, .. }) => {
match ty.as_ref() {
Type::Reference(type_ref) => {
if let Type::Path(type_path) = type_ref.elem.as_ref() {
type_path
.path
.segments
.iter()
.any(|seg| seg.ident == "ActorRef")
} else {
false
}
}
_ => false,
}
}
_ => false,
};
if !third_param_valid {
return Err(syn::Error::new_spanned(
&inputs[2],
"Third parameter must be '&ActorRef<Self>' or '&rsactor::ActorRef<Self>'",
));
}
let return_type_ty = match &method.sig.output {
ReturnType::Type(_, ty) => Some(ty.as_ref()),
ReturnType::Default => None,
};
let return_type = match return_type_ty {
Some(ty) => quote! { #ty },
None => quote! { () },
};
let is_result = return_type_ty.map(is_result_type).unwrap_or(false);
let should_generate_on_tell_result = if options.no_log {
false
} else if options.force_result {
if return_type_ty.is_none() {
return Err(syn::Error::new_spanned(
&method.sig,
"`#[handler(result)]` requires a return type, but this method returns `()`",
));
}
true
} else {
is_result
};
let method_name = &method.sig.ident;
let (impl_generics, _ty_generics, where_clause) = generics.split_for_impl();
let on_tell_result_impl = if should_generate_on_tell_result {
quote! {
fn on_tell_result(result: &Self::Reply, actor_ref: &rsactor::ActorRef<Self>) {
if let Err(ref e) = result {
rsactor::__log_handler_error(
&actor_ref.identity(),
std::any::type_name::<#message_type>(),
e
);
}
}
}
} else {
quote! {}
};
let impl_tokens = quote! {
impl #impl_generics rsactor::Message<#message_type> for #actor_type #where_clause {
type Reply = #return_type;
async fn handle(
&mut self,
msg: #message_type,
actor_ref: &rsactor::ActorRef<Self>,
) -> Self::Reply {
self.#method_name(msg, actor_ref).await
}
#on_tell_result_impl
}
};
Ok(impl_tokens)
}