Skip to main content

behavior_macros/
lib.rs

1//! `behavior-macros` — proc-macros for the behavior algebra.
2//!
3//! `#[behavior]` wires a fully declared inherent user-message fold and may
4//! generate its nominal send and closed birth products.
5
6use 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            // Cargo package keys and library target names differ for each
23            // Bombay crate. Explicit dependency renames remain unchanged.
24            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        // This package deliberately exposes the library target as `behavior`,
39        // not Cargo's normalized package name `bombay_behavior`. The same path
40        // works in its unit, integration, and rustdoc crates.
41        return Ok(quote!(::behavior));
42    }
43    if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
44        // `FoundCrate::Itself` identifies a package, but `crate` identifies
45        // the target currently being compiled. The facade's library target is
46        // `bombay`; sibling binaries and examples therefore reach its exports
47        // through `::bombay`, not through their own `crate` root.
48        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    // The Bombay facade owns the Actors catalogue its expansion sites compile
83    // against. A consumer may carry both the facade and a direct foundational
84    // actors dependency, and the two resolve to different revisions whenever
85    // the facade pins its own: generated paths must then agree with the
86    // facade-path imports the consumer actually writes (`bombay::atomic`), so
87    // the facade wins. The foundational crate remains the fallback when no
88    // facade is present.
89    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(&parameter.lifetime.ident.to_string()),
528            GenericParam::Type(parameter) => {
529                uses.identifiers.contains(&parameter.ident.to_string())
530            }
531            GenericParam::Const(parameter) => {
532                uses.identifiers.contains(&parameter.ident.to_string())
533            }
534        })
535        .collect();
536    generics.where_clause = None;
537    generics
538}
539
540fn named_send_interpretation(
541    loans: &[&Ident],
542    field_types: &[&Type],
543    _settlements: &Ident,
544    behavior: &TokenStream2,
545) -> TokenStream2 {
546    let turns = loans.iter().zip(field_types).enumerate().map(|(index, (loan, field_ty))| {
547        let current_loan = loans.iter().enumerate().map(|(position, retained)| {
548            if position == index { quote!(#retained) } else { quote!(_) }
549        }).collect::<Vec<_>>();
550        let remaining_loans = loans.iter().enumerate().map(|(position, retained)| {
551            if position >= index { quote!(#retained) } else { quote!(_) }
552        }).collect::<Vec<_>>();
553        let later_loans = &loans[index + 1..];
554        let later_types = &field_types[index + 1..];
555        quote! {
556            // Only the guaranteed-Send child future crosses this await.
557            // Its input/reply owners and every sibling remain in parent progress.
558            {
559                let ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting(custody)) = progress else { return; };
560                let (#(#current_loan,)*) = custody;
561                <#field_ty as #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>>::interpret(#loan, interpreter)
562            }.await;
563            // All direct child loans below are synchronous and end before
564            // constructing the next child producer.
565            {
566                let ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting(custody)) = progress else { return; };
567                let (#(#remaining_loans,)*) = custody;
568                <#field_ty as #behavior::SendSettlements>::finish_interpretation(#loan);
569                match #loan {
570                    ::core::option::Option::Some(#behavior::InterpretationProgress::Completed(#behavior::Interpretation::Complete(_))) => {},
571                    ::core::option::Option::Some(#behavior::InterpretationProgress::Completed(#behavior::Interpretation::Corrupt(_))) => {
572                        #(<#later_types as #behavior::SendSettlements>::unattempted(#later_loans);)*
573                        <Self as #behavior::SendSettlements>::finish_interpretation(progress);
574                        return;
575                    }
576                    _ => return,
577                }
578            }
579        }
580    }).collect::<Vec<_>>();
581    quote! {
582        <Self as #behavior::SendSettlements>::prepare_interpretation(progress);
583        #(#turns)*
584        <Self as #behavior::SendSettlements>::finish_interpretation(progress);
585    }
586}
587
588fn source_product_custody(
589    settlements: &[TokenStream2],
590    custody: &[TokenStream2],
591    behavior: &TokenStream2,
592) -> (TokenStream2, TokenStream2) {
593    settlements.iter().zip(custody).rev().fold((quote!(#behavior::NoSends), quote!(#behavior::NoSends)), |(later_settlements, later_custody), (earlier_settlements, earlier_custody)| (
594        quote!((#earlier_settlements, #later_settlements)),
595        quote!((::core::option::Option<#behavior::SourceProgress<#earlier_settlements, #earlier_custody>>, ::core::option::Option<#behavior::SourceProgress<#later_settlements, #later_custody>>)),
596    ))
597}
598
599fn named_settlement_contract(
600    name: &Ident,
601    settlement_type: &TokenStream2,
602    generics: &Generics,
603    fields: &[&Ident],
604    field_types: &[Type],
605    behavior: &TokenStream2,
606) -> TokenStream2 {
607    let mut classification_generics = generics.clone();
608    let mut custody_generics = generics.clone();
609    custody_generics
610        .params
611        .push(parse_quote!(__BombaySettlementHost));
612    custody_generics
613        .params
614        .push(parse_quote!(__BombaySettlementEvent));
615    custody_generics
616        .make_where_clause()
617        .predicates
618        .push(parse_quote!(__BombaySettlementHost: ::core::marker::Send));
619    let mut combined_status = quote!(#behavior::SettlementStatus::Accepted);
620    for (field, field_ty) in fields.iter().zip(field_types) {
621        classification_generics
622            .make_where_clause()
623            .predicates
624            .push(parse_quote!(#field_ty: #behavior::ClassifySettlement));
625        custody_generics
626            .make_where_clause()
627            .predicates
628            .push(parse_quote!(
629                #field_ty: #behavior::SourceSettlementCustody<
630                    __BombaySettlementHost,
631                    __BombaySettlementEvent,
632                > + ::core::marker::Send
633            ));
634        combined_status = quote! {
635            (#combined_status).combine(
636                #behavior::ClassifySettlement::settlement_status(&self.#field)
637            )
638        };
639    }
640    let (classification_impl, _, classification_where) = classification_generics.split_for_impl();
641    let (custody_impl, _, custody_where) = custody_generics.split_for_impl();
642    let source_settlements = field_types.iter().map(|ty| quote!(#ty)).collect::<Vec<_>>();
643    let source_custody = field_types.iter().map(|ty| quote!(<#ty as #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent>>::Custody)).collect::<Vec<_>>();
644    let (source_product, source_owner) =
645        source_product_custody(&source_settlements, &source_custody, behavior);
646    let source_input = fields.iter().rev().fold(
647        quote!(#behavior::NoSends),
648        |later, field| quote!((original.#field, #later)),
649    );
650    let source_output = fields
651        .iter()
652        .rev()
653        .fold(quote!(_), |later, field| quote!((#field, #later)));
654
655    quote! {
656        impl #classification_impl #behavior::ClassifySettlement
657            for #settlement_type #classification_where
658        {
659            fn settlement_status(&self) -> #behavior::SettlementStatus {
660                #combined_status
661            }
662        }
663
664        impl #custody_impl #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent> for #settlement_type #custody_where {
665            type Custody = #source_owner;
666            fn prepare_source(progress: &mut ::core::option::Option<#behavior::SourceProgress<Self, Self::Custody>>) {
667                let original = match progress.take() {
668                    ::core::option::Option::Some(#behavior::SourceProgress::Original(original)) => original,
669                    retained => { *progress = retained; return; }
670                };
671                // The concrete Core tuple prepare only decomposes; it calls no child method.
672                let mut product = ::core::option::Option::Some(#behavior::SourceProgress::Original(#source_input));
673                <#source_product as #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent>>::prepare_source(&mut product);
674                *progress = match product {
675                    ::core::option::Option::Some(#behavior::SourceProgress::Original(#source_output)) => ::core::option::Option::Some(#behavior::SourceProgress::Original(#name { #(#fields,)* })),
676                    ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)) => ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)),
677                    ::core::option::Option::Some(#behavior::SourceProgress::Completed(reply)) => ::core::option::Option::Some(#behavior::SourceProgress::Completed(reply.map(|#source_output| #name { #(#fields,)* }))),
678                    ::core::option::Option::None => ::core::option::Option::None,
679                };
680            }
681            fn offer_next_to_source(custody: &mut Self::Custody, host: &mut __BombaySettlementHost) -> impl ::core::future::Future<Output = ()> + ::core::marker::Send {
682                <#source_product as #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent>>::offer_next_to_source(custody, host)
683            }
684            fn finish_source(progress: &mut ::core::option::Option<#behavior::SourceProgress<Self, Self::Custody>>) {
685                let custody = match progress.take() {
686                    ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)) => custody,
687                    retained => { *progress = retained; return; }
688                };
689                let mut product = ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody));
690                <#source_product as #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent>>::finish_source(&mut product);
691                *progress = match product {
692                    ::core::option::Option::Some(#behavior::SourceProgress::Original(#source_output)) => ::core::option::Option::Some(#behavior::SourceProgress::Original(#name { #(#fields,)* })),
693                    ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)) => ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)),
694                    ::core::option::Option::Some(#behavior::SourceProgress::Completed(reply)) => ::core::option::Option::Some(#behavior::SourceProgress::Completed(reply.map(|#source_output| #name { #(#fields,)* }))),
695                    ::core::option::Option::None => ::core::option::Option::None,
696                };
697            }
698        }
699
700    }
701}
702
703#[allow(
704    clippy::too_many_lines,
705    reason = "the ordered send-product traits share one named-lane equation"
706)]
707fn named_send_contract(
708    name: &Ident,
709    generics: &Generics,
710    fields: &[&Ident],
711    field_types: &[&Type],
712    settlement_type: &TokenStream2,
713    settlement_name: &Ident,
714    behavior: &TokenStream2,
715) -> TokenStream2 {
716    let (_, type_generics, _) = generics.split_for_impl();
717    let mut effects_generics = generics.clone();
718    let mut logical_generics = generics.clone();
719    let mut settlement_generics = generics.clone();
720    let mut lawful_generics = generics.clone();
721    lawful_generics.params.push(parse_quote!(__BombayEvent));
722    let mut interpretation_generics = generics.clone();
723    interpretation_generics
724        .params
725        .push(parse_quote!(__BombayInterpreter));
726    interpretation_generics
727        .params
728        .push(parse_quote!(__BombayRootEvent));
729    interpretation_generics
730        .params
731        .push(parse_quote!(__BombayPath));
732    interpretation_generics
733        .make_where_clause()
734        .predicates
735        .push(parse_quote!(__BombayInterpreter: ::core::marker::Send));
736    interpretation_generics
737        .make_where_clause()
738        .predicates
739        .push(parse_quote!(#name #type_generics: ::core::marker::Send));
740    let mut logical_protocols = quote!(#behavior::NoBirthProtocols);
741    for field_ty in field_types {
742        effects_generics
743            .make_where_clause()
744            .predicates
745            .push(parse_quote!(#field_ty: #behavior::SendEffects));
746        logical_generics
747            .make_where_clause()
748            .predicates
749            .push(parse_quote!(#field_ty: #behavior::LogicalDeliveryProtocols));
750        settlement_generics
751            .make_where_clause()
752            .predicates
753            .push(parse_quote!(#field_ty: #behavior::SendSettlements));
754        lawful_generics
755            .make_where_clause()
756            .predicates
757            .push(parse_quote!(#field_ty: #behavior::SendsFor<__BombayEvent>));
758        interpretation_generics
759            .make_where_clause()
760            .predicates
761            .push(parse_quote!(
762                #field_ty: #behavior::InterpretSends<
763                    __BombayInterpreter,
764                    __BombayRootEvent,
765                    __BombayPath,
766                >
767            ));
768        logical_protocols = quote!(
769            <#logical_protocols as #behavior::BirthProtocolProduct>::Append<
770                <#field_ty as #behavior::LogicalDeliveryProtocols>::Protocols
771            >
772        );
773    }
774    interpretation_generics.make_where_clause().predicates.push(parse_quote!(
775        <#name #type_generics as #behavior::SendSettlements>::InterpretationCustody: ::core::marker::Send
776    ));
777    let (effects_impl, _, effects_where) = effects_generics.split_for_impl();
778    let (logical_impl, _, logical_where) = logical_generics.split_for_impl();
779    let (settlement_impl, _, settlement_where) = settlement_generics.split_for_impl();
780    let (lawful_impl, _, lawful_where) = lawful_generics.split_for_impl();
781    let (interpretation_impl, _, interpretation_where) = interpretation_generics.split_for_impl();
782    let interpretation_loans = fields
783        .iter()
784        .map(|field| {
785            let field_name = field.to_string();
786            format_ident!(
787                "__bombay_{}_interpretation",
788                field_name.trim_start_matches("r#"),
789                span = Span::mixed_site()
790            )
791        })
792        .collect::<Vec<_>>();
793    let loan_fields = interpretation_loans.iter().collect::<Vec<_>>();
794    let remaining_interpretations =
795        Ident::new("__bombay_remaining_interpretations", Span::mixed_site());
796    let interpretation =
797        named_send_interpretation(&loan_fields, field_types, settlement_name, behavior);
798    let source_settlements = field_types
799        .iter()
800        .map(|ty| quote!(<#ty as #behavior::SendSettlements>::Settlements))
801        .collect::<Vec<_>>();
802    let source_custody = field_types
803        .iter()
804        .map(|ty| quote!(<#ty as #behavior::SendSettlements>::SourceCustody))
805        .collect::<Vec<_>>();
806    let (_, source_owner) = source_product_custody(&source_settlements, &source_custody, behavior);
807    let interpretation_custody = field_types
808        .iter()
809        .map(|ty| {
810            quote!(
811                ::core::option::Option<#behavior::InterpretationProgress<
812                    #ty,
813                    <#ty as #behavior::SendSettlements>::InterpretationCustody,
814                    <#ty as #behavior::SendSettlements>::Settlements,
815                >>
816            )
817        })
818        .collect::<Vec<_>>();
819    let completed_controls = fields.iter().map(|_| quote!(::core::option::Option::Some(#behavior::InterpretationProgress::Completed(_)))).collect::<Vec<_>>();
820    let completed_fields = loan_fields
821        .iter()
822        .rev()
823        .fold(quote!(()), |later, field| quote!((#field,#later)));
824    let mut combine = quote!(#behavior::Interpretation::Complete(()));
825    // This pure structural fold invokes no field/user method and crosses no await.
826    for field in loan_fields.iter().rev() {
827        combine = quote! {
828            match (#field,#combine) {
829                (#behavior::Interpretation::Complete(#field),#behavior::Interpretation::Complete(#remaining_interpretations)) => #behavior::Interpretation::Complete((#field,#remaining_interpretations)),
830                (#behavior::Interpretation::Complete(#field) | #behavior::Interpretation::Corrupt(#field), #behavior::Interpretation::Corrupt(#remaining_interpretations)) |
831                (#behavior::Interpretation::Corrupt(#field),#behavior::Interpretation::Complete(#remaining_interpretations)) => #behavior::Interpretation::Corrupt((#field,#remaining_interpretations)),
832            }
833        };
834    }
835
836    quote! {
837        impl #effects_impl #behavior::SendEffects for #name #type_generics #effects_where {
838            fn empty() -> Self {
839                Self {
840                    #(#fields: <#field_types as #behavior::SendEffects>::empty(),)*
841                }
842            }
843
844            fn append(&mut self, other: Self) {
845                #(
846                    <#field_types as #behavior::SendEffects>::append(
847                        &mut self.#fields,
848                        other.#fields,
849                    );
850                )*
851            }
852        }
853
854        impl #logical_impl #behavior::LogicalDeliveryProtocols
855            for #name #type_generics #logical_where
856        {
857            type Protocols = #logical_protocols;
858        }
859
860        impl #lawful_impl #behavior::SendsFor<__BombayEvent>
861            for #name #type_generics #lawful_where
862        {}
863
864        impl #settlement_impl #behavior::SendSettlements
865            for #name #type_generics #settlement_where
866        {
867            type Settlements = #settlement_type;
868            type SourceCustody = #source_owner;
869            type InterpretationCustody = (#(#interpretation_custody,)*);
870
871            fn prepare_interpretation(progress: &mut ::core::option::Option<#behavior::InterpretationProgress<Self,Self::InterpretationCustody,Self::Settlements>>) {
872                match progress.take() {
873                    ::core::option::Option::Some(#behavior::InterpretationProgress::Original(original)) => {
874                        *progress = ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting((
875                            #(::core::option::Option::Some(#behavior::InterpretationProgress::Original(original.#fields)),)*
876                        )));
877                    }
878                    retained => *progress = retained,
879                }
880            }
881            fn finish_interpretation(progress: &mut ::core::option::Option<#behavior::InterpretationProgress<Self,Self::InterpretationCustody,Self::Settlements>>) {
882                let ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting(custody)) = progress else { return; };
883                let (#(#loan_fields,)*) = custody;
884                if !matches!((#(&*#loan_fields,)*), (#(#completed_controls,)*)) { return; }
885                match progress.take() {
886                    ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting((
887                        #(::core::option::Option::Some(#behavior::InterpretationProgress::Completed(#loan_fields)),)*
888                    ))) => {
889                        let completed = #combine;
890                        *progress = ::core::option::Option::Some(#behavior::InterpretationProgress::Completed(match completed {
891                            #behavior::Interpretation::Complete(#completed_fields) => #behavior::Interpretation::Complete(#settlement_name { #(#fields: #loan_fields,)* }),
892                            #behavior::Interpretation::Corrupt(#completed_fields) => #behavior::Interpretation::Corrupt(#settlement_name { #(#fields: #loan_fields,)* }),
893                        }));
894                    }
895                    retained => *progress = retained,
896                }
897            }
898            fn unattempted(progress: &mut ::core::option::Option<#behavior::InterpretationProgress<Self,Self::InterpretationCustody,Self::Settlements>>) {
899                <Self as #behavior::SendSettlements>::prepare_interpretation(progress);
900                if let ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting(custody)) = progress {
901                    let (#(#loan_fields,)*) = custody;
902                    #(<#field_types as #behavior::SendSettlements>::unattempted(#loan_fields);)*
903                }
904                <Self as #behavior::SendSettlements>::finish_interpretation(progress);
905            }
906        }
907
908        impl #interpretation_impl
909            #behavior::InterpretSends<
910                __BombayInterpreter,
911                __BombayRootEvent,
912                __BombayPath,
913            > for #name #type_generics #interpretation_where
914        {
915            fn interpret(
916                progress: &mut ::core::option::Option<#behavior::InterpretationProgress<Self,Self::InterpretationCustody,Self::Settlements>>,
917                interpreter: &mut __BombayInterpreter,
918            ) -> impl ::core::future::Future<Output=()> + ::core::marker::Send {
919                async move {
920                    #interpretation
921                }
922            }
923        }
924    }
925}
926
927#[allow(
928    clippy::too_many_lines,
929    reason = "each generated contract is one facet of the same named send product"
930)]
931fn derive_send_product_contract(input: DeriveInput) -> Result<TokenStream2> {
932    let behavior = behavior_crate()?;
933    let name = input.ident;
934    let generics = input.generics;
935    let Data::Struct(data) = input.data else {
936        return Err(Error::new_spanned(
937            name,
938            "send products must be named structs",
939        ));
940    };
941    let Fields::Named(fields) = data.fields else {
942        return Err(Error::new_spanned(
943            name,
944            "send products need named effect lanes",
945        ));
946    };
947    if fields.named.is_empty() {
948        return Err(Error::new_spanned(
949            name,
950            "empty send products must be omitted",
951        ));
952    }
953    let mut parameters = Vec::new();
954    for parameter in &generics.params {
955        let GenericParam::Type(parameter) = parameter else {
956            return Err(Error::new_spanned(
957                parameter,
958                "generic send products use only lane type parameters",
959            ));
960        };
961        parameters.push(&parameter.ident);
962    }
963    let mut field_names = Vec::new();
964    let mut field_types = Vec::new();
965    for field in &fields.named {
966        let Some(field_name) = field.ident.as_ref() else {
967            return Err(Error::new_spanned(field, "send lanes must be named"));
968        };
969        let Type::Path(path) = &field.ty else {
970            return Err(Error::new_spanned(
971                &field.ty,
972                "each send lane must be one product type parameter",
973            ));
974        };
975        let Some(segment) = path.path.get_ident() else {
976            return Err(Error::new_spanned(
977                &field.ty,
978                "each send lane must be one product type parameter",
979            ));
980        };
981        if path.qself.is_some() || !parameters.iter().any(|parameter| *parameter == segment) {
982            return Err(Error::new_spanned(
983                &field.ty,
984                "each send lane must be one product type parameter",
985            ));
986        }
987        field_names.push(field_name);
988        field_types.push(&field.ty);
989    }
990    let (_, type_generics, _) = generics.split_for_impl();
991    let settled_parameters = parameters
992        .iter()
993        .map(|parameter| quote!(<#parameter as #behavior::SendSettlements>::Settlements));
994    let settlement_type = quote!(#name<#(#settled_parameters),*>);
995    let send_contract = named_send_contract(
996        &name,
997        &generics,
998        &field_names,
999        &field_types,
1000        &settlement_type,
1001        &name,
1002        &behavior,
1003    );
1004    let settled_fields = field_types
1005        .iter()
1006        .map(|field_ty| (*field_ty).clone())
1007        .collect::<Vec<_>>();
1008    let settlement_owner = quote!(#name #type_generics);
1009    let settlement_contract = named_settlement_contract(
1010        &name,
1011        &settlement_owner,
1012        &generics,
1013        &field_names,
1014        &settled_fields,
1015        &behavior,
1016    );
1017    Ok(quote!(#send_contract #settlement_contract))
1018}
1019
1020/// Derive the ordered send and settlement contracts for a named generic
1021/// product whose fields are its lane type parameters.
1022#[proc_macro_derive(SendProduct)]
1023pub fn derive_send_product(input: TokenStream) -> TokenStream {
1024    let input = parse_macro_input!(input as DeriveInput);
1025    derive_send_product_contract(input)
1026        .unwrap_or_else(Error::into_compile_error)
1027        .into()
1028}
1029
1030#[allow(
1031    clippy::too_many_lines,
1032    reason = "the generated nominal product keeps every structural trait implementation adjacent"
1033)]
1034fn generate_sends(
1035    product: Option<SendsSpec>,
1036    actor: &Ident,
1037    item: &ItemImpl,
1038    behavior: &TokenStream2,
1039) -> (TokenStream2, TokenStream2) {
1040    let (product, visibility) = match product {
1041        None => return (quote!(#behavior::NoSends), quote!()),
1042        Some(SendsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
1043        Some(SendsSpec::Generated {
1044            product,
1045            visibility,
1046        }) => (product, visibility),
1047    };
1048    let name = format_ident!("{}Sends", actor);
1049    let settlements_name = format_ident!("{}Settlements", actor);
1050    let field_names: Vec<_> = product.fields.iter().map(|field| &field.name).collect();
1051    let field_types: Vec<_> = product.fields.iter().map(|field| &field.ty).collect();
1052    let lane_names: Vec<_> = field_names
1053        .iter()
1054        .map(|field| lane_name(&name, field))
1055        .collect();
1056    let actions_name = format_ident!("{}Actions", actor);
1057    let action_methods: Vec<_> = field_names
1058        .iter()
1059        .map(|field| {
1060            let field = field.to_string();
1061            format_ident!("send_{}", field.trim_start_matches("r#"))
1062        })
1063        .collect();
1064    let product_generics = product_generics(item, &product.fields);
1065    let (_, type_generics, _) = product_generics.split_for_impl();
1066    let generics = &product_generics;
1067    let actor_type = &item.self_ty;
1068    let mut action_trait_generics = product_generics.clone();
1069    action_trait_generics
1070        .make_where_clause()
1071        .predicates
1072        .push(parse_quote!(#actor_type: ::core::marker::Sized));
1073
1074    let action_trait_methods =
1075        action_methods
1076            .iter()
1077            .zip(lane_names.iter())
1078            .map(|(method, lane)| {
1079                quote! {
1080                    /// Append one value to this generated semantic send lane.
1081                    #[must_use]
1082                    fn #method<__BombayInput>(self, input: __BombayInput) -> Self
1083                    where
1084                        Self: #behavior::AppendSend<__BombayInput, #lane>;
1085                }
1086            });
1087
1088    let action_impl_methods = action_methods
1089        .iter()
1090        .zip(lane_names.iter())
1091        .map(|(method, lane)| {
1092            quote! {
1093                fn #method<__BombayInput>(self, input: __BombayInput) -> Self
1094                where
1095                    Self: #behavior::AppendSend<__BombayInput, #lane>,
1096                {
1097                    <Self as #behavior::AppendSend<__BombayInput, #lane>>::append_send(
1098                        self,
1099                        input,
1100                    )
1101                }
1102            }
1103        });
1104
1105    let mut actions_impl_generics = product_generics.clone();
1106    actions_impl_generics
1107        .params
1108        .push(parse_quote!(__BombayAddress));
1109    actions_impl_generics.params.push(parse_quote!(__BombayPh));
1110    actions_impl_generics
1111        .params
1112        .push(parse_quote!(__BombayBirth));
1113    actions_impl_generics
1114        .make_where_clause()
1115        .predicates
1116        .push(parse_quote!(__BombayAddress: #behavior::Address));
1117    actions_impl_generics
1118        .make_where_clause()
1119        .predicates
1120        .push(parse_quote!(__BombayBirth: #behavior::BirthMode));
1121    actions_impl_generics
1122        .make_where_clause()
1123        .predicates
1124        .push(parse_quote!(#actor_type: ::core::marker::Sized));
1125    let (actions_impl_generics, _, actions_where_clause) = actions_impl_generics.split_for_impl();
1126
1127    let send_impls = lane_names
1128        .iter()
1129        .zip(field_names.iter())
1130        .zip(field_types.iter())
1131        .map(|((lane, field), field_ty)| {
1132            let mut generics = product_generics.clone();
1133            generics.params.push(parse_quote!(__BombayInput));
1134            generics.make_where_clause().predicates.push(parse_quote!(
1135                #field_ty: #behavior::SendInput<__BombayInput, #behavior::Own>
1136            ));
1137            let (impl_generics, _, where_clause) = generics.split_for_impl();
1138            quote! {
1139                impl #impl_generics #behavior::SendInput<__BombayInput, #lane>
1140                    for #name #type_generics #where_clause
1141                {
1142                    fn emit(&mut self, input: __BombayInput) {
1143                        <#field_ty as #behavior::SendInput<
1144                            __BombayInput,
1145                            #behavior::Own,
1146                        >>::emit(&mut self.#field, input);
1147                    }
1148                }
1149            }
1150        });
1151
1152    let mut settlement_generics = product_generics.clone();
1153    for field_ty in &field_types {
1154        settlement_generics
1155            .make_where_clause()
1156            .predicates
1157            .push(parse_quote!(#field_ty: #behavior::SendSettlements));
1158    }
1159    let (settlement_impl_generics, settlement_type_generics, settlement_where_clause) =
1160        settlement_generics.split_for_impl();
1161    let settlement_type = quote!(#settlements_name #settlement_type_generics);
1162    let send_contract = named_send_contract(
1163        &name,
1164        &product_generics,
1165        &field_names,
1166        &field_types,
1167        &settlement_type,
1168        &settlements_name,
1169        behavior,
1170    );
1171    let settled_fields = field_types
1172        .iter()
1173        .map(|field_ty| parse_quote!(<#field_ty as #behavior::SendSettlements>::Settlements))
1174        .collect::<Vec<Type>>();
1175    let settlement_contract = named_settlement_contract(
1176        &settlements_name,
1177        &settlement_type,
1178        &settlement_generics,
1179        &field_names,
1180        &settled_fields,
1181        behavior,
1182    );
1183
1184    let items = quote! {
1185        #(#visibility enum #lane_names {})*
1186
1187        #visibility struct #name #generics {
1188            #(pub #field_names: #field_types,)*
1189        }
1190
1191        #visibility struct #settlements_name #settlement_impl_generics #settlement_where_clause {
1192            #(
1193                pub #field_names: <#field_types as #behavior::SendSettlements>::Settlements,
1194            )*
1195        }
1196
1197        #settlement_contract
1198
1199        /// Fluent, statically routed send-lane methods for this behavior's
1200        /// generated action product.
1201        #visibility trait #actions_name #action_trait_generics: ::core::marker::Sized {
1202            #(#action_trait_methods)*
1203        }
1204
1205        impl #actions_impl_generics #actions_name #type_generics
1206            for #behavior::Actions<
1207                __BombayAddress,
1208                __BombayPh,
1209                #name #type_generics,
1210                __BombayBirth,
1211            >
1212            #actions_where_clause
1213        {
1214            #(#action_impl_methods)*
1215        }
1216
1217        #send_contract
1218
1219        #(#send_impls)*
1220    };
1221    (quote!(#name #type_generics), items)
1222}
1223
1224fn generate_births(
1225    product: Option<BirthsSpec>,
1226    disposition: Option<CreationSettlementDisposition>,
1227    actor: &Ident,
1228    _addr: &Type,
1229    item: &ItemImpl,
1230    behavior: &TokenStream2,
1231) -> (TokenStream2, TokenStream2) {
1232    let product = match product {
1233        None => return (quote!(#behavior::NoBirths), quote!()),
1234        Some(BirthsSpec::Existing(ty)) => {
1235            if disposition.is_none() {
1236                return (quote!(#ty), quote!());
1237            }
1238            let child = quote!(<#ty as #behavior::BirthMode>::Child);
1239            let birth = match disposition {
1240                Some(CreationSettlementDisposition::ReturnToCreator) => {
1241                    quote!(#behavior::Births<#child>)
1242                }
1243                Some(CreationSettlementDisposition::RetainForRetirement) => {
1244                    quote!(#behavior::RetirementBirths<#child>)
1245                }
1246                None => unreachable!("a no-birth declaration has no settlement disposition"),
1247            };
1248            return (birth, quote!());
1249        }
1250        Some(BirthsSpec::Generated(product)) => product,
1251    };
1252    let name = format_ident!("{}Children", actor);
1253    let roles_name = format_ident!("{}Child", actor);
1254    let role_names = product
1255        .fields
1256        .iter()
1257        .map(|field| lane_name(&name, &field.name))
1258        .collect::<Vec<_>>();
1259    let role_values = product
1260        .fields
1261        .iter()
1262        .map(|field| format_ident!("{}", pascal_name(&field.name)))
1263        .collect::<Vec<_>>();
1264    let declared_child_types = product
1265        .fields
1266        .iter()
1267        .map(|field| &field.ty)
1268        .collect::<Vec<_>>();
1269    let role_positions = (0..product.fields.len())
1270        .map(|index| {
1271            (0..(product.fields.len() - index - 1)).fold(
1272                quote!(#behavior::ChildHead),
1273                |position, _| quote!(#behavior::ChildTail<#position>),
1274            )
1275        })
1276        .collect::<Vec<_>>();
1277    let product_generics = product_generics(item, &product.fields);
1278    let (_, type_generics, _) = product_generics.split_for_impl();
1279    let generics = &product_generics;
1280    let parent = &item.self_ty;
1281    let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
1282    let choice = match declared_child_types.as_slice() {
1283        [child] => quote!(#child),
1284        children => children.iter().fold(
1285            quote!(#behavior::Never),
1286            |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
1287        ),
1288    };
1289    let birth = match disposition {
1290        Some(CreationSettlementDisposition::ReturnToCreator) => {
1291            quote!(#behavior::Births<#name #type_generics>)
1292        }
1293        Some(CreationSettlementDisposition::RetainForRetirement) => {
1294            quote!(#behavior::RetirementBirths<#name #type_generics>)
1295        }
1296        None => unreachable!("a generated birth product has an explicit settlement disposition"),
1297    };
1298    (
1299        birth,
1300        quote! {
1301            pub type #name #generics = #choice;
1302
1303            #(
1304                #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1305                pub struct #role_names;
1306
1307                impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
1308                    #parent_where_clause
1309                {
1310                    type Child = #declared_child_types;
1311                    type Position = #role_positions;
1312                }
1313
1314                impl #parent_impl_generics #behavior::ChildOccurrence<#parent> for #role_names
1315                    #parent_where_clause
1316                {
1317                    type Resolution = #behavior::DeclaredChildOccurrence;
1318                }
1319            )*
1320
1321            pub struct #roles_name;
1322
1323            #[allow(
1324                non_upper_case_globals,
1325                reason = "generated role values retain the application-authored field spelling"
1326            )]
1327            impl #roles_name {
1328                #(pub const #role_values: #role_names = #role_names;)*
1329            }
1330
1331        },
1332    )
1333}
1334
1335/// Generate the mechanical `Behavior` implementation for a normal inherent
1336/// impl containing `receive(&mut self, from, message)` and, optionally,
1337/// `init(&mut self)`. Omitting `init` selects the behavior algebra's empty
1338/// initialization transition. The original impl and methods are preserved
1339/// unchanged.
1340#[proc_macro_attribute]
1341#[allow(
1342    clippy::too_many_lines,
1343    reason = "validation and the one coherent Behavior expansion remain in one entry point"
1344)]
1345pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
1346    let args = parse_macro_input!(args as BehaviorArgs);
1347    let item = parse_macro_input!(item as ItemImpl);
1348
1349    if item.trait_.is_some() {
1350        return Error::new_spanned(
1351            &item,
1352            "#[behavior] applies to an inherent impl, not a trait impl",
1353        )
1354        .to_compile_error()
1355        .into();
1356    }
1357
1358    let init = item.items.iter().find_map(|item| match item {
1359        ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
1360        _ => None,
1361    });
1362    let receive = item.items.iter().find_map(|item| match item {
1363        ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
1364        _ => None,
1365    });
1366    let creations_settled = item.items.iter().find_map(|item| match item {
1367        ImplItem::Fn(method) if method.sig.ident == "creations_settled" => Some(method),
1368        _ => None,
1369    });
1370    let Some(receive) = receive else {
1371        return Error::new_spanned(
1372            &item.self_ty,
1373            "#[behavior] requires a receive(&mut self, from, message) method",
1374        )
1375        .to_compile_error()
1376        .into();
1377    };
1378    if let Err(error) = init
1379        .map_or(Ok(()), validate_receiver)
1380        .and_then(|()| validate_receiver(receive))
1381    {
1382        return error.to_compile_error().into();
1383    }
1384    if let Some(init) = init
1385        && init.sig.inputs.len() != 1
1386    {
1387        return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
1388            .to_compile_error()
1389            .into();
1390    }
1391    if receive.sig.inputs.len() != 3 {
1392        return Error::new_spanned(
1393            &receive.sig,
1394            "receive must accept exactly &mut self, from, and message",
1395        )
1396        .to_compile_error()
1397        .into();
1398    }
1399    match (args.creation_settlements, creations_settled) {
1400        (Some(CreationSettlementDisposition::ReturnToCreator), Some(method)) => {
1401            if let Err(error) = validate_receiver(method) {
1402                return error.to_compile_error().into();
1403            }
1404            if method.sig.inputs.len() != 2 {
1405                return Error::new_spanned(
1406                    &method.sig,
1407                    "creations_settled must accept exactly &mut self and one CreationsSettled value",
1408                )
1409                .to_compile_error()
1410                .into();
1411            }
1412        }
1413        (Some(CreationSettlementDisposition::ReturnToCreator), None) => {
1414            return Error::new_spanned(
1415                &item.self_ty,
1416                "`creation_settlements = return_to_creator` requires a creations_settled transition",
1417            )
1418            .to_compile_error()
1419            .into();
1420        }
1421        (Some(CreationSettlementDisposition::RetainForRetirement), Some(method)) => {
1422            return Error::new_spanned(
1423                &method.sig,
1424                "retirement-owned creation settlements do not enter a creations_settled transition",
1425            )
1426            .to_compile_error()
1427            .into();
1428        }
1429        (Some(CreationSettlementDisposition::RetainForRetirement), None) | (None, None) => {}
1430        (None, Some(_)) => {}
1431    }
1432
1433    let BehaviorArgs {
1434        addr,
1435        message,
1436        sends,
1437        births,
1438        creation_settlements,
1439        error,
1440    } = args;
1441    let self_ty = &item.self_ty;
1442    let Type::Path(self_path) = self_ty.as_ref() else {
1443        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1444            .to_compile_error()
1445            .into();
1446    };
1447    let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
1448        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1449            .to_compile_error()
1450            .into();
1451    };
1452    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1453    let behavior = match behavior_crate() {
1454        Ok(behavior) => behavior,
1455        Err(error) => return error.to_compile_error().into(),
1456    };
1457    let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
1458    let initialize = init.map_or_else(
1459        || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
1460        |_| quote!(<#self_ty>::init(self)),
1461    );
1462
1463    let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
1464    let (births_ty, births_items) = generate_births(
1465        births,
1466        creation_settlements,
1467        self_name,
1468        &addr,
1469        &item,
1470        &behavior,
1471    );
1472    let (event_ty, transition) = match creation_settlements {
1473        Some(CreationSettlementDisposition::ReturnToCreator) => {
1474            let child = quote!(<#births_ty as #behavior::BirthMode>::Child);
1475            (
1476                quote!(#behavior::CreationEvent<#addr, #child, #message>),
1477                quote! {
1478                    match event {
1479                        #behavior::CreationEvent::Settlements(settlements) => {
1480                            <#self_ty>::creations_settled(self, settlements)
1481                        }
1482                        #behavior::CreationEvent::User(event) => {
1483                            <#self_ty>::receive(self, event.from, event.message)
1484                        }
1485                    }
1486                },
1487            )
1488        }
1489        Some(CreationSettlementDisposition::RetainForRetirement) | None => (
1490            quote!(#behavior::User<#addr, #message>),
1491            quote!(<#self_ty>::receive(self, event.from, event.message)),
1492        ),
1493    };
1494
1495    quote! {
1496        #sends_items
1497        #births_items
1498        #item
1499
1500        impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1501            type Addr = #addr;
1502            type Msg = #message;
1503        }
1504
1505        impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1506            type Protocol = Self;
1507            type Event = #event_ty;
1508            type Sends = #sends_ty;
1509            type Ph = #behavior::Never;
1510            type Error = #error;
1511            type Birth = #births_ty;
1512
1513            fn init(
1514                &mut self,
1515                _: #behavior::InitializationTurn,
1516            ) -> #behavior::BehaviorActed<Self> {
1517                #initialize
1518            }
1519
1520            fn transition(
1521                &mut self,
1522                _: #behavior::ActiveTurn,
1523                event: Self::Event,
1524            ) -> #behavior::BehaviorActed<Self> {
1525                #transition
1526            }
1527        }
1528
1529        impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1530            type Base = Self;
1531
1532            fn base(&self) -> &Self {
1533                self
1534            }
1535        }
1536    }
1537    .into()
1538}
1539
1540/// Derive one pool worker's ordinary actor protocol and behavior from its
1541/// assignment transition.
1542///
1543/// This attribute owns syntax only. The generated send product is the existing
1544/// typed parent request carrying the opaque completion produced by
1545/// `Assignment::complete`; assignment authority and delivery remain owned by
1546/// the Behavior Actors pool protocol and the runtime interpreter.
1547#[proc_macro_attribute]
1548pub fn pool_worker(args: TokenStream, item: TokenStream) -> TokenStream {
1549    let PoolWorkerArgs { addr, result } = parse_macro_input!(args as PoolWorkerArgs);
1550    let mut item = parse_macro_input!(item as ItemImpl);
1551
1552    if item.trait_.is_some() {
1553        return Error::new_spanned(
1554            &item,
1555            "#[pool_worker] applies to an inherent impl, not a trait impl",
1556        )
1557        .to_compile_error()
1558        .into();
1559    }
1560
1561    let self_ty = item.self_ty.clone();
1562    if !matches!(self_ty.as_ref(), Type::Path(path) if path.qself.is_none()) {
1563        return Error::new_spanned(&self_ty, "#[pool_worker] requires a nominal worker type")
1564            .to_compile_error()
1565            .into();
1566    }
1567
1568    let actors = match actors_crate() {
1569        Ok(actors) => actors,
1570        Err(error) => return error.to_compile_error().into(),
1571    };
1572    let behavior = match behavior_crate() {
1573        Ok(behavior) => behavior,
1574        Err(error) => return error.to_compile_error().into(),
1575    };
1576    let assignment = {
1577        let mut transitions = item.items.iter_mut().filter_map(|item| match item {
1578            ImplItem::Fn(method) if method.sig.ident == "transition" => Some(method),
1579            _ => None,
1580        });
1581        let Some(transition) = transitions.next() else {
1582            return Error::new_spanned(
1583                &self_ty,
1584                "#[pool_worker] requires transition(&mut self, Assignment<Job>)",
1585            )
1586            .to_compile_error()
1587            .into();
1588        };
1589        if let Some(duplicate) = transitions.next() {
1590            return Error::new_spanned(
1591                &duplicate.sig,
1592                "#[pool_worker] accepts exactly one transition method",
1593            )
1594            .to_compile_error()
1595            .into();
1596        }
1597        let assignment = match pool_worker_assignment(transition) {
1598            Ok(assignment) => assignment,
1599            Err(error) => return error.to_compile_error().into(),
1600        };
1601        transition.sig.output = parse_quote!(-> #behavior::BehaviorActed<Self>);
1602        assignment
1603    };
1604    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1605
1606    quote! {
1607        #item
1608
1609        impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1610            type Addr = #addr;
1611            type Msg = #assignment;
1612        }
1613
1614        impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1615            type Protocol = Self;
1616            type Event = #behavior::User<#addr, #assignment>;
1617            type Sends = #behavior::InterpreterRequests<
1618                #behavior::ReportToParent<#actors::atomic::Completion<#result>>
1619            >;
1620            type Ph = #behavior::Never;
1621            type Error = #behavior::Never;
1622            type Birth = #behavior::NoBirths;
1623
1624            fn transition(
1625                &mut self,
1626                _: #behavior::ActiveTurn,
1627                event: Self::Event,
1628            ) -> #behavior::BehaviorActed<Self> {
1629                <#self_ty>::transition(self, event.message)
1630            }
1631        }
1632
1633        impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1634            type Base = Self;
1635
1636            fn base(&self) -> &Self {
1637                self
1638            }
1639        }
1640    }
1641    .into()
1642}
1643
1644#[cfg(test)]
1645mod tests {
1646    use super::*;
1647
1648    fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
1649        if at == values.len() {
1650            output.push(values.join(", "));
1651            return;
1652        }
1653        for index in at..values.len() {
1654            values.swap(at, index);
1655            permutations(values, at + 1, output);
1656            values.swap(at, index);
1657        }
1658    }
1659
1660    #[test]
1661    fn every_argument_order_parses() {
1662        let mut declarations = [
1663            "addr = u64",
1664            "message = String",
1665            "sends = { first: Vec<u8>, second: Vec<u16> }",
1666            "births = { first: u8, second: u16 }",
1667            "creation_settlements = retain_for_retirement",
1668            "error = String",
1669        ];
1670        let mut cases = Vec::new();
1671        permutations(&mut declarations, 0, &mut cases);
1672        assert_eq!(cases.len(), 720);
1673        for case in cases {
1674            syn::parse_str::<BehaviorArgs>(&case)
1675                .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
1676        }
1677    }
1678
1679    #[test]
1680    fn capability_omission_and_trailing_comma_parse() {
1681        syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
1682            .expect("only required protocol declarations are sufficient");
1683        for declaration in ["NoBirths", "behavior::NoBirths"] {
1684            syn::parse_str::<BehaviorArgs>(&format!(
1685                "message = (), addr = u8, births = {declaration}"
1686            ))
1687            .unwrap_or_else(|error| panic!("explicit no-birth declaration did not parse: {error}"));
1688        }
1689    }
1690
1691    #[test]
1692    fn invalid_declaration_states_are_rejected() {
1693        for case in [
1694            "message = ()",
1695            "addr = u8",
1696            "addr = u8, message = (), addr = u16",
1697            "addr = u8, message = (), unknown = u8",
1698            "addr = u8, message = (), sends = {}",
1699            "addr = u8, message = (), births = {}",
1700            "addr = u8, message = (), births = {}, creation_settlements = retain_for_retirement",
1701            "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
1702            "addr = u8, message = (), births = { same: u8, same: u16 }, creation_settlements = retain_for_retirement",
1703            "addr = u8, message = (), creation_settlements = retain_for_retirement",
1704            "addr = u8, message = (), births = { child: u8 }, creation_settlements = discard",
1705            "addr = u8, message = (), births = { child: u8 }, creation_settlements = retain_for_retirement, creation_settlements = return_to_creator",
1706        ] {
1707            assert!(
1708                syn::parse_str::<BehaviorArgs>(case).is_err(),
1709                "invalid declaration parsed: `{case}`"
1710            );
1711        }
1712    }
1713
1714    #[test]
1715    fn pool_worker_arguments_require_one_address_and_result() {
1716        for valid in [
1717            "addr = u64, result = String",
1718            "result = String, addr = u64,",
1719        ] {
1720            syn::parse_str::<PoolWorkerArgs>(valid)
1721                .unwrap_or_else(|error| panic!("failed to parse `{valid}`: {error}"));
1722        }
1723        for invalid in [
1724            "addr = u64",
1725            "result = String",
1726            "addr = u64, result = String, addr = u32",
1727            "addr = u64, result = String, result = u8",
1728            "addr = u64, result = String, message = u8",
1729        ] {
1730            assert!(
1731                syn::parse_str::<PoolWorkerArgs>(invalid).is_err(),
1732                "invalid pool worker arguments parsed: `{invalid}`"
1733            );
1734        }
1735    }
1736
1737    #[test]
1738    fn pool_worker_transition_requires_the_domain_source_shape() {
1739        let valid: ItemImpl = syn::parse_str(
1740            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) \
1741             -> WorkerActed<Self> { result() } }",
1742        )
1743        .expect("valid inherent worker declaration parses");
1744        let method = valid
1745            .items
1746            .iter()
1747            .find_map(|item| match item {
1748                ImplItem::Fn(method) => Some(method),
1749                _ => None,
1750            })
1751            .expect("fixture declares one method");
1752        let assignment = pool_worker_assignment(method)
1753            .unwrap_or_else(|error| panic!("valid worker transition rejected: {error}"));
1754        assert_eq!(quote!(#assignment).to_string(), "Assignment < Job >");
1755
1756        for invalid in [
1757            "impl Worker { fn transition(self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1758            "impl Worker { async fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1759            "impl Worker { fn transition(&mut self, job: Job) -> WorkerActed<Self> { result() } }",
1760            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Job> { result() } }",
1761            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> Result<(), ()> { result() } }",
1762        ] {
1763            let declaration: ItemImpl = syn::parse_str(invalid)
1764                .unwrap_or_else(|error| panic!("invalid fixture did not parse: {error}"));
1765            let method = declaration
1766                .items
1767                .iter()
1768                .find_map(|item| match item {
1769                    ImplItem::Fn(method) => Some(method),
1770                    _ => None,
1771                })
1772                .expect("fixture declares one method");
1773            assert!(
1774                pool_worker_assignment(method).is_err(),
1775                "invalid pool worker transition was accepted: `{invalid}`"
1776            );
1777        }
1778    }
1779}