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_next_to_source(
741 self.#field,
742 host,
743 ).await {
744 #behavior::SourceCustody::Exhausted(#field) => #field,
745 #behavior::SourceCustody::Admitted(#field) => {
746 return #behavior::SourceCustody::Admitted(#settlements_name {
747 #(#prior_fields: #prior_fields,)*
748 #field: #field,
749 #(#later_fields: self.#later_fields,)*
750 });
751 }
752 #behavior::SourceCustody::Closed(#field) => {
753 return #behavior::SourceCustody::Closed(#settlements_name {
754 #(#prior_fields: #prior_fields,)*
755 #field: #field,
756 #(#later_fields: self.#later_fields,)*
757 });
758 }
759 };
760 }
761 });
762
763 let items = quote! {
764 #(pub enum #lane_names {})*
765
766 pub struct #name #generics {
767 #(pub #field_names: #field_types,)*
768 }
769
770 #[doc(hidden)]
771 pub struct #settlements_name #settlement_impl_generics #settlement_where_clause {
772 #(
773 pub #field_names: <#field_types as #behavior::SendSettlements>::Settlements,
774 )*
775 }
776
777 impl #classification_impl_generics #behavior::ClassifySettlement
778 for #settlements_name #classification_type_generics #classification_where_clause
779 {
780 fn settlement_status(&self) -> #behavior::SettlementStatus {
781 #combined_status
782 }
783 }
784
785 impl #custody_impl_generics
786 #behavior::SourceSettlementCustody<
787 __BombaySettlementHost,
788 __BombaySettlementEvent,
789 >
790 for #settlements_name #settlement_type_generics #custody_where_clause
791 {
792 fn offer_next_to_source(
793 self,
794 host: &mut __BombaySettlementHost,
795 ) -> impl ::core::future::Future<
796 Output = #behavior::SourceCustody<Self>,
797 > + ::core::marker::Send {
798 async move {
799 #(#custody_fields)*
800 #behavior::SourceCustody::Exhausted(#settlements_name {
801 #(#field_names: #field_names,)*
802 })
803 }
804 }
805 }
806
807 pub trait #actions_name #action_trait_generics: ::core::marker::Sized {
810 #(#action_trait_methods)*
811 }
812
813 impl #actions_impl_generics #actions_name #type_generics
814 for #behavior::Actions<
815 __BombayAddress,
816 __BombayPh,
817 #name #type_generics,
818 __BombayBirth,
819 >
820 #actions_where_clause
821 {
822 #(#action_impl_methods)*
823 }
824
825 impl #effects_impl_generics #behavior::SendEffects for #name #type_generics
826 #effects_where_clause
827 {
828 fn empty() -> Self {
829 Self {
830 #(#field_names: <#field_types as #behavior::SendEffects>::empty(),)*
831 }
832 }
833
834 fn append(&mut self, other: Self) {
835 #(
836 <#field_types as #behavior::SendEffects>::append(
837 &mut self.#field_names,
838 other.#field_names,
839 );
840 )*
841 }
842 }
843
844 impl #lawful_impl_generics #behavior::SendsFor<__BombayEvent>
845 for #name #type_generics #lawful_where_clause
846 {}
847
848 impl #settlement_impl_generics #behavior::SendSettlements
849 for #name #type_generics #settlement_where_clause
850 {
851 type Settlements = #settlements_name #settlement_type_generics;
852
853 fn unattempted(self) -> Self::Settlements {
854 #settlements_name {
855 #(
856 #field_names: <#field_types as #behavior::SendSettlements>::unattempted(
857 self.#field_names,
858 ),
859 )*
860 }
861 }
862 }
863
864 #(#send_impls)*
865
866 impl #interpret_impl_generics
867 #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>
868 for #name #type_generics #interpret_where_clause
869 {
870 fn interpret(
871 self,
872 interpreter: &mut __BombayInterpreter,
873 ) -> impl ::core::future::Future<
874 Output = #behavior::Interpretation<Self::Settlements>,
875 > + ::core::marker::Send {
876 async move {
877 #interpretation
878 }
879 }
880 }
881 };
882 (quote!(#name #type_generics), items)
883}
884
885fn generate_births(
886 product: Option<BirthsSpec>,
887 actor: &Ident,
888 _addr: &Type,
889 item: &ItemImpl,
890 behavior: &TokenStream2,
891) -> (TokenStream2, TokenStream2) {
892 let product = match product {
893 None => return (quote!(#behavior::NoBirths), quote!()),
894 Some(BirthsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
895 Some(BirthsSpec::Generated(product)) => product,
896 };
897 let name = format_ident!("{}Children", actor);
898 let roles_name = format_ident!("{}Child", actor);
899 let role_names = product
900 .fields
901 .iter()
902 .map(|field| lane_name(&name, &field.name))
903 .collect::<Vec<_>>();
904 let role_values = product
905 .fields
906 .iter()
907 .map(|field| format_ident!("{}", pascal_name(&field.name)))
908 .collect::<Vec<_>>();
909 let declared_child_types = product
910 .fields
911 .iter()
912 .map(|field| &field.ty)
913 .collect::<Vec<_>>();
914 let role_positions = (0..product.fields.len())
915 .map(|index| {
916 (0..(product.fields.len() - index - 1)).fold(
917 quote!(#behavior::ChildHead),
918 |position, _| quote!(#behavior::ChildTail<#position>),
919 )
920 })
921 .collect::<Vec<_>>();
922 let product_generics = product_generics(item, &product.fields);
923 let (_, type_generics, _) = product_generics.split_for_impl();
924 let generics = &product_generics;
925 let parent = &item.self_ty;
926 let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
927 let choice = match declared_child_types.as_slice() {
928 [child] => quote!(#child),
929 children => children.iter().fold(
930 quote!(#behavior::Never),
931 |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
932 ),
933 };
934 (
935 quote!(#behavior::Births<#name #type_generics>),
936 quote! {
937 pub type #name #generics = #choice;
938
939 #(
940 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
941 pub struct #role_names;
942
943 impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
944 #parent_where_clause
945 {
946 type Child = #declared_child_types;
947 type Position = #role_positions;
948 }
949
950 impl #parent_impl_generics #behavior::ChildOccurrence<#parent> for #role_names
951 #parent_where_clause
952 {
953 type Resolution = #behavior::DeclaredChildOccurrence;
954 }
955 )*
956
957 pub struct #roles_name;
958
959 #[allow(
960 non_upper_case_globals,
961 reason = "generated role values retain the application-authored field spelling"
962 )]
963 impl #roles_name {
964 #(pub const #role_values: #role_names = #role_names;)*
965 }
966
967 },
968 )
969}
970
971#[proc_macro_attribute]
977#[allow(
978 clippy::too_many_lines,
979 reason = "validation and the one coherent Behavior expansion remain in one entry point"
980)]
981pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
982 let args = parse_macro_input!(args as BehaviorArgs);
983 let item = parse_macro_input!(item as ItemImpl);
984
985 if item.trait_.is_some() {
986 return Error::new_spanned(
987 &item,
988 "#[behavior] applies to an inherent impl, not a trait impl",
989 )
990 .to_compile_error()
991 .into();
992 }
993
994 let init = item.items.iter().find_map(|item| match item {
995 ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
996 _ => None,
997 });
998 let receive = item.items.iter().find_map(|item| match item {
999 ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
1000 _ => None,
1001 });
1002 let Some(receive) = receive else {
1003 return Error::new_spanned(
1004 &item.self_ty,
1005 "#[behavior] requires a receive(&mut self, from, message) method",
1006 )
1007 .to_compile_error()
1008 .into();
1009 };
1010 if let Err(error) = init
1011 .map_or(Ok(()), validate_receiver)
1012 .and_then(|()| validate_receiver(receive))
1013 {
1014 return error.to_compile_error().into();
1015 }
1016 if let Some(init) = init
1017 && init.sig.inputs.len() != 1
1018 {
1019 return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
1020 .to_compile_error()
1021 .into();
1022 }
1023 if receive.sig.inputs.len() != 3 {
1024 return Error::new_spanned(
1025 &receive.sig,
1026 "receive must accept exactly &mut self, from, and message",
1027 )
1028 .to_compile_error()
1029 .into();
1030 }
1031
1032 let BehaviorArgs {
1033 addr,
1034 message,
1035 sends,
1036 births,
1037 error,
1038 } = args;
1039 let self_ty = &item.self_ty;
1040 let Type::Path(self_path) = self_ty.as_ref() else {
1041 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1042 .to_compile_error()
1043 .into();
1044 };
1045 let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
1046 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1047 .to_compile_error()
1048 .into();
1049 };
1050 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1051 let behavior = match behavior_crate() {
1052 Ok(behavior) => behavior,
1053 Err(error) => return error.to_compile_error().into(),
1054 };
1055 let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
1056 let initialize = init.map_or_else(
1057 || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
1058 |_| quote!(<#self_ty>::init(self)),
1059 );
1060
1061 let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
1062 let (births_ty, births_items) = generate_births(births, self_name, &addr, &item, &behavior);
1063
1064 quote! {
1065 #sends_items
1066 #births_items
1067 #item
1068
1069 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1070 type Addr = #addr;
1071 type Msg = #message;
1072 }
1073
1074 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1075 type Protocol = Self;
1076 type Event = #behavior::User<#addr, #message>;
1077 type Sends = #sends_ty;
1078 type Ph = #behavior::Never;
1079 type Error = #error;
1080 type Birth = #births_ty;
1081
1082 fn init(
1083 &mut self,
1084 _: #behavior::InitializationTurn,
1085 ) -> #behavior::BehaviorActed<Self> {
1086 #initialize
1087 }
1088
1089 fn transition(
1090 &mut self,
1091 _: #behavior::ActiveTurn,
1092 event: Self::Event,
1093 ) -> #behavior::BehaviorActed<Self> {
1094 <#self_ty>::receive(self, event.from, event.message)
1095 }
1096 }
1097
1098 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1099 type Base = Self;
1100
1101 fn base(&self) -> &Self {
1102 self
1103 }
1104 }
1105 }
1106 .into()
1107}
1108
1109#[proc_macro_attribute]
1117pub fn pool_worker(args: TokenStream, item: TokenStream) -> TokenStream {
1118 let PoolWorkerArgs { addr, result } = parse_macro_input!(args as PoolWorkerArgs);
1119 let mut item = parse_macro_input!(item as ItemImpl);
1120
1121 if item.trait_.is_some() {
1122 return Error::new_spanned(
1123 &item,
1124 "#[pool_worker] applies to an inherent impl, not a trait impl",
1125 )
1126 .to_compile_error()
1127 .into();
1128 }
1129
1130 let self_ty = item.self_ty.clone();
1131 if !matches!(self_ty.as_ref(), Type::Path(path) if path.qself.is_none()) {
1132 return Error::new_spanned(&self_ty, "#[pool_worker] requires a nominal worker type")
1133 .to_compile_error()
1134 .into();
1135 }
1136
1137 let actors = match actors_crate() {
1138 Ok(actors) => actors,
1139 Err(error) => return error.to_compile_error().into(),
1140 };
1141 let behavior = match behavior_crate() {
1142 Ok(behavior) => behavior,
1143 Err(error) => return error.to_compile_error().into(),
1144 };
1145 let assignment = {
1146 let mut transitions = item.items.iter_mut().filter_map(|item| match item {
1147 ImplItem::Fn(method) if method.sig.ident == "transition" => Some(method),
1148 _ => None,
1149 });
1150 let Some(transition) = transitions.next() else {
1151 return Error::new_spanned(
1152 &self_ty,
1153 "#[pool_worker] requires transition(&mut self, Assignment<Job>)",
1154 )
1155 .to_compile_error()
1156 .into();
1157 };
1158 if let Some(duplicate) = transitions.next() {
1159 return Error::new_spanned(
1160 &duplicate.sig,
1161 "#[pool_worker] accepts exactly one transition method",
1162 )
1163 .to_compile_error()
1164 .into();
1165 }
1166 let assignment = match pool_worker_assignment(transition) {
1167 Ok(assignment) => assignment,
1168 Err(error) => return error.to_compile_error().into(),
1169 };
1170 transition.sig.output = parse_quote!(-> #behavior::BehaviorActed<Self>);
1171 assignment
1172 };
1173 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1174
1175 quote! {
1176 #item
1177
1178 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1179 type Addr = #addr;
1180 type Msg = #assignment;
1181 }
1182
1183 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1184 type Protocol = Self;
1185 type Event = #behavior::User<#addr, #assignment>;
1186 type Sends = #behavior::InterpreterRequests<
1187 #behavior::ReportToParent<#actors::atomic::Completion<#result>>
1188 >;
1189 type Ph = #behavior::Never;
1190 type Error = #behavior::Never;
1191 type Birth = #behavior::NoBirths;
1192
1193 fn transition(
1194 &mut self,
1195 _: #behavior::ActiveTurn,
1196 event: Self::Event,
1197 ) -> #behavior::BehaviorActed<Self> {
1198 <#self_ty>::transition(self, event.message)
1199 }
1200 }
1201
1202 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1203 type Base = Self;
1204
1205 fn base(&self) -> &Self {
1206 self
1207 }
1208 }
1209 }
1210 .into()
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215 use super::*;
1216
1217 fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
1218 if at == values.len() {
1219 output.push(values.join(", "));
1220 return;
1221 }
1222 for index in at..values.len() {
1223 values.swap(at, index);
1224 permutations(values, at + 1, output);
1225 values.swap(at, index);
1226 }
1227 }
1228
1229 #[test]
1230 fn every_argument_order_parses() {
1231 let mut declarations = [
1232 "addr = u64",
1233 "message = String",
1234 "sends = { first: Vec<u8>, second: Vec<u16> }",
1235 "births = { first: u8, second: u16 }",
1236 "error = String",
1237 ];
1238 let mut cases = Vec::new();
1239 permutations(&mut declarations, 0, &mut cases);
1240 assert_eq!(cases.len(), 120);
1241 for case in cases {
1242 syn::parse_str::<BehaviorArgs>(&case)
1243 .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
1244 }
1245 }
1246
1247 #[test]
1248 fn capability_omission_and_trailing_comma_parse() {
1249 syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
1250 .expect("only required protocol declarations are sufficient");
1251 }
1252
1253 #[test]
1254 fn invalid_declaration_states_are_rejected() {
1255 for case in [
1256 "message = ()",
1257 "addr = u8",
1258 "addr = u8, message = (), addr = u16",
1259 "addr = u8, message = (), unknown = u8",
1260 "addr = u8, message = (), sends = {}",
1261 "addr = u8, message = (), births = {}",
1262 "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
1263 "addr = u8, message = (), births = { same: u8, same: u16 }",
1264 ] {
1265 assert!(
1266 syn::parse_str::<BehaviorArgs>(case).is_err(),
1267 "invalid declaration parsed: `{case}`"
1268 );
1269 }
1270 }
1271
1272 #[test]
1273 fn pool_worker_arguments_require_one_address_and_result() {
1274 for valid in [
1275 "addr = u64, result = String",
1276 "result = String, addr = u64,",
1277 ] {
1278 syn::parse_str::<PoolWorkerArgs>(valid)
1279 .unwrap_or_else(|error| panic!("failed to parse `{valid}`: {error}"));
1280 }
1281 for invalid in [
1282 "addr = u64",
1283 "result = String",
1284 "addr = u64, result = String, addr = u32",
1285 "addr = u64, result = String, result = u8",
1286 "addr = u64, result = String, message = u8",
1287 ] {
1288 assert!(
1289 syn::parse_str::<PoolWorkerArgs>(invalid).is_err(),
1290 "invalid pool worker arguments parsed: `{invalid}`"
1291 );
1292 }
1293 }
1294
1295 #[test]
1296 fn pool_worker_transition_requires_the_domain_source_shape() {
1297 let valid: ItemImpl = syn::parse_str(
1298 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) \
1299 -> WorkerActed<Self> { result() } }",
1300 )
1301 .expect("valid inherent worker declaration parses");
1302 let method = valid
1303 .items
1304 .iter()
1305 .find_map(|item| match item {
1306 ImplItem::Fn(method) => Some(method),
1307 _ => None,
1308 })
1309 .expect("fixture declares one method");
1310 let assignment = pool_worker_assignment(method)
1311 .unwrap_or_else(|error| panic!("valid worker transition rejected: {error}"));
1312 assert_eq!(quote!(#assignment).to_string(), "Assignment < Job >");
1313
1314 for invalid in [
1315 "impl Worker { fn transition(self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1316 "impl Worker { async fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1317 "impl Worker { fn transition(&mut self, job: Job) -> WorkerActed<Self> { result() } }",
1318 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Job> { result() } }",
1319 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> Result<(), ()> { result() } }",
1320 ] {
1321 let declaration: ItemImpl = syn::parse_str(invalid)
1322 .unwrap_or_else(|error| panic!("invalid fixture did not parse: {error}"));
1323 let method = declaration
1324 .items
1325 .iter()
1326 .find_map(|item| match item {
1327 ImplItem::Fn(method) => Some(method),
1328 _ => None,
1329 })
1330 .expect("fixture declares one method");
1331 assert!(
1332 pool_worker_assignment(method).is_err(),
1333 "invalid pool worker transition was accepted: `{invalid}`"
1334 );
1335 }
1336 }
1337}