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, GenericParam, Generics, Ident, ImplItem, ItemImpl, Result, ReturnType, Token,
14    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
78#[cfg(test)]
79mod crate_resolution_tests {
80    use super::*;
81
82    #[test]
83    fn facade_default_library_name_and_explicit_rename_resolve_distinctly() {
84        assert_eq!(
85            facade_crate_path(FoundCrate::Name("bombay_rs".to_owned())).to_string(),
86            ":: bombay"
87        );
88        assert_eq!(
89            facade_crate_path(FoundCrate::Name("runtime".to_owned())).to_string(),
90            ":: runtime"
91        );
92    }
93}
94
95struct NamedField {
96    name: Ident,
97    ty: Type,
98}
99
100struct NamedProduct {
101    fields: Vec<NamedField>,
102}
103
104enum SendsSpec {
105    Existing(Type),
106    Generated(NamedProduct),
107}
108
109enum BirthsSpec {
110    Existing(Type),
111    Generated(NamedProduct),
112}
113
114fn parse_product(input: ParseStream) -> Result<NamedProduct> {
115    let content;
116    braced!(content in input);
117    let mut fields = Vec::new();
118    let mut names = std::collections::BTreeSet::new();
119    while !content.is_empty() {
120        let name: Ident = content.parse()?;
121        if !names.insert(name.to_string()) {
122            return Err(Error::new_spanned(name, "duplicate product lane"));
123        }
124        content.parse::<Token![:]>()?;
125        fields.push(NamedField {
126            name,
127            ty: content.parse()?,
128        });
129        if content.peek(Token![,]) {
130            content.parse::<Token![,]>()?;
131        } else if !content.is_empty() {
132            return Err(content.error("expected `,` between product lanes"));
133        }
134    }
135    if fields.is_empty() {
136        return Err(input.error("empty products must be omitted"));
137    }
138    Ok(NamedProduct { fields })
139}
140
141struct BehaviorArgs {
142    addr: Type,
143    message: Type,
144    sends: Option<SendsSpec>,
145    births: Option<BirthsSpec>,
146    error: Option<Type>,
147}
148
149impl Parse for BehaviorArgs {
150    fn parse(input: ParseStream) -> Result<Self> {
151        let mut addr = None;
152        let mut message = None;
153        let mut sends = None;
154        let mut births = None;
155        let mut error = None;
156        while !input.is_empty() {
157            let key: Ident = input.parse()?;
158            input.parse::<Token![=]>()?;
159            match key.to_string().as_str() {
160                "addr" if addr.is_none() => addr = Some(input.parse()?),
161                "message" if message.is_none() => message = Some(input.parse()?),
162                "sends" if sends.is_none() => {
163                    sends = Some(if input.peek(syn::token::Brace) {
164                        SendsSpec::Generated(parse_product(input)?)
165                    } else {
166                        SendsSpec::Existing(input.parse()?)
167                    });
168                }
169                "births" if births.is_none() => {
170                    births = Some(if input.peek(syn::token::Brace) {
171                        BirthsSpec::Generated(parse_product(input)?)
172                    } else {
173                        BirthsSpec::Existing(input.parse()?)
174                    });
175                }
176                "error" if error.is_none() => error = Some(input.parse()?),
177                "addr" | "message" | "sends" | "births" | "error" => {
178                    return Err(Error::new_spanned(key, "duplicate behavior argument"));
179                }
180                _ => return Err(Error::new_spanned(key, "unknown behavior argument")),
181            }
182            if input.peek(Token![,]) {
183                input.parse::<Token![,]>()?;
184            } else if !input.is_empty() {
185                return Err(input.error("expected `,` between behavior arguments"));
186            }
187        }
188        Ok(Self {
189            addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
190            message: message.ok_or_else(|| input.error("missing `message`"))?,
191            sends,
192            births,
193            error,
194        })
195    }
196}
197
198fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
199    let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
200        return Err(Error::new_spanned(
201            &method.sig,
202            "behavior methods must begin with &mut self",
203        ));
204    };
205    if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
206        return Err(Error::new_spanned(
207            receiver,
208            "behavior methods must begin with &mut self",
209        ));
210    }
211    if method.sig.constness.is_some()
212        || method.sig.asyncness.is_some()
213        || matches!(method.sig.safety, syn::Safety::Unsafe(_))
214        || !method.sig.generics.params.is_empty()
215    {
216        return Err(Error::new_spanned(
217            &method.sig,
218            "behavior init and receive methods must be synchronous, safe, and non-generic",
219        ));
220    }
221    if matches!(method.sig.output, ReturnType::Default) {
222        return Err(Error::new_spanned(
223            &method.sig,
224            "behavior methods must declare their complete Actions result type",
225        ));
226    }
227    Ok(())
228}
229
230fn pascal_name(field: &Ident) -> String {
231    field
232        .to_string()
233        .split('_')
234        .filter(|part| !part.is_empty())
235        .map(|part| {
236            let mut chars = part.chars();
237            chars.next().map_or_else(String::new, |first| {
238                first.to_uppercase().chain(chars).collect()
239            })
240        })
241        .collect::<String>()
242}
243
244fn lane_name(product: &Ident, field: &Ident) -> Ident {
245    let lane = pascal_name(field);
246    format_ident!("{}{}", product, lane)
247}
248
249#[derive(Default)]
250struct GenericUses {
251    identifiers: std::collections::BTreeSet<String>,
252    lifetimes: std::collections::BTreeSet<String>,
253}
254
255impl<'ast> Visit<'ast> for GenericUses {
256    fn visit_ident(&mut self, ident: &'ast Ident) {
257        self.identifiers.insert(ident.to_string());
258    }
259
260    fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
261        self.lifetimes.insert(lifetime.ident.to_string());
262    }
263}
264
265fn product_generics(item: &ItemImpl, fields: &[NamedField]) -> Generics {
266    let mut uses = GenericUses::default();
267    for field in fields {
268        uses.visit_type(&field.ty);
269    }
270    let mut generics = item.generics.clone();
271    generics.params = generics
272        .params
273        .into_iter()
274        .filter(|parameter| match parameter {
275            GenericParam::Lifetime(parameter) => uses
276                .lifetimes
277                .contains(&parameter.lifetime.ident.to_string()),
278            GenericParam::Type(parameter) => {
279                uses.identifiers.contains(&parameter.ident.to_string())
280            }
281            GenericParam::Const(parameter) => {
282                uses.identifiers.contains(&parameter.ident.to_string())
283            }
284        })
285        .collect();
286    generics.where_clause = None;
287    generics
288}
289
290#[allow(
291    clippy::too_many_lines,
292    reason = "the generated nominal product keeps every structural trait implementation adjacent"
293)]
294fn generate_sends(
295    product: Option<SendsSpec>,
296    actor: &Ident,
297    item: &ItemImpl,
298    behavior: &TokenStream2,
299) -> (TokenStream2, TokenStream2) {
300    let product = match product {
301        None => return (quote!(#behavior::NoSends), quote!()),
302        Some(SendsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
303        Some(SendsSpec::Generated(product)) => product,
304    };
305    let name = format_ident!("{}Sends", actor);
306    let field_names: Vec<_> = product.fields.iter().map(|field| &field.name).collect();
307    let field_types: Vec<_> = product.fields.iter().map(|field| &field.ty).collect();
308    let lane_names: Vec<_> = field_names
309        .iter()
310        .map(|field| lane_name(&name, field))
311        .collect();
312    let actions_name = format_ident!("{}Actions", actor);
313    let action_methods: Vec<_> = field_names
314        .iter()
315        .map(|field| {
316            let field = field.to_string();
317            format_ident!("send_{}", field.trim_start_matches("r#"))
318        })
319        .collect();
320    let product_generics = product_generics(item, &product.fields);
321    let (_, type_generics, _) = product_generics.split_for_impl();
322    let generics = &product_generics;
323    let actor_type = &item.self_ty;
324    let mut action_trait_generics = product_generics.clone();
325    action_trait_generics
326        .make_where_clause()
327        .predicates
328        .push(parse_quote!(#actor_type: ::core::marker::Sized));
329
330    let action_trait_methods =
331        action_methods
332            .iter()
333            .zip(lane_names.iter())
334            .map(|(method, lane)| {
335                quote! {
336                    /// Append one value to this generated semantic send lane.
337                    #[must_use]
338                    fn #method<__BombayInput>(self, input: __BombayInput) -> Self
339                    where
340                        Self: #behavior::AppendSend<__BombayInput, #lane>;
341                }
342            });
343
344    let action_impl_methods = action_methods
345        .iter()
346        .zip(lane_names.iter())
347        .map(|(method, lane)| {
348            quote! {
349                fn #method<__BombayInput>(self, input: __BombayInput) -> Self
350                where
351                    Self: #behavior::AppendSend<__BombayInput, #lane>,
352                {
353                    <Self as #behavior::AppendSend<__BombayInput, #lane>>::append_send(
354                        self,
355                        input,
356                    )
357                }
358            }
359        });
360
361    let mut actions_impl_generics = product_generics.clone();
362    actions_impl_generics
363        .params
364        .push(parse_quote!(__BombayAddress));
365    actions_impl_generics.params.push(parse_quote!(__BombayPh));
366    actions_impl_generics
367        .params
368        .push(parse_quote!(__BombayBirth));
369    actions_impl_generics
370        .make_where_clause()
371        .predicates
372        .push(parse_quote!(__BombayAddress: #behavior::Address));
373    actions_impl_generics
374        .make_where_clause()
375        .predicates
376        .push(parse_quote!(__BombayBirth: #behavior::BirthMode));
377    actions_impl_generics
378        .make_where_clause()
379        .predicates
380        .push(parse_quote!(#actor_type: ::core::marker::Sized));
381    let (actions_impl_generics, _, actions_where_clause) = actions_impl_generics.split_for_impl();
382
383    let send_impls = lane_names
384        .iter()
385        .zip(field_names.iter())
386        .zip(field_types.iter())
387        .map(|((lane, field), field_ty)| {
388            let mut generics = product_generics.clone();
389            generics.params.push(parse_quote!(__BombayInput));
390            generics.make_where_clause().predicates.push(parse_quote!(
391                #field_ty: #behavior::SendInput<__BombayInput, #behavior::Own>
392            ));
393            let (impl_generics, _, where_clause) = generics.split_for_impl();
394            quote! {
395                impl #impl_generics #behavior::SendInput<__BombayInput, #lane>
396                    for #name #type_generics #where_clause
397                {
398                    fn emit(&mut self, input: __BombayInput) {
399                        <#field_ty as #behavior::SendInput<
400                            __BombayInput,
401                            #behavior::Own,
402                        >>::emit(&mut self.#field, input);
403                    }
404                }
405            }
406        });
407
408    let mut effects_generics = product_generics.clone();
409    for field_ty in &field_types {
410        effects_generics
411            .make_where_clause()
412            .predicates
413            .push(parse_quote!(#field_ty: #behavior::SendEffects));
414    }
415    let (effects_impl_generics, _, effects_where_clause) = effects_generics.split_for_impl();
416
417    let mut lawful_generics = product_generics.clone();
418    lawful_generics.params.push(parse_quote!(__BombayEvent));
419    for field_ty in &field_types {
420        lawful_generics
421            .make_where_clause()
422            .predicates
423            .push(parse_quote!(#field_ty: #behavior::SendsFor<__BombayEvent>));
424    }
425    let (lawful_impl_generics, _, lawful_where_clause) = lawful_generics.split_for_impl();
426
427    let mut interpret_generics = product_generics.clone();
428    interpret_generics
429        .params
430        .push(parse_quote!(__BombayInterpreter));
431    interpret_generics
432        .params
433        .push(parse_quote!(__BombayRootEvent));
434    interpret_generics.params.push(parse_quote!(__BombayPath));
435    interpret_generics
436        .make_where_clause()
437        .predicates
438        .push(parse_quote!(__BombayInterpreter: #behavior::SendInterpreter));
439    for field_ty in &field_types {
440        interpret_generics
441            .make_where_clause()
442            .predicates
443            .push(parse_quote!(
444                #field_ty: #behavior::InterpretSends<
445                    __BombayInterpreter,
446                    __BombayRootEvent,
447                    __BombayPath,
448                >
449            ));
450    }
451    interpret_generics
452        .make_where_clause()
453        .predicates
454        .push(parse_quote!(#name #type_generics: ::core::marker::Send));
455    let (interpret_impl_generics, _, interpret_where_clause) = interpret_generics.split_for_impl();
456
457    let items = quote! {
458        #(pub enum #lane_names {})*
459
460        pub struct #name #generics {
461            #(pub #field_names: #field_types,)*
462        }
463
464        /// Fluent, statically routed send-lane methods for this behavior's
465        /// generated action product.
466        pub trait #actions_name #action_trait_generics: ::core::marker::Sized {
467            #(#action_trait_methods)*
468        }
469
470        impl #actions_impl_generics #actions_name #type_generics
471            for #behavior::Actions<
472                __BombayAddress,
473                __BombayPh,
474                #name #type_generics,
475                __BombayBirth,
476            >
477            #actions_where_clause
478        {
479            #(#action_impl_methods)*
480        }
481
482        impl #effects_impl_generics #behavior::SendEffects for #name #type_generics
483            #effects_where_clause
484        {
485            fn empty() -> Self {
486                Self {
487                    #(#field_names: <#field_types as #behavior::SendEffects>::empty(),)*
488                }
489            }
490
491            fn append(&mut self, other: Self) {
492                #(
493                    <#field_types as #behavior::SendEffects>::append(
494                        &mut self.#field_names,
495                        other.#field_names,
496                    );
497                )*
498            }
499        }
500
501        impl #lawful_impl_generics #behavior::SendsFor<__BombayEvent>
502            for #name #type_generics #lawful_where_clause
503        {}
504
505        #(#send_impls)*
506
507        impl #interpret_impl_generics
508            #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>
509            for #name #type_generics #interpret_where_clause
510        {
511            fn interpret(
512                self,
513                interpreter: &mut __BombayInterpreter,
514            ) -> impl ::core::future::Future<
515                Output = ::core::result::Result<(), __BombayInterpreter::Error>,
516            > + ::core::marker::Send {
517                async move {
518                    #(
519                        <#field_types as #behavior::InterpretSends<
520                            __BombayInterpreter,
521                            __BombayRootEvent,
522                            __BombayPath,
523                        >>::interpret(self.#field_names, interpreter).await?;
524                    )*
525                    ::core::result::Result::Ok(())
526                }
527            }
528        }
529    };
530    (quote!(#name #type_generics), items)
531}
532
533fn generate_births(
534    product: Option<BirthsSpec>,
535    actor: &Ident,
536    addr: &Type,
537    item: &ItemImpl,
538    behavior: &TokenStream2,
539) -> (TokenStream2, TokenStream2) {
540    let product = match product {
541        None => return (quote!(#behavior::NoBirths), quote!()),
542        Some(BirthsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
543        Some(BirthsSpec::Generated(product)) => product,
544    };
545    let name = format_ident!("{}Children", actor);
546    let roles_name = format_ident!("{}Child", actor);
547    let routes_name = format_ident!("{}ChildrenRoutes", actor);
548    let field_names = product
549        .fields
550        .iter()
551        .map(|field| &field.name)
552        .collect::<Vec<_>>();
553    let role_names = product
554        .fields
555        .iter()
556        .map(|field| lane_name(&name, &field.name))
557        .collect::<Vec<_>>();
558    let role_values = product
559        .fields
560        .iter()
561        .map(|field| format_ident!("{}", pascal_name(&field.name)))
562        .collect::<Vec<_>>();
563    let declared_child_types = product
564        .fields
565        .iter()
566        .map(|field| &field.ty)
567        .collect::<Vec<_>>();
568    let role_positions = (0..product.fields.len())
569        .map(|index| {
570            (0..(product.fields.len() - index - 1)).fold(
571                quote!(#behavior::ChildHead),
572                |position, _| quote!(#behavior::ChildTail<#position>),
573            )
574        })
575        .collect::<Vec<_>>();
576    let child_types = product.fields.iter().map(|field| &field.ty);
577    let product_generics = product_generics(item, &product.fields);
578    let (impl_generics, type_generics, where_clause) = product_generics.split_for_impl();
579    let generics = &product_generics;
580    let parent = &item.self_ty;
581    let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
582    let choice = child_types.fold(
583        quote!(#behavior::Never),
584        |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
585    );
586    (
587        quote!(#behavior::Births<#name #type_generics>),
588        quote! {
589            pub type #name #generics = #choice;
590
591            #(
592                #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
593                pub struct #role_names;
594
595                impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
596                    #parent_where_clause
597                {
598                    type Child = #declared_child_types;
599                    type Position = #role_positions;
600                }
601            )*
602
603            pub struct #roles_name;
604
605            #[allow(non_upper_case_globals)]
606            impl #roles_name {
607                #(pub const #role_values: #role_names = #role_names;)*
608            }
609
610            pub struct #routes_name #generics {
611                #(
612                    pub #field_names: #behavior::ChildRoute<#declared_child_types, #role_names>,
613                )*
614            }
615
616            impl #impl_generics #routes_name #type_generics #where_clause {
617                #[must_use]
618                pub fn new(
619                    #(#field_names: <#addr as #behavior::Address>::Nonce,)*
620                ) -> Self {
621                    Self {
622                        #(
623                            #field_names: #behavior::ChildRoute::new(#field_names),
624                        )*
625                    }
626                }
627            }
628        },
629    )
630}
631
632/// Generate the mechanical `Behavior` implementation for a normal inherent
633/// impl containing `receive(&mut self, from, message)` and, optionally,
634/// `init(&mut self)`. Omitting `init` selects the behavior algebra's empty
635/// initialization transition. The original impl and methods are preserved
636/// unchanged.
637#[proc_macro_attribute]
638#[allow(
639    clippy::too_many_lines,
640    reason = "validation and the one coherent Behavior expansion remain in one entry point"
641)]
642pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
643    let args = parse_macro_input!(args as BehaviorArgs);
644    let item = parse_macro_input!(item as ItemImpl);
645
646    if item.trait_.is_some() {
647        return Error::new_spanned(
648            &item,
649            "#[behavior] applies to an inherent impl, not a trait impl",
650        )
651        .to_compile_error()
652        .into();
653    }
654
655    let init = item.items.iter().find_map(|item| match item {
656        ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
657        _ => None,
658    });
659    let receive = item.items.iter().find_map(|item| match item {
660        ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
661        _ => None,
662    });
663    let Some(receive) = receive else {
664        return Error::new_spanned(
665            &item.self_ty,
666            "#[behavior] requires a receive(&mut self, from, message) method",
667        )
668        .to_compile_error()
669        .into();
670    };
671    if let Err(error) = init
672        .map_or(Ok(()), validate_receiver)
673        .and_then(|()| validate_receiver(receive))
674    {
675        return error.to_compile_error().into();
676    }
677    if let Some(init) = init
678        && init.sig.inputs.len() != 1
679    {
680        return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
681            .to_compile_error()
682            .into();
683    }
684    if receive.sig.inputs.len() != 3 {
685        return Error::new_spanned(
686            &receive.sig,
687            "receive must accept exactly &mut self, from, and message",
688        )
689        .to_compile_error()
690        .into();
691    }
692
693    let BehaviorArgs {
694        addr,
695        message,
696        sends,
697        births,
698        error,
699    } = args;
700    let self_ty = &item.self_ty;
701    let Type::Path(self_path) = self_ty.as_ref() else {
702        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
703            .to_compile_error()
704            .into();
705    };
706    let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
707        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
708            .to_compile_error()
709            .into();
710    };
711    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
712    let behavior = match behavior_crate() {
713        Ok(behavior) => behavior,
714        Err(error) => return error.to_compile_error().into(),
715    };
716    let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
717    let initialize = init.map_or_else(
718        || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
719        |_| quote!(<#self_ty>::init(self)),
720    );
721
722    let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
723    let (births_ty, births_items) = generate_births(births, self_name, &addr, &item, &behavior);
724
725    quote! {
726        #sends_items
727        #births_items
728        #item
729
730        impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
731            type Addr = #addr;
732            type Msg = #message;
733        }
734
735        impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
736            type Protocol = Self;
737            type Event = #behavior::User<#addr, #message>;
738            type Sends = #sends_ty;
739            type Ph = #behavior::Never;
740            type Error = #error;
741            type Birth = #births_ty;
742
743            fn init(
744                &mut self,
745                _: #behavior::InitializationTurn,
746            ) -> #behavior::BehaviorActed<Self> {
747                #initialize
748            }
749
750            fn transition(
751                &mut self,
752                _: #behavior::ActiveTurn,
753                event: Self::Event,
754            ) -> #behavior::BehaviorActed<Self> {
755                <#self_ty>::receive(self, event.from, event.message)
756            }
757        }
758
759        impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
760            type Base = Self;
761
762            fn base(&self) -> &Self {
763                self
764            }
765        }
766    }
767    .into()
768}
769
770#[cfg(test)]
771mod tests {
772    use super::*;
773
774    fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
775        if at == values.len() {
776            output.push(values.join(", "));
777            return;
778        }
779        for index in at..values.len() {
780            values.swap(at, index);
781            permutations(values, at + 1, output);
782            values.swap(at, index);
783        }
784    }
785
786    #[test]
787    fn every_argument_order_parses() {
788        let mut declarations = [
789            "addr = u64",
790            "message = String",
791            "sends = { first: Vec<u8>, second: Vec<u16> }",
792            "births = { first: u8, second: u16 }",
793            "error = String",
794        ];
795        let mut cases = Vec::new();
796        permutations(&mut declarations, 0, &mut cases);
797        assert_eq!(cases.len(), 120);
798        for case in cases {
799            syn::parse_str::<BehaviorArgs>(&case)
800                .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
801        }
802    }
803
804    #[test]
805    fn capability_omission_and_trailing_comma_parse() {
806        syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
807            .expect("only required protocol declarations are sufficient");
808    }
809
810    #[test]
811    fn invalid_declaration_states_are_rejected() {
812        for case in [
813            "message = ()",
814            "addr = u8",
815            "addr = u8, message = (), addr = u16",
816            "addr = u8, message = (), unknown = u8",
817            "addr = u8, message = (), sends = {}",
818            "addr = u8, message = (), births = {}",
819            "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
820            "addr = u8, message = (), births = { same: u8, same: u16 }",
821        ] {
822            assert!(
823                syn::parse_str::<BehaviorArgs>(case).is_err(),
824                "invalid declaration parsed: `{case}`"
825            );
826        }
827    }
828}