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