1use 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 return Ok(quote!(::behavior));
33 }
34 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
35 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(¶meter.lifetime.ident.to_string()),
237 GenericParam::Type(parameter) => {
238 uses.identifiers.contains(¶meter.ident.to_string())
239 }
240 GenericParam::Const(parameter) => {
241 uses.identifiers.contains(¶meter.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#[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}