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 Data, DeriveInput, Error, Fields, FnArg, GenericArgument, GenericParam, Generics, Ident,
14 ImplItem, ItemImpl, PathArguments, Result, ReturnType, Token, Type, Visibility, braced,
15 parse_macro_input, parse_quote,
16};
17
18fn crate_path(found: FoundCrate) -> TokenStream2 {
19 match found {
20 FoundCrate::Itself => quote!(crate),
21 FoundCrate::Name(name) => {
22 let name = match name.as_str() {
25 "bombay_behavior" => "behavior",
26 "bombay_behavior_actors" => "behavior_actors",
27 "bombay_rs" => "bombay",
28 _ => &name,
29 };
30 let name = syn::Ident::new(&name, Span::call_site());
31 quote!(::#name)
32 }
33 }
34}
35
36fn behavior_crate() -> Result<TokenStream2> {
37 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
38 return Ok(quote!(::behavior));
42 }
43 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
44 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
49 Ok(quote!(crate::behavior))
50 } else {
51 Ok(quote!(::bombay::behavior))
52 };
53 }
54 if let Ok(found) = crate_name("bombay-behavior") {
55 return Ok(crate_path(found));
56 }
57 if let Ok(found) = crate_name("bombay-rs") {
58 let bombay = crate_path(found);
59 return Ok(quote!(#bombay::behavior));
60 }
61 Err(Error::new(
62 Span::call_site(),
63 "could not resolve `bombay-behavior` directly or through `bombay-rs`",
64 ))
65}
66
67fn actors_crate() -> Result<TokenStream2> {
68 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior-actors") {
69 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("behavior_actors") {
70 Ok(quote!(crate))
71 } else {
72 Ok(quote!(::behavior_actors))
73 };
74 }
75 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
76 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
77 Ok(quote!(crate))
78 } else {
79 Ok(quote!(::bombay))
80 };
81 }
82 if let Ok(found) = crate_name("bombay-rs") {
90 return Ok(crate_path(found));
91 }
92 if let Ok(found) = crate_name("bombay-behavior-actors") {
93 return Ok(crate_path(found));
94 }
95 Err(Error::new(
96 Span::call_site(),
97 "could not resolve `bombay-behavior-actors` through `bombay-rs` or directly",
98 ))
99}
100
101#[cfg(test)]
102mod crate_resolution_tests {
103 use super::*;
104
105 #[test]
106 fn package_library_names_and_explicit_renames_resolve_distinctly() {
107 assert_eq!(
108 crate_path(FoundCrate::Name("bombay_rs".to_owned())).to_string(),
109 ":: bombay"
110 );
111 assert_eq!(
112 crate_path(FoundCrate::Name("bombay_behavior".to_owned())).to_string(),
113 ":: behavior"
114 );
115 assert_eq!(
116 crate_path(FoundCrate::Name("bombay_behavior_actors".to_owned())).to_string(),
117 ":: behavior_actors"
118 );
119 assert_eq!(
120 crate_path(FoundCrate::Name("runtime".to_owned())).to_string(),
121 ":: runtime"
122 );
123 }
124}
125
126struct NamedField {
127 name: Ident,
128 ty: Type,
129}
130
131struct NamedProduct {
132 fields: Vec<NamedField>,
133}
134
135#[allow(
136 clippy::large_enum_variant,
137 reason = "boxing syn::Type would add lint-driven indirection to compile-time parser state"
138)]
139enum SendsSpec {
140 Existing(Type),
141 Generated {
142 product: NamedProduct,
143 visibility: Visibility,
144 },
145}
146
147#[allow(
148 clippy::large_enum_variant,
149 reason = "boxing syn::Type would add lint-driven indirection to compile-time parser state"
150)]
151enum BirthsSpec {
152 Existing(Type),
153 Generated(NamedProduct),
154}
155
156impl BirthsSpec {
157 fn is_no_births(&self) -> bool {
158 let Self::Existing(Type::Path(path)) = self else {
159 return false;
160 };
161 path.qself.is_none()
162 && path.path.segments.last().is_some_and(|segment| {
163 segment.ident == "NoBirths" && matches!(segment.arguments, syn::PathArguments::None)
164 })
165 }
166}
167
168#[derive(Clone, Copy)]
169enum CreationSettlementDisposition {
170 ReturnToCreator,
171 RetainForRetirement,
172}
173
174fn parse_product(input: ParseStream) -> Result<NamedProduct> {
175 let content;
176 braced!(content in input);
177 let mut fields = Vec::new();
178 let mut names = std::collections::BTreeSet::new();
179 while !content.is_empty() {
180 let name: Ident = content.parse()?;
181 if !names.insert(name.to_string()) {
182 return Err(Error::new_spanned(name, "duplicate product lane"));
183 }
184 content.parse::<Token![:]>()?;
185 fields.push(NamedField {
186 name,
187 ty: content.parse()?,
188 });
189 if content.peek(Token![,]) {
190 content.parse::<Token![,]>()?;
191 } else if !content.is_empty() {
192 return Err(content.error("expected `,` between product lanes"));
193 }
194 }
195 if fields.is_empty() {
196 return Err(input.error("empty products must be omitted"));
197 }
198 Ok(NamedProduct { fields })
199}
200
201struct BehaviorArgs {
202 addr: Type,
203 message: Type,
204 sends: Option<SendsSpec>,
205 births: Option<BirthsSpec>,
206 creation_settlements: Option<CreationSettlementDisposition>,
207 error: Option<Type>,
208}
209
210struct PoolWorkerArgs {
211 addr: Type,
212 result: Type,
213}
214
215impl Parse for PoolWorkerArgs {
216 fn parse(input: ParseStream) -> Result<Self> {
217 let mut addr = None;
218 let mut result = None;
219 while !input.is_empty() {
220 let key: Ident = input.parse()?;
221 input.parse::<Token![=]>()?;
222 match key.to_string().as_str() {
223 "addr" if addr.is_none() => addr = Some(input.parse()?),
224 "result" if result.is_none() => result = Some(input.parse()?),
225 "addr" | "result" => {
226 return Err(Error::new_spanned(key, "duplicate pool worker argument"));
227 }
228 _ => return Err(Error::new_spanned(key, "unknown pool worker argument")),
229 }
230 if input.peek(Token![,]) {
231 input.parse::<Token![,]>()?;
232 } else if !input.is_empty() {
233 return Err(input.error("expected `,` between pool worker arguments"));
234 }
235 }
236 Ok(Self {
237 addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
238 result: result.ok_or_else(|| input.error("missing `result`"))?,
239 })
240 }
241}
242
243impl Parse for BehaviorArgs {
244 fn parse(input: ParseStream) -> Result<Self> {
245 let mut addr = None;
246 let mut message = None;
247 let mut sends = None;
248 let mut births = None;
249 let mut creation_settlements = None;
250 let mut error = None;
251 while !input.is_empty() {
252 let key: Ident = input.parse()?;
253 input.parse::<Token![=]>()?;
254 match key.to_string().as_str() {
255 "addr" if addr.is_none() => addr = Some(input.parse()?),
256 "message" if message.is_none() => message = Some(input.parse()?),
257 "sends" if sends.is_none() => {
258 let visibility: Visibility = input.parse()?;
259 sends = Some(if input.peek(syn::token::Brace) {
260 SendsSpec::Generated {
261 product: parse_product(input)?,
262 visibility,
263 }
264 } else if matches!(visibility, Visibility::Inherited) {
265 SendsSpec::Existing(input.parse()?)
266 } else {
267 return Err(input.error("send-product visibility requires named lanes"));
268 });
269 }
270 "births" if births.is_none() => {
271 births = Some(if input.peek(syn::token::Brace) {
272 BirthsSpec::Generated(parse_product(input)?)
273 } else {
274 BirthsSpec::Existing(input.parse()?)
275 });
276 }
277 "creation_settlements" if creation_settlements.is_none() => {
278 let disposition: Ident = input.parse()?;
279 creation_settlements = Some(match disposition.to_string().as_str() {
280 "return_to_creator" => CreationSettlementDisposition::ReturnToCreator,
281 "retain_for_retirement" => {
282 CreationSettlementDisposition::RetainForRetirement
283 }
284 _ => {
285 return Err(Error::new_spanned(
286 disposition,
287 "creation settlements must `return_to_creator` or `retain_for_retirement`",
288 ));
289 }
290 });
291 }
292 "error" if error.is_none() => error = Some(input.parse()?),
293 "addr" | "message" | "sends" | "births" | "creation_settlements" | "error" => {
294 return Err(Error::new_spanned(key, "duplicate behavior argument"));
295 }
296 _ => return Err(Error::new_spanned(key, "unknown behavior argument")),
297 }
298 if input.peek(Token![,]) {
299 input.parse::<Token![,]>()?;
300 } else if !input.is_empty() {
301 return Err(input.error("expected `,` between behavior arguments"));
302 }
303 }
304 match (&births, creation_settlements) {
305 (Some(births), None) if !births.is_no_births() => {
306 return Err(input.error(
307 "birth-owning behaviors must declare `creation_settlements = return_to_creator` or `creation_settlements = retain_for_retirement`",
308 ));
309 }
310 (None, Some(_)) => {
311 return Err(input.error("`creation_settlements` requires a `births` declaration"));
312 }
313 (Some(_), Some(_)) | (Some(_), None) | (None, None) => {}
314 }
315 Ok(Self {
316 addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
317 message: message.ok_or_else(|| input.error("missing `message`"))?,
318 sends,
319 births,
320 creation_settlements,
321 error,
322 })
323 }
324}
325
326fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
327 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
328 return Err(Error::new_spanned(
329 &method.sig,
330 "behavior methods must begin with &mut self",
331 ));
332 };
333 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
334 return Err(Error::new_spanned(
335 receiver,
336 "behavior methods must begin with &mut self",
337 ));
338 }
339 if method.sig.constness.is_some()
340 || method.sig.asyncness.is_some()
341 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
342 || !method.sig.generics.params.is_empty()
343 {
344 return Err(Error::new_spanned(
345 &method.sig,
346 "behavior methods must be synchronous, safe, and non-generic",
347 ));
348 }
349 if matches!(method.sig.output, ReturnType::Default) {
350 return Err(Error::new_spanned(
351 &method.sig,
352 "behavior methods must declare their complete Actions result type",
353 ));
354 }
355 Ok(())
356}
357
358fn validate_pool_worker_output(output: &ReturnType) -> Result<()> {
359 let ReturnType::Type(_, output) = output else {
360 return Err(Error::new_spanned(
361 output,
362 "pool worker transition must return WorkerActed<Self>",
363 ));
364 };
365 let Type::Path(output) = output.as_ref() else {
366 return Err(Error::new_spanned(
367 output,
368 "pool worker transition must return WorkerActed<Self>",
369 ));
370 };
371 if output.qself.is_some() || output.path.segments.len() != 1 {
372 return Err(Error::new_spanned(
373 output,
374 "pool worker transition must return WorkerActed<Self>",
375 ));
376 }
377 let segment = output
378 .path
379 .segments
380 .first()
381 .ok_or_else(|| Error::new_spanned(output, "missing pool worker return type"))?;
382 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
383 return Err(Error::new_spanned(
384 output,
385 "pool worker transition must return WorkerActed<Self>",
386 ));
387 };
388 let mut arguments = arguments.args.iter();
389 let Some(GenericArgument::Type(Type::Path(self_type))) = arguments.next() else {
390 return Err(Error::new_spanned(
391 output,
392 "pool worker transition must return WorkerActed<Self>",
393 ));
394 };
395 let self_segment = self_type.path.segments.first();
396 if segment.ident != "WorkerActed"
397 || arguments.next().is_some()
398 || self_type.qself.is_some()
399 || self_type.path.segments.len() != 1
400 || !matches!(self_segment, Some(segment) if segment.ident == "Self" && matches!(segment.arguments, PathArguments::None))
401 {
402 return Err(Error::new_spanned(
403 output,
404 "pool worker transition must return WorkerActed<Self>",
405 ));
406 }
407 Ok(())
408}
409
410fn pool_worker_assignment(method: &syn::ImplItemFn) -> Result<Type> {
411 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
412 return Err(Error::new_spanned(
413 &method.sig,
414 "pool worker transition must begin with &mut self",
415 ));
416 };
417 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
418 return Err(Error::new_spanned(
419 receiver,
420 "pool worker transition must begin with &mut self",
421 ));
422 }
423 if method.sig.constness.is_some()
424 || method.sig.asyncness.is_some()
425 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
426 || method.sig.abi.is_some()
427 || method.sig.variadic.is_some()
428 || !method.sig.generics.params.is_empty()
429 {
430 return Err(Error::new_spanned(
431 &method.sig,
432 "pool worker transition must be synchronous, safe, and non-generic",
433 ));
434 }
435 if method.sig.inputs.len() != 2 {
436 return Err(Error::new_spanned(
437 &method.sig,
438 "pool worker transition must accept exactly &mut self and one Assignment<Job>",
439 ));
440 }
441 validate_pool_worker_output(&method.sig.output)?;
442
443 let Some(FnArg::Typed(assignment)) = method.sig.inputs.iter().nth(1) else {
444 return Err(Error::new_spanned(
445 &method.sig,
446 "pool worker transition must accept one Assignment<Job>",
447 ));
448 };
449 let Type::Path(assignment_path) = assignment.ty.as_ref() else {
450 return Err(Error::new_spanned(
451 &assignment.ty,
452 "pool worker transition input must be Assignment<Job>",
453 ));
454 };
455 let Some(segment) = assignment_path.path.segments.last() else {
456 return Err(Error::new_spanned(
457 &assignment.ty,
458 "pool worker transition input must be Assignment<Job>",
459 ));
460 };
461 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
462 return Err(Error::new_spanned(
463 &assignment.ty,
464 "pool worker transition input must be Assignment<Job>",
465 ));
466 };
467 let mut arguments = arguments.args.iter();
468 if segment.ident != "Assignment"
469 || !matches!(arguments.next(), Some(GenericArgument::Type(_)))
470 || arguments.next().is_some()
471 {
472 return Err(Error::new_spanned(
473 &assignment.ty,
474 "pool worker transition input must be Assignment<Job>",
475 ));
476 }
477 Ok(assignment.ty.as_ref().clone())
478}
479
480fn pascal_name(field: &Ident) -> String {
481 field
482 .to_string()
483 .split('_')
484 .filter(|part| !part.is_empty())
485 .map(|part| {
486 let mut chars = part.chars();
487 chars.next().map_or_else(String::new, |first| {
488 first.to_uppercase().chain(chars).collect()
489 })
490 })
491 .collect::<String>()
492}
493
494fn lane_name(product: &Ident, field: &Ident) -> Ident {
495 let lane = pascal_name(field);
496 format_ident!("{}{}", product, lane)
497}
498
499#[derive(Default)]
500struct GenericUses {
501 identifiers: std::collections::BTreeSet<String>,
502 lifetimes: std::collections::BTreeSet<String>,
503}
504
505impl<'ast> Visit<'ast> for GenericUses {
506 fn visit_ident(&mut self, ident: &'ast Ident) {
507 self.identifiers.insert(ident.to_string());
508 }
509
510 fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
511 self.lifetimes.insert(lifetime.ident.to_string());
512 }
513}
514
515fn product_generics(item: &ItemImpl, fields: &[NamedField]) -> Generics {
516 let mut uses = GenericUses::default();
517 for field in fields {
518 uses.visit_type(&field.ty);
519 }
520 let mut generics = item.generics.clone();
521 generics.params = generics
522 .params
523 .into_iter()
524 .filter(|parameter| match parameter {
525 GenericParam::Lifetime(parameter) => uses
526 .lifetimes
527 .contains(¶meter.lifetime.ident.to_string()),
528 GenericParam::Type(parameter) => {
529 uses.identifiers.contains(¶meter.ident.to_string())
530 }
531 GenericParam::Const(parameter) => {
532 uses.identifiers.contains(¶meter.ident.to_string())
533 }
534 })
535 .collect();
536 generics.where_clause = None;
537 generics
538}
539
540fn named_send_interpretation(
541 fields: &[&Ident],
542 field_types: &[&Type],
543 settlements: &Ident,
544 behavior: &TokenStream2,
545) -> TokenStream2 {
546 let mut interpretation = quote! {
547 #behavior::Interpretation::Complete(#settlements {
548 #(#fields: #fields,)*
549 })
550 };
551 for index in (0..fields.len()).rev() {
552 let field = fields[index];
553 let field_ty = field_types[index];
554 let prior_fields = &fields[..index];
555 let later_fields = &fields[index + 1..];
556 let later_types = &field_types[index + 1..];
557 let on_complete = interpretation;
558 interpretation = quote! {
559 match <#field_ty as #behavior::InterpretSends<
560 __BombayInterpreter,
561 __BombayRootEvent,
562 __BombayPath,
563 >>::interpret(self.#field, interpreter).await {
564 #behavior::Interpretation::Complete(#field) => #on_complete,
565 #behavior::Interpretation::Corrupt(#field) => {
566 #behavior::Interpretation::Corrupt(#settlements {
567 #(#prior_fields: #prior_fields,)*
568 #field: #field,
569 #(
570 #later_fields: <#later_types as #behavior::SendSettlements>::unattempted(
571 self.#later_fields,
572 ),
573 )*
574 })
575 }
576 }
577 };
578 }
579 interpretation
580}
581
582fn named_send_custody(
583 fields: &[&Ident],
584 settlements: &Ident,
585 behavior: &TokenStream2,
586) -> Vec<TokenStream2> {
587 fields
588 .iter()
589 .enumerate()
590 .map(|(index, field)| {
591 let prior_fields = &fields[..index];
592 let later_fields = &fields[index + 1..];
593 quote! {
594 let #field = match #behavior::SourceSettlementCustody::offer_next_to_source(
595 self.#field,
596 host,
597 ).await {
598 #behavior::SourceCustody::Exhausted(#field) => #field,
599 #behavior::SourceCustody::Retained(#field) => {
600 terminal_custody = __BombayTerminalCustody::Required;
601 #field
602 }
603 #behavior::SourceCustody::Admitted(#field) => {
604 return #behavior::SourceCustody::Admitted(#settlements {
605 #(#prior_fields: #prior_fields,)*
606 #field: #field,
607 #(#later_fields: self.#later_fields,)*
608 });
609 }
610 #behavior::SourceCustody::Closed(#field) => {
611 return #behavior::SourceCustody::Closed(#settlements {
612 #(#prior_fields: #prior_fields,)*
613 #field: #field,
614 #(#later_fields: self.#later_fields,)*
615 });
616 }
617 };
618 }
619 })
620 .collect()
621}
622
623fn named_settlement_contract(
624 name: &Ident,
625 settlement_type: &TokenStream2,
626 generics: &Generics,
627 fields: &[&Ident],
628 field_types: &[Type],
629 behavior: &TokenStream2,
630) -> TokenStream2 {
631 let mut classification_generics = generics.clone();
632 let mut custody_generics = generics.clone();
633 custody_generics
634 .params
635 .push(parse_quote!(__BombaySettlementHost));
636 custody_generics
637 .params
638 .push(parse_quote!(__BombaySettlementEvent));
639 custody_generics
640 .make_where_clause()
641 .predicates
642 .push(parse_quote!(__BombaySettlementHost: ::core::marker::Send));
643 let mut combined_status = quote!(#behavior::SettlementStatus::Accepted);
644 for (field, field_ty) in fields.iter().zip(field_types) {
645 classification_generics
646 .make_where_clause()
647 .predicates
648 .push(parse_quote!(#field_ty: #behavior::ClassifySettlement));
649 custody_generics
650 .make_where_clause()
651 .predicates
652 .push(parse_quote!(
653 #field_ty: #behavior::SourceSettlementCustody<
654 __BombaySettlementHost,
655 __BombaySettlementEvent,
656 > + ::core::marker::Send
657 ));
658 combined_status = quote! {
659 (#combined_status).combine(
660 #behavior::ClassifySettlement::settlement_status(&self.#field)
661 )
662 };
663 }
664 let (classification_impl, _, classification_where) = classification_generics.split_for_impl();
665 let (custody_impl, _, custody_where) = custody_generics.split_for_impl();
666 let custody_fields = named_send_custody(fields, name, behavior);
667
668 quote! {
669 impl #classification_impl #behavior::ClassifySettlement
670 for #settlement_type #classification_where
671 {
672 fn settlement_status(&self) -> #behavior::SettlementStatus {
673 #combined_status
674 }
675 }
676
677 impl #custody_impl
678 #behavior::SourceSettlementCustody<
679 __BombaySettlementHost,
680 __BombaySettlementEvent,
681 > for #settlement_type #custody_where
682 {
683 fn offer_next_to_source(
684 self,
685 host: &mut __BombaySettlementHost,
686 ) -> impl ::core::future::Future<
687 Output = #behavior::SourceCustody<Self>,
688 > + ::core::marker::Send {
689 async move {
690 enum __BombayTerminalCustody {
691 Unrequired,
692 Required,
693 }
694 let mut terminal_custody = __BombayTerminalCustody::Unrequired;
695 #(#custody_fields)*
696 let settlements = #name {
697 #(#fields: #fields,)*
698 };
699 match terminal_custody {
700 __BombayTerminalCustody::Unrequired => {
701 #behavior::SourceCustody::Exhausted(settlements)
702 }
703 __BombayTerminalCustody::Required => {
704 #behavior::SourceCustody::Retained(settlements)
705 }
706 }
707 }
708 }
709 }
710 }
711}
712
713#[allow(
714 clippy::too_many_lines,
715 reason = "the ordered send-product traits share one named-lane equation"
716)]
717fn named_send_contract(
718 name: &Ident,
719 generics: &Generics,
720 fields: &[&Ident],
721 field_types: &[&Type],
722 settlement_type: &TokenStream2,
723 settlement_name: &Ident,
724 behavior: &TokenStream2,
725) -> TokenStream2 {
726 let (_, type_generics, _) = generics.split_for_impl();
727 let mut effects_generics = generics.clone();
728 let mut logical_generics = generics.clone();
729 let mut settlement_generics = generics.clone();
730 let mut lawful_generics = generics.clone();
731 lawful_generics.params.push(parse_quote!(__BombayEvent));
732 let mut interpretation_generics = generics.clone();
733 interpretation_generics
734 .params
735 .push(parse_quote!(__BombayInterpreter));
736 interpretation_generics
737 .params
738 .push(parse_quote!(__BombayRootEvent));
739 interpretation_generics
740 .params
741 .push(parse_quote!(__BombayPath));
742 interpretation_generics
743 .make_where_clause()
744 .predicates
745 .push(parse_quote!(__BombayInterpreter: ::core::marker::Send));
746 interpretation_generics
747 .make_where_clause()
748 .predicates
749 .push(parse_quote!(#name #type_generics: ::core::marker::Send));
750 let mut logical_protocols = quote!(#behavior::NoBirthProtocols);
751 for field_ty in field_types {
752 effects_generics
753 .make_where_clause()
754 .predicates
755 .push(parse_quote!(#field_ty: #behavior::SendEffects));
756 logical_generics
757 .make_where_clause()
758 .predicates
759 .push(parse_quote!(#field_ty: #behavior::LogicalDeliveryProtocols));
760 settlement_generics
761 .make_where_clause()
762 .predicates
763 .push(parse_quote!(#field_ty: #behavior::SendSettlements));
764 lawful_generics
765 .make_where_clause()
766 .predicates
767 .push(parse_quote!(#field_ty: #behavior::SendsFor<__BombayEvent>));
768 interpretation_generics
769 .make_where_clause()
770 .predicates
771 .push(parse_quote!(
772 #field_ty: #behavior::InterpretSends<
773 __BombayInterpreter,
774 __BombayRootEvent,
775 __BombayPath,
776 >
777 ));
778 logical_protocols = quote!(
779 <#logical_protocols as #behavior::BirthProtocolProduct>::Append<
780 <#field_ty as #behavior::LogicalDeliveryProtocols>::Protocols
781 >
782 );
783 }
784 let (effects_impl, _, effects_where) = effects_generics.split_for_impl();
785 let (logical_impl, _, logical_where) = logical_generics.split_for_impl();
786 let (settlement_impl, _, settlement_where) = settlement_generics.split_for_impl();
787 let (lawful_impl, _, lawful_where) = lawful_generics.split_for_impl();
788 let (interpretation_impl, _, interpretation_where) = interpretation_generics.split_for_impl();
789 let interpretation = named_send_interpretation(fields, field_types, settlement_name, behavior);
790
791 quote! {
792 impl #effects_impl #behavior::SendEffects for #name #type_generics #effects_where {
793 fn empty() -> Self {
794 Self {
795 #(#fields: <#field_types as #behavior::SendEffects>::empty(),)*
796 }
797 }
798
799 fn append(&mut self, other: Self) {
800 #(
801 <#field_types as #behavior::SendEffects>::append(
802 &mut self.#fields,
803 other.#fields,
804 );
805 )*
806 }
807 }
808
809 impl #logical_impl #behavior::LogicalDeliveryProtocols
810 for #name #type_generics #logical_where
811 {
812 type Protocols = #logical_protocols;
813 }
814
815 impl #lawful_impl #behavior::SendsFor<__BombayEvent>
816 for #name #type_generics #lawful_where
817 {}
818
819 impl #settlement_impl #behavior::SendSettlements
820 for #name #type_generics #settlement_where
821 {
822 type Settlements = #settlement_type;
823
824 fn unattempted(self) -> Self::Settlements {
825 #settlement_name {
826 #(
827 #fields: <#field_types as #behavior::SendSettlements>::unattempted(
828 self.#fields,
829 ),
830 )*
831 }
832 }
833 }
834
835 impl #interpretation_impl
836 #behavior::InterpretSends<
837 __BombayInterpreter,
838 __BombayRootEvent,
839 __BombayPath,
840 > for #name #type_generics #interpretation_where
841 {
842 fn interpret(
843 self,
844 interpreter: &mut __BombayInterpreter,
845 ) -> impl ::core::future::Future<
846 Output = #behavior::Interpretation<Self::Settlements>,
847 > + ::core::marker::Send {
848 async move {
849 #interpretation
850 }
851 }
852 }
853 }
854}
855
856#[allow(
857 clippy::too_many_lines,
858 reason = "each generated contract is one facet of the same named send product"
859)]
860fn derive_send_product_contract(input: DeriveInput) -> Result<TokenStream2> {
861 let behavior = behavior_crate()?;
862 let name = input.ident;
863 let generics = input.generics;
864 let Data::Struct(data) = input.data else {
865 return Err(Error::new_spanned(
866 name,
867 "send products must be named structs",
868 ));
869 };
870 let Fields::Named(fields) = data.fields else {
871 return Err(Error::new_spanned(
872 name,
873 "send products need named effect lanes",
874 ));
875 };
876 if fields.named.is_empty() {
877 return Err(Error::new_spanned(
878 name,
879 "empty send products must be omitted",
880 ));
881 }
882 let mut parameters = Vec::new();
883 for parameter in &generics.params {
884 let GenericParam::Type(parameter) = parameter else {
885 return Err(Error::new_spanned(
886 parameter,
887 "generic send products use only lane type parameters",
888 ));
889 };
890 parameters.push(¶meter.ident);
891 }
892 let mut field_names = Vec::new();
893 let mut field_types = Vec::new();
894 for field in &fields.named {
895 let Some(field_name) = field.ident.as_ref() else {
896 return Err(Error::new_spanned(field, "send lanes must be named"));
897 };
898 let Type::Path(path) = &field.ty else {
899 return Err(Error::new_spanned(
900 &field.ty,
901 "each send lane must be one product type parameter",
902 ));
903 };
904 let Some(segment) = path.path.get_ident() else {
905 return Err(Error::new_spanned(
906 &field.ty,
907 "each send lane must be one product type parameter",
908 ));
909 };
910 if path.qself.is_some() || !parameters.iter().any(|parameter| *parameter == segment) {
911 return Err(Error::new_spanned(
912 &field.ty,
913 "each send lane must be one product type parameter",
914 ));
915 }
916 field_names.push(field_name);
917 field_types.push(&field.ty);
918 }
919 let (_, type_generics, _) = generics.split_for_impl();
920 let settled_parameters = parameters
921 .iter()
922 .map(|parameter| quote!(<#parameter as #behavior::SendSettlements>::Settlements));
923 let settlement_type = quote!(#name<#(#settled_parameters),*>);
924 let send_contract = named_send_contract(
925 &name,
926 &generics,
927 &field_names,
928 &field_types,
929 &settlement_type,
930 &name,
931 &behavior,
932 );
933 let settled_fields = field_types
934 .iter()
935 .map(|field_ty| (*field_ty).clone())
936 .collect::<Vec<_>>();
937 let settlement_owner = quote!(#name #type_generics);
938 let settlement_contract = named_settlement_contract(
939 &name,
940 &settlement_owner,
941 &generics,
942 &field_names,
943 &settled_fields,
944 &behavior,
945 );
946 Ok(quote!(#send_contract #settlement_contract))
947}
948
949#[proc_macro_derive(SendProduct)]
952pub fn derive_send_product(input: TokenStream) -> TokenStream {
953 let input = parse_macro_input!(input as DeriveInput);
954 derive_send_product_contract(input)
955 .unwrap_or_else(Error::into_compile_error)
956 .into()
957}
958
959#[allow(
960 clippy::too_many_lines,
961 reason = "the generated nominal product keeps every structural trait implementation adjacent"
962)]
963fn generate_sends(
964 product: Option<SendsSpec>,
965 actor: &Ident,
966 item: &ItemImpl,
967 behavior: &TokenStream2,
968) -> (TokenStream2, TokenStream2) {
969 let (product, visibility) = match product {
970 None => return (quote!(#behavior::NoSends), quote!()),
971 Some(SendsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
972 Some(SendsSpec::Generated {
973 product,
974 visibility,
975 }) => (product, visibility),
976 };
977 let name = format_ident!("{}Sends", actor);
978 let settlements_name = format_ident!("{}Settlements", actor);
979 let field_names: Vec<_> = product.fields.iter().map(|field| &field.name).collect();
980 let field_types: Vec<_> = product.fields.iter().map(|field| &field.ty).collect();
981 let lane_names: Vec<_> = field_names
982 .iter()
983 .map(|field| lane_name(&name, field))
984 .collect();
985 let actions_name = format_ident!("{}Actions", actor);
986 let action_methods: Vec<_> = field_names
987 .iter()
988 .map(|field| {
989 let field = field.to_string();
990 format_ident!("send_{}", field.trim_start_matches("r#"))
991 })
992 .collect();
993 let product_generics = product_generics(item, &product.fields);
994 let (_, type_generics, _) = product_generics.split_for_impl();
995 let generics = &product_generics;
996 let actor_type = &item.self_ty;
997 let mut action_trait_generics = product_generics.clone();
998 action_trait_generics
999 .make_where_clause()
1000 .predicates
1001 .push(parse_quote!(#actor_type: ::core::marker::Sized));
1002
1003 let action_trait_methods =
1004 action_methods
1005 .iter()
1006 .zip(lane_names.iter())
1007 .map(|(method, lane)| {
1008 quote! {
1009 #[must_use]
1011 fn #method<__BombayInput>(self, input: __BombayInput) -> Self
1012 where
1013 Self: #behavior::AppendSend<__BombayInput, #lane>;
1014 }
1015 });
1016
1017 let action_impl_methods = action_methods
1018 .iter()
1019 .zip(lane_names.iter())
1020 .map(|(method, lane)| {
1021 quote! {
1022 fn #method<__BombayInput>(self, input: __BombayInput) -> Self
1023 where
1024 Self: #behavior::AppendSend<__BombayInput, #lane>,
1025 {
1026 <Self as #behavior::AppendSend<__BombayInput, #lane>>::append_send(
1027 self,
1028 input,
1029 )
1030 }
1031 }
1032 });
1033
1034 let mut actions_impl_generics = product_generics.clone();
1035 actions_impl_generics
1036 .params
1037 .push(parse_quote!(__BombayAddress));
1038 actions_impl_generics.params.push(parse_quote!(__BombayPh));
1039 actions_impl_generics
1040 .params
1041 .push(parse_quote!(__BombayBirth));
1042 actions_impl_generics
1043 .make_where_clause()
1044 .predicates
1045 .push(parse_quote!(__BombayAddress: #behavior::Address));
1046 actions_impl_generics
1047 .make_where_clause()
1048 .predicates
1049 .push(parse_quote!(__BombayBirth: #behavior::BirthMode));
1050 actions_impl_generics
1051 .make_where_clause()
1052 .predicates
1053 .push(parse_quote!(#actor_type: ::core::marker::Sized));
1054 let (actions_impl_generics, _, actions_where_clause) = actions_impl_generics.split_for_impl();
1055
1056 let send_impls = lane_names
1057 .iter()
1058 .zip(field_names.iter())
1059 .zip(field_types.iter())
1060 .map(|((lane, field), field_ty)| {
1061 let mut generics = product_generics.clone();
1062 generics.params.push(parse_quote!(__BombayInput));
1063 generics.make_where_clause().predicates.push(parse_quote!(
1064 #field_ty: #behavior::SendInput<__BombayInput, #behavior::Own>
1065 ));
1066 let (impl_generics, _, where_clause) = generics.split_for_impl();
1067 quote! {
1068 impl #impl_generics #behavior::SendInput<__BombayInput, #lane>
1069 for #name #type_generics #where_clause
1070 {
1071 fn emit(&mut self, input: __BombayInput) {
1072 <#field_ty as #behavior::SendInput<
1073 __BombayInput,
1074 #behavior::Own,
1075 >>::emit(&mut self.#field, input);
1076 }
1077 }
1078 }
1079 });
1080
1081 let mut settlement_generics = product_generics.clone();
1082 for field_ty in &field_types {
1083 settlement_generics
1084 .make_where_clause()
1085 .predicates
1086 .push(parse_quote!(#field_ty: #behavior::SendSettlements));
1087 }
1088 let (settlement_impl_generics, settlement_type_generics, settlement_where_clause) =
1089 settlement_generics.split_for_impl();
1090 let settlement_type = quote!(#settlements_name #settlement_type_generics);
1091 let send_contract = named_send_contract(
1092 &name,
1093 &product_generics,
1094 &field_names,
1095 &field_types,
1096 &settlement_type,
1097 &settlements_name,
1098 behavior,
1099 );
1100 let settled_fields = field_types
1101 .iter()
1102 .map(|field_ty| parse_quote!(<#field_ty as #behavior::SendSettlements>::Settlements))
1103 .collect::<Vec<Type>>();
1104 let settlement_contract = named_settlement_contract(
1105 &settlements_name,
1106 &settlement_type,
1107 &settlement_generics,
1108 &field_names,
1109 &settled_fields,
1110 behavior,
1111 );
1112
1113 let items = quote! {
1114 #(#visibility enum #lane_names {})*
1115
1116 #visibility struct #name #generics {
1117 #(pub #field_names: #field_types,)*
1118 }
1119
1120 #visibility struct #settlements_name #settlement_impl_generics #settlement_where_clause {
1121 #(
1122 pub #field_names: <#field_types as #behavior::SendSettlements>::Settlements,
1123 )*
1124 }
1125
1126 #settlement_contract
1127
1128 #visibility trait #actions_name #action_trait_generics: ::core::marker::Sized {
1131 #(#action_trait_methods)*
1132 }
1133
1134 impl #actions_impl_generics #actions_name #type_generics
1135 for #behavior::Actions<
1136 __BombayAddress,
1137 __BombayPh,
1138 #name #type_generics,
1139 __BombayBirth,
1140 >
1141 #actions_where_clause
1142 {
1143 #(#action_impl_methods)*
1144 }
1145
1146 #send_contract
1147
1148 #(#send_impls)*
1149 };
1150 (quote!(#name #type_generics), items)
1151}
1152
1153fn generate_births(
1154 product: Option<BirthsSpec>,
1155 disposition: Option<CreationSettlementDisposition>,
1156 actor: &Ident,
1157 _addr: &Type,
1158 item: &ItemImpl,
1159 behavior: &TokenStream2,
1160) -> (TokenStream2, TokenStream2) {
1161 let product = match product {
1162 None => return (quote!(#behavior::NoBirths), quote!()),
1163 Some(BirthsSpec::Existing(ty)) => {
1164 if disposition.is_none() {
1165 return (quote!(#ty), quote!());
1166 }
1167 let child = quote!(<#ty as #behavior::BirthMode>::Child);
1168 let birth = match disposition {
1169 Some(CreationSettlementDisposition::ReturnToCreator) => {
1170 quote!(#behavior::Births<#child>)
1171 }
1172 Some(CreationSettlementDisposition::RetainForRetirement) => {
1173 quote!(#behavior::RetirementBirths<#child>)
1174 }
1175 None => unreachable!("a no-birth declaration has no settlement disposition"),
1176 };
1177 return (birth, quote!());
1178 }
1179 Some(BirthsSpec::Generated(product)) => product,
1180 };
1181 let name = format_ident!("{}Children", actor);
1182 let roles_name = format_ident!("{}Child", actor);
1183 let role_names = product
1184 .fields
1185 .iter()
1186 .map(|field| lane_name(&name, &field.name))
1187 .collect::<Vec<_>>();
1188 let role_values = product
1189 .fields
1190 .iter()
1191 .map(|field| format_ident!("{}", pascal_name(&field.name)))
1192 .collect::<Vec<_>>();
1193 let declared_child_types = product
1194 .fields
1195 .iter()
1196 .map(|field| &field.ty)
1197 .collect::<Vec<_>>();
1198 let role_positions = (0..product.fields.len())
1199 .map(|index| {
1200 (0..(product.fields.len() - index - 1)).fold(
1201 quote!(#behavior::ChildHead),
1202 |position, _| quote!(#behavior::ChildTail<#position>),
1203 )
1204 })
1205 .collect::<Vec<_>>();
1206 let product_generics = product_generics(item, &product.fields);
1207 let (_, type_generics, _) = product_generics.split_for_impl();
1208 let generics = &product_generics;
1209 let parent = &item.self_ty;
1210 let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
1211 let choice = match declared_child_types.as_slice() {
1212 [child] => quote!(#child),
1213 children => children.iter().fold(
1214 quote!(#behavior::Never),
1215 |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
1216 ),
1217 };
1218 let birth = match disposition {
1219 Some(CreationSettlementDisposition::ReturnToCreator) => {
1220 quote!(#behavior::Births<#name #type_generics>)
1221 }
1222 Some(CreationSettlementDisposition::RetainForRetirement) => {
1223 quote!(#behavior::RetirementBirths<#name #type_generics>)
1224 }
1225 None => unreachable!("a generated birth product has an explicit settlement disposition"),
1226 };
1227 (
1228 birth,
1229 quote! {
1230 pub type #name #generics = #choice;
1231
1232 #(
1233 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1234 pub struct #role_names;
1235
1236 impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
1237 #parent_where_clause
1238 {
1239 type Child = #declared_child_types;
1240 type Position = #role_positions;
1241 }
1242
1243 impl #parent_impl_generics #behavior::ChildOccurrence<#parent> for #role_names
1244 #parent_where_clause
1245 {
1246 type Resolution = #behavior::DeclaredChildOccurrence;
1247 }
1248 )*
1249
1250 pub struct #roles_name;
1251
1252 #[allow(
1253 non_upper_case_globals,
1254 reason = "generated role values retain the application-authored field spelling"
1255 )]
1256 impl #roles_name {
1257 #(pub const #role_values: #role_names = #role_names;)*
1258 }
1259
1260 },
1261 )
1262}
1263
1264#[proc_macro_attribute]
1270#[allow(
1271 clippy::too_many_lines,
1272 reason = "validation and the one coherent Behavior expansion remain in one entry point"
1273)]
1274pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
1275 let args = parse_macro_input!(args as BehaviorArgs);
1276 let item = parse_macro_input!(item as ItemImpl);
1277
1278 if item.trait_.is_some() {
1279 return Error::new_spanned(
1280 &item,
1281 "#[behavior] applies to an inherent impl, not a trait impl",
1282 )
1283 .to_compile_error()
1284 .into();
1285 }
1286
1287 let init = item.items.iter().find_map(|item| match item {
1288 ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
1289 _ => None,
1290 });
1291 let receive = item.items.iter().find_map(|item| match item {
1292 ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
1293 _ => None,
1294 });
1295 let creations_settled = item.items.iter().find_map(|item| match item {
1296 ImplItem::Fn(method) if method.sig.ident == "creations_settled" => Some(method),
1297 _ => None,
1298 });
1299 let Some(receive) = receive else {
1300 return Error::new_spanned(
1301 &item.self_ty,
1302 "#[behavior] requires a receive(&mut self, from, message) method",
1303 )
1304 .to_compile_error()
1305 .into();
1306 };
1307 if let Err(error) = init
1308 .map_or(Ok(()), validate_receiver)
1309 .and_then(|()| validate_receiver(receive))
1310 {
1311 return error.to_compile_error().into();
1312 }
1313 if let Some(init) = init
1314 && init.sig.inputs.len() != 1
1315 {
1316 return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
1317 .to_compile_error()
1318 .into();
1319 }
1320 if receive.sig.inputs.len() != 3 {
1321 return Error::new_spanned(
1322 &receive.sig,
1323 "receive must accept exactly &mut self, from, and message",
1324 )
1325 .to_compile_error()
1326 .into();
1327 }
1328 match (args.creation_settlements, creations_settled) {
1329 (Some(CreationSettlementDisposition::ReturnToCreator), Some(method)) => {
1330 if let Err(error) = validate_receiver(method) {
1331 return error.to_compile_error().into();
1332 }
1333 if method.sig.inputs.len() != 2 {
1334 return Error::new_spanned(
1335 &method.sig,
1336 "creations_settled must accept exactly &mut self and one CreationsSettled value",
1337 )
1338 .to_compile_error()
1339 .into();
1340 }
1341 }
1342 (Some(CreationSettlementDisposition::ReturnToCreator), None) => {
1343 return Error::new_spanned(
1344 &item.self_ty,
1345 "`creation_settlements = return_to_creator` requires a creations_settled transition",
1346 )
1347 .to_compile_error()
1348 .into();
1349 }
1350 (Some(CreationSettlementDisposition::RetainForRetirement), Some(method)) => {
1351 return Error::new_spanned(
1352 &method.sig,
1353 "retirement-owned creation settlements do not enter a creations_settled transition",
1354 )
1355 .to_compile_error()
1356 .into();
1357 }
1358 (Some(CreationSettlementDisposition::RetainForRetirement), None) | (None, None) => {}
1359 (None, Some(_)) => {}
1360 }
1361
1362 let BehaviorArgs {
1363 addr,
1364 message,
1365 sends,
1366 births,
1367 creation_settlements,
1368 error,
1369 } = args;
1370 let self_ty = &item.self_ty;
1371 let Type::Path(self_path) = self_ty.as_ref() else {
1372 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1373 .to_compile_error()
1374 .into();
1375 };
1376 let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
1377 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1378 .to_compile_error()
1379 .into();
1380 };
1381 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1382 let behavior = match behavior_crate() {
1383 Ok(behavior) => behavior,
1384 Err(error) => return error.to_compile_error().into(),
1385 };
1386 let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
1387 let initialize = init.map_or_else(
1388 || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
1389 |_| quote!(<#self_ty>::init(self)),
1390 );
1391
1392 let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
1393 let (births_ty, births_items) = generate_births(
1394 births,
1395 creation_settlements,
1396 self_name,
1397 &addr,
1398 &item,
1399 &behavior,
1400 );
1401 let (event_ty, transition) = match creation_settlements {
1402 Some(CreationSettlementDisposition::ReturnToCreator) => {
1403 let child = quote!(<#births_ty as #behavior::BirthMode>::Child);
1404 (
1405 quote!(#behavior::CreationEvent<#addr, #child, #message>),
1406 quote! {
1407 match event {
1408 #behavior::CreationEvent::Settlements(settlements) => {
1409 <#self_ty>::creations_settled(self, settlements)
1410 }
1411 #behavior::CreationEvent::User(event) => {
1412 <#self_ty>::receive(self, event.from, event.message)
1413 }
1414 }
1415 },
1416 )
1417 }
1418 Some(CreationSettlementDisposition::RetainForRetirement) | None => (
1419 quote!(#behavior::User<#addr, #message>),
1420 quote!(<#self_ty>::receive(self, event.from, event.message)),
1421 ),
1422 };
1423
1424 quote! {
1425 #sends_items
1426 #births_items
1427 #item
1428
1429 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1430 type Addr = #addr;
1431 type Msg = #message;
1432 }
1433
1434 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1435 type Protocol = Self;
1436 type Event = #event_ty;
1437 type Sends = #sends_ty;
1438 type Ph = #behavior::Never;
1439 type Error = #error;
1440 type Birth = #births_ty;
1441
1442 fn init(
1443 &mut self,
1444 _: #behavior::InitializationTurn,
1445 ) -> #behavior::BehaviorActed<Self> {
1446 #initialize
1447 }
1448
1449 fn transition(
1450 &mut self,
1451 _: #behavior::ActiveTurn,
1452 event: Self::Event,
1453 ) -> #behavior::BehaviorActed<Self> {
1454 #transition
1455 }
1456 }
1457
1458 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1459 type Base = Self;
1460
1461 fn base(&self) -> &Self {
1462 self
1463 }
1464 }
1465 }
1466 .into()
1467}
1468
1469#[proc_macro_attribute]
1477pub fn pool_worker(args: TokenStream, item: TokenStream) -> TokenStream {
1478 let PoolWorkerArgs { addr, result } = parse_macro_input!(args as PoolWorkerArgs);
1479 let mut item = parse_macro_input!(item as ItemImpl);
1480
1481 if item.trait_.is_some() {
1482 return Error::new_spanned(
1483 &item,
1484 "#[pool_worker] applies to an inherent impl, not a trait impl",
1485 )
1486 .to_compile_error()
1487 .into();
1488 }
1489
1490 let self_ty = item.self_ty.clone();
1491 if !matches!(self_ty.as_ref(), Type::Path(path) if path.qself.is_none()) {
1492 return Error::new_spanned(&self_ty, "#[pool_worker] requires a nominal worker type")
1493 .to_compile_error()
1494 .into();
1495 }
1496
1497 let actors = match actors_crate() {
1498 Ok(actors) => actors,
1499 Err(error) => return error.to_compile_error().into(),
1500 };
1501 let behavior = match behavior_crate() {
1502 Ok(behavior) => behavior,
1503 Err(error) => return error.to_compile_error().into(),
1504 };
1505 let assignment = {
1506 let mut transitions = item.items.iter_mut().filter_map(|item| match item {
1507 ImplItem::Fn(method) if method.sig.ident == "transition" => Some(method),
1508 _ => None,
1509 });
1510 let Some(transition) = transitions.next() else {
1511 return Error::new_spanned(
1512 &self_ty,
1513 "#[pool_worker] requires transition(&mut self, Assignment<Job>)",
1514 )
1515 .to_compile_error()
1516 .into();
1517 };
1518 if let Some(duplicate) = transitions.next() {
1519 return Error::new_spanned(
1520 &duplicate.sig,
1521 "#[pool_worker] accepts exactly one transition method",
1522 )
1523 .to_compile_error()
1524 .into();
1525 }
1526 let assignment = match pool_worker_assignment(transition) {
1527 Ok(assignment) => assignment,
1528 Err(error) => return error.to_compile_error().into(),
1529 };
1530 transition.sig.output = parse_quote!(-> #behavior::BehaviorActed<Self>);
1531 assignment
1532 };
1533 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1534
1535 quote! {
1536 #item
1537
1538 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1539 type Addr = #addr;
1540 type Msg = #assignment;
1541 }
1542
1543 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1544 type Protocol = Self;
1545 type Event = #behavior::User<#addr, #assignment>;
1546 type Sends = #behavior::InterpreterRequests<
1547 #behavior::ReportToParent<#actors::atomic::Completion<#result>>
1548 >;
1549 type Ph = #behavior::Never;
1550 type Error = #behavior::Never;
1551 type Birth = #behavior::NoBirths;
1552
1553 fn transition(
1554 &mut self,
1555 _: #behavior::ActiveTurn,
1556 event: Self::Event,
1557 ) -> #behavior::BehaviorActed<Self> {
1558 <#self_ty>::transition(self, event.message)
1559 }
1560 }
1561
1562 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1563 type Base = Self;
1564
1565 fn base(&self) -> &Self {
1566 self
1567 }
1568 }
1569 }
1570 .into()
1571}
1572
1573#[cfg(test)]
1574mod tests {
1575 use super::*;
1576
1577 fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
1578 if at == values.len() {
1579 output.push(values.join(", "));
1580 return;
1581 }
1582 for index in at..values.len() {
1583 values.swap(at, index);
1584 permutations(values, at + 1, output);
1585 values.swap(at, index);
1586 }
1587 }
1588
1589 #[test]
1590 fn every_argument_order_parses() {
1591 let mut declarations = [
1592 "addr = u64",
1593 "message = String",
1594 "sends = { first: Vec<u8>, second: Vec<u16> }",
1595 "births = { first: u8, second: u16 }",
1596 "creation_settlements = retain_for_retirement",
1597 "error = String",
1598 ];
1599 let mut cases = Vec::new();
1600 permutations(&mut declarations, 0, &mut cases);
1601 assert_eq!(cases.len(), 720);
1602 for case in cases {
1603 syn::parse_str::<BehaviorArgs>(&case)
1604 .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
1605 }
1606 }
1607
1608 #[test]
1609 fn capability_omission_and_trailing_comma_parse() {
1610 syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
1611 .expect("only required protocol declarations are sufficient");
1612 for declaration in ["NoBirths", "behavior::NoBirths"] {
1613 syn::parse_str::<BehaviorArgs>(&format!(
1614 "message = (), addr = u8, births = {declaration}"
1615 ))
1616 .unwrap_or_else(|error| panic!("explicit no-birth declaration did not parse: {error}"));
1617 }
1618 }
1619
1620 #[test]
1621 fn invalid_declaration_states_are_rejected() {
1622 for case in [
1623 "message = ()",
1624 "addr = u8",
1625 "addr = u8, message = (), addr = u16",
1626 "addr = u8, message = (), unknown = u8",
1627 "addr = u8, message = (), sends = {}",
1628 "addr = u8, message = (), births = {}",
1629 "addr = u8, message = (), births = {}, creation_settlements = retain_for_retirement",
1630 "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
1631 "addr = u8, message = (), births = { same: u8, same: u16 }, creation_settlements = retain_for_retirement",
1632 "addr = u8, message = (), creation_settlements = retain_for_retirement",
1633 "addr = u8, message = (), births = { child: u8 }, creation_settlements = discard",
1634 "addr = u8, message = (), births = { child: u8 }, creation_settlements = retain_for_retirement, creation_settlements = return_to_creator",
1635 ] {
1636 assert!(
1637 syn::parse_str::<BehaviorArgs>(case).is_err(),
1638 "invalid declaration parsed: `{case}`"
1639 );
1640 }
1641 }
1642
1643 #[test]
1644 fn pool_worker_arguments_require_one_address_and_result() {
1645 for valid in [
1646 "addr = u64, result = String",
1647 "result = String, addr = u64,",
1648 ] {
1649 syn::parse_str::<PoolWorkerArgs>(valid)
1650 .unwrap_or_else(|error| panic!("failed to parse `{valid}`: {error}"));
1651 }
1652 for invalid in [
1653 "addr = u64",
1654 "result = String",
1655 "addr = u64, result = String, addr = u32",
1656 "addr = u64, result = String, result = u8",
1657 "addr = u64, result = String, message = u8",
1658 ] {
1659 assert!(
1660 syn::parse_str::<PoolWorkerArgs>(invalid).is_err(),
1661 "invalid pool worker arguments parsed: `{invalid}`"
1662 );
1663 }
1664 }
1665
1666 #[test]
1667 fn pool_worker_transition_requires_the_domain_source_shape() {
1668 let valid: ItemImpl = syn::parse_str(
1669 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) \
1670 -> WorkerActed<Self> { result() } }",
1671 )
1672 .expect("valid inherent worker declaration parses");
1673 let method = valid
1674 .items
1675 .iter()
1676 .find_map(|item| match item {
1677 ImplItem::Fn(method) => Some(method),
1678 _ => None,
1679 })
1680 .expect("fixture declares one method");
1681 let assignment = pool_worker_assignment(method)
1682 .unwrap_or_else(|error| panic!("valid worker transition rejected: {error}"));
1683 assert_eq!(quote!(#assignment).to_string(), "Assignment < Job >");
1684
1685 for invalid in [
1686 "impl Worker { fn transition(self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1687 "impl Worker { async fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1688 "impl Worker { fn transition(&mut self, job: Job) -> WorkerActed<Self> { result() } }",
1689 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Job> { result() } }",
1690 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> Result<(), ()> { result() } }",
1691 ] {
1692 let declaration: ItemImpl = syn::parse_str(invalid)
1693 .unwrap_or_else(|error| panic!("invalid fixture did not parse: {error}"));
1694 let method = declaration
1695 .items
1696 .iter()
1697 .find_map(|item| match item {
1698 ImplItem::Fn(method) => Some(method),
1699 _ => None,
1700 })
1701 .expect("fixture declares one method");
1702 assert!(
1703 pool_worker_assignment(method).is_err(),
1704 "invalid pool worker transition was accepted: `{invalid}`"
1705 );
1706 }
1707 }
1708}