1#![forbid(unsafe_code)]
2use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use syn::{
5 parse::Parse, parse_macro_input, Attribute, FnArg, GenericArgument, Ident, ImplItem,
6 ImplItemFn, ItemImpl, ItemTrait, Pat, PathArguments, ReturnType, TraitItem, Type, TypePath,
7};
8
9fn to_snake_case(s: &str) -> String {
12 let mut result = String::new();
13 let chars: Vec<char> = s.chars().collect();
14 for (i, &ch) in chars.iter().enumerate() {
15 if ch.is_uppercase() {
16 if i > 0 {
20 let prev_lower = chars[i - 1].is_lowercase();
21 let next_lower = chars.get(i + 1).is_some_and(|c| c.is_lowercase());
22 if prev_lower || next_lower {
23 result.push('_');
24 }
25 }
26 result.push(ch.to_ascii_lowercase());
27 } else {
28 result.push(ch);
29 }
30 }
31 result
32}
33
34fn to_pascal_case(s: &str) -> String {
35 s.split('_')
36 .filter(|part| !part.is_empty())
37 .map(|part| {
38 let mut chars = part.chars();
39 match chars.next() {
40 None => String::new(),
41 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
42 }
43 })
44 .collect()
45}
46
47fn strip_protocol_suffix(name: &str) -> String {
48 name.strip_suffix("Protocol").unwrap_or(name).to_string()
49}
50
51fn conditional_timeout(meta: &syn::Meta) -> bool {
55 if let syn::Meta::List(list) = meta {
56 if list.path.is_ident("cfg_attr") {
57 use syn::parse::Parser;
58 let arguments =
59 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated;
60 if let Ok(arguments) = arguments.parse2(list.tokens.clone()) {
61 return arguments.iter().skip(1).any(|meta| {
62 meta.path().is_ident("request_timeout") || conditional_timeout(meta)
63 });
64 }
65 }
66 }
67 false
68}
69
70enum MethodKind {
71 Send,
72 Request(Box<Type>),
73}
74
75#[derive(Clone, Copy)]
76enum RuntimeMode {
77 Tasks,
78 Threads,
79}
80
81fn classify_return_type(ret: &ReturnType) -> Result<MethodKind, &Type> {
82 match ret {
83 ReturnType::Default => Ok(MethodKind::Send),
84 ReturnType::Type(_, ty) => {
85 if is_unit_type(ty) {
86 return Ok(MethodKind::Send);
87 }
88 if let Some(inner) = extract_response_inner(ty) {
89 return Ok(MethodKind::Request(inner));
90 }
91 if let Some(inner) = extract_result_inner(ty) {
92 if is_unit_type(&inner) {
93 return Ok(MethodKind::Send);
94 }
95 return Err(ty);
98 }
99 Err(ty)
100 }
101 }
102}
103
104fn extract_response_inner(ty: &Type) -> Option<Box<Type>> {
105 if let Type::Path(TypePath { path, .. }) = ty {
106 let seg = path.segments.last()?;
107 if seg.ident == "Response" {
108 if let PathArguments::AngleBracketed(args) = &seg.arguments {
109 if let Some(GenericArgument::Type(inner)) = args.args.first() {
110 return Some(Box::new(inner.clone()));
111 }
112 }
113 }
114 }
115 None
116}
117
118fn extract_result_inner(ty: &Type) -> Option<Box<Type>> {
119 if let Type::Path(TypePath { path, .. }) = ty {
120 let seg = path.segments.last()?;
121 if seg.ident == "Result" {
122 if let PathArguments::AngleBracketed(args) = &seg.arguments {
123 if let Some(GenericArgument::Type(inner)) = args.args.first() {
124 return Some(Box::new(inner.clone()));
125 }
126 }
127 }
128 }
129 None
130}
131
132fn is_unit_type(ty: &Type) -> bool {
133 if let Type::Tuple(tuple) = ty {
134 return tuple.elems.is_empty();
135 }
136 false
137}
138
139fn is_prelude_or_primitive(name: &str) -> bool {
142 matches!(
143 name,
144 "bool" | "char" | "str"
146 | "i8" | "i16" | "i32" | "i64" | "i128" | "isize"
147 | "u8" | "u16" | "u32" | "u64" | "u128" | "usize"
148 | "f32" | "f64"
149 | "Box" | "String" | "Vec"
151 | "Option" | "Some" | "None"
152 | "Result" | "Ok" | "Err"
153 | "ToString" | "ToOwned"
154 )
155}
156
157fn qualify_type_with_super(ty: &Type) -> Type {
165 match ty {
166 Type::Path(TypePath { qself, path }) => {
167 if qself.is_some() || path.leading_colon.is_some() {
169 return ty.clone();
170 }
171 if let Some(first) = path.segments.first() {
172 let s = first.ident.to_string();
173 if matches!(
174 s.as_str(),
175 "crate" | "super" | "self" | "std" | "core" | "alloc"
176 ) {
177 return ty.clone();
178 }
179 }
180
181 let qualified_segments: syn::punctuated::Punctuated<_, _> = path
183 .segments
184 .iter()
185 .map(|seg| {
186 let mut new_seg = seg.clone();
187 if let PathArguments::AngleBracketed(ref mut args) = new_seg.arguments {
188 for arg in &mut args.args {
189 if let GenericArgument::Type(ref mut inner) = arg {
190 *inner = qualify_type_with_super(inner);
191 }
192 }
193 }
194 new_seg
195 })
196 .collect();
197
198 if let Some(first) = path.segments.first() {
200 if is_prelude_or_primitive(&first.ident.to_string()) {
201 return Type::Path(TypePath {
202 qself: None,
203 path: syn::Path {
204 leading_colon: None,
205 segments: qualified_segments,
206 },
207 });
208 }
209 }
210
211 let mut segments = syn::punctuated::Punctuated::new();
213 segments.push(syn::PathSegment {
214 ident: format_ident!("super"),
215 arguments: PathArguments::None,
216 });
217 for seg in qualified_segments {
218 segments.push(seg);
219 }
220
221 Type::Path(TypePath {
222 qself: None,
223 path: syn::Path {
224 leading_colon: None,
225 segments,
226 },
227 })
228 }
229 Type::Reference(r) => {
230 let mut new = r.clone();
231 new.elem = Box::new(qualify_type_with_super(&r.elem));
232 Type::Reference(new)
233 }
234 Type::Tuple(t) => {
235 let mut new = t.clone();
236 for elem in &mut new.elems {
237 *elem = qualify_type_with_super(elem);
238 }
239 Type::Tuple(new)
240 }
241 Type::Array(a) => {
242 let mut new = a.clone();
243 *new.elem = qualify_type_with_super(&a.elem);
244 Type::Array(new)
245 }
246 Type::Slice(s) => {
247 let mut new = s.clone();
248 *new.elem = qualify_type_with_super(&s.elem);
249 Type::Slice(new)
250 }
251 Type::Paren(p) => {
252 let mut new = p.clone();
253 *new.elem = qualify_type_with_super(&p.elem);
254 Type::Paren(new)
255 }
256 Type::TraitObject(t) => {
257 let mut new = t.clone();
258 for bound in &mut new.bounds {
259 if let syn::TypeParamBound::Trait(tb) = bound {
260 qualify_path_with_super(&mut tb.path);
261 }
262 }
263 Type::TraitObject(new)
264 }
265 Type::ImplTrait(t) => {
266 let mut new = t.clone();
267 for bound in &mut new.bounds {
268 if let syn::TypeParamBound::Trait(tb) = bound {
269 qualify_path_with_super(&mut tb.path);
270 }
271 }
272 Type::ImplTrait(new)
273 }
274 Type::BareFn(f) => {
275 let mut new = f.clone();
276 for arg in &mut new.inputs {
277 arg.ty = qualify_type_with_super(&arg.ty);
278 }
279 if let ReturnType::Type(_, ref mut ty) = new.output {
280 **ty = qualify_type_with_super(ty);
281 }
282 Type::BareFn(new)
283 }
284 _ => ty.clone(),
285 }
286}
287
288fn qualify_path_with_super(path: &mut syn::Path) {
291 for seg in &mut path.segments {
292 match &mut seg.arguments {
293 PathArguments::AngleBracketed(ref mut args) => {
294 for arg in &mut args.args {
295 if let GenericArgument::Type(ref mut inner) = arg {
296 *inner = qualify_type_with_super(inner);
297 }
298 }
299 }
300 PathArguments::Parenthesized(ref mut args) => {
301 for input in &mut args.inputs {
302 *input = qualify_type_with_super(input);
303 }
304 if let syn::ReturnType::Type(_, ref mut ty) = args.output {
305 **ty = qualify_type_with_super(ty);
306 }
307 }
308 PathArguments::None => {}
309 }
310 }
311}
312
313struct ProtocolInfo<'a> {
314 trait_name: &'a Ident,
315 mod_name: &'a Ident,
316 ref_name: &'a Ident,
317 converter_trait: &'a Ident,
318 converter_method: &'a Ident,
319 target_trait: &'a Ident,
320}
321
322fn transport_signature(
324 info: &ProtocolInfo,
325 method: &ProtocolMethodInfo,
326 index: usize,
327) -> (Ident, proc_macro2::TokenStream) {
328 let name = format_ident!("__dispatch_{}", index);
329 let module = info.mod_name;
330 let message = &method.struct_name;
331 let (timeout, result) = match &method.kind {
332 MethodKind::Send => (
333 quote! {},
334 quote! { ::std::result::Result<(), spawned_concurrency::ActorError> },
335 ),
336 MethodKind::Request(result) => (
337 quote! { , timeout: ::std::time::Duration },
338 quote! { spawned_concurrency::Response<#result> },
339 ),
340 };
341 let signature =
342 quote! { fn #name(target: &Self, message: #module::#message #timeout) -> #result };
343 (name, signature)
344}
345
346fn generate_target_impl(
349 info: &ProtocolInfo,
350 methods: &[ProtocolMethodInfo],
351 runtime_path: &proc_macro2::TokenStream,
352 mode: RuntimeMode,
353) -> proc_macro2::TokenStream {
354 let ProtocolInfo {
355 trait_name,
356 mod_name,
357 target_trait,
358 ..
359 } = info;
360
361 let handler_bounds: Vec<_> = methods
363 .iter()
364 .filter(|m| m.cfg_attrs.is_empty())
365 .map(|m| {
366 let sn = &m.struct_name;
367 quote! { #runtime_path::Handler<#mod_name::#sn> }
368 })
369 .collect();
370
371 let mut cfg_groups: Vec<(String, Vec<&ProtocolMethodInfo>)> = Vec::new();
374 for m in methods.iter().filter(|m| !m.cfg_attrs.is_empty()) {
375 let key: String = m
376 .cfg_attrs
377 .iter()
378 .map(|a| quote!(#a).to_string())
379 .collect::<Vec<_>>()
380 .join(",");
381 if let Some(group) = cfg_groups.iter_mut().find(|(k, _)| k == &key) {
382 group.1.push(m);
383 } else {
384 cfg_groups.push((key, vec![m]));
385 }
386 }
387
388 let mode_suffix = match mode {
389 RuntimeMode::Tasks => format_ident!("Tasks"),
390 RuntimeMode::Threads => format_ident!("Threads"),
391 };
392
393 let mut marker_trait_defs = Vec::new();
394 let mut marker_trait_bounds = Vec::new();
395
396 for (i, (_key, group_methods)) in cfg_groups.iter().enumerate() {
397 let marker_name = format_ident!("__{}Cfg{}{}", trait_name, i, mode_suffix);
398 let cfg_attrs = &group_methods[0].cfg_attrs;
399 let group_handler_bounds: Vec<_> = group_methods
400 .iter()
401 .map(|m| {
402 let sn = &m.struct_name;
403 quote! { #runtime_path::Handler<#mod_name::#sn> }
404 })
405 .collect();
406
407 let cfg_predicates: Vec<proc_macro2::TokenStream> = cfg_attrs
410 .iter()
411 .filter(|a| a.path().is_ident("cfg"))
412 .filter_map(|a| a.parse_args::<proc_macro2::TokenStream>().ok())
413 .collect();
414
415 let (positive_cfg, negated_cfg) = if cfg_predicates.len() == 1 {
416 let pred = &cfg_predicates[0];
417 (quote! { #[cfg(#pred)] }, quote! { #[cfg(not(#pred))] })
418 } else {
419 (
420 quote! { #[cfg(all(#(#cfg_predicates),*))] },
421 quote! { #[cfg(not(all(#(#cfg_predicates),*)))] },
422 )
423 };
424
425 marker_trait_defs.push(quote! {
426 #positive_cfg
427 #[doc(hidden)]
428 trait #marker_name: #(#group_handler_bounds)+* {}
429 #positive_cfg
430 impl<__T: #(#group_handler_bounds)+*> #marker_name for __T {}
431
432 #negated_cfg
433 #[doc(hidden)]
434 trait #marker_name {}
435 #negated_cfg
436 impl<__T> #marker_name for __T {}
437 });
438
439 marker_trait_bounds.push(quote! { + #marker_name });
440 }
441
442 let method_impls = methods.iter().enumerate().map(|(index, m)| {
443 let (_, signature) = transport_signature(info, m, index);
444 let cfg = &m.cfg_attrs;
445 let body = match &m.kind {
446 MethodKind::Send => quote! { target.send(message) },
447 MethodKind::Request(_) => match mode {
448 RuntimeMode::Tasks => quote! { spawned_concurrency::Response::from_with_timeout(target.request_raw(message), timeout) },
449 RuntimeMode::Threads => quote! { spawned_concurrency::Response::ready(target.request_with_timeout(message, timeout)) },
450 },
451 };
452 quote! { #(#cfg)* #signature { #body } }
453 });
454 quote! {
455 #(#marker_trait_defs)*
456 impl<__A: #runtime_path::Actor #(+ #handler_bounds)* #(#marker_trait_bounds)*>
457 #target_trait for #runtime_path::ActorRef<__A>
458 {
459 #(#method_impls)*
460 }
461 }
462}
463
464fn generate_protocol_adapters(
467 info: &ProtocolInfo,
468 methods: &[ProtocolMethodInfo],
469 definition: &ItemTrait,
470) -> proc_macro2::TokenStream {
471 let ProtocolInfo {
472 trait_name,
473 mod_name,
474 ref_name,
475 converter_trait,
476 converter_method,
477 target_trait,
478 } = info;
479 let signatures = methods.iter().enumerate().map(|(index, m)| {
480 let (_, signature) = transport_signature(info, m, index);
481 let cfg = &m.cfg_attrs;
482 quote! { #(#cfg)* #signature; }
483 });
484 let delegates = methods.iter().enumerate().map(|(index, m)| {
485 let (name, signature) = transport_signature(info, m, index);
486 let cfg = &m.cfg_attrs;
487 let timeout = match &m.kind { MethodKind::Send => quote! {}, MethodKind::Request(_) => quote! { , timeout } };
488 quote! {
489 #(#cfg)* #signature {
490 <__T as #target_trait>::#name(
491 spawned_concurrency::response::RequestOptions::actor_ref(target), message #timeout
492 )
493 }
494 }
495 });
496 let method_impls: Vec<_> = methods.iter().enumerate().map(|(index, m)| {
497 let method = &m.method_name;
498 let dispatch = format_ident!("__dispatch_{}", index);
499 let params = &m.params;
500 let attrs = &m.method_attrs;
501 let result = &m.ret_type;
502 let message = &m.struct_name;
503 let fields = &m.field_names;
504 let construct = if fields.is_empty() {
505 quote! { #mod_name::#message }
506 } else {
507 quote! { #mod_name::#message { #(#fields),* } }
508 };
509 let body = match &m.kind {
510 MethodKind::Send => {
511 let call = quote! { <Self as #target_trait>::#dispatch(self, #construct) };
512 let unit = match result {
513 ReturnType::Default => true,
514 ReturnType::Type(_, ty) => is_unit_type(ty),
515 };
516 if unit { quote! { let _ = #call; } } else { call }
517 }
518 MethodKind::Request(_) => {
519 let timeout = &m.timeout;
520 let binding = syn::Ident::new("__spawned_request_timeout", proc_macro2::Span::mixed_site());
521 quote! {
522 let #binding: ::std::time::Duration = match <Self as #target_trait>::__timeout_override(self) {
525 Some(timeout) => timeout,
526 None => #timeout,
527 };
528 <Self as #target_trait>::#dispatch(self, #construct, #binding)
529 }
530 }
531 };
532 quote! {
533 #(#attrs)*
534 fn #method(&self, #(#params),*) #result { #body }
535 }
536 }).collect();
537 let mut public_impls = Vec::new();
540 for (runtime, asynchronous) in [
541 (quote! { spawned_concurrency::tasks }, true),
542 (quote! { spawned_concurrency::threads }, false),
543 ] {
544 let actor = quote! { #runtime::ActorRef<__A> };
545 for target in [
546 actor.clone(),
547 quote! { spawned_concurrency::response::RequestOptions<#actor> },
548 ] {
549 let mut predicates: Vec<_> = definition
550 .generics
551 .where_clause
552 .iter()
553 .flat_map(|clause| clause.predicates.iter())
554 .map(|p| quote! { #p })
555 .collect();
556 let supertraits = &definition.supertraits;
557 if !supertraits.is_empty() {
558 predicates.push(quote! { Self: #supertraits });
559 }
560 let implementation = quote! {
561 impl<__A: #runtime::Actor> #trait_name for #target
562 where Self: #target_trait, #(#predicates,)*
563 { #(#method_impls)* }
564 impl<__A: #runtime::Actor> #converter_trait for #target
565 where Self: #target_trait + #trait_name
566 {
567 fn #converter_method(&self) -> #ref_name { ::std::sync::Arc::new(self.clone()) }
568 }
569 };
570 public_impls.push(if asynchronous {
571 quote! { spawned_concurrency::__spawned_async_items! { #implementation } }
572 } else {
573 implementation
574 });
575 }
576 }
577 quote! {
578 #[doc(hidden)]
579 trait #target_trait: Clone + Send + Sync + 'static {
580 fn __timeout_override(_target: &Self) -> Option<::std::time::Duration> { None }
581 #(#signatures)*
582 }
583 impl<__T: #target_trait> #target_trait for spawned_concurrency::response::RequestOptions<__T> {
584 fn __timeout_override(target: &Self) -> Option<::std::time::Duration> {
585 Some(spawned_concurrency::response::RequestOptions::timeout(target))
586 }
587 #(#delegates)*
588 }
589 #(#public_impls)*
590 }
591}
592
593struct ProtocolMethodInfo {
594 method_name: Ident,
595 struct_name: Ident,
596 field_names: Vec<Ident>,
597 field_types: Vec<Type>,
598 kind: MethodKind,
599 params: Vec<FnArg>,
600 ret_type: ReturnType,
601 method_attrs: Vec<Attribute>,
603 cfg_attrs: Vec<Attribute>,
604 timeout: syn::Expr,
605}
606
607#[proc_macro_attribute]
683pub fn protocol(attr: TokenStream, item: TokenStream) -> TokenStream {
684 let options = parse_macro_input!(attr with syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated);
685 let mut protocol_timeout = None;
686 for option in options {
687 if !option.path.is_ident("timeout") {
688 return syn::Error::new_spanned(
689 option.path,
690 "unsupported protocol option; expected timeout = Duration expression",
691 )
692 .to_compile_error()
693 .into();
694 }
695 if protocol_timeout.replace(option.value).is_some() {
696 return syn::Error::new_spanned(option.path, "duplicate protocol timeout")
697 .to_compile_error()
698 .into();
699 }
700 }
701 let mut trait_def = parse_macro_input!(item as ItemTrait);
702 let trait_name = &trait_def.ident;
703 let trait_vis = &trait_def.vis;
704
705 if !trait_def.generics.params.is_empty() {
706 return syn::Error::new_spanned(
707 &trait_def.generics,
708 "generic type parameters on protocol traits are not supported",
709 )
710 .to_compile_error()
711 .into();
712 }
713
714 let base_name = strip_protocol_suffix(&trait_name.to_string());
715 let mod_name = format_ident!("{}", to_snake_case(&trait_name.to_string()));
716 let ref_name = format_ident!("{}Ref", base_name);
717 let converter_trait = format_ident!("To{}Ref", base_name);
718 let converter_method = format_ident!("to_{}_ref", to_snake_case(&base_name));
719
720 let mut methods: Vec<ProtocolMethodInfo> = Vec::new();
721
722 for item in &mut trait_def.items {
723 if !matches!(item, TraitItem::Fn(_)) {
724 return syn::Error::new_spanned(
725 item,
726 "protocol traits may only contain methods; \
727 associated types, constants, and other items are not supported",
728 )
729 .to_compile_error()
730 .into();
731 }
732 if let TraitItem::Fn(method) = item {
733 if let Some(attribute) = method.attrs.iter().find(|a| conditional_timeout(&a.meta)) {
734 return syn::Error::new_spanned(
735 attribute,
736 "conditional request_timeout is not supported; use #[request_timeout(...)] directly",
737 ).to_compile_error().into();
738 }
739 if method.sig.asyncness.is_some() {
740 return syn::Error::new_spanned(
741 &method.sig,
742 "protocol methods must not be async; \
743 use Response<T> as the return type for requests",
744 )
745 .to_compile_error()
746 .into();
747 }
748
749 match method.sig.inputs.first() {
751 Some(FnArg::Receiver(r)) if r.reference.is_some() && r.mutability.is_none() => {}
752 _ => {
753 return syn::Error::new_spanned(
754 &method.sig,
755 "protocol methods must take `&self` as the first parameter",
756 )
757 .to_compile_error()
758 .into();
759 }
760 }
761
762 let method_name = method.sig.ident.clone();
763 let struct_name = format_ident!("{}", to_pascal_case(&method_name.to_string()));
764
765 let mut field_names: Vec<Ident> = Vec::new();
766 let mut field_types: Vec<Type> = Vec::new();
767 let mut params: Vec<FnArg> = Vec::new();
768
769 for arg in method.sig.inputs.iter().skip(1) {
770 if let FnArg::Typed(pat_type) = arg {
771 if let Pat::Ident(pat_ident) = &*pat_type.pat {
772 field_names.push(pat_ident.ident.clone());
773 field_types.push((*pat_type.ty).clone());
774 } else {
775 return syn::Error::new_spanned(
776 &pat_type.pat,
777 "protocol methods only support simple identifier patterns \
778 (e.g., `name: Type`)",
779 )
780 .to_compile_error()
781 .into();
782 }
783 }
784 params.push(arg.clone());
785 }
786
787 let kind = match classify_return_type(&method.sig.output) {
788 Ok(kind) => kind,
789 Err(ty) => {
790 return syn::Error::new_spanned(
791 ty,
792 "unsupported return type in protocol method; \
793 use Response<T> for requests (works in both async and sync modes), \
794 Result<(), ActorError> for sends, or no return type for fire-and-forget",
795 )
796 .to_compile_error()
797 .into();
798 }
799 };
800
801 let mut method_timeout = None;
802 for attr in method
803 .attrs
804 .iter()
805 .filter(|a| a.path().is_ident("request_timeout"))
806 {
807 if !matches!(kind, MethodKind::Request(_)) {
808 return syn::Error::new_spanned(
809 attr,
810 "request_timeout is only valid on Response<T> methods",
811 )
812 .to_compile_error()
813 .into();
814 }
815 let expression = match attr.parse_args::<syn::Expr>() {
816 Ok(expression) => expression,
817 Err(error) => return error.to_compile_error().into(),
818 };
819 if method_timeout.replace(expression).is_some() {
820 return syn::Error::new_spanned(attr, "duplicate request_timeout")
821 .to_compile_error()
822 .into();
823 }
824 }
825 method
826 .attrs
827 .retain(|a| !a.path().is_ident("request_timeout"));
828 let timeout = method_timeout
829 .or_else(|| protocol_timeout.clone())
830 .unwrap_or_else(
831 || syn::parse_quote! { spawned_concurrency::response::DEFAULT_REQUEST_TIMEOUT },
832 );
833
834 let method_attrs: Vec<Attribute> = method
835 .attrs
836 .iter()
837 .filter(|a| {
838 a.path().is_ident("doc")
839 || a.path().is_ident("cfg")
840 || a.path().is_ident("cfg_attr")
841 })
842 .cloned()
843 .collect();
844 let cfg_attrs: Vec<Attribute> = method
845 .attrs
846 .iter()
847 .filter(|a| a.path().is_ident("cfg") || a.path().is_ident("cfg_attr"))
848 .cloned()
849 .collect();
850
851 methods.push(ProtocolMethodInfo {
852 method_name,
853 struct_name,
854 field_names,
855 field_types,
856 kind,
857 params,
858 ret_type: method.sig.output.clone(),
859 method_attrs,
860 cfg_attrs,
861 timeout,
862 });
863 }
864 }
865
866 let msg_structs: Vec<_> = methods
871 .iter()
872 .map(|m| {
873 let struct_name = &m.struct_name;
874 let field_names = &m.field_names;
875 let qualified_field_types: Vec<Type> =
876 m.field_types.iter().map(qualify_type_with_super).collect();
877 let method_attrs = &m.method_attrs;
878 let cfg_attrs = &m.cfg_attrs;
879 let msg_result_ty: Type = match &m.kind {
880 MethodKind::Send => syn::parse_quote! { () },
881 MethodKind::Request(inner) => qualify_type_with_super(inner),
882 };
883
884 if field_names.is_empty() {
885 quote! {
886 #(#method_attrs)*
887 #[derive(Clone)]
888 pub struct #struct_name;
889 #(#cfg_attrs)*
890 impl Message for #struct_name {
891 type Result = #msg_result_ty;
892 }
893 }
894 } else {
895 quote! {
896 #(#method_attrs)*
897 pub struct #struct_name {
898 #(pub #field_names: #qualified_field_types,)*
899 }
900 #(#cfg_attrs)*
901 impl Message for #struct_name {
902 type Result = #msg_result_ty;
903 }
904 }
905 }
906 })
907 .collect();
908
909 let target_trait = format_ident!("__{}Target", trait_name);
911 let tasks = quote! { spawned_concurrency::tasks };
912 let threads = quote! { spawned_concurrency::threads };
913 let proto_info = ProtocolInfo {
914 trait_name,
915 mod_name: &mod_name,
916 ref_name: &ref_name,
917 converter_trait: &converter_trait,
918 converter_method: &converter_method,
919 target_trait: &target_trait,
920 };
921 let tasks_impl = generate_target_impl(&proto_info, &methods, &tasks, RuntimeMode::Tasks);
922 let threads_impl = generate_target_impl(&proto_info, &methods, &threads, RuntimeMode::Threads);
923 let adapters = generate_protocol_adapters(&proto_info, &methods, &trait_def);
924 let blanket_impls = quote! {
925 #adapters
926 spawned_concurrency::__spawned_async_items! { #tasks_impl }
927 #threads_impl
928 };
929
930 let ref_doc = format!(
931 "Type-erased reference to any actor implementing [`{trait_name}`].\n\n\
932 Use this type to store protocol references without depending on the concrete actor type."
933 );
934
935 let output = quote! {
938 #[allow(dead_code)]
939 #trait_def
940
941 #[doc = #ref_doc]
942 #trait_vis type #ref_name = ::std::sync::Arc<dyn #trait_name>;
943
944 #trait_vis mod #mod_name {
945 use super::*;
946 use spawned_concurrency::message::Message;
947 #(#msg_structs)*
948 }
949
950 #trait_vis trait #converter_trait {
951 fn #converter_method(&self) -> #ref_name;
952 }
953
954 impl #converter_trait for #ref_name {
955 fn #converter_method(&self) -> #ref_name {
956 ::std::sync::Arc::clone(self)
957 }
958 }
959
960 #blanket_impls
961 };
962
963 output.into()
964}
965
966#[proc_macro_attribute]
1043pub fn actor(attr: TokenStream, item: TokenStream) -> TokenStream {
1044 let mut impl_block = parse_macro_input!(item as ItemImpl);
1045
1046 let self_ty = &impl_block.self_ty;
1047 let (impl_generics, _, where_clause) = impl_block.generics.split_for_impl();
1048
1049 let bridge_traits: Vec<Ident> = if attr.is_empty() {
1051 Vec::new()
1052 } else {
1053 let parser = |input: syn::parse::ParseStream| -> syn::Result<Vec<Ident>> {
1054 let mut protocols = Vec::new();
1055 while !input.is_empty() {
1056 let key: Ident = input.parse()?;
1057 if key != "protocol" {
1058 return Err(syn::Error::new(
1059 key.span(),
1060 "unknown parameter, expected `protocol`",
1061 ));
1062 }
1063 if input.peek(syn::Token![=]) {
1064 let _: syn::Token![=] = input.parse()?;
1066 protocols.push(input.parse()?);
1067 } else {
1068 let content;
1070 syn::parenthesized!(content in input);
1071 let punctuated = content.parse_terminated(Ident::parse, syn::Token![,])?;
1072 protocols.extend(punctuated);
1073 }
1074 if input.peek(syn::Token![,]) {
1075 let _: syn::Token![,] = input.parse()?;
1076 }
1077 }
1078 Ok(protocols)
1079 };
1080 match syn::parse::Parser::parse(parser, attr) {
1081 Ok(traits) => traits,
1082 Err(e) => return e.to_compile_error().into(),
1083 }
1084 };
1085
1086 let mut started_method: Option<ImplItemFn> = None;
1088 let mut stopped_method: Option<ImplItemFn> = None;
1089 let mut exit_method: Option<ImplItemFn> = None;
1090 let mut has_async = false;
1091
1092 let mut items_to_keep = Vec::new();
1093 for item in impl_block.items.drain(..) {
1094 if let ImplItem::Fn(ref method) = item {
1095 let is_started = method.attrs.iter().any(|a| a.path().is_ident("started"));
1096 let is_stopped = method.attrs.iter().any(|a| a.path().is_ident("stopped"));
1097
1098 let is_exit = method
1099 .attrs
1100 .iter()
1101 .any(|a| a.path().is_ident("exit_received"));
1102 if is_exit {
1103 if exit_method.is_some() {
1104 return syn::Error::new_spanned(
1105 &method.sig,
1106 "only one #[exit_received] method is allowed per actor",
1107 )
1108 .to_compile_error()
1109 .into();
1110 }
1111 if is_started
1112 || is_stopped
1113 || method.attrs.iter().any(|a| {
1114 a.path().is_ident("handler")
1115 || a.path().is_ident("send_handler")
1116 || a.path().is_ident("request_handler")
1117 })
1118 {
1119 return syn::Error::new_spanned(&method.sig, "#[exit_received] cannot be combined with other lifecycle or handler attributes").to_compile_error().into();
1120 }
1121 if method.sig.inputs.len() != 3
1122 || !matches!(method.sig.inputs.first(), Some(FnArg::Receiver(r)) if r.reference.is_some() && r.mutability.is_some())
1123 {
1124 return syn::Error::new_spanned(
1125 &method.sig,
1126 "#[exit_received] must take (&mut self, Exit, &Context<Self>)",
1127 )
1128 .to_compile_error()
1129 .into();
1130 }
1131 let mut m = method.clone();
1132 m.attrs.retain(|a| !a.path().is_ident("exit_received"));
1133 m.vis = syn::Visibility::Inherited;
1134 m.sig.ident = format_ident!("exit_received");
1135 if m.sig.asyncness.is_some() {
1136 has_async = true;
1137 }
1138 exit_method = Some(m);
1139 continue;
1140 }
1141
1142 if is_started {
1143 if started_method.is_some() {
1144 return syn::Error::new_spanned(
1145 &method.sig,
1146 "only one #[started] method is allowed per actor",
1147 )
1148 .to_compile_error()
1149 .into();
1150 }
1151 if method.attrs.iter().any(|a| {
1152 a.path().is_ident("handler")
1153 || a.path().is_ident("send_handler")
1154 || a.path().is_ident("request_handler")
1155 }) {
1156 return syn::Error::new_spanned(
1157 &method.sig,
1158 "#[started] cannot be combined with handler attributes",
1159 )
1160 .to_compile_error()
1161 .into();
1162 }
1163 if method.sig.inputs.len() != 2 {
1165 return syn::Error::new_spanned(
1166 &method.sig,
1167 "#[started] method must take exactly (&mut self, &Context<Self>)",
1168 )
1169 .to_compile_error()
1170 .into();
1171 }
1172 if !matches!(method.sig.inputs.first(), Some(FnArg::Receiver(r)) if r.mutability.is_some())
1173 {
1174 return syn::Error::new_spanned(
1175 &method.sig,
1176 "#[started] method's first parameter must be `&mut self`",
1177 )
1178 .to_compile_error()
1179 .into();
1180 }
1181 let mut m = method.clone();
1182 m.attrs.retain(|a| !a.path().is_ident("started"));
1183 m.vis = syn::Visibility::Inherited;
1184 m.sig.ident = format_ident!("started");
1185 if m.sig.asyncness.is_some() {
1186 has_async = true;
1187 }
1188 started_method = Some(m);
1189 continue;
1190 }
1191
1192 if is_stopped {
1193 if stopped_method.is_some() {
1194 return syn::Error::new_spanned(
1195 &method.sig,
1196 "only one #[stopped] method is allowed per actor",
1197 )
1198 .to_compile_error()
1199 .into();
1200 }
1201 if method.attrs.iter().any(|a| {
1202 a.path().is_ident("handler")
1203 || a.path().is_ident("send_handler")
1204 || a.path().is_ident("request_handler")
1205 }) {
1206 return syn::Error::new_spanned(
1207 &method.sig,
1208 "#[stopped] cannot be combined with handler attributes",
1209 )
1210 .to_compile_error()
1211 .into();
1212 }
1213 if method.sig.inputs.len() != 2 {
1215 return syn::Error::new_spanned(
1216 &method.sig,
1217 "#[stopped] method must take exactly (&mut self, &Context<Self>)",
1218 )
1219 .to_compile_error()
1220 .into();
1221 }
1222 if !matches!(method.sig.inputs.first(), Some(FnArg::Receiver(r)) if r.mutability.is_some())
1223 {
1224 return syn::Error::new_spanned(
1225 &method.sig,
1226 "#[stopped] method's first parameter must be `&mut self`",
1227 )
1228 .to_compile_error()
1229 .into();
1230 }
1231 let mut m = method.clone();
1232 m.attrs.retain(|a| !a.path().is_ident("stopped"));
1233 m.vis = syn::Visibility::Inherited;
1234 m.sig.ident = format_ident!("stopped");
1235 if m.sig.asyncness.is_some() {
1236 has_async = true;
1237 }
1238 stopped_method = Some(m);
1239 continue;
1240 }
1241 }
1242 items_to_keep.push(item);
1243 }
1244 impl_block.items = items_to_keep;
1245
1246 let mut handler_impls = Vec::new();
1248
1249 for item in &mut impl_block.items {
1250 if let ImplItem::Fn(method) = item {
1251 let handler_idx = method.attrs.iter().position(|attr| {
1252 attr.path().is_ident("handler")
1253 || attr.path().is_ident("send_handler")
1254 || attr.path().is_ident("request_handler")
1255 });
1256
1257 if let Some(idx) = handler_idx {
1258 method.attrs.remove(idx);
1259
1260 let extra_attrs: Vec<_> = method
1263 .attrs
1264 .iter()
1265 .filter(|a| {
1266 !a.path().is_ident("handler")
1267 && !a.path().is_ident("send_handler")
1268 && !a.path().is_ident("request_handler")
1269 && !a.path().is_ident("started")
1270 && !a.path().is_ident("stopped")
1271 })
1272 .cloned()
1273 .collect();
1274
1275 let method_name = &method.sig.ident;
1276 if method.sig.asyncness.is_some() {
1277 has_async = true;
1278 }
1279
1280 let param_count = method.sig.inputs.len();
1282 if param_count != 3 {
1283 return syn::Error::new_spanned(
1284 &method.sig,
1285 format!(
1286 "handler method must have 3 parameters (&mut self, msg: M, ctx: &Context<Self>), found {param_count}"
1287 ),
1288 )
1289 .to_compile_error()
1290 .into();
1291 }
1292
1293 let msg_ty = match method.sig.inputs.iter().nth(1) {
1295 Some(FnArg::Typed(pat_type)) => &*pat_type.ty,
1296 _ => {
1297 return syn::Error::new_spanned(
1298 &method.sig,
1299 "handler method must have signature: fn(&mut self, msg: M, ctx: &Context<Self>) -> R",
1300 )
1301 .to_compile_error()
1302 .into();
1303 }
1304 };
1305
1306 let ret_ty: Box<Type> = match &method.sig.output {
1308 ReturnType::Default => syn::parse_quote! { () },
1309 ReturnType::Type(_, ty) => ty.clone(),
1310 };
1311
1312 let handler_impl = if method.sig.asyncness.is_some() {
1313 quote! {
1314 #(#extra_attrs)*
1315 impl #impl_generics Handler<#msg_ty> for #self_ty #where_clause {
1316 async fn handle(&mut self, msg: #msg_ty, ctx: &Context<Self>) -> #ret_ty {
1317 self.#method_name(msg, ctx).await
1318 }
1319 }
1320 }
1321 } else {
1322 quote! {
1323 #(#extra_attrs)*
1324 impl #impl_generics Handler<#msg_ty> for #self_ty #where_clause {
1325 fn handle(&mut self, msg: #msg_ty, ctx: &Context<Self>) -> #ret_ty {
1326 self.#method_name(msg, ctx)
1327 }
1328 }
1329 }
1330 };
1331
1332 handler_impls.push(handler_impl);
1333 }
1334 }
1335 }
1336
1337 let lifecycle_methods: Vec<&ImplItemFn> = [
1339 started_method.as_ref(),
1340 stopped_method.as_ref(),
1341 exit_method.as_ref(),
1342 ]
1343 .into_iter()
1344 .flatten()
1345 .collect();
1346
1347 let protocol_doc = if bridge_traits.is_empty() {
1348 quote! {}
1349 } else {
1350 let lines: Vec<String> = bridge_traits.iter().map(|t| format!("- [`{t}`]")).collect();
1351 let doc_body = format!(
1352 "# Protocol\n\n\
1353 When started, `ActorRef<{ty}>` implements:\n\n\
1354 {lines}\n\n\
1355 See the protocol trait docs for the full API.",
1356 ty = quote!(#self_ty),
1357 lines = lines.join("\n"),
1358 );
1359 quote! { #[doc = #doc_body] }
1360 };
1361
1362 let actor_impl = quote! {
1363 #protocol_doc
1364 impl #impl_generics Actor for #self_ty #where_clause {
1365 #(#lifecycle_methods)*
1366 }
1367 };
1368
1369 let runtime_path = if has_async {
1371 quote! { spawned_concurrency::tasks }
1372 } else {
1373 quote! { spawned_concurrency::threads }
1374 };
1375
1376 let bridge_asserts: Vec<_> = bridge_traits
1377 .iter()
1378 .map(|trait_name| {
1379 quote! {
1380 const _: () = {
1381 fn _assert_bridge<__T: #trait_name>() {}
1382 fn _check() {
1383 _assert_bridge::<#runtime_path::ActorRef<#self_ty>>();
1384 }
1385 };
1386 }
1387 })
1388 .collect();
1389
1390 let output = quote! {
1391 #actor_impl
1392 #impl_block
1393 #(#handler_impls)*
1394 #(#bridge_asserts)*
1395 };
1396
1397 output.into()
1398}