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    Error, FnArg, GenericArgument, GenericParam, Generics, Ident, ImplItem, ItemImpl,
14    PathArguments, Result, ReturnType, Token, Type, braced, parse_macro_input, parse_quote,
15};
16
17fn crate_path(found: FoundCrate) -> TokenStream2 {
18    match found {
19        FoundCrate::Itself => quote!(crate),
20        FoundCrate::Name(name) => {
21            let name = syn::Ident::new(&name, Span::call_site());
22            quote!(::#name)
23        }
24    }
25}
26
27fn facade_crate_path(found: FoundCrate) -> TokenStream2 {
28    match found {
29        FoundCrate::Itself => quote!(crate),
30        FoundCrate::Name(name) => {
31            // Cargo names the package `bombay-rs`, while its public library
32            // target is `bombay`. `proc_macro_crate` reports the normalized
33            // package name for an unrenamed dependency, but generated Rust
34            // must address the library target. An explicit dependency rename
35            // remains the caller's actual extern-crate name.
36            let name = if name == "bombay_rs" {
37                "bombay".to_owned()
38            } else {
39                name
40            };
41            let name = syn::Ident::new(&name, Span::call_site());
42            quote!(::#name)
43        }
44    }
45}
46
47fn behavior_crate() -> Result<TokenStream2> {
48    if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
49        // This package deliberately exposes the library target as `behavior`,
50        // not Cargo's normalized package name `bombay_behavior`. The same path
51        // works in its unit, integration, and rustdoc crates.
52        return Ok(quote!(::behavior));
53    }
54    if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
55        // `FoundCrate::Itself` identifies a package, but `crate` identifies
56        // the target currently being compiled. The facade's library target is
57        // `bombay`; sibling binaries and examples therefore reach its exports
58        // through `::bombay`, not through their own `crate` root.
59        return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
60            Ok(quote!(crate::behavior))
61        } else {
62            Ok(quote!(::bombay::behavior))
63        };
64    }
65    if let Ok(found) = crate_name("bombay-behavior") {
66        return Ok(crate_path(found));
67    }
68    if let Ok(found) = crate_name("bombay-rs") {
69        let bombay = facade_crate_path(found);
70        return Ok(quote!(#bombay::behavior));
71    }
72    Err(Error::new(
73        Span::call_site(),
74        "could not resolve `bombay-behavior` directly or through `bombay-rs`",
75    ))
76}
77
78fn actors_crate() -> Result<TokenStream2> {
79    if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior-actors") {
80        return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("behavior_actors") {
81            Ok(quote!(crate))
82        } else {
83            Ok(quote!(::behavior_actors))
84        };
85    }
86    if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
87        return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
88            Ok(quote!(crate::behavior))
89        } else {
90            Ok(quote!(::bombay::behavior))
91        };
92    }
93    if let Ok(found) = crate_name("bombay-behavior-actors") {
94        return Ok(crate_path(found));
95    }
96    if let Ok(found) = crate_name("bombay-rs") {
97        let bombay = facade_crate_path(found);
98        return Ok(quote!(#bombay::behavior));
99    }
100    Err(Error::new(
101        Span::call_site(),
102        "could not resolve `bombay-behavior-actors` directly or through `bombay-rs`",
103    ))
104}
105
106#[cfg(test)]
107mod crate_resolution_tests {
108    use super::*;
109
110    #[test]
111    fn facade_default_library_name_and_explicit_rename_resolve_distinctly() {
112        assert_eq!(
113            facade_crate_path(FoundCrate::Name("bombay_rs".to_owned())).to_string(),
114            ":: bombay"
115        );
116        assert_eq!(
117            facade_crate_path(FoundCrate::Name("runtime".to_owned())).to_string(),
118            ":: runtime"
119        );
120    }
121}
122
123struct NamedField {
124    name: Ident,
125    ty: Type,
126}
127
128struct NamedProduct {
129    fields: Vec<NamedField>,
130}
131
132#[allow(
133    clippy::large_enum_variant,
134    reason = "boxing syn::Type would add lint-driven indirection to compile-time parser state"
135)]
136enum SendsSpec {
137    Existing(Type),
138    Generated(NamedProduct),
139}
140
141#[allow(
142    clippy::large_enum_variant,
143    reason = "boxing syn::Type would add lint-driven indirection to compile-time parser state"
144)]
145enum BirthsSpec {
146    Existing(Type),
147    Generated(NamedProduct),
148}
149
150fn parse_product(input: ParseStream) -> Result<NamedProduct> {
151    let content;
152    braced!(content in input);
153    let mut fields = Vec::new();
154    let mut names = std::collections::BTreeSet::new();
155    while !content.is_empty() {
156        let name: Ident = content.parse()?;
157        if !names.insert(name.to_string()) {
158            return Err(Error::new_spanned(name, "duplicate product lane"));
159        }
160        content.parse::<Token![:]>()?;
161        fields.push(NamedField {
162            name,
163            ty: content.parse()?,
164        });
165        if content.peek(Token![,]) {
166            content.parse::<Token![,]>()?;
167        } else if !content.is_empty() {
168            return Err(content.error("expected `,` between product lanes"));
169        }
170    }
171    if fields.is_empty() {
172        return Err(input.error("empty products must be omitted"));
173    }
174    Ok(NamedProduct { fields })
175}
176
177struct BehaviorArgs {
178    addr: Type,
179    message: Type,
180    sends: Option<SendsSpec>,
181    births: Option<BirthsSpec>,
182    error: Option<Type>,
183}
184
185struct PoolWorkerArgs {
186    addr: Type,
187    result: Type,
188}
189
190impl Parse for PoolWorkerArgs {
191    fn parse(input: ParseStream) -> Result<Self> {
192        let mut addr = None;
193        let mut result = None;
194        while !input.is_empty() {
195            let key: Ident = input.parse()?;
196            input.parse::<Token![=]>()?;
197            match key.to_string().as_str() {
198                "addr" if addr.is_none() => addr = Some(input.parse()?),
199                "result" if result.is_none() => result = Some(input.parse()?),
200                "addr" | "result" => {
201                    return Err(Error::new_spanned(key, "duplicate pool worker argument"));
202                }
203                _ => return Err(Error::new_spanned(key, "unknown pool worker argument")),
204            }
205            if input.peek(Token![,]) {
206                input.parse::<Token![,]>()?;
207            } else if !input.is_empty() {
208                return Err(input.error("expected `,` between pool worker arguments"));
209            }
210        }
211        Ok(Self {
212            addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
213            result: result.ok_or_else(|| input.error("missing `result`"))?,
214        })
215    }
216}
217
218impl Parse for BehaviorArgs {
219    fn parse(input: ParseStream) -> Result<Self> {
220        let mut addr = None;
221        let mut message = None;
222        let mut sends = None;
223        let mut births = None;
224        let mut error = None;
225        while !input.is_empty() {
226            let key: Ident = input.parse()?;
227            input.parse::<Token![=]>()?;
228            match key.to_string().as_str() {
229                "addr" if addr.is_none() => addr = Some(input.parse()?),
230                "message" if message.is_none() => message = Some(input.parse()?),
231                "sends" if sends.is_none() => {
232                    sends = Some(if input.peek(syn::token::Brace) {
233                        SendsSpec::Generated(parse_product(input)?)
234                    } else {
235                        SendsSpec::Existing(input.parse()?)
236                    });
237                }
238                "births" if births.is_none() => {
239                    births = Some(if input.peek(syn::token::Brace) {
240                        BirthsSpec::Generated(parse_product(input)?)
241                    } else {
242                        BirthsSpec::Existing(input.parse()?)
243                    });
244                }
245                "error" if error.is_none() => error = Some(input.parse()?),
246                "addr" | "message" | "sends" | "births" | "error" => {
247                    return Err(Error::new_spanned(key, "duplicate behavior argument"));
248                }
249                _ => return Err(Error::new_spanned(key, "unknown behavior argument")),
250            }
251            if input.peek(Token![,]) {
252                input.parse::<Token![,]>()?;
253            } else if !input.is_empty() {
254                return Err(input.error("expected `,` between behavior arguments"));
255            }
256        }
257        Ok(Self {
258            addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
259            message: message.ok_or_else(|| input.error("missing `message`"))?,
260            sends,
261            births,
262            error,
263        })
264    }
265}
266
267fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
268    let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
269        return Err(Error::new_spanned(
270            &method.sig,
271            "behavior methods must begin with &mut self",
272        ));
273    };
274    if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
275        return Err(Error::new_spanned(
276            receiver,
277            "behavior methods must begin with &mut self",
278        ));
279    }
280    if method.sig.constness.is_some()
281        || method.sig.asyncness.is_some()
282        || matches!(method.sig.safety, syn::Safety::Unsafe(_))
283        || !method.sig.generics.params.is_empty()
284    {
285        return Err(Error::new_spanned(
286            &method.sig,
287            "behavior init and receive methods must be synchronous, safe, and non-generic",
288        ));
289    }
290    if matches!(method.sig.output, ReturnType::Default) {
291        return Err(Error::new_spanned(
292            &method.sig,
293            "behavior methods must declare their complete Actions result type",
294        ));
295    }
296    Ok(())
297}
298
299fn validate_pool_worker_output(output: &ReturnType) -> Result<()> {
300    let ReturnType::Type(_, output) = output else {
301        return Err(Error::new_spanned(
302            output,
303            "pool worker transition must return WorkerActed<Self>",
304        ));
305    };
306    let Type::Path(output) = output.as_ref() else {
307        return Err(Error::new_spanned(
308            output,
309            "pool worker transition must return WorkerActed<Self>",
310        ));
311    };
312    if output.qself.is_some() || output.path.segments.len() != 1 {
313        return Err(Error::new_spanned(
314            output,
315            "pool worker transition must return WorkerActed<Self>",
316        ));
317    }
318    let segment = output
319        .path
320        .segments
321        .first()
322        .ok_or_else(|| Error::new_spanned(output, "missing pool worker return type"))?;
323    let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
324        return Err(Error::new_spanned(
325            output,
326            "pool worker transition must return WorkerActed<Self>",
327        ));
328    };
329    let mut arguments = arguments.args.iter();
330    let Some(GenericArgument::Type(Type::Path(self_type))) = arguments.next() else {
331        return Err(Error::new_spanned(
332            output,
333            "pool worker transition must return WorkerActed<Self>",
334        ));
335    };
336    let self_segment = self_type.path.segments.first();
337    if segment.ident != "WorkerActed"
338        || arguments.next().is_some()
339        || self_type.qself.is_some()
340        || self_type.path.segments.len() != 1
341        || !matches!(self_segment, Some(segment) if segment.ident == "Self" && matches!(segment.arguments, PathArguments::None))
342    {
343        return Err(Error::new_spanned(
344            output,
345            "pool worker transition must return WorkerActed<Self>",
346        ));
347    }
348    Ok(())
349}
350
351fn pool_worker_assignment(method: &syn::ImplItemFn) -> Result<Type> {
352    let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
353        return Err(Error::new_spanned(
354            &method.sig,
355            "pool worker transition must begin with &mut self",
356        ));
357    };
358    if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
359        return Err(Error::new_spanned(
360            receiver,
361            "pool worker transition must begin with &mut self",
362        ));
363    }
364    if method.sig.constness.is_some()
365        || method.sig.asyncness.is_some()
366        || matches!(method.sig.safety, syn::Safety::Unsafe(_))
367        || method.sig.abi.is_some()
368        || method.sig.variadic.is_some()
369        || !method.sig.generics.params.is_empty()
370    {
371        return Err(Error::new_spanned(
372            &method.sig,
373            "pool worker transition must be synchronous, safe, and non-generic",
374        ));
375    }
376    if method.sig.inputs.len() != 2 {
377        return Err(Error::new_spanned(
378            &method.sig,
379            "pool worker transition must accept exactly &mut self and one Assignment<Job>",
380        ));
381    }
382    validate_pool_worker_output(&method.sig.output)?;
383
384    let Some(FnArg::Typed(assignment)) = method.sig.inputs.iter().nth(1) else {
385        return Err(Error::new_spanned(
386            &method.sig,
387            "pool worker transition must accept one Assignment<Job>",
388        ));
389    };
390    let Type::Path(assignment_path) = assignment.ty.as_ref() else {
391        return Err(Error::new_spanned(
392            &assignment.ty,
393            "pool worker transition input must be Assignment<Job>",
394        ));
395    };
396    let Some(segment) = assignment_path.path.segments.last() else {
397        return Err(Error::new_spanned(
398            &assignment.ty,
399            "pool worker transition input must be Assignment<Job>",
400        ));
401    };
402    let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
403        return Err(Error::new_spanned(
404            &assignment.ty,
405            "pool worker transition input must be Assignment<Job>",
406        ));
407    };
408    let mut arguments = arguments.args.iter();
409    if segment.ident != "Assignment"
410        || !matches!(arguments.next(), Some(GenericArgument::Type(_)))
411        || arguments.next().is_some()
412    {
413        return Err(Error::new_spanned(
414            &assignment.ty,
415            "pool worker transition input must be Assignment<Job>",
416        ));
417    }
418    Ok(assignment.ty.as_ref().clone())
419}
420
421fn pascal_name(field: &Ident) -> String {
422    field
423        .to_string()
424        .split('_')
425        .filter(|part| !part.is_empty())
426        .map(|part| {
427            let mut chars = part.chars();
428            chars.next().map_or_else(String::new, |first| {
429                first.to_uppercase().chain(chars).collect()
430            })
431        })
432        .collect::<String>()
433}
434
435fn lane_name(product: &Ident, field: &Ident) -> Ident {
436    let lane = pascal_name(field);
437    format_ident!("{}{}", product, lane)
438}
439
440#[derive(Default)]
441struct GenericUses {
442    identifiers: std::collections::BTreeSet<String>,
443    lifetimes: std::collections::BTreeSet<String>,
444}
445
446impl<'ast> Visit<'ast> for GenericUses {
447    fn visit_ident(&mut self, ident: &'ast Ident) {
448        self.identifiers.insert(ident.to_string());
449    }
450
451    fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
452        self.lifetimes.insert(lifetime.ident.to_string());
453    }
454}
455
456fn product_generics(item: &ItemImpl, fields: &[NamedField]) -> Generics {
457    let mut uses = GenericUses::default();
458    for field in fields {
459        uses.visit_type(&field.ty);
460    }
461    let mut generics = item.generics.clone();
462    generics.params = generics
463        .params
464        .into_iter()
465        .filter(|parameter| match parameter {
466            GenericParam::Lifetime(parameter) => uses
467                .lifetimes
468                .contains(&parameter.lifetime.ident.to_string()),
469            GenericParam::Type(parameter) => {
470                uses.identifiers.contains(&parameter.ident.to_string())
471            }
472            GenericParam::Const(parameter) => {
473                uses.identifiers.contains(&parameter.ident.to_string())
474            }
475        })
476        .collect();
477    generics.where_clause = None;
478    generics
479}
480
481#[allow(
482    clippy::too_many_lines,
483    reason = "the generated nominal product keeps every structural trait implementation adjacent"
484)]
485fn generate_sends(
486    product: Option<SendsSpec>,
487    actor: &Ident,
488    item: &ItemImpl,
489    behavior: &TokenStream2,
490) -> (TokenStream2, TokenStream2) {
491    let product = match product {
492        None => return (quote!(#behavior::NoSends), quote!()),
493        Some(SendsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
494        Some(SendsSpec::Generated(product)) => product,
495    };
496    let name = format_ident!("{}Sends", actor);
497    let settlements_name = format_ident!("{}Settlements", actor);
498    let field_names: Vec<_> = product.fields.iter().map(|field| &field.name).collect();
499    let field_types: Vec<_> = product.fields.iter().map(|field| &field.ty).collect();
500    let lane_names: Vec<_> = field_names
501        .iter()
502        .map(|field| lane_name(&name, field))
503        .collect();
504    let actions_name = format_ident!("{}Actions", actor);
505    let action_methods: Vec<_> = field_names
506        .iter()
507        .map(|field| {
508            let field = field.to_string();
509            format_ident!("send_{}", field.trim_start_matches("r#"))
510        })
511        .collect();
512    let product_generics = product_generics(item, &product.fields);
513    let (_, type_generics, _) = product_generics.split_for_impl();
514    let generics = &product_generics;
515    let actor_type = &item.self_ty;
516    let mut action_trait_generics = product_generics.clone();
517    action_trait_generics
518        .make_where_clause()
519        .predicates
520        .push(parse_quote!(#actor_type: ::core::marker::Sized));
521
522    let action_trait_methods =
523        action_methods
524            .iter()
525            .zip(lane_names.iter())
526            .map(|(method, lane)| {
527                quote! {
528                    /// Append one value to this generated semantic send lane.
529                    #[must_use]
530                    fn #method<__BombayInput>(self, input: __BombayInput) -> Self
531                    where
532                        Self: #behavior::AppendSend<__BombayInput, #lane>;
533                }
534            });
535
536    let action_impl_methods = action_methods
537        .iter()
538        .zip(lane_names.iter())
539        .map(|(method, lane)| {
540            quote! {
541                fn #method<__BombayInput>(self, input: __BombayInput) -> Self
542                where
543                    Self: #behavior::AppendSend<__BombayInput, #lane>,
544                {
545                    <Self as #behavior::AppendSend<__BombayInput, #lane>>::append_send(
546                        self,
547                        input,
548                    )
549                }
550            }
551        });
552
553    let mut actions_impl_generics = product_generics.clone();
554    actions_impl_generics
555        .params
556        .push(parse_quote!(__BombayAddress));
557    actions_impl_generics.params.push(parse_quote!(__BombayPh));
558    actions_impl_generics
559        .params
560        .push(parse_quote!(__BombayBirth));
561    actions_impl_generics
562        .make_where_clause()
563        .predicates
564        .push(parse_quote!(__BombayAddress: #behavior::Address));
565    actions_impl_generics
566        .make_where_clause()
567        .predicates
568        .push(parse_quote!(__BombayBirth: #behavior::BirthMode));
569    actions_impl_generics
570        .make_where_clause()
571        .predicates
572        .push(parse_quote!(#actor_type: ::core::marker::Sized));
573    let (actions_impl_generics, _, actions_where_clause) = actions_impl_generics.split_for_impl();
574
575    let send_impls = lane_names
576        .iter()
577        .zip(field_names.iter())
578        .zip(field_types.iter())
579        .map(|((lane, field), field_ty)| {
580            let mut generics = product_generics.clone();
581            generics.params.push(parse_quote!(__BombayInput));
582            generics.make_where_clause().predicates.push(parse_quote!(
583                #field_ty: #behavior::SendInput<__BombayInput, #behavior::Own>
584            ));
585            let (impl_generics, _, where_clause) = generics.split_for_impl();
586            quote! {
587                impl #impl_generics #behavior::SendInput<__BombayInput, #lane>
588                    for #name #type_generics #where_clause
589                {
590                    fn emit(&mut self, input: __BombayInput) {
591                        <#field_ty as #behavior::SendInput<
592                            __BombayInput,
593                            #behavior::Own,
594                        >>::emit(&mut self.#field, input);
595                    }
596                }
597            }
598        });
599
600    let mut effects_generics = product_generics.clone();
601    for field_ty in &field_types {
602        effects_generics
603            .make_where_clause()
604            .predicates
605            .push(parse_quote!(#field_ty: #behavior::SendEffects));
606    }
607    let (effects_impl_generics, _, effects_where_clause) = effects_generics.split_for_impl();
608
609    let mut lawful_generics = product_generics.clone();
610    lawful_generics.params.push(parse_quote!(__BombayEvent));
611    for field_ty in &field_types {
612        lawful_generics
613            .make_where_clause()
614            .predicates
615            .push(parse_quote!(#field_ty: #behavior::SendsFor<__BombayEvent>));
616    }
617    let (lawful_impl_generics, _, lawful_where_clause) = lawful_generics.split_for_impl();
618
619    let mut interpret_generics = product_generics.clone();
620    interpret_generics
621        .params
622        .push(parse_quote!(__BombayInterpreter));
623    interpret_generics
624        .params
625        .push(parse_quote!(__BombayRootEvent));
626    interpret_generics.params.push(parse_quote!(__BombayPath));
627    interpret_generics
628        .make_where_clause()
629        .predicates
630        .push(parse_quote!(__BombayInterpreter: ::core::marker::Send));
631    for field_ty in &field_types {
632        interpret_generics
633            .make_where_clause()
634            .predicates
635            .push(parse_quote!(
636                #field_ty: #behavior::InterpretSends<
637                    __BombayInterpreter,
638                    __BombayRootEvent,
639                    __BombayPath,
640                >
641            ));
642    }
643    interpret_generics
644        .make_where_clause()
645        .predicates
646        .push(parse_quote!(#name #type_generics: ::core::marker::Send));
647    let (interpret_impl_generics, _, interpret_where_clause) = interpret_generics.split_for_impl();
648    let mut settlement_generics = product_generics.clone();
649    for field_ty in &field_types {
650        settlement_generics
651            .make_where_clause()
652            .predicates
653            .push(parse_quote!(#field_ty: #behavior::SendSettlements));
654    }
655    let (settlement_impl_generics, settlement_type_generics, settlement_where_clause) =
656        settlement_generics.split_for_impl();
657    let mut classification_generics = settlement_generics.clone();
658    for field_ty in &field_types {
659        classification_generics
660            .make_where_clause()
661            .predicates
662            .push(parse_quote!(
663                <#field_ty as #behavior::SendSettlements>::Settlements:
664                    #behavior::ClassifySettlement
665            ));
666    }
667    let (classification_impl_generics, classification_type_generics, classification_where_clause) =
668        classification_generics.split_for_impl();
669    let mut custody_generics = settlement_generics.clone();
670    custody_generics
671        .params
672        .push(parse_quote!(__BombaySettlementHost));
673    custody_generics
674        .params
675        .push(parse_quote!(__BombaySettlementEvent));
676    custody_generics
677        .make_where_clause()
678        .predicates
679        .push(parse_quote!(__BombaySettlementHost: ::core::marker::Send));
680    for field_ty in &field_types {
681        custody_generics
682            .make_where_clause()
683            .predicates
684            .push(parse_quote!(
685                <#field_ty as #behavior::SendSettlements>::Settlements:
686                    #behavior::SourceSettlementCustody<
687                        __BombaySettlementHost,
688                        __BombaySettlementEvent,
689                    >
690                    + ::core::marker::Send
691            ));
692    }
693    let (custody_impl_generics, _, custody_where_clause) = custody_generics.split_for_impl();
694    let mut combined_status = quote!(#behavior::SettlementStatus::Accepted);
695    for field in &field_names {
696        combined_status = quote! {
697            (#combined_status).combine(
698                #behavior::ClassifySettlement::settlement_status(&self.#field)
699            )
700        };
701    }
702
703    let mut interpretation = quote! {
704        #behavior::Interpretation::Complete(#settlements_name {
705            #(#field_names: #field_names,)*
706        })
707    };
708    for index in (0..field_names.len()).rev() {
709        let field = field_names[index];
710        let field_ty = field_types[index];
711        let prior_fields = &field_names[..index];
712        let later_fields = &field_names[index + 1..];
713        let later_types = &field_types[index + 1..];
714        let on_complete = interpretation;
715        interpretation = quote! {
716            match <#field_ty as #behavior::InterpretSends<
717                __BombayInterpreter,
718                __BombayRootEvent,
719                __BombayPath,
720            >>::interpret(self.#field, interpreter).await {
721                #behavior::Interpretation::Complete(#field) => #on_complete,
722                #behavior::Interpretation::Corrupt(#field) => {
723                    #behavior::Interpretation::Corrupt(#settlements_name {
724                        #(#prior_fields: #prior_fields,)*
725                        #field: #field,
726                        #(
727                            #later_fields: <#later_types as #behavior::SendSettlements>::unattempted(
728                                self.#later_fields,
729                            ),
730                        )*
731                    })
732                }
733            }
734        };
735    }
736    let custody_fields = field_names.iter().enumerate().map(|(index, field)| {
737        let prior_fields = &field_names[..index];
738        let later_fields = &field_names[index + 1..];
739        quote! {
740            let #field = match #behavior::SourceSettlementCustody::offer_next_to_source(
741                self.#field,
742                host,
743            ).await {
744                #behavior::SourceCustody::Exhausted(#field) => #field,
745                #behavior::SourceCustody::Admitted(#field) => {
746                    return #behavior::SourceCustody::Admitted(#settlements_name {
747                        #(#prior_fields: #prior_fields,)*
748                        #field: #field,
749                        #(#later_fields: self.#later_fields,)*
750                    });
751                }
752                #behavior::SourceCustody::Closed(#field) => {
753                    return #behavior::SourceCustody::Closed(#settlements_name {
754                        #(#prior_fields: #prior_fields,)*
755                        #field: #field,
756                        #(#later_fields: self.#later_fields,)*
757                    });
758                }
759            };
760        }
761    });
762
763    let items = quote! {
764        #(pub enum #lane_names {})*
765
766        pub struct #name #generics {
767            #(pub #field_names: #field_types,)*
768        }
769
770        #[doc(hidden)]
771        pub struct #settlements_name #settlement_impl_generics #settlement_where_clause {
772            #(
773                pub #field_names: <#field_types as #behavior::SendSettlements>::Settlements,
774            )*
775        }
776
777        impl #classification_impl_generics #behavior::ClassifySettlement
778            for #settlements_name #classification_type_generics #classification_where_clause
779        {
780            fn settlement_status(&self) -> #behavior::SettlementStatus {
781                #combined_status
782            }
783        }
784
785        impl #custody_impl_generics
786            #behavior::SourceSettlementCustody<
787                __BombaySettlementHost,
788                __BombaySettlementEvent,
789            >
790            for #settlements_name #settlement_type_generics #custody_where_clause
791        {
792            fn offer_next_to_source(
793                self,
794                host: &mut __BombaySettlementHost,
795            ) -> impl ::core::future::Future<
796                Output = #behavior::SourceCustody<Self>,
797            > + ::core::marker::Send {
798                async move {
799                    #(#custody_fields)*
800                    #behavior::SourceCustody::Exhausted(#settlements_name {
801                        #(#field_names: #field_names,)*
802                    })
803                }
804            }
805        }
806
807        /// Fluent, statically routed send-lane methods for this behavior's
808        /// generated action product.
809        pub trait #actions_name #action_trait_generics: ::core::marker::Sized {
810            #(#action_trait_methods)*
811        }
812
813        impl #actions_impl_generics #actions_name #type_generics
814            for #behavior::Actions<
815                __BombayAddress,
816                __BombayPh,
817                #name #type_generics,
818                __BombayBirth,
819            >
820            #actions_where_clause
821        {
822            #(#action_impl_methods)*
823        }
824
825        impl #effects_impl_generics #behavior::SendEffects for #name #type_generics
826            #effects_where_clause
827        {
828            fn empty() -> Self {
829                Self {
830                    #(#field_names: <#field_types as #behavior::SendEffects>::empty(),)*
831                }
832            }
833
834            fn append(&mut self, other: Self) {
835                #(
836                    <#field_types as #behavior::SendEffects>::append(
837                        &mut self.#field_names,
838                        other.#field_names,
839                    );
840                )*
841            }
842        }
843
844        impl #lawful_impl_generics #behavior::SendsFor<__BombayEvent>
845            for #name #type_generics #lawful_where_clause
846        {}
847
848        impl #settlement_impl_generics #behavior::SendSettlements
849            for #name #type_generics #settlement_where_clause
850        {
851            type Settlements = #settlements_name #settlement_type_generics;
852
853            fn unattempted(self) -> Self::Settlements {
854                #settlements_name {
855                    #(
856                        #field_names: <#field_types as #behavior::SendSettlements>::unattempted(
857                            self.#field_names,
858                        ),
859                    )*
860                }
861            }
862        }
863
864        #(#send_impls)*
865
866        impl #interpret_impl_generics
867            #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>
868            for #name #type_generics #interpret_where_clause
869        {
870            fn interpret(
871                self,
872                interpreter: &mut __BombayInterpreter,
873            ) -> impl ::core::future::Future<
874                Output = #behavior::Interpretation<Self::Settlements>,
875            > + ::core::marker::Send {
876                async move {
877                    #interpretation
878                }
879            }
880        }
881    };
882    (quote!(#name #type_generics), items)
883}
884
885fn generate_births(
886    product: Option<BirthsSpec>,
887    actor: &Ident,
888    _addr: &Type,
889    item: &ItemImpl,
890    behavior: &TokenStream2,
891) -> (TokenStream2, TokenStream2) {
892    let product = match product {
893        None => return (quote!(#behavior::NoBirths), quote!()),
894        Some(BirthsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
895        Some(BirthsSpec::Generated(product)) => product,
896    };
897    let name = format_ident!("{}Children", actor);
898    let roles_name = format_ident!("{}Child", actor);
899    let role_names = product
900        .fields
901        .iter()
902        .map(|field| lane_name(&name, &field.name))
903        .collect::<Vec<_>>();
904    let role_values = product
905        .fields
906        .iter()
907        .map(|field| format_ident!("{}", pascal_name(&field.name)))
908        .collect::<Vec<_>>();
909    let declared_child_types = product
910        .fields
911        .iter()
912        .map(|field| &field.ty)
913        .collect::<Vec<_>>();
914    let role_positions = (0..product.fields.len())
915        .map(|index| {
916            (0..(product.fields.len() - index - 1)).fold(
917                quote!(#behavior::ChildHead),
918                |position, _| quote!(#behavior::ChildTail<#position>),
919            )
920        })
921        .collect::<Vec<_>>();
922    let product_generics = product_generics(item, &product.fields);
923    let (_, type_generics, _) = product_generics.split_for_impl();
924    let generics = &product_generics;
925    let parent = &item.self_ty;
926    let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
927    let choice = match declared_child_types.as_slice() {
928        [child] => quote!(#child),
929        children => children.iter().fold(
930            quote!(#behavior::Never),
931            |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
932        ),
933    };
934    (
935        quote!(#behavior::Births<#name #type_generics>),
936        quote! {
937            pub type #name #generics = #choice;
938
939            #(
940                #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
941                pub struct #role_names;
942
943                impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
944                    #parent_where_clause
945                {
946                    type Child = #declared_child_types;
947                    type Position = #role_positions;
948                }
949
950                impl #parent_impl_generics #behavior::ChildOccurrence<#parent> for #role_names
951                    #parent_where_clause
952                {
953                    type Resolution = #behavior::DeclaredChildOccurrence;
954                }
955            )*
956
957            pub struct #roles_name;
958
959            #[allow(
960                non_upper_case_globals,
961                reason = "generated role values retain the application-authored field spelling"
962            )]
963            impl #roles_name {
964                #(pub const #role_values: #role_names = #role_names;)*
965            }
966
967        },
968    )
969}
970
971/// Generate the mechanical `Behavior` implementation for a normal inherent
972/// impl containing `receive(&mut self, from, message)` and, optionally,
973/// `init(&mut self)`. Omitting `init` selects the behavior algebra's empty
974/// initialization transition. The original impl and methods are preserved
975/// unchanged.
976#[proc_macro_attribute]
977#[allow(
978    clippy::too_many_lines,
979    reason = "validation and the one coherent Behavior expansion remain in one entry point"
980)]
981pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
982    let args = parse_macro_input!(args as BehaviorArgs);
983    let item = parse_macro_input!(item as ItemImpl);
984
985    if item.trait_.is_some() {
986        return Error::new_spanned(
987            &item,
988            "#[behavior] applies to an inherent impl, not a trait impl",
989        )
990        .to_compile_error()
991        .into();
992    }
993
994    let init = item.items.iter().find_map(|item| match item {
995        ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
996        _ => None,
997    });
998    let receive = item.items.iter().find_map(|item| match item {
999        ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
1000        _ => None,
1001    });
1002    let Some(receive) = receive else {
1003        return Error::new_spanned(
1004            &item.self_ty,
1005            "#[behavior] requires a receive(&mut self, from, message) method",
1006        )
1007        .to_compile_error()
1008        .into();
1009    };
1010    if let Err(error) = init
1011        .map_or(Ok(()), validate_receiver)
1012        .and_then(|()| validate_receiver(receive))
1013    {
1014        return error.to_compile_error().into();
1015    }
1016    if let Some(init) = init
1017        && init.sig.inputs.len() != 1
1018    {
1019        return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
1020            .to_compile_error()
1021            .into();
1022    }
1023    if receive.sig.inputs.len() != 3 {
1024        return Error::new_spanned(
1025            &receive.sig,
1026            "receive must accept exactly &mut self, from, and message",
1027        )
1028        .to_compile_error()
1029        .into();
1030    }
1031
1032    let BehaviorArgs {
1033        addr,
1034        message,
1035        sends,
1036        births,
1037        error,
1038    } = args;
1039    let self_ty = &item.self_ty;
1040    let Type::Path(self_path) = self_ty.as_ref() else {
1041        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1042            .to_compile_error()
1043            .into();
1044    };
1045    let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
1046        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1047            .to_compile_error()
1048            .into();
1049    };
1050    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1051    let behavior = match behavior_crate() {
1052        Ok(behavior) => behavior,
1053        Err(error) => return error.to_compile_error().into(),
1054    };
1055    let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
1056    let initialize = init.map_or_else(
1057        || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
1058        |_| quote!(<#self_ty>::init(self)),
1059    );
1060
1061    let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
1062    let (births_ty, births_items) = generate_births(births, self_name, &addr, &item, &behavior);
1063
1064    quote! {
1065        #sends_items
1066        #births_items
1067        #item
1068
1069        impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1070            type Addr = #addr;
1071            type Msg = #message;
1072        }
1073
1074        impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1075            type Protocol = Self;
1076            type Event = #behavior::User<#addr, #message>;
1077            type Sends = #sends_ty;
1078            type Ph = #behavior::Never;
1079            type Error = #error;
1080            type Birth = #births_ty;
1081
1082            fn init(
1083                &mut self,
1084                _: #behavior::InitializationTurn,
1085            ) -> #behavior::BehaviorActed<Self> {
1086                #initialize
1087            }
1088
1089            fn transition(
1090                &mut self,
1091                _: #behavior::ActiveTurn,
1092                event: Self::Event,
1093            ) -> #behavior::BehaviorActed<Self> {
1094                <#self_ty>::receive(self, event.from, event.message)
1095            }
1096        }
1097
1098        impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1099            type Base = Self;
1100
1101            fn base(&self) -> &Self {
1102                self
1103            }
1104        }
1105    }
1106    .into()
1107}
1108
1109/// Derive one pool worker's ordinary actor protocol and behavior from its
1110/// assignment transition.
1111///
1112/// This attribute owns syntax only. The generated send product is the existing
1113/// typed parent request carrying the opaque completion produced by
1114/// `Assignment::complete`; assignment authority and delivery remain owned by
1115/// the Behavior Actors pool protocol and the runtime interpreter.
1116#[proc_macro_attribute]
1117pub fn pool_worker(args: TokenStream, item: TokenStream) -> TokenStream {
1118    let PoolWorkerArgs { addr, result } = parse_macro_input!(args as PoolWorkerArgs);
1119    let mut item = parse_macro_input!(item as ItemImpl);
1120
1121    if item.trait_.is_some() {
1122        return Error::new_spanned(
1123            &item,
1124            "#[pool_worker] applies to an inherent impl, not a trait impl",
1125        )
1126        .to_compile_error()
1127        .into();
1128    }
1129
1130    let self_ty = item.self_ty.clone();
1131    if !matches!(self_ty.as_ref(), Type::Path(path) if path.qself.is_none()) {
1132        return Error::new_spanned(&self_ty, "#[pool_worker] requires a nominal worker type")
1133            .to_compile_error()
1134            .into();
1135    }
1136
1137    let actors = match actors_crate() {
1138        Ok(actors) => actors,
1139        Err(error) => return error.to_compile_error().into(),
1140    };
1141    let behavior = match behavior_crate() {
1142        Ok(behavior) => behavior,
1143        Err(error) => return error.to_compile_error().into(),
1144    };
1145    let assignment = {
1146        let mut transitions = item.items.iter_mut().filter_map(|item| match item {
1147            ImplItem::Fn(method) if method.sig.ident == "transition" => Some(method),
1148            _ => None,
1149        });
1150        let Some(transition) = transitions.next() else {
1151            return Error::new_spanned(
1152                &self_ty,
1153                "#[pool_worker] requires transition(&mut self, Assignment<Job>)",
1154            )
1155            .to_compile_error()
1156            .into();
1157        };
1158        if let Some(duplicate) = transitions.next() {
1159            return Error::new_spanned(
1160                &duplicate.sig,
1161                "#[pool_worker] accepts exactly one transition method",
1162            )
1163            .to_compile_error()
1164            .into();
1165        }
1166        let assignment = match pool_worker_assignment(transition) {
1167            Ok(assignment) => assignment,
1168            Err(error) => return error.to_compile_error().into(),
1169        };
1170        transition.sig.output = parse_quote!(-> #behavior::BehaviorActed<Self>);
1171        assignment
1172    };
1173    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1174
1175    quote! {
1176        #item
1177
1178        impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1179            type Addr = #addr;
1180            type Msg = #assignment;
1181        }
1182
1183        impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1184            type Protocol = Self;
1185            type Event = #behavior::User<#addr, #assignment>;
1186            type Sends = #behavior::InterpreterRequests<
1187                #behavior::ReportToParent<#actors::atomic::Completion<#result>>
1188            >;
1189            type Ph = #behavior::Never;
1190            type Error = #behavior::Never;
1191            type Birth = #behavior::NoBirths;
1192
1193            fn transition(
1194                &mut self,
1195                _: #behavior::ActiveTurn,
1196                event: Self::Event,
1197            ) -> #behavior::BehaviorActed<Self> {
1198                <#self_ty>::transition(self, event.message)
1199            }
1200        }
1201
1202        impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1203            type Base = Self;
1204
1205            fn base(&self) -> &Self {
1206                self
1207            }
1208        }
1209    }
1210    .into()
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215    use super::*;
1216
1217    fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
1218        if at == values.len() {
1219            output.push(values.join(", "));
1220            return;
1221        }
1222        for index in at..values.len() {
1223            values.swap(at, index);
1224            permutations(values, at + 1, output);
1225            values.swap(at, index);
1226        }
1227    }
1228
1229    #[test]
1230    fn every_argument_order_parses() {
1231        let mut declarations = [
1232            "addr = u64",
1233            "message = String",
1234            "sends = { first: Vec<u8>, second: Vec<u16> }",
1235            "births = { first: u8, second: u16 }",
1236            "error = String",
1237        ];
1238        let mut cases = Vec::new();
1239        permutations(&mut declarations, 0, &mut cases);
1240        assert_eq!(cases.len(), 120);
1241        for case in cases {
1242            syn::parse_str::<BehaviorArgs>(&case)
1243                .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
1244        }
1245    }
1246
1247    #[test]
1248    fn capability_omission_and_trailing_comma_parse() {
1249        syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
1250            .expect("only required protocol declarations are sufficient");
1251    }
1252
1253    #[test]
1254    fn invalid_declaration_states_are_rejected() {
1255        for case in [
1256            "message = ()",
1257            "addr = u8",
1258            "addr = u8, message = (), addr = u16",
1259            "addr = u8, message = (), unknown = u8",
1260            "addr = u8, message = (), sends = {}",
1261            "addr = u8, message = (), births = {}",
1262            "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
1263            "addr = u8, message = (), births = { same: u8, same: u16 }",
1264        ] {
1265            assert!(
1266                syn::parse_str::<BehaviorArgs>(case).is_err(),
1267                "invalid declaration parsed: `{case}`"
1268            );
1269        }
1270    }
1271
1272    #[test]
1273    fn pool_worker_arguments_require_one_address_and_result() {
1274        for valid in [
1275            "addr = u64, result = String",
1276            "result = String, addr = u64,",
1277        ] {
1278            syn::parse_str::<PoolWorkerArgs>(valid)
1279                .unwrap_or_else(|error| panic!("failed to parse `{valid}`: {error}"));
1280        }
1281        for invalid in [
1282            "addr = u64",
1283            "result = String",
1284            "addr = u64, result = String, addr = u32",
1285            "addr = u64, result = String, result = u8",
1286            "addr = u64, result = String, message = u8",
1287        ] {
1288            assert!(
1289                syn::parse_str::<PoolWorkerArgs>(invalid).is_err(),
1290                "invalid pool worker arguments parsed: `{invalid}`"
1291            );
1292        }
1293    }
1294
1295    #[test]
1296    fn pool_worker_transition_requires_the_domain_source_shape() {
1297        let valid: ItemImpl = syn::parse_str(
1298            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) \
1299             -> WorkerActed<Self> { result() } }",
1300        )
1301        .expect("valid inherent worker declaration parses");
1302        let method = valid
1303            .items
1304            .iter()
1305            .find_map(|item| match item {
1306                ImplItem::Fn(method) => Some(method),
1307                _ => None,
1308            })
1309            .expect("fixture declares one method");
1310        let assignment = pool_worker_assignment(method)
1311            .unwrap_or_else(|error| panic!("valid worker transition rejected: {error}"));
1312        assert_eq!(quote!(#assignment).to_string(), "Assignment < Job >");
1313
1314        for invalid in [
1315            "impl Worker { fn transition(self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1316            "impl Worker { async fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1317            "impl Worker { fn transition(&mut self, job: Job) -> WorkerActed<Self> { result() } }",
1318            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Job> { result() } }",
1319            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> Result<(), ()> { result() } }",
1320        ] {
1321            let declaration: ItemImpl = syn::parse_str(invalid)
1322                .unwrap_or_else(|error| panic!("invalid fixture did not parse: {error}"));
1323            let method = declaration
1324                .items
1325                .iter()
1326                .find_map(|item| match item {
1327                    ImplItem::Fn(method) => Some(method),
1328                    _ => None,
1329                })
1330                .expect("fixture declares one method");
1331            assert!(
1332                pool_worker_assignment(method).is_err(),
1333                "invalid pool worker transition was accepted: `{invalid}`"
1334            );
1335        }
1336    }
1337}