1use proc_macro::TokenStream;
15use proc_macro_crate::{FoundCrate, crate_name};
16use proc_macro2::Span;
17use quote::{format_ident, quote};
18use syn::parse::{Parse, ParseStream};
19use syn::punctuated::Punctuated;
20use syn::{
21 Error, Expr, FnArg, ImplItem, ItemImpl, LitInt, Result, ReturnType, Token, Type,
22 parse_macro_input,
23};
24
25mod behavior_kw {
26 syn::custom_keyword!(addr);
27 syn::custom_keyword!(message);
28 syn::custom_keyword!(sends);
29 syn::custom_keyword!(births);
30 syn::custom_keyword!(error);
31}
32
33fn behavior_crate() -> Result<proc_macro2::TokenStream> {
34 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
35 return Ok(quote!(::behavior));
39 }
40 match crate_name("bombay-behavior") {
41 Ok(FoundCrate::Itself) => Ok(quote!(::behavior)),
42 Ok(FoundCrate::Name(name)) => {
43 let name = syn::Ident::new(&name, Span::call_site());
44 Ok(quote!(::#name))
45 }
46 Err(error) => Err(Error::new(
47 Span::call_site(),
48 format!("could not resolve the bombay-behavior crate: {error}"),
49 )),
50 }
51}
52
53struct BehaviorArgs {
54 addr: Type,
55 message: Type,
56 sends: Type,
57 births: Type,
58 error: Type,
59}
60
61impl Parse for BehaviorArgs {
62 fn parse(input: ParseStream) -> Result<Self> {
63 input.parse::<behavior_kw::addr>()?;
64 input.parse::<Token![=]>()?;
65 let addr = input.parse()?;
66 input.parse::<Token![,]>()?;
67 input.parse::<behavior_kw::message>()?;
68 input.parse::<Token![=]>()?;
69 let message = input.parse()?;
70 input.parse::<Token![,]>()?;
71 input.parse::<behavior_kw::sends>()?;
72 input.parse::<Token![=]>()?;
73 let sends = input.parse()?;
74 input.parse::<Token![,]>()?;
75 input.parse::<behavior_kw::births>()?;
76 input.parse::<Token![=]>()?;
77 let births = input.parse()?;
78 input.parse::<Token![,]>()?;
79 input.parse::<behavior_kw::error>()?;
80 input.parse::<Token![=]>()?;
81 let error = input.parse()?;
82 if input.peek(Token![,]) {
83 input.parse::<Token![,]>()?;
84 }
85 if !input.is_empty() {
86 return Err(input
87 .error("expected exactly addr, message, sends, births, and error in that order"));
88 }
89 Ok(Self {
90 addr,
91 message,
92 sends,
93 births,
94 error,
95 })
96 }
97}
98
99fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
100 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
101 return Err(Error::new_spanned(
102 &method.sig,
103 "behavior methods must begin with &mut self",
104 ));
105 };
106 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
107 return Err(Error::new_spanned(
108 receiver,
109 "behavior methods must begin with &mut self",
110 ));
111 }
112 if method.sig.constness.is_some()
113 || method.sig.asyncness.is_some()
114 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
115 || !method.sig.generics.params.is_empty()
116 {
117 return Err(Error::new_spanned(
118 &method.sig,
119 "behavior init and receive methods must be synchronous, safe, and non-generic",
120 ));
121 }
122 if matches!(method.sig.output, ReturnType::Default) {
123 return Err(Error::new_spanned(
124 &method.sig,
125 "behavior methods must declare their complete Actions result type",
126 ));
127 }
128 Ok(())
129}
130
131#[proc_macro_attribute]
135pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
136 let args = parse_macro_input!(args as BehaviorArgs);
137 let item = parse_macro_input!(item as ItemImpl);
138
139 if item.trait_.is_some() {
140 return Error::new_spanned(
141 &item,
142 "#[behavior] applies to an inherent impl, not a trait impl",
143 )
144 .to_compile_error()
145 .into();
146 }
147
148 let init = item.items.iter().find_map(|item| match item {
149 ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
150 _ => None,
151 });
152 let receive = item.items.iter().find_map(|item| match item {
153 ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
154 _ => None,
155 });
156 let Some(init) = init else {
157 return Error::new_spanned(
158 &item.self_ty,
159 "#[behavior] requires an init(&mut self) method",
160 )
161 .to_compile_error()
162 .into();
163 };
164 let Some(receive) = receive else {
165 return Error::new_spanned(
166 &item.self_ty,
167 "#[behavior] requires a receive(&mut self, from, message) method",
168 )
169 .to_compile_error()
170 .into();
171 };
172 if let Err(error) = validate_receiver(init).and_then(|()| validate_receiver(receive)) {
173 return error.to_compile_error().into();
174 }
175 if init.sig.inputs.len() != 1 {
176 return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
177 .to_compile_error()
178 .into();
179 }
180 if receive.sig.inputs.len() != 3 {
181 return Error::new_spanned(
182 &receive.sig,
183 "receive must accept exactly &mut self, from, and message",
184 )
185 .to_compile_error()
186 .into();
187 }
188
189 let BehaviorArgs {
190 addr,
191 message,
192 sends,
193 births,
194 error,
195 } = args;
196 let self_ty = &item.self_ty;
197 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
198 let behavior = match behavior_crate() {
199 Ok(behavior) => behavior,
200 Err(error) => return error.to_compile_error().into(),
201 };
202
203 quote! {
204 #item
205
206 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
207 type Addr = #addr;
208 type Msg = #message;
209 type Event = #behavior::User<#addr, #message>;
210 type Sends = #sends;
211 type Ph = #behavior::Never;
212 type Error = #error;
213 type Birth = #births;
214
215 fn init(&mut self) -> #behavior::BehaviorActed<Self> {
216 <#self_ty>::init(self)
217 }
218
219 fn transition(
220 &mut self,
221 event: Self::Event,
222 ) -> #behavior::BehaviorActed<Self> {
223 <#self_ty>::receive(self, event.from, event.message)
224 }
225 }
226 }
227 .into()
228}
229
230struct Compose {
232 count: LitInt,
233 ty: Type,
234 build: Expr,
235}
236
237impl Parse for Compose {
238 fn parse(input: ParseStream) -> Result<Self> {
239 let content;
240 syn::parenthesized!(content in input);
241 let count: Expr = content.parse()?;
242 let Expr::Lit(syn::ExprLit {
243 lit: syn::Lit::Int(count),
244 ..
245 }) = count
246 else {
247 return Err(Error::new_spanned(
248 count,
249 "worker count must be a usize literal (ranges are computed at expansion)",
250 ));
251 };
252 content.parse::<Token![,]>()?;
253 let ty: Type = content.parse()?;
254 content.parse::<Token![,]>()?;
255 let build: Expr = content.parse()?;
256 Ok(Compose { count, ty, build })
257 }
258}
259
260struct Specs(Punctuated<Compose, Token![,]>);
261
262impl Parse for Specs {
263 fn parse(input: ParseStream) -> Result<Self> {
264 Ok(Specs(Punctuated::parse_terminated(input)?))
265 }
266}
267
268#[proc_macro]
273pub fn workers(input: TokenStream) -> TokenStream {
274 let Specs(specs) = parse_macro_input!(input as Specs);
275 let specs: Vec<Compose> = specs.into_iter().collect();
276 if specs.is_empty() {
277 return Error::new(
278 proc_macro2::Span::call_site(),
279 "workers! needs at least one (count, Type, build_fn) spec",
280 )
281 .to_compile_error()
282 .into();
283 }
284
285 let first_ty = &specs[0].ty;
286 let behavior = match behavior_crate() {
287 Ok(behavior) => behavior,
288 Err(error) => return error.to_compile_error().into(),
289 };
290 let variants: Vec<_> = specs
291 .iter()
292 .enumerate()
293 .map(|(i, _)| format_ident!("V{i}"))
294 .collect();
295 let variant_defs = specs.iter().zip(&variants).map(|(s, v)| {
296 let ty = &s.ty;
297 quote! { #v(#ty) }
298 });
299
300 let mut start = 0_usize;
301 let mut build_arms = Vec::new();
302 for (s, v) in specs.iter().zip(&variants) {
303 let n: usize = match s.count.base10_parse() {
304 Ok(n) => n,
305 Err(e) => return e.to_compile_error().into(),
306 };
307 let end = start + n;
308 let build = &s.build;
309 build_arms.push(quote! { #start..#end => Crew::#v((#build)(i)) });
310 start = end;
311 }
312 let total = start;
313
314 let step_arms = variants
315 .iter()
316 .map(|v| quote! { Crew::#v(b) => b.transition(ev) });
317 let init_arms = variants.iter().map(|v| quote! { Crew::#v(b) => b.init() });
318
319 let out = quote! {
320 {
321 enum Crew {
323 #(#variant_defs),*
324 }
325
326 impl #behavior::Behavior for Crew {
327 type Addr = <#first_ty as #behavior::Behavior>::Addr;
328 type Msg = <#first_ty as #behavior::Behavior>::Msg;
329 type Event = <#first_ty as #behavior::Behavior>::Event;
330 type Sends = <#first_ty as #behavior::Behavior>::Sends;
331 type Ph = #behavior::Never;
332 type Error = <#first_ty as #behavior::Behavior>::Error;
333 type Birth = <#first_ty as #behavior::Behavior>::Birth;
334
335 fn init(&mut self) -> ::core::result::Result<
336 #behavior::Actions<Self::Addr, Self::Ph, Self::Sends, Self::Birth>,
337 Self::Error,
338 > {
339 match self {
340 #(#init_arms),*
341 }
342 }
343
344 fn transition(
345 &mut self,
346 ev: Self::Event,
347 ) -> ::core::result::Result<
348 #behavior::Actions<Self::Addr, Self::Ph, Self::Sends, Self::Birth>,
349 Self::Error,
350 > {
351 match self {
352 #(#step_arms),*
353 }
354 }
355
356 }
357
358 fn crew_build(i: usize) -> Crew {
359 match i {
360 #(#build_arms,)*
361 _ => unreachable!("workers!: fleet index out of range — driver/behavior desync"),
362 }
363 }
364
365 (#total, crew_build as fn(usize) -> Crew)
366 }
367 };
368 out.into()
369}