1use proc_macro::TokenStream;
7use proc_macro_crate::{FoundCrate, crate_name};
8use proc_macro2::{Span, TokenStream as TokenStream2};
9use quote::{format_ident, quote};
10use syn::parse::{Parse, ParseStream};
11use syn::visit::Visit;
12use syn::{
13 Error, FnArg, GenericArgument, GenericParam, Generics, Ident, ImplItem, ItemImpl,
14 PathArguments, Result, ReturnType, Token, Type, braced, parse_macro_input, parse_quote,
15};
16
17fn crate_path(found: FoundCrate) -> TokenStream2 {
18 match found {
19 FoundCrate::Itself => quote!(crate),
20 FoundCrate::Name(name) => {
21 let name = syn::Ident::new(&name, Span::call_site());
22 quote!(::#name)
23 }
24 }
25}
26
27fn facade_crate_path(found: FoundCrate) -> TokenStream2 {
28 match found {
29 FoundCrate::Itself => quote!(crate),
30 FoundCrate::Name(name) => {
31 let name = if name == "bombay_rs" {
37 "bombay".to_owned()
38 } else {
39 name
40 };
41 let name = syn::Ident::new(&name, Span::call_site());
42 quote!(::#name)
43 }
44 }
45}
46
47fn behavior_crate() -> Result<TokenStream2> {
48 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
49 return Ok(quote!(::behavior));
53 }
54 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
55 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
60 Ok(quote!(crate::behavior))
61 } else {
62 Ok(quote!(::bombay::behavior))
63 };
64 }
65 if let Ok(found) = crate_name("bombay-behavior") {
66 return Ok(crate_path(found));
67 }
68 if let Ok(found) = crate_name("bombay-rs") {
69 let bombay = facade_crate_path(found);
70 return Ok(quote!(#bombay::behavior));
71 }
72 Err(Error::new(
73 Span::call_site(),
74 "could not resolve `bombay-behavior` directly or through `bombay-rs`",
75 ))
76}
77
78fn actors_crate() -> Result<TokenStream2> {
79 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior-actors") {
80 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("behavior_actors") {
81 Ok(quote!(crate))
82 } else {
83 Ok(quote!(::behavior_actors))
84 };
85 }
86 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
87 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
88 Ok(quote!(crate::behavior))
89 } else {
90 Ok(quote!(::bombay::behavior))
91 };
92 }
93 if let Ok(found) = crate_name("bombay-behavior-actors") {
94 return Ok(crate_path(found));
95 }
96 if let Ok(found) = crate_name("bombay-rs") {
97 let bombay = facade_crate_path(found);
98 return Ok(quote!(#bombay::behavior));
99 }
100 Err(Error::new(
101 Span::call_site(),
102 "could not resolve `bombay-behavior-actors` directly or through `bombay-rs`",
103 ))
104}
105
106#[cfg(test)]
107mod crate_resolution_tests {
108 use super::*;
109
110 #[test]
111 fn facade_default_library_name_and_explicit_rename_resolve_distinctly() {
112 assert_eq!(
113 facade_crate_path(FoundCrate::Name("bombay_rs".to_owned())).to_string(),
114 ":: bombay"
115 );
116 assert_eq!(
117 facade_crate_path(FoundCrate::Name("runtime".to_owned())).to_string(),
118 ":: runtime"
119 );
120 }
121}
122
123struct NamedField {
124 name: Ident,
125 ty: Type,
126}
127
128struct NamedProduct {
129 fields: Vec<NamedField>,
130}
131
132#[allow(
133 clippy::large_enum_variant,
134 reason = "boxing syn::Type would add lint-driven indirection to compile-time parser state"
135)]
136enum SendsSpec {
137 Existing(Type),
138 Generated(NamedProduct),
139}
140
141#[allow(
142 clippy::large_enum_variant,
143 reason = "boxing syn::Type would add lint-driven indirection to compile-time parser state"
144)]
145enum BirthsSpec {
146 Existing(Type),
147 Generated(NamedProduct),
148}
149
150fn parse_product(input: ParseStream) -> Result<NamedProduct> {
151 let content;
152 braced!(content in input);
153 let mut fields = Vec::new();
154 let mut names = std::collections::BTreeSet::new();
155 while !content.is_empty() {
156 let name: Ident = content.parse()?;
157 if !names.insert(name.to_string()) {
158 return Err(Error::new_spanned(name, "duplicate product lane"));
159 }
160 content.parse::<Token![:]>()?;
161 fields.push(NamedField {
162 name,
163 ty: content.parse()?,
164 });
165 if content.peek(Token![,]) {
166 content.parse::<Token![,]>()?;
167 } else if !content.is_empty() {
168 return Err(content.error("expected `,` between product lanes"));
169 }
170 }
171 if fields.is_empty() {
172 return Err(input.error("empty products must be omitted"));
173 }
174 Ok(NamedProduct { fields })
175}
176
177struct BehaviorArgs {
178 addr: Type,
179 message: Type,
180 sends: Option<SendsSpec>,
181 births: Option<BirthsSpec>,
182 error: Option<Type>,
183}
184
185struct PoolWorkerArgs {
186 addr: Type,
187 result: Type,
188}
189
190impl Parse for PoolWorkerArgs {
191 fn parse(input: ParseStream) -> Result<Self> {
192 let mut addr = None;
193 let mut result = None;
194 while !input.is_empty() {
195 let key: Ident = input.parse()?;
196 input.parse::<Token![=]>()?;
197 match key.to_string().as_str() {
198 "addr" if addr.is_none() => addr = Some(input.parse()?),
199 "result" if result.is_none() => result = Some(input.parse()?),
200 "addr" | "result" => {
201 return Err(Error::new_spanned(key, "duplicate pool worker argument"));
202 }
203 _ => return Err(Error::new_spanned(key, "unknown pool worker argument")),
204 }
205 if input.peek(Token![,]) {
206 input.parse::<Token![,]>()?;
207 } else if !input.is_empty() {
208 return Err(input.error("expected `,` between pool worker arguments"));
209 }
210 }
211 Ok(Self {
212 addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
213 result: result.ok_or_else(|| input.error("missing `result`"))?,
214 })
215 }
216}
217
218impl Parse for BehaviorArgs {
219 fn parse(input: ParseStream) -> Result<Self> {
220 let mut addr = None;
221 let mut message = None;
222 let mut sends = None;
223 let mut births = None;
224 let mut error = None;
225 while !input.is_empty() {
226 let key: Ident = input.parse()?;
227 input.parse::<Token![=]>()?;
228 match key.to_string().as_str() {
229 "addr" if addr.is_none() => addr = Some(input.parse()?),
230 "message" if message.is_none() => message = Some(input.parse()?),
231 "sends" if sends.is_none() => {
232 sends = Some(if input.peek(syn::token::Brace) {
233 SendsSpec::Generated(parse_product(input)?)
234 } else {
235 SendsSpec::Existing(input.parse()?)
236 });
237 }
238 "births" if births.is_none() => {
239 births = Some(if input.peek(syn::token::Brace) {
240 BirthsSpec::Generated(parse_product(input)?)
241 } else {
242 BirthsSpec::Existing(input.parse()?)
243 });
244 }
245 "error" if error.is_none() => error = Some(input.parse()?),
246 "addr" | "message" | "sends" | "births" | "error" => {
247 return Err(Error::new_spanned(key, "duplicate behavior argument"));
248 }
249 _ => return Err(Error::new_spanned(key, "unknown behavior argument")),
250 }
251 if input.peek(Token![,]) {
252 input.parse::<Token![,]>()?;
253 } else if !input.is_empty() {
254 return Err(input.error("expected `,` between behavior arguments"));
255 }
256 }
257 Ok(Self {
258 addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
259 message: message.ok_or_else(|| input.error("missing `message`"))?,
260 sends,
261 births,
262 error,
263 })
264 }
265}
266
267fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
268 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
269 return Err(Error::new_spanned(
270 &method.sig,
271 "behavior methods must begin with &mut self",
272 ));
273 };
274 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
275 return Err(Error::new_spanned(
276 receiver,
277 "behavior methods must begin with &mut self",
278 ));
279 }
280 if method.sig.constness.is_some()
281 || method.sig.asyncness.is_some()
282 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
283 || !method.sig.generics.params.is_empty()
284 {
285 return Err(Error::new_spanned(
286 &method.sig,
287 "behavior init and receive methods must be synchronous, safe, and non-generic",
288 ));
289 }
290 if matches!(method.sig.output, ReturnType::Default) {
291 return Err(Error::new_spanned(
292 &method.sig,
293 "behavior methods must declare their complete Actions result type",
294 ));
295 }
296 Ok(())
297}
298
299fn validate_pool_worker_output(output: &ReturnType) -> Result<()> {
300 let ReturnType::Type(_, output) = output else {
301 return Err(Error::new_spanned(
302 output,
303 "pool worker transition must return WorkerActed<Self>",
304 ));
305 };
306 let Type::Path(output) = output.as_ref() else {
307 return Err(Error::new_spanned(
308 output,
309 "pool worker transition must return WorkerActed<Self>",
310 ));
311 };
312 if output.qself.is_some() || output.path.segments.len() != 1 {
313 return Err(Error::new_spanned(
314 output,
315 "pool worker transition must return WorkerActed<Self>",
316 ));
317 }
318 let segment = output
319 .path
320 .segments
321 .first()
322 .ok_or_else(|| Error::new_spanned(output, "missing pool worker return type"))?;
323 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
324 return Err(Error::new_spanned(
325 output,
326 "pool worker transition must return WorkerActed<Self>",
327 ));
328 };
329 let mut arguments = arguments.args.iter();
330 let Some(GenericArgument::Type(Type::Path(self_type))) = arguments.next() else {
331 return Err(Error::new_spanned(
332 output,
333 "pool worker transition must return WorkerActed<Self>",
334 ));
335 };
336 let self_segment = self_type.path.segments.first();
337 if segment.ident != "WorkerActed"
338 || arguments.next().is_some()
339 || self_type.qself.is_some()
340 || self_type.path.segments.len() != 1
341 || !matches!(self_segment, Some(segment) if segment.ident == "Self" && matches!(segment.arguments, PathArguments::None))
342 {
343 return Err(Error::new_spanned(
344 output,
345 "pool worker transition must return WorkerActed<Self>",
346 ));
347 }
348 Ok(())
349}
350
351fn pool_worker_assignment(method: &syn::ImplItemFn) -> Result<Type> {
352 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
353 return Err(Error::new_spanned(
354 &method.sig,
355 "pool worker transition must begin with &mut self",
356 ));
357 };
358 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
359 return Err(Error::new_spanned(
360 receiver,
361 "pool worker transition must begin with &mut self",
362 ));
363 }
364 if method.sig.constness.is_some()
365 || method.sig.asyncness.is_some()
366 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
367 || method.sig.abi.is_some()
368 || method.sig.variadic.is_some()
369 || !method.sig.generics.params.is_empty()
370 {
371 return Err(Error::new_spanned(
372 &method.sig,
373 "pool worker transition must be synchronous, safe, and non-generic",
374 ));
375 }
376 if method.sig.inputs.len() != 2 {
377 return Err(Error::new_spanned(
378 &method.sig,
379 "pool worker transition must accept exactly &mut self and one Assignment<Job>",
380 ));
381 }
382 validate_pool_worker_output(&method.sig.output)?;
383
384 let Some(FnArg::Typed(assignment)) = method.sig.inputs.iter().nth(1) else {
385 return Err(Error::new_spanned(
386 &method.sig,
387 "pool worker transition must accept one Assignment<Job>",
388 ));
389 };
390 let Type::Path(assignment_path) = assignment.ty.as_ref() else {
391 return Err(Error::new_spanned(
392 &assignment.ty,
393 "pool worker transition input must be Assignment<Job>",
394 ));
395 };
396 let Some(segment) = assignment_path.path.segments.last() else {
397 return Err(Error::new_spanned(
398 &assignment.ty,
399 "pool worker transition input must be Assignment<Job>",
400 ));
401 };
402 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
403 return Err(Error::new_spanned(
404 &assignment.ty,
405 "pool worker transition input must be Assignment<Job>",
406 ));
407 };
408 let mut arguments = arguments.args.iter();
409 if segment.ident != "Assignment"
410 || !matches!(arguments.next(), Some(GenericArgument::Type(_)))
411 || arguments.next().is_some()
412 {
413 return Err(Error::new_spanned(
414 &assignment.ty,
415 "pool worker transition input must be Assignment<Job>",
416 ));
417 }
418 Ok(assignment.ty.as_ref().clone())
419}
420
421fn pascal_name(field: &Ident) -> String {
422 field
423 .to_string()
424 .split('_')
425 .filter(|part| !part.is_empty())
426 .map(|part| {
427 let mut chars = part.chars();
428 chars.next().map_or_else(String::new, |first| {
429 first.to_uppercase().chain(chars).collect()
430 })
431 })
432 .collect::<String>()
433}
434
435fn lane_name(product: &Ident, field: &Ident) -> Ident {
436 let lane = pascal_name(field);
437 format_ident!("{}{}", product, lane)
438}
439
440#[derive(Default)]
441struct GenericUses {
442 identifiers: std::collections::BTreeSet<String>,
443 lifetimes: std::collections::BTreeSet<String>,
444}
445
446impl<'ast> Visit<'ast> for GenericUses {
447 fn visit_ident(&mut self, ident: &'ast Ident) {
448 self.identifiers.insert(ident.to_string());
449 }
450
451 fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
452 self.lifetimes.insert(lifetime.ident.to_string());
453 }
454}
455
456fn product_generics(item: &ItemImpl, fields: &[NamedField]) -> Generics {
457 let mut uses = GenericUses::default();
458 for field in fields {
459 uses.visit_type(&field.ty);
460 }
461 let mut generics = item.generics.clone();
462 generics.params = generics
463 .params
464 .into_iter()
465 .filter(|parameter| match parameter {
466 GenericParam::Lifetime(parameter) => uses
467 .lifetimes
468 .contains(¶meter.lifetime.ident.to_string()),
469 GenericParam::Type(parameter) => {
470 uses.identifiers.contains(¶meter.ident.to_string())
471 }
472 GenericParam::Const(parameter) => {
473 uses.identifiers.contains(¶meter.ident.to_string())
474 }
475 })
476 .collect();
477 generics.where_clause = None;
478 generics
479}
480
481#[allow(
482 clippy::too_many_lines,
483 reason = "the generated nominal product keeps every structural trait implementation adjacent"
484)]
485fn generate_sends(
486 product: Option<SendsSpec>,
487 actor: &Ident,
488 item: &ItemImpl,
489 behavior: &TokenStream2,
490) -> (TokenStream2, TokenStream2) {
491 let product = match product {
492 None => return (quote!(#behavior::NoSends), quote!()),
493 Some(SendsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
494 Some(SendsSpec::Generated(product)) => product,
495 };
496 let name = format_ident!("{}Sends", actor);
497 let settlements_name = format_ident!("{}Settlements", actor);
498 let field_names: Vec<_> = product.fields.iter().map(|field| &field.name).collect();
499 let field_types: Vec<_> = product.fields.iter().map(|field| &field.ty).collect();
500 let lane_names: Vec<_> = field_names
501 .iter()
502 .map(|field| lane_name(&name, field))
503 .collect();
504 let actions_name = format_ident!("{}Actions", actor);
505 let action_methods: Vec<_> = field_names
506 .iter()
507 .map(|field| {
508 let field = field.to_string();
509 format_ident!("send_{}", field.trim_start_matches("r#"))
510 })
511 .collect();
512 let product_generics = product_generics(item, &product.fields);
513 let (_, type_generics, _) = product_generics.split_for_impl();
514 let generics = &product_generics;
515 let actor_type = &item.self_ty;
516 let mut action_trait_generics = product_generics.clone();
517 action_trait_generics
518 .make_where_clause()
519 .predicates
520 .push(parse_quote!(#actor_type: ::core::marker::Sized));
521
522 let action_trait_methods =
523 action_methods
524 .iter()
525 .zip(lane_names.iter())
526 .map(|(method, lane)| {
527 quote! {
528 #[must_use]
530 fn #method<__BombayInput>(self, input: __BombayInput) -> Self
531 where
532 Self: #behavior::AppendSend<__BombayInput, #lane>;
533 }
534 });
535
536 let action_impl_methods = action_methods
537 .iter()
538 .zip(lane_names.iter())
539 .map(|(method, lane)| {
540 quote! {
541 fn #method<__BombayInput>(self, input: __BombayInput) -> Self
542 where
543 Self: #behavior::AppendSend<__BombayInput, #lane>,
544 {
545 <Self as #behavior::AppendSend<__BombayInput, #lane>>::append_send(
546 self,
547 input,
548 )
549 }
550 }
551 });
552
553 let mut actions_impl_generics = product_generics.clone();
554 actions_impl_generics
555 .params
556 .push(parse_quote!(__BombayAddress));
557 actions_impl_generics.params.push(parse_quote!(__BombayPh));
558 actions_impl_generics
559 .params
560 .push(parse_quote!(__BombayBirth));
561 actions_impl_generics
562 .make_where_clause()
563 .predicates
564 .push(parse_quote!(__BombayAddress: #behavior::Address));
565 actions_impl_generics
566 .make_where_clause()
567 .predicates
568 .push(parse_quote!(__BombayBirth: #behavior::BirthMode));
569 actions_impl_generics
570 .make_where_clause()
571 .predicates
572 .push(parse_quote!(#actor_type: ::core::marker::Sized));
573 let (actions_impl_generics, _, actions_where_clause) = actions_impl_generics.split_for_impl();
574
575 let send_impls = lane_names
576 .iter()
577 .zip(field_names.iter())
578 .zip(field_types.iter())
579 .map(|((lane, field), field_ty)| {
580 let mut generics = product_generics.clone();
581 generics.params.push(parse_quote!(__BombayInput));
582 generics.make_where_clause().predicates.push(parse_quote!(
583 #field_ty: #behavior::SendInput<__BombayInput, #behavior::Own>
584 ));
585 let (impl_generics, _, where_clause) = generics.split_for_impl();
586 quote! {
587 impl #impl_generics #behavior::SendInput<__BombayInput, #lane>
588 for #name #type_generics #where_clause
589 {
590 fn emit(&mut self, input: __BombayInput) {
591 <#field_ty as #behavior::SendInput<
592 __BombayInput,
593 #behavior::Own,
594 >>::emit(&mut self.#field, input);
595 }
596 }
597 }
598 });
599
600 let mut effects_generics = product_generics.clone();
601 for field_ty in &field_types {
602 effects_generics
603 .make_where_clause()
604 .predicates
605 .push(parse_quote!(#field_ty: #behavior::SendEffects));
606 }
607 let (effects_impl_generics, _, effects_where_clause) = effects_generics.split_for_impl();
608
609 let mut lawful_generics = product_generics.clone();
610 lawful_generics.params.push(parse_quote!(__BombayEvent));
611 for field_ty in &field_types {
612 lawful_generics
613 .make_where_clause()
614 .predicates
615 .push(parse_quote!(#field_ty: #behavior::SendsFor<__BombayEvent>));
616 }
617 let (lawful_impl_generics, _, lawful_where_clause) = lawful_generics.split_for_impl();
618
619 let mut interpret_generics = product_generics.clone();
620 interpret_generics
621 .params
622 .push(parse_quote!(__BombayInterpreter));
623 interpret_generics
624 .params
625 .push(parse_quote!(__BombayRootEvent));
626 interpret_generics.params.push(parse_quote!(__BombayPath));
627 interpret_generics
628 .make_where_clause()
629 .predicates
630 .push(parse_quote!(__BombayInterpreter: ::core::marker::Send));
631 for field_ty in &field_types {
632 interpret_generics
633 .make_where_clause()
634 .predicates
635 .push(parse_quote!(
636 #field_ty: #behavior::InterpretSends<
637 __BombayInterpreter,
638 __BombayRootEvent,
639 __BombayPath,
640 >
641 ));
642 }
643 interpret_generics
644 .make_where_clause()
645 .predicates
646 .push(parse_quote!(#name #type_generics: ::core::marker::Send));
647 let (interpret_impl_generics, _, interpret_where_clause) = interpret_generics.split_for_impl();
648 let mut settlement_generics = product_generics.clone();
649 for field_ty in &field_types {
650 settlement_generics
651 .make_where_clause()
652 .predicates
653 .push(parse_quote!(#field_ty: #behavior::SendSettlements));
654 }
655 let (settlement_impl_generics, settlement_type_generics, settlement_where_clause) =
656 settlement_generics.split_for_impl();
657 let mut classification_generics = settlement_generics.clone();
658 for field_ty in &field_types {
659 classification_generics
660 .make_where_clause()
661 .predicates
662 .push(parse_quote!(
663 <#field_ty as #behavior::SendSettlements>::Settlements:
664 #behavior::ClassifySettlement
665 ));
666 }
667 let (classification_impl_generics, classification_type_generics, classification_where_clause) =
668 classification_generics.split_for_impl();
669 let mut custody_generics = settlement_generics.clone();
670 custody_generics
671 .params
672 .push(parse_quote!(__BombaySettlementHost));
673 custody_generics
674 .params
675 .push(parse_quote!(__BombaySettlementEvent));
676 custody_generics
677 .make_where_clause()
678 .predicates
679 .push(parse_quote!(__BombaySettlementHost: ::core::marker::Send));
680 for field_ty in &field_types {
681 custody_generics
682 .make_where_clause()
683 .predicates
684 .push(parse_quote!(
685 <#field_ty as #behavior::SendSettlements>::Settlements:
686 #behavior::SourceSettlementCustody<
687 __BombaySettlementHost,
688 __BombaySettlementEvent,
689 >
690 + ::core::marker::Send
691 ));
692 }
693 let (custody_impl_generics, _, custody_where_clause) = custody_generics.split_for_impl();
694 let mut combined_status = quote!(#behavior::SettlementStatus::Accepted);
695 for field in &field_names {
696 combined_status = quote! {
697 (#combined_status).combine(
698 #behavior::ClassifySettlement::settlement_status(&self.#field)
699 )
700 };
701 }
702
703 let mut interpretation = quote! {
704 #behavior::Interpretation::Complete(#settlements_name {
705 #(#field_names: #field_names,)*
706 })
707 };
708 for index in (0..field_names.len()).rev() {
709 let field = field_names[index];
710 let field_ty = field_types[index];
711 let prior_fields = &field_names[..index];
712 let later_fields = &field_names[index + 1..];
713 let later_types = &field_types[index + 1..];
714 let on_complete = interpretation;
715 interpretation = quote! {
716 match <#field_ty as #behavior::InterpretSends<
717 __BombayInterpreter,
718 __BombayRootEvent,
719 __BombayPath,
720 >>::interpret(self.#field, interpreter).await {
721 #behavior::Interpretation::Complete(#field) => #on_complete,
722 #behavior::Interpretation::Corrupt(#field) => {
723 #behavior::Interpretation::Corrupt(#settlements_name {
724 #(#prior_fields: #prior_fields,)*
725 #field: #field,
726 #(
727 #later_fields: <#later_types as #behavior::SendSettlements>::unattempted(
728 self.#later_fields,
729 ),
730 )*
731 })
732 }
733 }
734 };
735 }
736 let custody_fields = field_names.iter().enumerate().map(|(index, field)| {
737 let prior_fields = &field_names[..index];
738 let later_fields = &field_names[index + 1..];
739 quote! {
740 let #field = match #behavior::SourceSettlementCustody::offer_to_source(
741 self.#field,
742 host,
743 ).await {
744 #behavior::SourceCustody::Open(#field) => #field,
745 #behavior::SourceCustody::Closed(#field) => {
746 return #behavior::SourceCustody::Closed(#settlements_name {
747 #(#prior_fields: #prior_fields,)*
748 #field: #field,
749 #(#later_fields: self.#later_fields,)*
750 });
751 }
752 };
753 }
754 });
755
756 let items = quote! {
757 #(pub enum #lane_names {})*
758
759 pub struct #name #generics {
760 #(pub #field_names: #field_types,)*
761 }
762
763 #[doc(hidden)]
764 pub struct #settlements_name #settlement_impl_generics #settlement_where_clause {
765 #(
766 pub #field_names: <#field_types as #behavior::SendSettlements>::Settlements,
767 )*
768 }
769
770 impl #classification_impl_generics #behavior::ClassifySettlement
771 for #settlements_name #classification_type_generics #classification_where_clause
772 {
773 fn settlement_status(&self) -> #behavior::SettlementStatus {
774 #combined_status
775 }
776 }
777
778 impl #custody_impl_generics
779 #behavior::SourceSettlementCustody<
780 __BombaySettlementHost,
781 __BombaySettlementEvent,
782 >
783 for #settlements_name #settlement_type_generics #custody_where_clause
784 {
785 fn offer_to_source(
786 self,
787 host: &mut __BombaySettlementHost,
788 ) -> impl ::core::future::Future<
789 Output = #behavior::SourceCustody<Self>,
790 > + ::core::marker::Send {
791 async move {
792 #(#custody_fields)*
793 #behavior::SourceCustody::Open(#settlements_name {
794 #(#field_names: #field_names,)*
795 })
796 }
797 }
798 }
799
800 pub trait #actions_name #action_trait_generics: ::core::marker::Sized {
803 #(#action_trait_methods)*
804 }
805
806 impl #actions_impl_generics #actions_name #type_generics
807 for #behavior::Actions<
808 __BombayAddress,
809 __BombayPh,
810 #name #type_generics,
811 __BombayBirth,
812 >
813 #actions_where_clause
814 {
815 #(#action_impl_methods)*
816 }
817
818 impl #effects_impl_generics #behavior::SendEffects for #name #type_generics
819 #effects_where_clause
820 {
821 fn empty() -> Self {
822 Self {
823 #(#field_names: <#field_types as #behavior::SendEffects>::empty(),)*
824 }
825 }
826
827 fn append(&mut self, other: Self) {
828 #(
829 <#field_types as #behavior::SendEffects>::append(
830 &mut self.#field_names,
831 other.#field_names,
832 );
833 )*
834 }
835 }
836
837 impl #lawful_impl_generics #behavior::SendsFor<__BombayEvent>
838 for #name #type_generics #lawful_where_clause
839 {}
840
841 impl #settlement_impl_generics #behavior::SendSettlements
842 for #name #type_generics #settlement_where_clause
843 {
844 type Settlements = #settlements_name #settlement_type_generics;
845
846 fn unattempted(self) -> Self::Settlements {
847 #settlements_name {
848 #(
849 #field_names: <#field_types as #behavior::SendSettlements>::unattempted(
850 self.#field_names,
851 ),
852 )*
853 }
854 }
855 }
856
857 #(#send_impls)*
858
859 impl #interpret_impl_generics
860 #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>
861 for #name #type_generics #interpret_where_clause
862 {
863 fn interpret(
864 self,
865 interpreter: &mut __BombayInterpreter,
866 ) -> impl ::core::future::Future<
867 Output = #behavior::Interpretation<Self::Settlements>,
868 > + ::core::marker::Send {
869 async move {
870 #interpretation
871 }
872 }
873 }
874 };
875 (quote!(#name #type_generics), items)
876}
877
878fn generate_births(
879 product: Option<BirthsSpec>,
880 actor: &Ident,
881 _addr: &Type,
882 item: &ItemImpl,
883 behavior: &TokenStream2,
884) -> (TokenStream2, TokenStream2) {
885 let product = match product {
886 None => return (quote!(#behavior::NoBirths), quote!()),
887 Some(BirthsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
888 Some(BirthsSpec::Generated(product)) => product,
889 };
890 let name = format_ident!("{}Children", actor);
891 let roles_name = format_ident!("{}Child", actor);
892 let role_names = product
893 .fields
894 .iter()
895 .map(|field| lane_name(&name, &field.name))
896 .collect::<Vec<_>>();
897 let role_values = product
898 .fields
899 .iter()
900 .map(|field| format_ident!("{}", pascal_name(&field.name)))
901 .collect::<Vec<_>>();
902 let declared_child_types = product
903 .fields
904 .iter()
905 .map(|field| &field.ty)
906 .collect::<Vec<_>>();
907 let role_positions = (0..product.fields.len())
908 .map(|index| {
909 (0..(product.fields.len() - index - 1)).fold(
910 quote!(#behavior::ChildHead),
911 |position, _| quote!(#behavior::ChildTail<#position>),
912 )
913 })
914 .collect::<Vec<_>>();
915 let product_generics = product_generics(item, &product.fields);
916 let (_, type_generics, _) = product_generics.split_for_impl();
917 let generics = &product_generics;
918 let parent = &item.self_ty;
919 let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
920 let choice = match declared_child_types.as_slice() {
921 [child] => quote!(#child),
922 children => children.iter().fold(
923 quote!(#behavior::Never),
924 |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
925 ),
926 };
927 (
928 quote!(#behavior::Births<#name #type_generics>),
929 quote! {
930 pub type #name #generics = #choice;
931
932 #(
933 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
934 pub struct #role_names;
935
936 impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
937 #parent_where_clause
938 {
939 type Child = #declared_child_types;
940 type Position = #role_positions;
941 }
942
943 impl #parent_impl_generics #behavior::ChildOccurrence<#parent> for #role_names
944 #parent_where_clause
945 {
946 type Resolution = #behavior::DeclaredChildOccurrence;
947 }
948 )*
949
950 pub struct #roles_name;
951
952 #[allow(
953 non_upper_case_globals,
954 reason = "generated role values retain the application-authored field spelling"
955 )]
956 impl #roles_name {
957 #(pub const #role_values: #role_names = #role_names;)*
958 }
959
960 },
961 )
962}
963
964#[proc_macro_attribute]
970#[allow(
971 clippy::too_many_lines,
972 reason = "validation and the one coherent Behavior expansion remain in one entry point"
973)]
974pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
975 let args = parse_macro_input!(args as BehaviorArgs);
976 let item = parse_macro_input!(item as ItemImpl);
977
978 if item.trait_.is_some() {
979 return Error::new_spanned(
980 &item,
981 "#[behavior] applies to an inherent impl, not a trait impl",
982 )
983 .to_compile_error()
984 .into();
985 }
986
987 let init = item.items.iter().find_map(|item| match item {
988 ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
989 _ => None,
990 });
991 let receive = item.items.iter().find_map(|item| match item {
992 ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
993 _ => None,
994 });
995 let Some(receive) = receive else {
996 return Error::new_spanned(
997 &item.self_ty,
998 "#[behavior] requires a receive(&mut self, from, message) method",
999 )
1000 .to_compile_error()
1001 .into();
1002 };
1003 if let Err(error) = init
1004 .map_or(Ok(()), validate_receiver)
1005 .and_then(|()| validate_receiver(receive))
1006 {
1007 return error.to_compile_error().into();
1008 }
1009 if let Some(init) = init
1010 && init.sig.inputs.len() != 1
1011 {
1012 return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
1013 .to_compile_error()
1014 .into();
1015 }
1016 if receive.sig.inputs.len() != 3 {
1017 return Error::new_spanned(
1018 &receive.sig,
1019 "receive must accept exactly &mut self, from, and message",
1020 )
1021 .to_compile_error()
1022 .into();
1023 }
1024
1025 let BehaviorArgs {
1026 addr,
1027 message,
1028 sends,
1029 births,
1030 error,
1031 } = args;
1032 let self_ty = &item.self_ty;
1033 let Type::Path(self_path) = self_ty.as_ref() else {
1034 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1035 .to_compile_error()
1036 .into();
1037 };
1038 let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
1039 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1040 .to_compile_error()
1041 .into();
1042 };
1043 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1044 let behavior = match behavior_crate() {
1045 Ok(behavior) => behavior,
1046 Err(error) => return error.to_compile_error().into(),
1047 };
1048 let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
1049 let initialize = init.map_or_else(
1050 || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
1051 |_| quote!(<#self_ty>::init(self)),
1052 );
1053
1054 let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
1055 let (births_ty, births_items) = generate_births(births, self_name, &addr, &item, &behavior);
1056
1057 quote! {
1058 #sends_items
1059 #births_items
1060 #item
1061
1062 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1063 type Addr = #addr;
1064 type Msg = #message;
1065 }
1066
1067 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1068 type Protocol = Self;
1069 type Event = #behavior::User<#addr, #message>;
1070 type Sends = #sends_ty;
1071 type Ph = #behavior::Never;
1072 type Error = #error;
1073 type Birth = #births_ty;
1074
1075 fn init(
1076 &mut self,
1077 _: #behavior::InitializationTurn,
1078 ) -> #behavior::BehaviorActed<Self> {
1079 #initialize
1080 }
1081
1082 fn transition(
1083 &mut self,
1084 _: #behavior::ActiveTurn,
1085 event: Self::Event,
1086 ) -> #behavior::BehaviorActed<Self> {
1087 <#self_ty>::receive(self, event.from, event.message)
1088 }
1089 }
1090
1091 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1092 type Base = Self;
1093
1094 fn base(&self) -> &Self {
1095 self
1096 }
1097 }
1098 }
1099 .into()
1100}
1101
1102#[proc_macro_attribute]
1110pub fn pool_worker(args: TokenStream, item: TokenStream) -> TokenStream {
1111 let PoolWorkerArgs { addr, result } = parse_macro_input!(args as PoolWorkerArgs);
1112 let mut item = parse_macro_input!(item as ItemImpl);
1113
1114 if item.trait_.is_some() {
1115 return Error::new_spanned(
1116 &item,
1117 "#[pool_worker] applies to an inherent impl, not a trait impl",
1118 )
1119 .to_compile_error()
1120 .into();
1121 }
1122
1123 let self_ty = item.self_ty.clone();
1124 if !matches!(self_ty.as_ref(), Type::Path(path) if path.qself.is_none()) {
1125 return Error::new_spanned(&self_ty, "#[pool_worker] requires a nominal worker type")
1126 .to_compile_error()
1127 .into();
1128 }
1129
1130 let actors = match actors_crate() {
1131 Ok(actors) => actors,
1132 Err(error) => return error.to_compile_error().into(),
1133 };
1134 let behavior = match behavior_crate() {
1135 Ok(behavior) => behavior,
1136 Err(error) => return error.to_compile_error().into(),
1137 };
1138 let assignment = {
1139 let mut transitions = item.items.iter_mut().filter_map(|item| match item {
1140 ImplItem::Fn(method) if method.sig.ident == "transition" => Some(method),
1141 _ => None,
1142 });
1143 let Some(transition) = transitions.next() else {
1144 return Error::new_spanned(
1145 &self_ty,
1146 "#[pool_worker] requires transition(&mut self, Assignment<Job>)",
1147 )
1148 .to_compile_error()
1149 .into();
1150 };
1151 if let Some(duplicate) = transitions.next() {
1152 return Error::new_spanned(
1153 &duplicate.sig,
1154 "#[pool_worker] accepts exactly one transition method",
1155 )
1156 .to_compile_error()
1157 .into();
1158 }
1159 let assignment = match pool_worker_assignment(transition) {
1160 Ok(assignment) => assignment,
1161 Err(error) => return error.to_compile_error().into(),
1162 };
1163 transition.sig.output = parse_quote!(-> #behavior::BehaviorActed<Self>);
1164 assignment
1165 };
1166 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1167
1168 quote! {
1169 #item
1170
1171 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1172 type Addr = #addr;
1173 type Msg = #assignment;
1174 }
1175
1176 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1177 type Protocol = Self;
1178 type Event = #behavior::User<#addr, #assignment>;
1179 type Sends = #behavior::InterpreterRequests<
1180 #behavior::ReportToParent<#actors::atomic::Completion<#result>>
1181 >;
1182 type Ph = #behavior::Never;
1183 type Error = #behavior::Never;
1184 type Birth = #behavior::NoBirths;
1185
1186 fn transition(
1187 &mut self,
1188 _: #behavior::ActiveTurn,
1189 event: Self::Event,
1190 ) -> #behavior::BehaviorActed<Self> {
1191 <#self_ty>::transition(self, event.message)
1192 }
1193 }
1194
1195 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1196 type Base = Self;
1197
1198 fn base(&self) -> &Self {
1199 self
1200 }
1201 }
1202 }
1203 .into()
1204}
1205
1206#[cfg(test)]
1207mod tests {
1208 use super::*;
1209
1210 fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
1211 if at == values.len() {
1212 output.push(values.join(", "));
1213 return;
1214 }
1215 for index in at..values.len() {
1216 values.swap(at, index);
1217 permutations(values, at + 1, output);
1218 values.swap(at, index);
1219 }
1220 }
1221
1222 #[test]
1223 fn every_argument_order_parses() {
1224 let mut declarations = [
1225 "addr = u64",
1226 "message = String",
1227 "sends = { first: Vec<u8>, second: Vec<u16> }",
1228 "births = { first: u8, second: u16 }",
1229 "error = String",
1230 ];
1231 let mut cases = Vec::new();
1232 permutations(&mut declarations, 0, &mut cases);
1233 assert_eq!(cases.len(), 120);
1234 for case in cases {
1235 syn::parse_str::<BehaviorArgs>(&case)
1236 .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
1237 }
1238 }
1239
1240 #[test]
1241 fn capability_omission_and_trailing_comma_parse() {
1242 syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
1243 .expect("only required protocol declarations are sufficient");
1244 }
1245
1246 #[test]
1247 fn invalid_declaration_states_are_rejected() {
1248 for case in [
1249 "message = ()",
1250 "addr = u8",
1251 "addr = u8, message = (), addr = u16",
1252 "addr = u8, message = (), unknown = u8",
1253 "addr = u8, message = (), sends = {}",
1254 "addr = u8, message = (), births = {}",
1255 "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
1256 "addr = u8, message = (), births = { same: u8, same: u16 }",
1257 ] {
1258 assert!(
1259 syn::parse_str::<BehaviorArgs>(case).is_err(),
1260 "invalid declaration parsed: `{case}`"
1261 );
1262 }
1263 }
1264
1265 #[test]
1266 fn pool_worker_arguments_require_one_address_and_result() {
1267 for valid in [
1268 "addr = u64, result = String",
1269 "result = String, addr = u64,",
1270 ] {
1271 syn::parse_str::<PoolWorkerArgs>(valid)
1272 .unwrap_or_else(|error| panic!("failed to parse `{valid}`: {error}"));
1273 }
1274 for invalid in [
1275 "addr = u64",
1276 "result = String",
1277 "addr = u64, result = String, addr = u32",
1278 "addr = u64, result = String, result = u8",
1279 "addr = u64, result = String, message = u8",
1280 ] {
1281 assert!(
1282 syn::parse_str::<PoolWorkerArgs>(invalid).is_err(),
1283 "invalid pool worker arguments parsed: `{invalid}`"
1284 );
1285 }
1286 }
1287
1288 #[test]
1289 fn pool_worker_transition_requires_the_domain_source_shape() {
1290 let valid: ItemImpl = syn::parse_str(
1291 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) \
1292 -> WorkerActed<Self> { result() } }",
1293 )
1294 .expect("valid inherent worker declaration parses");
1295 let method = valid
1296 .items
1297 .iter()
1298 .find_map(|item| match item {
1299 ImplItem::Fn(method) => Some(method),
1300 _ => None,
1301 })
1302 .expect("fixture declares one method");
1303 let assignment = pool_worker_assignment(method)
1304 .unwrap_or_else(|error| panic!("valid worker transition rejected: {error}"));
1305 assert_eq!(quote!(#assignment).to_string(), "Assignment < Job >");
1306
1307 for invalid in [
1308 "impl Worker { fn transition(self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1309 "impl Worker { async fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1310 "impl Worker { fn transition(&mut self, job: Job) -> WorkerActed<Self> { result() } }",
1311 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Job> { result() } }",
1312 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> Result<(), ()> { result() } }",
1313 ] {
1314 let declaration: ItemImpl = syn::parse_str(invalid)
1315 .unwrap_or_else(|error| panic!("invalid fixture did not parse: {error}"));
1316 let method = declaration
1317 .items
1318 .iter()
1319 .find_map(|item| match item {
1320 ImplItem::Fn(method) => Some(method),
1321 _ => None,
1322 })
1323 .expect("fixture declares one method");
1324 assert!(
1325 pool_worker_assignment(method).is_err(),
1326 "invalid pool worker transition was accepted: `{invalid}`"
1327 );
1328 }
1329 }
1330}