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 behavior_crate() -> Result<TokenStream2> {
28    if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
29        // This package deliberately exposes the library target as `behavior`,
30        // not Cargo's normalized package name `bombay_behavior`. The same path
31        // works in its unit, integration, and rustdoc crates.
32        return Ok(quote!(::behavior));
33    }
34    if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
35        // `FoundCrate::Itself` identifies a package, but `crate` identifies
36        // the target currently being compiled. The facade's library target is
37        // `bombay`; sibling binaries and examples therefore reach its exports
38        // through `::bombay`, not through their own `crate` root.
39        return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
40            Ok(quote!(crate::behavior))
41        } else {
42            Ok(quote!(::bombay::behavior))
43        };
44    }
45    if let Ok(found) = crate_name("bombay-behavior") {
46        return Ok(crate_path(found));
47    }
48    if let Ok(found) = crate_name("bombay-rs") {
49        let bombay = crate_path(found);
50        return Ok(quote!(#bombay::behavior));
51    }
52    Err(Error::new(
53        Span::call_site(),
54        "could not resolve `bombay-behavior` directly or through `bombay-rs`",
55    ))
56}
57
58struct NamedField {
59    name: Ident,
60    ty: Type,
61}
62
63struct NamedProduct {
64    fields: Vec<NamedField>,
65}
66
67enum SendsSpec {
68    Existing(Type),
69    Generated(NamedProduct),
70}
71
72enum BirthsSpec {
73    Existing(Type),
74    Generated(NamedProduct),
75}
76
77fn parse_product(input: ParseStream) -> Result<NamedProduct> {
78    let content;
79    braced!(content in input);
80    let mut fields = Vec::new();
81    let mut names = std::collections::BTreeSet::new();
82    while !content.is_empty() {
83        let name: Ident = content.parse()?;
84        if !names.insert(name.to_string()) {
85            return Err(Error::new_spanned(name, "duplicate product lane"));
86        }
87        content.parse::<Token![:]>()?;
88        fields.push(NamedField {
89            name,
90            ty: content.parse()?,
91        });
92        if content.peek(Token![,]) {
93            content.parse::<Token![,]>()?;
94        } else if !content.is_empty() {
95            return Err(content.error("expected `,` between product lanes"));
96        }
97    }
98    if fields.is_empty() {
99        return Err(input.error("empty products must be omitted"));
100    }
101    Ok(NamedProduct { fields })
102}
103
104struct BehaviorArgs {
105    addr: Type,
106    message: Type,
107    sends: Option<SendsSpec>,
108    births: Option<BirthsSpec>,
109    error: Option<Type>,
110}
111
112impl Parse for BehaviorArgs {
113    fn parse(input: ParseStream) -> Result<Self> {
114        let mut addr = None;
115        let mut message = None;
116        let mut sends = None;
117        let mut births = None;
118        let mut error = None;
119        while !input.is_empty() {
120            let key: Ident = input.parse()?;
121            input.parse::<Token![=]>()?;
122            match key.to_string().as_str() {
123                "addr" if addr.is_none() => addr = Some(input.parse()?),
124                "message" if message.is_none() => message = Some(input.parse()?),
125                "sends" if sends.is_none() => {
126                    sends = Some(if input.peek(syn::token::Brace) {
127                        SendsSpec::Generated(parse_product(input)?)
128                    } else {
129                        SendsSpec::Existing(input.parse()?)
130                    });
131                }
132                "births" if births.is_none() => {
133                    births = Some(if input.peek(syn::token::Brace) {
134                        BirthsSpec::Generated(parse_product(input)?)
135                    } else {
136                        BirthsSpec::Existing(input.parse()?)
137                    });
138                }
139                "error" if error.is_none() => error = Some(input.parse()?),
140                "addr" | "message" | "sends" | "births" | "error" => {
141                    return Err(Error::new_spanned(key, "duplicate behavior argument"));
142                }
143                _ => return Err(Error::new_spanned(key, "unknown behavior argument")),
144            }
145            if input.peek(Token![,]) {
146                input.parse::<Token![,]>()?;
147            } else if !input.is_empty() {
148                return Err(input.error("expected `,` between behavior arguments"));
149            }
150        }
151        Ok(Self {
152            addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
153            message: message.ok_or_else(|| input.error("missing `message`"))?,
154            sends,
155            births,
156            error,
157        })
158    }
159}
160
161fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
162    let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
163        return Err(Error::new_spanned(
164            &method.sig,
165            "behavior methods must begin with &mut self",
166        ));
167    };
168    if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
169        return Err(Error::new_spanned(
170            receiver,
171            "behavior methods must begin with &mut self",
172        ));
173    }
174    if method.sig.constness.is_some()
175        || method.sig.asyncness.is_some()
176        || matches!(method.sig.safety, syn::Safety::Unsafe(_))
177        || !method.sig.generics.params.is_empty()
178    {
179        return Err(Error::new_spanned(
180            &method.sig,
181            "behavior init and receive methods must be synchronous, safe, and non-generic",
182        ));
183    }
184    if matches!(method.sig.output, ReturnType::Default) {
185        return Err(Error::new_spanned(
186            &method.sig,
187            "behavior methods must declare their complete Actions result type",
188        ));
189    }
190    Ok(())
191}
192
193fn lane_name(product: &Ident, field: &Ident) -> Ident {
194    let lane = field
195        .to_string()
196        .split('_')
197        .filter(|part| !part.is_empty())
198        .map(|part| {
199            let mut chars = part.chars();
200            chars.next().map_or_else(String::new, |first| {
201                first.to_uppercase().chain(chars).collect()
202            })
203        })
204        .collect::<String>();
205    format_ident!("{}{}", product, lane)
206}
207
208#[derive(Default)]
209struct GenericUses {
210    identifiers: std::collections::BTreeSet<String>,
211    lifetimes: std::collections::BTreeSet<String>,
212}
213
214impl<'ast> Visit<'ast> for GenericUses {
215    fn visit_ident(&mut self, ident: &'ast Ident) {
216        self.identifiers.insert(ident.to_string());
217    }
218
219    fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
220        self.lifetimes.insert(lifetime.ident.to_string());
221    }
222}
223
224fn product_generics(item: &ItemImpl, fields: &[NamedField]) -> Generics {
225    let mut uses = GenericUses::default();
226    for field in fields {
227        uses.visit_type(&field.ty);
228    }
229    let mut generics = item.generics.clone();
230    generics.params = generics
231        .params
232        .into_iter()
233        .filter(|parameter| match parameter {
234            GenericParam::Lifetime(parameter) => uses
235                .lifetimes
236                .contains(&parameter.lifetime.ident.to_string()),
237            GenericParam::Type(parameter) => {
238                uses.identifiers.contains(&parameter.ident.to_string())
239            }
240            GenericParam::Const(parameter) => {
241                uses.identifiers.contains(&parameter.ident.to_string())
242            }
243        })
244        .collect();
245    generics.where_clause = None;
246    generics
247}
248
249#[allow(
250    clippy::too_many_lines,
251    reason = "the generated nominal product keeps every structural trait implementation adjacent"
252)]
253fn generate_sends(
254    product: Option<SendsSpec>,
255    actor: &Ident,
256    item: &ItemImpl,
257    behavior: &TokenStream2,
258) -> (TokenStream2, TokenStream2) {
259    let product = match product {
260        None => return (quote!(#behavior::NoSends), quote!()),
261        Some(SendsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
262        Some(SendsSpec::Generated(product)) => product,
263    };
264    let name = format_ident!("{}Sends", actor);
265    let field_names: Vec<_> = product.fields.iter().map(|field| &field.name).collect();
266    let field_types: Vec<_> = product.fields.iter().map(|field| &field.ty).collect();
267    let lane_names: Vec<_> = field_names
268        .iter()
269        .map(|field| lane_name(&name, field))
270        .collect();
271    let product_generics = product_generics(item, &product.fields);
272    let (_, type_generics, _) = product_generics.split_for_impl();
273    let generics = &product_generics;
274
275    let send_impls = lane_names
276        .iter()
277        .zip(field_names.iter())
278        .zip(field_types.iter())
279        .map(|((lane, field), field_ty)| {
280            let mut generics = product_generics.clone();
281            generics.params.push(parse_quote!(__BombayInput));
282            generics.make_where_clause().predicates.push(parse_quote!(
283                #field_ty: #behavior::SendInput<__BombayInput, #behavior::Own>
284            ));
285            let (impl_generics, _, where_clause) = generics.split_for_impl();
286            quote! {
287                impl #impl_generics #behavior::SendInput<__BombayInput, #lane>
288                    for #name #type_generics #where_clause
289                {
290                    fn emit(&mut self, input: __BombayInput) {
291                        <#field_ty as #behavior::SendInput<
292                            __BombayInput,
293                            #behavior::Own,
294                        >>::emit(&mut self.#field, input);
295                    }
296                }
297            }
298        });
299
300    let mut effects_generics = product_generics.clone();
301    for field_ty in &field_types {
302        effects_generics
303            .make_where_clause()
304            .predicates
305            .push(parse_quote!(#field_ty: #behavior::SendEffects));
306    }
307    let (effects_impl_generics, _, effects_where_clause) = effects_generics.split_for_impl();
308
309    let mut lawful_generics = product_generics.clone();
310    lawful_generics.params.push(parse_quote!(__BombayEvent));
311    for field_ty in &field_types {
312        lawful_generics
313            .make_where_clause()
314            .predicates
315            .push(parse_quote!(#field_ty: #behavior::SendsFor<__BombayEvent>));
316    }
317    let (lawful_impl_generics, _, lawful_where_clause) = lawful_generics.split_for_impl();
318
319    let mut interpret_generics = product_generics.clone();
320    interpret_generics
321        .params
322        .push(parse_quote!(__BombayInterpreter));
323    interpret_generics
324        .params
325        .push(parse_quote!(__BombayRootEvent));
326    interpret_generics.params.push(parse_quote!(__BombayPath));
327    interpret_generics
328        .make_where_clause()
329        .predicates
330        .push(parse_quote!(__BombayInterpreter: #behavior::SendInterpreter));
331    for field_ty in &field_types {
332        interpret_generics
333            .make_where_clause()
334            .predicates
335            .push(parse_quote!(
336                #field_ty: #behavior::InterpretSends<
337                    __BombayInterpreter,
338                    __BombayRootEvent,
339                    __BombayPath,
340                >
341            ));
342    }
343    interpret_generics
344        .make_where_clause()
345        .predicates
346        .push(parse_quote!(#name #type_generics: ::core::marker::Send));
347    let (interpret_impl_generics, _, interpret_where_clause) = interpret_generics.split_for_impl();
348
349    let items = quote! {
350        #(pub enum #lane_names {})*
351
352        pub struct #name #generics {
353            #(pub #field_names: #field_types,)*
354        }
355
356        impl #effects_impl_generics #behavior::SendEffects for #name #type_generics
357            #effects_where_clause
358        {
359            fn empty() -> Self {
360                Self {
361                    #(#field_names: <#field_types as #behavior::SendEffects>::empty(),)*
362                }
363            }
364
365            fn append(&mut self, other: Self) {
366                #(
367                    <#field_types as #behavior::SendEffects>::append(
368                        &mut self.#field_names,
369                        other.#field_names,
370                    );
371                )*
372            }
373        }
374
375        impl #lawful_impl_generics #behavior::SendsFor<__BombayEvent>
376            for #name #type_generics #lawful_where_clause
377        {}
378
379        #(#send_impls)*
380
381        impl #interpret_impl_generics
382            #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>
383            for #name #type_generics #interpret_where_clause
384        {
385            fn interpret(
386                self,
387                interpreter: &mut __BombayInterpreter,
388            ) -> impl ::core::future::Future<
389                Output = ::core::result::Result<(), __BombayInterpreter::Error>,
390            > + ::core::marker::Send {
391                async move {
392                    #(
393                        <#field_types as #behavior::InterpretSends<
394                            __BombayInterpreter,
395                            __BombayRootEvent,
396                            __BombayPath,
397                        >>::interpret(self.#field_names, interpreter).await?;
398                    )*
399                    ::core::result::Result::Ok(())
400                }
401            }
402        }
403    };
404    (quote!(#name #type_generics), items)
405}
406
407fn generate_births(
408    product: Option<BirthsSpec>,
409    actor: &Ident,
410    item: &ItemImpl,
411    behavior: &TokenStream2,
412) -> (TokenStream2, TokenStream2) {
413    let product = match product {
414        None => return (quote!(#behavior::NoBirths), quote!()),
415        Some(BirthsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
416        Some(BirthsSpec::Generated(product)) => product,
417    };
418    let name = format_ident!("{}Children", actor);
419    let child_types = product.fields.iter().map(|field| &field.ty);
420    let product_generics = product_generics(item, &product.fields);
421    let (_, type_generics, _) = product_generics.split_for_impl();
422    let generics = &product_generics;
423    let choice = child_types.fold(
424        quote!(#behavior::Never),
425        |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
426    );
427    (
428        quote!(#behavior::Births<#name #type_generics>),
429        quote!(pub type #name #generics = #choice;),
430    )
431}
432
433/// Generate the mechanical `Behavior` implementation for a normal inherent
434/// impl containing `receive(&mut self, from, message)` and, optionally,
435/// `init(&mut self)`. Omitting `init` selects the behavior algebra's empty
436/// initialization transition. The original impl and methods are preserved
437/// unchanged.
438#[proc_macro_attribute]
439#[allow(
440    clippy::too_many_lines,
441    reason = "validation and the one coherent Behavior expansion remain in one entry point"
442)]
443pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
444    let args = parse_macro_input!(args as BehaviorArgs);
445    let item = parse_macro_input!(item as ItemImpl);
446
447    if item.trait_.is_some() {
448        return Error::new_spanned(
449            &item,
450            "#[behavior] applies to an inherent impl, not a trait impl",
451        )
452        .to_compile_error()
453        .into();
454    }
455
456    let init = item.items.iter().find_map(|item| match item {
457        ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
458        _ => None,
459    });
460    let receive = item.items.iter().find_map(|item| match item {
461        ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
462        _ => None,
463    });
464    let Some(receive) = receive else {
465        return Error::new_spanned(
466            &item.self_ty,
467            "#[behavior] requires a receive(&mut self, from, message) method",
468        )
469        .to_compile_error()
470        .into();
471    };
472    if let Err(error) = init
473        .map_or(Ok(()), validate_receiver)
474        .and_then(|()| validate_receiver(receive))
475    {
476        return error.to_compile_error().into();
477    }
478    if let Some(init) = init
479        && init.sig.inputs.len() != 1
480    {
481        return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
482            .to_compile_error()
483            .into();
484    }
485    if receive.sig.inputs.len() != 3 {
486        return Error::new_spanned(
487            &receive.sig,
488            "receive must accept exactly &mut self, from, and message",
489        )
490        .to_compile_error()
491        .into();
492    }
493
494    let BehaviorArgs {
495        addr,
496        message,
497        sends,
498        births,
499        error,
500    } = args;
501    let self_ty = &item.self_ty;
502    let Type::Path(self_path) = self_ty.as_ref() else {
503        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
504            .to_compile_error()
505            .into();
506    };
507    let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
508        return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
509            .to_compile_error()
510            .into();
511    };
512    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
513    let behavior = match behavior_crate() {
514        Ok(behavior) => behavior,
515        Err(error) => return error.to_compile_error().into(),
516    };
517    let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
518    let initialize = init.map_or_else(
519        || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
520        |_| quote!(<#self_ty>::init(self)),
521    );
522
523    let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
524    let (births_ty, births_items) = generate_births(births, self_name, &item, &behavior);
525
526    quote! {
527        #sends_items
528        #births_items
529        #item
530
531        impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
532            type Addr = #addr;
533            type Msg = #message;
534        }
535
536        impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
537            type Protocol = Self;
538            type Event = #behavior::User<#addr, #message>;
539            type Sends = #sends_ty;
540            type Ph = #behavior::Never;
541            type Error = #error;
542            type Birth = #births_ty;
543
544            fn init(
545                &mut self,
546                _: #behavior::InitializationTurn,
547            ) -> #behavior::BehaviorActed<Self> {
548                #initialize
549            }
550
551            fn transition(
552                &mut self,
553                _: #behavior::ActiveTurn,
554                event: Self::Event,
555            ) -> #behavior::BehaviorActed<Self> {
556                <#self_ty>::receive(self, event.from, event.message)
557            }
558        }
559
560        impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
561            type Base = Self;
562
563            fn base(&self) -> &Self {
564                self
565            }
566        }
567    }
568    .into()
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574
575    fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
576        if at == values.len() {
577            output.push(values.join(", "));
578            return;
579        }
580        for index in at..values.len() {
581            values.swap(at, index);
582            permutations(values, at + 1, output);
583            values.swap(at, index);
584        }
585    }
586
587    #[test]
588    fn every_argument_order_parses() {
589        let mut declarations = [
590            "addr = u64",
591            "message = String",
592            "sends = { first: Vec<u8>, second: Vec<u16> }",
593            "births = { first: u8, second: u16 }",
594            "error = String",
595        ];
596        let mut cases = Vec::new();
597        permutations(&mut declarations, 0, &mut cases);
598        assert_eq!(cases.len(), 120);
599        for case in cases {
600            syn::parse_str::<BehaviorArgs>(&case)
601                .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
602        }
603    }
604
605    #[test]
606    fn capability_omission_and_trailing_comma_parse() {
607        syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
608            .expect("only required protocol declarations are sufficient");
609    }
610
611    #[test]
612    fn invalid_declaration_states_are_rejected() {
613        for case in [
614            "message = ()",
615            "addr = u8",
616            "addr = u8, message = (), addr = u16",
617            "addr = u8, message = (), unknown = u8",
618            "addr = u8, message = (), sends = {}",
619            "addr = u8, message = (), births = {}",
620            "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
621            "addr = u8, message = (), births = { same: u8, same: u16 }",
622        ] {
623            assert!(
624                syn::parse_str::<BehaviorArgs>(case).is_err(),
625                "invalid declaration parsed: `{case}`"
626            );
627        }
628    }
629}