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_to_source(
741                self.#field,
742                host,
743            ).await {
744                #behavior::SourceCustody::Open(#field) => #field,
745                #behavior::SourceCustody::Closed(#field) => {
746                    return #behavior::SourceCustody::Closed(#settlements_name {
747                        #(#prior_fields: #prior_fields,)*
748                        #field: #field,
749                        #(#later_fields: self.#later_fields,)*
750                    });
751                }
752            };
753        }
754    });
755
756    let items = quote! {
757        #(pub enum #lane_names {})*
758
759        pub struct #name #generics {
760            #(pub #field_names: #field_types,)*
761        }
762
763        #[doc(hidden)]
764        pub struct #settlements_name #settlement_impl_generics #settlement_where_clause {
765            #(
766                pub #field_names: <#field_types as #behavior::SendSettlements>::Settlements,
767            )*
768        }
769
770        impl #classification_impl_generics #behavior::ClassifySettlement
771            for #settlements_name #classification_type_generics #classification_where_clause
772        {
773            fn settlement_status(&self) -> #behavior::SettlementStatus {
774                #combined_status
775            }
776        }
777
778        impl #custody_impl_generics
779            #behavior::SourceSettlementCustody<
780                __BombaySettlementHost,
781                __BombaySettlementEvent,
782            >
783            for #settlements_name #settlement_type_generics #custody_where_clause
784        {
785            fn offer_to_source(
786                self,
787                host: &mut __BombaySettlementHost,
788            ) -> impl ::core::future::Future<
789                Output = #behavior::SourceCustody<Self>,
790            > + ::core::marker::Send {
791                async move {
792                    #(#custody_fields)*
793                    #behavior::SourceCustody::Open(#settlements_name {
794                        #(#field_names: #field_names,)*
795                    })
796                }
797            }
798        }
799
800        /// Fluent, statically routed send-lane methods for this behavior's
801        /// generated action product.
802        pub trait #actions_name #action_trait_generics: ::core::marker::Sized {
803            #(#action_trait_methods)*
804        }
805
806        impl #actions_impl_generics #actions_name #type_generics
807            for #behavior::Actions<
808                __BombayAddress,
809                __BombayPh,
810                #name #type_generics,
811                __BombayBirth,
812            >
813            #actions_where_clause
814        {
815            #(#action_impl_methods)*
816        }
817
818        impl #effects_impl_generics #behavior::SendEffects for #name #type_generics
819            #effects_where_clause
820        {
821            fn empty() -> Self {
822                Self {
823                    #(#field_names: <#field_types as #behavior::SendEffects>::empty(),)*
824                }
825            }
826
827            fn append(&mut self, other: Self) {
828                #(
829                    <#field_types as #behavior::SendEffects>::append(
830                        &mut self.#field_names,
831                        other.#field_names,
832                    );
833                )*
834            }
835        }
836
837        impl #lawful_impl_generics #behavior::SendsFor<__BombayEvent>
838            for #name #type_generics #lawful_where_clause
839        {}
840
841        impl #settlement_impl_generics #behavior::SendSettlements
842            for #name #type_generics #settlement_where_clause
843        {
844            type Settlements = #settlements_name #settlement_type_generics;
845
846            fn unattempted(self) -> Self::Settlements {
847                #settlements_name {
848                    #(
849                        #field_names: <#field_types as #behavior::SendSettlements>::unattempted(
850                            self.#field_names,
851                        ),
852                    )*
853                }
854            }
855        }
856
857        #(#send_impls)*
858
859        impl #interpret_impl_generics
860            #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>
861            for #name #type_generics #interpret_where_clause
862        {
863            fn interpret(
864                self,
865                interpreter: &mut __BombayInterpreter,
866            ) -> impl ::core::future::Future<
867                Output = #behavior::Interpretation<Self::Settlements>,
868            > + ::core::marker::Send {
869                async move {
870                    #interpretation
871                }
872            }
873        }
874    };
875    (quote!(#name #type_generics), items)
876}
877
878fn generate_births(
879    product: Option<BirthsSpec>,
880    actor: &Ident,
881    _addr: &Type,
882    item: &ItemImpl,
883    behavior: &TokenStream2,
884) -> (TokenStream2, TokenStream2) {
885    let product = match product {
886        None => return (quote!(#behavior::NoBirths), quote!()),
887        Some(BirthsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
888        Some(BirthsSpec::Generated(product)) => product,
889    };
890    let name = format_ident!("{}Children", actor);
891    let roles_name = format_ident!("{}Child", actor);
892    let role_names = product
893        .fields
894        .iter()
895        .map(|field| lane_name(&name, &field.name))
896        .collect::<Vec<_>>();
897    let role_values = product
898        .fields
899        .iter()
900        .map(|field| format_ident!("{}", pascal_name(&field.name)))
901        .collect::<Vec<_>>();
902    let declared_child_types = product
903        .fields
904        .iter()
905        .map(|field| &field.ty)
906        .collect::<Vec<_>>();
907    let role_positions = (0..product.fields.len())
908        .map(|index| {
909            (0..(product.fields.len() - index - 1)).fold(
910                quote!(#behavior::ChildHead),
911                |position, _| quote!(#behavior::ChildTail<#position>),
912            )
913        })
914        .collect::<Vec<_>>();
915    let product_generics = product_generics(item, &product.fields);
916    let (_, type_generics, _) = product_generics.split_for_impl();
917    let generics = &product_generics;
918    let parent = &item.self_ty;
919    let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
920    let choice = match declared_child_types.as_slice() {
921        [child] => quote!(#child),
922        children => children.iter().fold(
923            quote!(#behavior::Never),
924            |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
925        ),
926    };
927    (
928        quote!(#behavior::Births<#name #type_generics>),
929        quote! {
930            pub type #name #generics = #choice;
931
932            #(
933                #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
934                pub struct #role_names;
935
936                impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
937                    #parent_where_clause
938                {
939                    type Child = #declared_child_types;
940                    type Position = #role_positions;
941                }
942
943                impl #parent_impl_generics #behavior::ChildOccurrence<#parent> for #role_names
944                    #parent_where_clause
945                {
946                    type Resolution = #behavior::DeclaredChildOccurrence;
947                }
948            )*
949
950            pub struct #roles_name;
951
952            #[allow(
953                non_upper_case_globals,
954                reason = "generated role values retain the application-authored field spelling"
955            )]
956            impl #roles_name {
957                #(pub const #role_values: #role_names = #role_names;)*
958            }
959
960        },
961    )
962}
963
964/// Generate the mechanical `Behavior` implementation for a normal inherent
965/// impl containing `receive(&mut self, from, message)` and, optionally,
966/// `init(&mut self)`. Omitting `init` selects the behavior algebra's empty
967/// initialization transition. The original impl and methods are preserved
968/// unchanged.
969#[proc_macro_attribute]
970#[allow(
971    clippy::too_many_lines,
972    reason = "validation and the one coherent Behavior expansion remain in one entry point"
973)]
974pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
975    let args = parse_macro_input!(args as BehaviorArgs);
976    let item = parse_macro_input!(item as ItemImpl);
977
978    if item.trait_.is_some() {
979        return Error::new_spanned(
980            &item,
981            "#[behavior] applies to an inherent impl, not a trait impl",
982        )
983        .to_compile_error()
984        .into();
985    }
986
987    let init = item.items.iter().find_map(|item| match item {
988        ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
989        _ => None,
990    });
991    let receive = item.items.iter().find_map(|item| match item {
992        ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
993        _ => None,
994    });
995    let Some(receive) = receive else {
996        return Error::new_spanned(
997            &item.self_ty,
998            "#[behavior] requires a receive(&mut self, from, message) method",
999        )
1000        .to_compile_error()
1001        .into();
1002    };
1003    if let Err(error) = init
1004        .map_or(Ok(()), validate_receiver)
1005        .and_then(|()| validate_receiver(receive))
1006    {
1007        return error.to_compile_error().into();
1008    }
1009    if let Some(init) = init
1010        && init.sig.inputs.len() != 1
1011    {
1012        return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
1013            .to_compile_error()
1014            .into();
1015    }
1016    if receive.sig.inputs.len() != 3 {
1017        return Error::new_spanned(
1018            &receive.sig,
1019            "receive must accept exactly &mut self, from, and message",
1020        )
1021        .to_compile_error()
1022        .into();
1023    }
1024
1025    let BehaviorArgs {
1026        addr,
1027        message,
1028        sends,
1029        births,
1030        error,
1031    } = args;
1032    let self_ty = &item.self_ty;
1033    let Type::Path(self_path) = self_ty.as_ref() else {
1034        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1035            .to_compile_error()
1036            .into();
1037    };
1038    let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
1039        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1040            .to_compile_error()
1041            .into();
1042    };
1043    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1044    let behavior = match behavior_crate() {
1045        Ok(behavior) => behavior,
1046        Err(error) => return error.to_compile_error().into(),
1047    };
1048    let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
1049    let initialize = init.map_or_else(
1050        || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
1051        |_| quote!(<#self_ty>::init(self)),
1052    );
1053
1054    let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
1055    let (births_ty, births_items) = generate_births(births, self_name, &addr, &item, &behavior);
1056
1057    quote! {
1058        #sends_items
1059        #births_items
1060        #item
1061
1062        impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1063            type Addr = #addr;
1064            type Msg = #message;
1065        }
1066
1067        impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1068            type Protocol = Self;
1069            type Event = #behavior::User<#addr, #message>;
1070            type Sends = #sends_ty;
1071            type Ph = #behavior::Never;
1072            type Error = #error;
1073            type Birth = #births_ty;
1074
1075            fn init(
1076                &mut self,
1077                _: #behavior::InitializationTurn,
1078            ) -> #behavior::BehaviorActed<Self> {
1079                #initialize
1080            }
1081
1082            fn transition(
1083                &mut self,
1084                _: #behavior::ActiveTurn,
1085                event: Self::Event,
1086            ) -> #behavior::BehaviorActed<Self> {
1087                <#self_ty>::receive(self, event.from, event.message)
1088            }
1089        }
1090
1091        impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1092            type Base = Self;
1093
1094            fn base(&self) -> &Self {
1095                self
1096            }
1097        }
1098    }
1099    .into()
1100}
1101
1102/// Derive one pool worker's ordinary actor protocol and behavior from its
1103/// assignment transition.
1104///
1105/// This attribute owns syntax only. The generated send product is the existing
1106/// typed parent request carrying the opaque completion produced by
1107/// `Assignment::complete`; assignment authority and delivery remain owned by
1108/// the Behavior Actors pool protocol and the runtime interpreter.
1109#[proc_macro_attribute]
1110pub fn pool_worker(args: TokenStream, item: TokenStream) -> TokenStream {
1111    let PoolWorkerArgs { addr, result } = parse_macro_input!(args as PoolWorkerArgs);
1112    let mut item = parse_macro_input!(item as ItemImpl);
1113
1114    if item.trait_.is_some() {
1115        return Error::new_spanned(
1116            &item,
1117            "#[pool_worker] applies to an inherent impl, not a trait impl",
1118        )
1119        .to_compile_error()
1120        .into();
1121    }
1122
1123    let self_ty = item.self_ty.clone();
1124    if !matches!(self_ty.as_ref(), Type::Path(path) if path.qself.is_none()) {
1125        return Error::new_spanned(&self_ty, "#[pool_worker] requires a nominal worker type")
1126            .to_compile_error()
1127            .into();
1128    }
1129
1130    let actors = match actors_crate() {
1131        Ok(actors) => actors,
1132        Err(error) => return error.to_compile_error().into(),
1133    };
1134    let assignment = {
1135        let mut transitions = item.items.iter_mut().filter_map(|item| match item {
1136            ImplItem::Fn(method) if method.sig.ident == "transition" => Some(method),
1137            _ => None,
1138        });
1139        let Some(transition) = transitions.next() else {
1140            return Error::new_spanned(
1141                &self_ty,
1142                "#[pool_worker] requires transition(&mut self, Assignment<Job>)",
1143            )
1144            .to_compile_error()
1145            .into();
1146        };
1147        if let Some(duplicate) = transitions.next() {
1148            return Error::new_spanned(
1149                &duplicate.sig,
1150                "#[pool_worker] accepts exactly one transition method",
1151            )
1152            .to_compile_error()
1153            .into();
1154        }
1155        let assignment = match pool_worker_assignment(transition) {
1156            Ok(assignment) => assignment,
1157            Err(error) => return error.to_compile_error().into(),
1158        };
1159        transition.sig.output = parse_quote!(-> #actors::BehaviorActed<Self>);
1160        assignment
1161    };
1162    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1163
1164    quote! {
1165        #item
1166
1167        impl #impl_generics #actors::Protocol for #self_ty #where_clause {
1168            type Addr = #addr;
1169            type Msg = #assignment;
1170        }
1171
1172        impl #impl_generics #actors::Behavior for #self_ty #where_clause {
1173            type Protocol = Self;
1174            type Event = #actors::User<#addr, #assignment>;
1175            type Sends = #actors::InterpreterRequests<
1176                #actors::ReportToParent<#actors::atomic::Completion<#result>>
1177            >;
1178            type Ph = #actors::Never;
1179            type Error = #actors::Never;
1180            type Birth = #actors::NoBirths;
1181
1182            fn transition(
1183                &mut self,
1184                _: #actors::ActiveTurn,
1185                event: Self::Event,
1186            ) -> #actors::BehaviorActed<Self> {
1187                <#self_ty>::transition(self, event.message)
1188            }
1189        }
1190
1191        impl #impl_generics #actors::BehaviorBase for #self_ty #where_clause {
1192            type Base = Self;
1193
1194            fn base(&self) -> &Self {
1195                self
1196            }
1197        }
1198    }
1199    .into()
1200}
1201
1202#[cfg(test)]
1203mod tests {
1204    use super::*;
1205
1206    fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
1207        if at == values.len() {
1208            output.push(values.join(", "));
1209            return;
1210        }
1211        for index in at..values.len() {
1212            values.swap(at, index);
1213            permutations(values, at + 1, output);
1214            values.swap(at, index);
1215        }
1216    }
1217
1218    #[test]
1219    fn every_argument_order_parses() {
1220        let mut declarations = [
1221            "addr = u64",
1222            "message = String",
1223            "sends = { first: Vec<u8>, second: Vec<u16> }",
1224            "births = { first: u8, second: u16 }",
1225            "error = String",
1226        ];
1227        let mut cases = Vec::new();
1228        permutations(&mut declarations, 0, &mut cases);
1229        assert_eq!(cases.len(), 120);
1230        for case in cases {
1231            syn::parse_str::<BehaviorArgs>(&case)
1232                .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
1233        }
1234    }
1235
1236    #[test]
1237    fn capability_omission_and_trailing_comma_parse() {
1238        syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
1239            .expect("only required protocol declarations are sufficient");
1240    }
1241
1242    #[test]
1243    fn invalid_declaration_states_are_rejected() {
1244        for case in [
1245            "message = ()",
1246            "addr = u8",
1247            "addr = u8, message = (), addr = u16",
1248            "addr = u8, message = (), unknown = u8",
1249            "addr = u8, message = (), sends = {}",
1250            "addr = u8, message = (), births = {}",
1251            "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
1252            "addr = u8, message = (), births = { same: u8, same: u16 }",
1253        ] {
1254            assert!(
1255                syn::parse_str::<BehaviorArgs>(case).is_err(),
1256                "invalid declaration parsed: `{case}`"
1257            );
1258        }
1259    }
1260
1261    #[test]
1262    fn pool_worker_arguments_require_one_address_and_result() {
1263        for valid in [
1264            "addr = u64, result = String",
1265            "result = String, addr = u64,",
1266        ] {
1267            syn::parse_str::<PoolWorkerArgs>(valid)
1268                .unwrap_or_else(|error| panic!("failed to parse `{valid}`: {error}"));
1269        }
1270        for invalid in [
1271            "addr = u64",
1272            "result = String",
1273            "addr = u64, result = String, addr = u32",
1274            "addr = u64, result = String, result = u8",
1275            "addr = u64, result = String, message = u8",
1276        ] {
1277            assert!(
1278                syn::parse_str::<PoolWorkerArgs>(invalid).is_err(),
1279                "invalid pool worker arguments parsed: `{invalid}`"
1280            );
1281        }
1282    }
1283
1284    #[test]
1285    fn pool_worker_transition_requires_the_domain_source_shape() {
1286        let valid: ItemImpl = syn::parse_str(
1287            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) \
1288             -> WorkerActed<Self> { result() } }",
1289        )
1290        .expect("valid inherent worker declaration parses");
1291        let method = valid
1292            .items
1293            .iter()
1294            .find_map(|item| match item {
1295                ImplItem::Fn(method) => Some(method),
1296                _ => None,
1297            })
1298            .expect("fixture declares one method");
1299        let assignment = pool_worker_assignment(method)
1300            .unwrap_or_else(|error| panic!("valid worker transition rejected: {error}"));
1301        assert_eq!(quote!(#assignment).to_string(), "Assignment < Job >");
1302
1303        for invalid in [
1304            "impl Worker { fn transition(self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1305            "impl Worker { async fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1306            "impl Worker { fn transition(&mut self, job: Job) -> WorkerActed<Self> { result() } }",
1307            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Job> { result() } }",
1308            "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> Result<(), ()> { result() } }",
1309        ] {
1310            let declaration: ItemImpl = syn::parse_str(invalid)
1311                .unwrap_or_else(|error| panic!("invalid fixture did not parse: {error}"));
1312            let method = declaration
1313                .items
1314                .iter()
1315                .find_map(|item| match item {
1316                    ImplItem::Fn(method) => Some(method),
1317                    _ => None,
1318                })
1319                .expect("fixture declares one method");
1320            assert!(
1321                pool_worker_assignment(method).is_err(),
1322                "invalid pool worker transition was accepted: `{invalid}`"
1323            );
1324        }
1325    }
1326}