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