Skip to main content

behavior_macros/
lib.rs

1//! `behavior-macros` — proc-macros for the behavior algebra.
2//!
3//! The crate exposes one optional authoring macro: `#[behavior]`. It wires an
4//! ordinary inherent user-message fold to the concrete `Behavior` trait.
5
6use proc_macro::TokenStream;
7use proc_macro_crate::{FoundCrate, crate_name};
8use proc_macro2::Span;
9use quote::quote;
10use syn::parse::{Parse, ParseStream};
11use syn::{Error, FnArg, ImplItem, ItemImpl, Result, ReturnType, Token, Type, parse_macro_input};
12
13mod behavior_kw {
14    syn::custom_keyword!(addr);
15    syn::custom_keyword!(message);
16    syn::custom_keyword!(sends);
17    syn::custom_keyword!(births);
18    syn::custom_keyword!(error);
19}
20
21fn behavior_crate() -> Result<proc_macro2::TokenStream> {
22    if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
23        // This package deliberately exposes the library target as `behavior`,
24        // not Cargo's normalized package name `bombay_behavior`. The same path
25        // works in its unit, integration, and rustdoc crates.
26        return Ok(quote!(::behavior));
27    }
28    match crate_name("bombay-behavior") {
29        Ok(FoundCrate::Itself) => Ok(quote!(::behavior)),
30        Ok(FoundCrate::Name(name)) => {
31            let name = syn::Ident::new(&name, Span::call_site());
32            Ok(quote!(::#name))
33        }
34        Err(error) => Err(Error::new(
35            Span::call_site(),
36            format!("could not resolve the bombay-behavior crate: {error}"),
37        )),
38    }
39}
40
41struct BehaviorArgs {
42    addr: Type,
43    message: Type,
44    sends: Type,
45    births: Type,
46    error: Type,
47}
48
49impl Parse for BehaviorArgs {
50    fn parse(input: ParseStream) -> Result<Self> {
51        input.parse::<behavior_kw::addr>()?;
52        input.parse::<Token![=]>()?;
53        let addr = input.parse()?;
54        input.parse::<Token![,]>()?;
55        input.parse::<behavior_kw::message>()?;
56        input.parse::<Token![=]>()?;
57        let message = input.parse()?;
58        input.parse::<Token![,]>()?;
59        input.parse::<behavior_kw::sends>()?;
60        input.parse::<Token![=]>()?;
61        let sends = input.parse()?;
62        input.parse::<Token![,]>()?;
63        input.parse::<behavior_kw::births>()?;
64        input.parse::<Token![=]>()?;
65        let births = input.parse()?;
66        input.parse::<Token![,]>()?;
67        input.parse::<behavior_kw::error>()?;
68        input.parse::<Token![=]>()?;
69        let error = input.parse()?;
70        if input.peek(Token![,]) {
71            input.parse::<Token![,]>()?;
72        }
73        if !input.is_empty() {
74            return Err(input
75                .error("expected exactly addr, message, sends, births, and error in that order"));
76        }
77        Ok(Self {
78            addr,
79            message,
80            sends,
81            births,
82            error,
83        })
84    }
85}
86
87fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
88    let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
89        return Err(Error::new_spanned(
90            &method.sig,
91            "behavior methods must begin with &mut self",
92        ));
93    };
94    if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
95        return Err(Error::new_spanned(
96            receiver,
97            "behavior methods must begin with &mut self",
98        ));
99    }
100    if method.sig.constness.is_some()
101        || method.sig.asyncness.is_some()
102        || matches!(method.sig.safety, syn::Safety::Unsafe(_))
103        || !method.sig.generics.params.is_empty()
104    {
105        return Err(Error::new_spanned(
106            &method.sig,
107            "behavior init and receive methods must be synchronous, safe, and non-generic",
108        ));
109    }
110    if matches!(method.sig.output, ReturnType::Default) {
111        return Err(Error::new_spanned(
112            &method.sig,
113            "behavior methods must declare their complete Actions result type",
114        ));
115    }
116    Ok(())
117}
118
119/// Generate the mechanical `Behavior` implementation for a normal inherent
120/// impl containing `receive(&mut self, from, message)` and, optionally,
121/// `init(&mut self)`. Omitting `init` selects the behavior algebra's empty
122/// initialization transition. The original impl and methods are preserved
123/// unchanged.
124#[proc_macro_attribute]
125pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
126    let args = parse_macro_input!(args as BehaviorArgs);
127    let item = parse_macro_input!(item as ItemImpl);
128
129    if item.trait_.is_some() {
130        return Error::new_spanned(
131            &item,
132            "#[behavior] applies to an inherent impl, not a trait impl",
133        )
134        .to_compile_error()
135        .into();
136    }
137
138    let init = item.items.iter().find_map(|item| match item {
139        ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
140        _ => None,
141    });
142    let receive = item.items.iter().find_map(|item| match item {
143        ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
144        _ => None,
145    });
146    let Some(receive) = receive else {
147        return Error::new_spanned(
148            &item.self_ty,
149            "#[behavior] requires a receive(&mut self, from, message) method",
150        )
151        .to_compile_error()
152        .into();
153    };
154    if let Err(error) = init
155        .map_or(Ok(()), validate_receiver)
156        .and_then(|()| validate_receiver(receive))
157    {
158        return error.to_compile_error().into();
159    }
160    if let Some(init) = init
161        && init.sig.inputs.len() != 1
162    {
163        return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
164            .to_compile_error()
165            .into();
166    }
167    if receive.sig.inputs.len() != 3 {
168        return Error::new_spanned(
169            &receive.sig,
170            "receive must accept exactly &mut self, from, and message",
171        )
172        .to_compile_error()
173        .into();
174    }
175
176    let BehaviorArgs {
177        addr,
178        message,
179        sends,
180        births,
181        error,
182    } = args;
183    let self_ty = &item.self_ty;
184    let (impl_generics, _, where_clause) = item.generics.split_for_impl();
185    let behavior = match behavior_crate() {
186        Ok(behavior) => behavior,
187        Err(error) => return error.to_compile_error().into(),
188    };
189    let initialize = init.map_or_else(
190        || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
191        |_| quote!(<#self_ty>::init(self)),
192    );
193
194    quote! {
195        #item
196
197        impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
198            type Addr = #addr;
199            type Msg = #message;
200            type Event = #behavior::User<#addr, #message>;
201            type Sends = #sends;
202            type Ph = #behavior::Never;
203            type Error = #error;
204            type Birth = #births;
205
206            fn init(
207                &mut self,
208                _: #behavior::InitializationTurn,
209            ) -> #behavior::BehaviorActed<Self> {
210                #initialize
211            }
212
213            fn transition(
214                &mut self,
215                _: #behavior::ActiveTurn,
216                event: Self::Event,
217            ) -> #behavior::BehaviorActed<Self> {
218                <#self_ty>::receive(self, event.from, event.message)
219            }
220        }
221
222        impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
223            type Base = Self;
224
225            fn base(&self) -> &Self {
226                self
227            }
228        }
229    }
230    .into()
231}