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 Data, DeriveInput, Error, Fields, FnArg, GenericArgument, GenericParam, Generics, Ident,
14 ImplItem, ItemImpl, PathArguments, Result, ReturnType, Token, Type, Visibility, braced,
15 parse_macro_input, parse_quote,
16};
17
18fn crate_path(found: FoundCrate) -> TokenStream2 {
19 match found {
20 FoundCrate::Itself => quote!(crate),
21 FoundCrate::Name(name) => {
22 let name = match name.as_str() {
25 "bombay_behavior" => "behavior",
26 "bombay_behavior_actors" => "behavior_actors",
27 "bombay_rs" => "bombay",
28 _ => &name,
29 };
30 let name = syn::Ident::new(&name, Span::call_site());
31 quote!(::#name)
32 }
33 }
34}
35
36fn behavior_crate() -> Result<TokenStream2> {
37 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
38 return Ok(quote!(::behavior));
42 }
43 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
44 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
49 Ok(quote!(crate::behavior))
50 } else {
51 Ok(quote!(::bombay::behavior))
52 };
53 }
54 if let Ok(found) = crate_name("bombay-behavior") {
55 return Ok(crate_path(found));
56 }
57 if let Ok(found) = crate_name("bombay-rs") {
58 let bombay = crate_path(found);
59 return Ok(quote!(#bombay::behavior));
60 }
61 Err(Error::new(
62 Span::call_site(),
63 "could not resolve `bombay-behavior` directly or through `bombay-rs`",
64 ))
65}
66
67fn actors_crate() -> Result<TokenStream2> {
68 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior-actors") {
69 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("behavior_actors") {
70 Ok(quote!(crate))
71 } else {
72 Ok(quote!(::behavior_actors))
73 };
74 }
75 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
76 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
77 Ok(quote!(crate))
78 } else {
79 Ok(quote!(::bombay))
80 };
81 }
82 if let Ok(found) = crate_name("bombay-rs") {
90 return Ok(crate_path(found));
91 }
92 if let Ok(found) = crate_name("bombay-behavior-actors") {
93 return Ok(crate_path(found));
94 }
95 Err(Error::new(
96 Span::call_site(),
97 "could not resolve `bombay-behavior-actors` through `bombay-rs` or directly",
98 ))
99}
100
101#[cfg(test)]
102mod crate_resolution_tests {
103 use super::*;
104
105 #[test]
106 fn package_library_names_and_explicit_renames_resolve_distinctly() {
107 assert_eq!(
108 crate_path(FoundCrate::Name("bombay_rs".to_owned())).to_string(),
109 ":: bombay"
110 );
111 assert_eq!(
112 crate_path(FoundCrate::Name("bombay_behavior".to_owned())).to_string(),
113 ":: behavior"
114 );
115 assert_eq!(
116 crate_path(FoundCrate::Name("bombay_behavior_actors".to_owned())).to_string(),
117 ":: behavior_actors"
118 );
119 assert_eq!(
120 crate_path(FoundCrate::Name("runtime".to_owned())).to_string(),
121 ":: runtime"
122 );
123 }
124}
125
126struct NamedField {
127 name: Ident,
128 ty: Type,
129}
130
131struct NamedProduct {
132 fields: Vec<NamedField>,
133}
134
135#[allow(
136 clippy::large_enum_variant,
137 reason = "boxing syn::Type would add lint-driven indirection to compile-time parser state"
138)]
139enum SendsSpec {
140 Existing(Type),
141 Generated {
142 product: NamedProduct,
143 visibility: Visibility,
144 },
145}
146
147#[allow(
148 clippy::large_enum_variant,
149 reason = "boxing syn::Type would add lint-driven indirection to compile-time parser state"
150)]
151enum BirthsSpec {
152 Existing(Type),
153 Generated(NamedProduct),
154}
155
156impl BirthsSpec {
157 fn is_no_births(&self) -> bool {
158 let Self::Existing(Type::Path(path)) = self else {
159 return false;
160 };
161 path.qself.is_none()
162 && path.path.segments.last().is_some_and(|segment| {
163 segment.ident == "NoBirths" && matches!(segment.arguments, syn::PathArguments::None)
164 })
165 }
166}
167
168#[derive(Clone, Copy)]
169enum CreationSettlementDisposition {
170 ReturnToCreator,
171 RetainForRetirement,
172}
173
174fn parse_product(input: ParseStream) -> Result<NamedProduct> {
175 let content;
176 braced!(content in input);
177 let mut fields = Vec::new();
178 let mut names = std::collections::BTreeSet::new();
179 while !content.is_empty() {
180 let name: Ident = content.parse()?;
181 if !names.insert(name.to_string()) {
182 return Err(Error::new_spanned(name, "duplicate product lane"));
183 }
184 content.parse::<Token![:]>()?;
185 fields.push(NamedField {
186 name,
187 ty: content.parse()?,
188 });
189 if content.peek(Token![,]) {
190 content.parse::<Token![,]>()?;
191 } else if !content.is_empty() {
192 return Err(content.error("expected `,` between product lanes"));
193 }
194 }
195 if fields.is_empty() {
196 return Err(input.error("empty products must be omitted"));
197 }
198 Ok(NamedProduct { fields })
199}
200
201struct BehaviorArgs {
202 addr: Type,
203 message: Type,
204 sends: Option<SendsSpec>,
205 births: Option<BirthsSpec>,
206 creation_settlements: Option<CreationSettlementDisposition>,
207 error: Option<Type>,
208}
209
210struct PoolWorkerArgs {
211 addr: Type,
212 result: Type,
213}
214
215impl Parse for PoolWorkerArgs {
216 fn parse(input: ParseStream) -> Result<Self> {
217 let mut addr = None;
218 let mut result = None;
219 while !input.is_empty() {
220 let key: Ident = input.parse()?;
221 input.parse::<Token![=]>()?;
222 match key.to_string().as_str() {
223 "addr" if addr.is_none() => addr = Some(input.parse()?),
224 "result" if result.is_none() => result = Some(input.parse()?),
225 "addr" | "result" => {
226 return Err(Error::new_spanned(key, "duplicate pool worker argument"));
227 }
228 _ => return Err(Error::new_spanned(key, "unknown pool worker argument")),
229 }
230 if input.peek(Token![,]) {
231 input.parse::<Token![,]>()?;
232 } else if !input.is_empty() {
233 return Err(input.error("expected `,` between pool worker arguments"));
234 }
235 }
236 Ok(Self {
237 addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
238 result: result.ok_or_else(|| input.error("missing `result`"))?,
239 })
240 }
241}
242
243impl Parse for BehaviorArgs {
244 fn parse(input: ParseStream) -> Result<Self> {
245 let mut addr = None;
246 let mut message = None;
247 let mut sends = None;
248 let mut births = None;
249 let mut creation_settlements = None;
250 let mut error = None;
251 while !input.is_empty() {
252 let key: Ident = input.parse()?;
253 input.parse::<Token![=]>()?;
254 match key.to_string().as_str() {
255 "addr" if addr.is_none() => addr = Some(input.parse()?),
256 "message" if message.is_none() => message = Some(input.parse()?),
257 "sends" if sends.is_none() => {
258 let visibility: Visibility = input.parse()?;
259 sends = Some(if input.peek(syn::token::Brace) {
260 SendsSpec::Generated {
261 product: parse_product(input)?,
262 visibility,
263 }
264 } else if matches!(visibility, Visibility::Inherited) {
265 SendsSpec::Existing(input.parse()?)
266 } else {
267 return Err(input.error("send-product visibility requires named lanes"));
268 });
269 }
270 "births" if births.is_none() => {
271 births = Some(if input.peek(syn::token::Brace) {
272 BirthsSpec::Generated(parse_product(input)?)
273 } else {
274 BirthsSpec::Existing(input.parse()?)
275 });
276 }
277 "creation_settlements" if creation_settlements.is_none() => {
278 let disposition: Ident = input.parse()?;
279 creation_settlements = Some(match disposition.to_string().as_str() {
280 "return_to_creator" => CreationSettlementDisposition::ReturnToCreator,
281 "retain_for_retirement" => {
282 CreationSettlementDisposition::RetainForRetirement
283 }
284 _ => {
285 return Err(Error::new_spanned(
286 disposition,
287 "creation settlements must `return_to_creator` or `retain_for_retirement`",
288 ));
289 }
290 });
291 }
292 "error" if error.is_none() => error = Some(input.parse()?),
293 "addr" | "message" | "sends" | "births" | "creation_settlements" | "error" => {
294 return Err(Error::new_spanned(key, "duplicate behavior argument"));
295 }
296 _ => return Err(Error::new_spanned(key, "unknown behavior argument")),
297 }
298 if input.peek(Token![,]) {
299 input.parse::<Token![,]>()?;
300 } else if !input.is_empty() {
301 return Err(input.error("expected `,` between behavior arguments"));
302 }
303 }
304 match (&births, creation_settlements) {
305 (Some(births), None) if !births.is_no_births() => {
306 return Err(input.error(
307 "birth-owning behaviors must declare `creation_settlements = return_to_creator` or `creation_settlements = retain_for_retirement`",
308 ));
309 }
310 (None, Some(_)) => {
311 return Err(input.error("`creation_settlements` requires a `births` declaration"));
312 }
313 (Some(_), Some(_)) | (Some(_), None) | (None, None) => {}
314 }
315 Ok(Self {
316 addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
317 message: message.ok_or_else(|| input.error("missing `message`"))?,
318 sends,
319 births,
320 creation_settlements,
321 error,
322 })
323 }
324}
325
326fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
327 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
328 return Err(Error::new_spanned(
329 &method.sig,
330 "behavior methods must begin with &mut self",
331 ));
332 };
333 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
334 return Err(Error::new_spanned(
335 receiver,
336 "behavior methods must begin with &mut self",
337 ));
338 }
339 if method.sig.constness.is_some()
340 || method.sig.asyncness.is_some()
341 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
342 || !method.sig.generics.params.is_empty()
343 {
344 return Err(Error::new_spanned(
345 &method.sig,
346 "behavior methods must be synchronous, safe, and non-generic",
347 ));
348 }
349 if matches!(method.sig.output, ReturnType::Default) {
350 return Err(Error::new_spanned(
351 &method.sig,
352 "behavior methods must declare their complete Actions result type",
353 ));
354 }
355 Ok(())
356}
357
358fn validate_pool_worker_output(output: &ReturnType) -> Result<()> {
359 let ReturnType::Type(_, output) = output else {
360 return Err(Error::new_spanned(
361 output,
362 "pool worker transition must return WorkerActed<Self>",
363 ));
364 };
365 let Type::Path(output) = output.as_ref() else {
366 return Err(Error::new_spanned(
367 output,
368 "pool worker transition must return WorkerActed<Self>",
369 ));
370 };
371 if output.qself.is_some() || output.path.segments.len() != 1 {
372 return Err(Error::new_spanned(
373 output,
374 "pool worker transition must return WorkerActed<Self>",
375 ));
376 }
377 let segment = output
378 .path
379 .segments
380 .first()
381 .ok_or_else(|| Error::new_spanned(output, "missing pool worker return type"))?;
382 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
383 return Err(Error::new_spanned(
384 output,
385 "pool worker transition must return WorkerActed<Self>",
386 ));
387 };
388 let mut arguments = arguments.args.iter();
389 let Some(GenericArgument::Type(Type::Path(self_type))) = arguments.next() else {
390 return Err(Error::new_spanned(
391 output,
392 "pool worker transition must return WorkerActed<Self>",
393 ));
394 };
395 let self_segment = self_type.path.segments.first();
396 if segment.ident != "WorkerActed"
397 || arguments.next().is_some()
398 || self_type.qself.is_some()
399 || self_type.path.segments.len() != 1
400 || !matches!(self_segment, Some(segment) if segment.ident == "Self" && matches!(segment.arguments, PathArguments::None))
401 {
402 return Err(Error::new_spanned(
403 output,
404 "pool worker transition must return WorkerActed<Self>",
405 ));
406 }
407 Ok(())
408}
409
410fn pool_worker_assignment(method: &syn::ImplItemFn) -> Result<Type> {
411 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
412 return Err(Error::new_spanned(
413 &method.sig,
414 "pool worker transition must begin with &mut self",
415 ));
416 };
417 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
418 return Err(Error::new_spanned(
419 receiver,
420 "pool worker transition must begin with &mut self",
421 ));
422 }
423 if method.sig.constness.is_some()
424 || method.sig.asyncness.is_some()
425 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
426 || method.sig.abi.is_some()
427 || method.sig.variadic.is_some()
428 || !method.sig.generics.params.is_empty()
429 {
430 return Err(Error::new_spanned(
431 &method.sig,
432 "pool worker transition must be synchronous, safe, and non-generic",
433 ));
434 }
435 if method.sig.inputs.len() != 2 {
436 return Err(Error::new_spanned(
437 &method.sig,
438 "pool worker transition must accept exactly &mut self and one Assignment<Job>",
439 ));
440 }
441 validate_pool_worker_output(&method.sig.output)?;
442
443 let Some(FnArg::Typed(assignment)) = method.sig.inputs.iter().nth(1) else {
444 return Err(Error::new_spanned(
445 &method.sig,
446 "pool worker transition must accept one Assignment<Job>",
447 ));
448 };
449 let Type::Path(assignment_path) = assignment.ty.as_ref() else {
450 return Err(Error::new_spanned(
451 &assignment.ty,
452 "pool worker transition input must be Assignment<Job>",
453 ));
454 };
455 let Some(segment) = assignment_path.path.segments.last() else {
456 return Err(Error::new_spanned(
457 &assignment.ty,
458 "pool worker transition input must be Assignment<Job>",
459 ));
460 };
461 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
462 return Err(Error::new_spanned(
463 &assignment.ty,
464 "pool worker transition input must be Assignment<Job>",
465 ));
466 };
467 let mut arguments = arguments.args.iter();
468 if segment.ident != "Assignment"
469 || !matches!(arguments.next(), Some(GenericArgument::Type(_)))
470 || arguments.next().is_some()
471 {
472 return Err(Error::new_spanned(
473 &assignment.ty,
474 "pool worker transition input must be Assignment<Job>",
475 ));
476 }
477 Ok(assignment.ty.as_ref().clone())
478}
479
480fn pascal_name(field: &Ident) -> String {
481 field
482 .to_string()
483 .split('_')
484 .filter(|part| !part.is_empty())
485 .map(|part| {
486 let mut chars = part.chars();
487 chars.next().map_or_else(String::new, |first| {
488 first.to_uppercase().chain(chars).collect()
489 })
490 })
491 .collect::<String>()
492}
493
494fn lane_name(product: &Ident, field: &Ident) -> Ident {
495 let lane = pascal_name(field);
496 format_ident!("{}{}", product, lane)
497}
498
499#[derive(Default)]
500struct GenericUses {
501 identifiers: std::collections::BTreeSet<String>,
502 lifetimes: std::collections::BTreeSet<String>,
503}
504
505impl<'ast> Visit<'ast> for GenericUses {
506 fn visit_ident(&mut self, ident: &'ast Ident) {
507 self.identifiers.insert(ident.to_string());
508 }
509
510 fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
511 self.lifetimes.insert(lifetime.ident.to_string());
512 }
513}
514
515fn product_generics(item: &ItemImpl, fields: &[NamedField]) -> Generics {
516 let mut uses = GenericUses::default();
517 for field in fields {
518 uses.visit_type(&field.ty);
519 }
520 let mut generics = item.generics.clone();
521 generics.params = generics
522 .params
523 .into_iter()
524 .filter(|parameter| match parameter {
525 GenericParam::Lifetime(parameter) => uses
526 .lifetimes
527 .contains(¶meter.lifetime.ident.to_string()),
528 GenericParam::Type(parameter) => {
529 uses.identifiers.contains(¶meter.ident.to_string())
530 }
531 GenericParam::Const(parameter) => {
532 uses.identifiers.contains(¶meter.ident.to_string())
533 }
534 })
535 .collect();
536 generics.where_clause = None;
537 generics
538}
539
540fn named_send_interpretation(
541 loans: &[&Ident],
542 field_types: &[&Type],
543 _settlements: &Ident,
544 behavior: &TokenStream2,
545) -> TokenStream2 {
546 let turns = loans.iter().zip(field_types).enumerate().map(|(index, (loan, field_ty))| {
547 let current_loan = loans.iter().enumerate().map(|(position, retained)| {
548 if position == index { quote!(#retained) } else { quote!(_) }
549 }).collect::<Vec<_>>();
550 let remaining_loans = loans.iter().enumerate().map(|(position, retained)| {
551 if position >= index { quote!(#retained) } else { quote!(_) }
552 }).collect::<Vec<_>>();
553 let later_loans = &loans[index + 1..];
554 let later_types = &field_types[index + 1..];
555 quote! {
556 {
559 let ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting(custody)) = progress else { return; };
560 let (#(#current_loan,)*) = custody;
561 <#field_ty as #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>>::interpret(#loan, interpreter)
562 }.await;
563 {
566 let ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting(custody)) = progress else { return; };
567 let (#(#remaining_loans,)*) = custody;
568 <#field_ty as #behavior::SendSettlements>::finish_interpretation(#loan);
569 match #loan {
570 ::core::option::Option::Some(#behavior::InterpretationProgress::Completed(#behavior::Interpretation::Complete(_))) => {},
571 ::core::option::Option::Some(#behavior::InterpretationProgress::Completed(#behavior::Interpretation::Corrupt(_))) => {
572 #(<#later_types as #behavior::SendSettlements>::unattempted(#later_loans);)*
573 <Self as #behavior::SendSettlements>::finish_interpretation(progress);
574 return;
575 }
576 _ => return,
577 }
578 }
579 }
580 }).collect::<Vec<_>>();
581 quote! {
582 <Self as #behavior::SendSettlements>::prepare_interpretation(progress);
583 #(#turns)*
584 <Self as #behavior::SendSettlements>::finish_interpretation(progress);
585 }
586}
587
588fn source_product_custody(
589 settlements: &[TokenStream2],
590 custody: &[TokenStream2],
591 behavior: &TokenStream2,
592) -> (TokenStream2, TokenStream2) {
593 settlements.iter().zip(custody).rev().fold((quote!(#behavior::NoSends), quote!(#behavior::NoSends)), |(later_settlements, later_custody), (earlier_settlements, earlier_custody)| (
594 quote!((#earlier_settlements, #later_settlements)),
595 quote!((::core::option::Option<#behavior::SourceProgress<#earlier_settlements, #earlier_custody>>, ::core::option::Option<#behavior::SourceProgress<#later_settlements, #later_custody>>)),
596 ))
597}
598
599fn named_settlement_contract(
600 name: &Ident,
601 settlement_type: &TokenStream2,
602 generics: &Generics,
603 fields: &[&Ident],
604 field_types: &[Type],
605 behavior: &TokenStream2,
606) -> TokenStream2 {
607 let mut classification_generics = generics.clone();
608 let mut custody_generics = generics.clone();
609 custody_generics
610 .params
611 .push(parse_quote!(__BombaySettlementHost));
612 custody_generics
613 .params
614 .push(parse_quote!(__BombaySettlementEvent));
615 custody_generics
616 .make_where_clause()
617 .predicates
618 .push(parse_quote!(__BombaySettlementHost: ::core::marker::Send));
619 let mut combined_status = quote!(#behavior::SettlementStatus::Accepted);
620 for (field, field_ty) in fields.iter().zip(field_types) {
621 classification_generics
622 .make_where_clause()
623 .predicates
624 .push(parse_quote!(#field_ty: #behavior::ClassifySettlement));
625 custody_generics
626 .make_where_clause()
627 .predicates
628 .push(parse_quote!(
629 #field_ty: #behavior::SourceSettlementCustody<
630 __BombaySettlementHost,
631 __BombaySettlementEvent,
632 > + ::core::marker::Send
633 ));
634 combined_status = quote! {
635 (#combined_status).combine(
636 #behavior::ClassifySettlement::settlement_status(&self.#field)
637 )
638 };
639 }
640 let (classification_impl, _, classification_where) = classification_generics.split_for_impl();
641 let (custody_impl, _, custody_where) = custody_generics.split_for_impl();
642 let source_settlements = field_types.iter().map(|ty| quote!(#ty)).collect::<Vec<_>>();
643 let source_custody = field_types.iter().map(|ty| quote!(<#ty as #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent>>::Custody)).collect::<Vec<_>>();
644 let (source_product, source_owner) =
645 source_product_custody(&source_settlements, &source_custody, behavior);
646 let source_input = fields.iter().rev().fold(
647 quote!(#behavior::NoSends),
648 |later, field| quote!((original.#field, #later)),
649 );
650 let source_output = fields
651 .iter()
652 .rev()
653 .fold(quote!(_), |later, field| quote!((#field, #later)));
654
655 quote! {
656 impl #classification_impl #behavior::ClassifySettlement
657 for #settlement_type #classification_where
658 {
659 fn settlement_status(&self) -> #behavior::SettlementStatus {
660 #combined_status
661 }
662 }
663
664 impl #custody_impl #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent> for #settlement_type #custody_where {
665 type Custody = #source_owner;
666 fn prepare_source(progress: &mut ::core::option::Option<#behavior::SourceProgress<Self, Self::Custody>>) {
667 let original = match progress.take() {
668 ::core::option::Option::Some(#behavior::SourceProgress::Original(original)) => original,
669 retained => { *progress = retained; return; }
670 };
671 let mut product = ::core::option::Option::Some(#behavior::SourceProgress::Original(#source_input));
673 <#source_product as #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent>>::prepare_source(&mut product);
674 *progress = match product {
675 ::core::option::Option::Some(#behavior::SourceProgress::Original(#source_output)) => ::core::option::Option::Some(#behavior::SourceProgress::Original(#name { #(#fields,)* })),
676 ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)) => ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)),
677 ::core::option::Option::Some(#behavior::SourceProgress::Completed(reply)) => ::core::option::Option::Some(#behavior::SourceProgress::Completed(reply.map(|#source_output| #name { #(#fields,)* }))),
678 ::core::option::Option::None => ::core::option::Option::None,
679 };
680 }
681 fn offer_next_to_source(custody: &mut Self::Custody, host: &mut __BombaySettlementHost) -> impl ::core::future::Future<Output = ()> + ::core::marker::Send {
682 <#source_product as #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent>>::offer_next_to_source(custody, host)
683 }
684 fn finish_source(progress: &mut ::core::option::Option<#behavior::SourceProgress<Self, Self::Custody>>) {
685 let custody = match progress.take() {
686 ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)) => custody,
687 retained => { *progress = retained; return; }
688 };
689 let mut product = ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody));
690 <#source_product as #behavior::SourceSettlementCustody<__BombaySettlementHost, __BombaySettlementEvent>>::finish_source(&mut product);
691 *progress = match product {
692 ::core::option::Option::Some(#behavior::SourceProgress::Original(#source_output)) => ::core::option::Option::Some(#behavior::SourceProgress::Original(#name { #(#fields,)* })),
693 ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)) => ::core::option::Option::Some(#behavior::SourceProgress::Offering(custody)),
694 ::core::option::Option::Some(#behavior::SourceProgress::Completed(reply)) => ::core::option::Option::Some(#behavior::SourceProgress::Completed(reply.map(|#source_output| #name { #(#fields,)* }))),
695 ::core::option::Option::None => ::core::option::Option::None,
696 };
697 }
698 }
699
700 }
701}
702
703#[allow(
704 clippy::too_many_lines,
705 reason = "the ordered send-product traits share one named-lane equation"
706)]
707fn named_send_contract(
708 name: &Ident,
709 generics: &Generics,
710 fields: &[&Ident],
711 field_types: &[&Type],
712 settlement_type: &TokenStream2,
713 settlement_name: &Ident,
714 behavior: &TokenStream2,
715) -> TokenStream2 {
716 let (_, type_generics, _) = generics.split_for_impl();
717 let mut effects_generics = generics.clone();
718 let mut logical_generics = generics.clone();
719 let mut settlement_generics = generics.clone();
720 let mut lawful_generics = generics.clone();
721 lawful_generics.params.push(parse_quote!(__BombayEvent));
722 let mut interpretation_generics = generics.clone();
723 interpretation_generics
724 .params
725 .push(parse_quote!(__BombayInterpreter));
726 interpretation_generics
727 .params
728 .push(parse_quote!(__BombayRootEvent));
729 interpretation_generics
730 .params
731 .push(parse_quote!(__BombayPath));
732 interpretation_generics
733 .make_where_clause()
734 .predicates
735 .push(parse_quote!(__BombayInterpreter: ::core::marker::Send));
736 interpretation_generics
737 .make_where_clause()
738 .predicates
739 .push(parse_quote!(#name #type_generics: ::core::marker::Send));
740 let mut logical_protocols = quote!(#behavior::NoBirthProtocols);
741 for field_ty in field_types {
742 effects_generics
743 .make_where_clause()
744 .predicates
745 .push(parse_quote!(#field_ty: #behavior::SendEffects));
746 logical_generics
747 .make_where_clause()
748 .predicates
749 .push(parse_quote!(#field_ty: #behavior::LogicalDeliveryProtocols));
750 settlement_generics
751 .make_where_clause()
752 .predicates
753 .push(parse_quote!(#field_ty: #behavior::SendSettlements));
754 lawful_generics
755 .make_where_clause()
756 .predicates
757 .push(parse_quote!(#field_ty: #behavior::SendsFor<__BombayEvent>));
758 interpretation_generics
759 .make_where_clause()
760 .predicates
761 .push(parse_quote!(
762 #field_ty: #behavior::InterpretSends<
763 __BombayInterpreter,
764 __BombayRootEvent,
765 __BombayPath,
766 >
767 ));
768 logical_protocols = quote!(
769 <#logical_protocols as #behavior::BirthProtocolProduct>::Append<
770 <#field_ty as #behavior::LogicalDeliveryProtocols>::Protocols
771 >
772 );
773 }
774 interpretation_generics.make_where_clause().predicates.push(parse_quote!(
775 <#name #type_generics as #behavior::SendSettlements>::InterpretationCustody: ::core::marker::Send
776 ));
777 let (effects_impl, _, effects_where) = effects_generics.split_for_impl();
778 let (logical_impl, _, logical_where) = logical_generics.split_for_impl();
779 let (settlement_impl, _, settlement_where) = settlement_generics.split_for_impl();
780 let (lawful_impl, _, lawful_where) = lawful_generics.split_for_impl();
781 let (interpretation_impl, _, interpretation_where) = interpretation_generics.split_for_impl();
782 let interpretation_loans = fields
783 .iter()
784 .map(|field| {
785 let field_name = field.to_string();
786 format_ident!(
787 "__bombay_{}_interpretation",
788 field_name.trim_start_matches("r#"),
789 span = Span::mixed_site()
790 )
791 })
792 .collect::<Vec<_>>();
793 let loan_fields = interpretation_loans.iter().collect::<Vec<_>>();
794 let remaining_interpretations =
795 Ident::new("__bombay_remaining_interpretations", Span::mixed_site());
796 let interpretation =
797 named_send_interpretation(&loan_fields, field_types, settlement_name, behavior);
798 let source_settlements = field_types
799 .iter()
800 .map(|ty| quote!(<#ty as #behavior::SendSettlements>::Settlements))
801 .collect::<Vec<_>>();
802 let source_custody = field_types
803 .iter()
804 .map(|ty| quote!(<#ty as #behavior::SendSettlements>::SourceCustody))
805 .collect::<Vec<_>>();
806 let (_, source_owner) = source_product_custody(&source_settlements, &source_custody, behavior);
807 let interpretation_custody = field_types
808 .iter()
809 .map(|ty| {
810 quote!(
811 ::core::option::Option<#behavior::InterpretationProgress<
812 #ty,
813 <#ty as #behavior::SendSettlements>::InterpretationCustody,
814 <#ty as #behavior::SendSettlements>::Settlements,
815 >>
816 )
817 })
818 .collect::<Vec<_>>();
819 let completed_controls = fields.iter().map(|_| quote!(::core::option::Option::Some(#behavior::InterpretationProgress::Completed(_)))).collect::<Vec<_>>();
820 let completed_fields = loan_fields
821 .iter()
822 .rev()
823 .fold(quote!(()), |later, field| quote!((#field,#later)));
824 let mut combine = quote!(#behavior::Interpretation::Complete(()));
825 for field in loan_fields.iter().rev() {
827 combine = quote! {
828 match (#field,#combine) {
829 (#behavior::Interpretation::Complete(#field),#behavior::Interpretation::Complete(#remaining_interpretations)) => #behavior::Interpretation::Complete((#field,#remaining_interpretations)),
830 (#behavior::Interpretation::Complete(#field) | #behavior::Interpretation::Corrupt(#field), #behavior::Interpretation::Corrupt(#remaining_interpretations)) |
831 (#behavior::Interpretation::Corrupt(#field),#behavior::Interpretation::Complete(#remaining_interpretations)) => #behavior::Interpretation::Corrupt((#field,#remaining_interpretations)),
832 }
833 };
834 }
835
836 quote! {
837 impl #effects_impl #behavior::SendEffects for #name #type_generics #effects_where {
838 fn empty() -> Self {
839 Self {
840 #(#fields: <#field_types as #behavior::SendEffects>::empty(),)*
841 }
842 }
843
844 fn append(&mut self, other: Self) {
845 #(
846 <#field_types as #behavior::SendEffects>::append(
847 &mut self.#fields,
848 other.#fields,
849 );
850 )*
851 }
852 }
853
854 impl #logical_impl #behavior::LogicalDeliveryProtocols
855 for #name #type_generics #logical_where
856 {
857 type Protocols = #logical_protocols;
858 }
859
860 impl #lawful_impl #behavior::SendsFor<__BombayEvent>
861 for #name #type_generics #lawful_where
862 {}
863
864 impl #settlement_impl #behavior::SendSettlements
865 for #name #type_generics #settlement_where
866 {
867 type Settlements = #settlement_type;
868 type SourceCustody = #source_owner;
869 type InterpretationCustody = (#(#interpretation_custody,)*);
870
871 fn prepare_interpretation(progress: &mut ::core::option::Option<#behavior::InterpretationProgress<Self,Self::InterpretationCustody,Self::Settlements>>) {
872 match progress.take() {
873 ::core::option::Option::Some(#behavior::InterpretationProgress::Original(original)) => {
874 *progress = ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting((
875 #(::core::option::Option::Some(#behavior::InterpretationProgress::Original(original.#fields)),)*
876 )));
877 }
878 retained => *progress = retained,
879 }
880 }
881 fn finish_interpretation(progress: &mut ::core::option::Option<#behavior::InterpretationProgress<Self,Self::InterpretationCustody,Self::Settlements>>) {
882 let ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting(custody)) = progress else { return; };
883 let (#(#loan_fields,)*) = custody;
884 if !matches!((#(&*#loan_fields,)*), (#(#completed_controls,)*)) { return; }
885 match progress.take() {
886 ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting((
887 #(::core::option::Option::Some(#behavior::InterpretationProgress::Completed(#loan_fields)),)*
888 ))) => {
889 let completed = #combine;
890 *progress = ::core::option::Option::Some(#behavior::InterpretationProgress::Completed(match completed {
891 #behavior::Interpretation::Complete(#completed_fields) => #behavior::Interpretation::Complete(#settlement_name { #(#fields: #loan_fields,)* }),
892 #behavior::Interpretation::Corrupt(#completed_fields) => #behavior::Interpretation::Corrupt(#settlement_name { #(#fields: #loan_fields,)* }),
893 }));
894 }
895 retained => *progress = retained,
896 }
897 }
898 fn unattempted(progress: &mut ::core::option::Option<#behavior::InterpretationProgress<Self,Self::InterpretationCustody,Self::Settlements>>) {
899 <Self as #behavior::SendSettlements>::prepare_interpretation(progress);
900 if let ::core::option::Option::Some(#behavior::InterpretationProgress::Interpreting(custody)) = progress {
901 let (#(#loan_fields,)*) = custody;
902 #(<#field_types as #behavior::SendSettlements>::unattempted(#loan_fields);)*
903 }
904 <Self as #behavior::SendSettlements>::finish_interpretation(progress);
905 }
906 }
907
908 impl #interpretation_impl
909 #behavior::InterpretSends<
910 __BombayInterpreter,
911 __BombayRootEvent,
912 __BombayPath,
913 > for #name #type_generics #interpretation_where
914 {
915 fn interpret(
916 progress: &mut ::core::option::Option<#behavior::InterpretationProgress<Self,Self::InterpretationCustody,Self::Settlements>>,
917 interpreter: &mut __BombayInterpreter,
918 ) -> impl ::core::future::Future<Output=()> + ::core::marker::Send {
919 async move {
920 #interpretation
921 }
922 }
923 }
924 }
925}
926
927#[allow(
928 clippy::too_many_lines,
929 reason = "each generated contract is one facet of the same named send product"
930)]
931fn derive_send_product_contract(input: DeriveInput) -> Result<TokenStream2> {
932 let behavior = behavior_crate()?;
933 let name = input.ident;
934 let generics = input.generics;
935 let Data::Struct(data) = input.data else {
936 return Err(Error::new_spanned(
937 name,
938 "send products must be named structs",
939 ));
940 };
941 let Fields::Named(fields) = data.fields else {
942 return Err(Error::new_spanned(
943 name,
944 "send products need named effect lanes",
945 ));
946 };
947 if fields.named.is_empty() {
948 return Err(Error::new_spanned(
949 name,
950 "empty send products must be omitted",
951 ));
952 }
953 let mut parameters = Vec::new();
954 for parameter in &generics.params {
955 let GenericParam::Type(parameter) = parameter else {
956 return Err(Error::new_spanned(
957 parameter,
958 "generic send products use only lane type parameters",
959 ));
960 };
961 parameters.push(¶meter.ident);
962 }
963 let mut field_names = Vec::new();
964 let mut field_types = Vec::new();
965 for field in &fields.named {
966 let Some(field_name) = field.ident.as_ref() else {
967 return Err(Error::new_spanned(field, "send lanes must be named"));
968 };
969 let Type::Path(path) = &field.ty else {
970 return Err(Error::new_spanned(
971 &field.ty,
972 "each send lane must be one product type parameter",
973 ));
974 };
975 let Some(segment) = path.path.get_ident() else {
976 return Err(Error::new_spanned(
977 &field.ty,
978 "each send lane must be one product type parameter",
979 ));
980 };
981 if path.qself.is_some() || !parameters.iter().any(|parameter| *parameter == segment) {
982 return Err(Error::new_spanned(
983 &field.ty,
984 "each send lane must be one product type parameter",
985 ));
986 }
987 field_names.push(field_name);
988 field_types.push(&field.ty);
989 }
990 let (_, type_generics, _) = generics.split_for_impl();
991 let settled_parameters = parameters
992 .iter()
993 .map(|parameter| quote!(<#parameter as #behavior::SendSettlements>::Settlements));
994 let settlement_type = quote!(#name<#(#settled_parameters),*>);
995 let send_contract = named_send_contract(
996 &name,
997 &generics,
998 &field_names,
999 &field_types,
1000 &settlement_type,
1001 &name,
1002 &behavior,
1003 );
1004 let settled_fields = field_types
1005 .iter()
1006 .map(|field_ty| (*field_ty).clone())
1007 .collect::<Vec<_>>();
1008 let settlement_owner = quote!(#name #type_generics);
1009 let settlement_contract = named_settlement_contract(
1010 &name,
1011 &settlement_owner,
1012 &generics,
1013 &field_names,
1014 &settled_fields,
1015 &behavior,
1016 );
1017 Ok(quote!(#send_contract #settlement_contract))
1018}
1019
1020#[proc_macro_derive(SendProduct)]
1023pub fn derive_send_product(input: TokenStream) -> TokenStream {
1024 let input = parse_macro_input!(input as DeriveInput);
1025 derive_send_product_contract(input)
1026 .unwrap_or_else(Error::into_compile_error)
1027 .into()
1028}
1029
1030#[allow(
1031 clippy::too_many_lines,
1032 reason = "the generated nominal product keeps every structural trait implementation adjacent"
1033)]
1034fn generate_sends(
1035 product: Option<SendsSpec>,
1036 actor: &Ident,
1037 item: &ItemImpl,
1038 behavior: &TokenStream2,
1039) -> (TokenStream2, TokenStream2) {
1040 let (product, visibility) = match product {
1041 None => return (quote!(#behavior::NoSends), quote!()),
1042 Some(SendsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
1043 Some(SendsSpec::Generated {
1044 product,
1045 visibility,
1046 }) => (product, visibility),
1047 };
1048 let name = format_ident!("{}Sends", actor);
1049 let settlements_name = format_ident!("{}Settlements", actor);
1050 let field_names: Vec<_> = product.fields.iter().map(|field| &field.name).collect();
1051 let field_types: Vec<_> = product.fields.iter().map(|field| &field.ty).collect();
1052 let lane_names: Vec<_> = field_names
1053 .iter()
1054 .map(|field| lane_name(&name, field))
1055 .collect();
1056 let actions_name = format_ident!("{}Actions", actor);
1057 let action_methods: Vec<_> = field_names
1058 .iter()
1059 .map(|field| {
1060 let field = field.to_string();
1061 format_ident!("send_{}", field.trim_start_matches("r#"))
1062 })
1063 .collect();
1064 let product_generics = product_generics(item, &product.fields);
1065 let (_, type_generics, _) = product_generics.split_for_impl();
1066 let generics = &product_generics;
1067 let actor_type = &item.self_ty;
1068 let mut action_trait_generics = product_generics.clone();
1069 action_trait_generics
1070 .make_where_clause()
1071 .predicates
1072 .push(parse_quote!(#actor_type: ::core::marker::Sized));
1073
1074 let action_trait_methods =
1075 action_methods
1076 .iter()
1077 .zip(lane_names.iter())
1078 .map(|(method, lane)| {
1079 quote! {
1080 #[must_use]
1082 fn #method<__BombayInput>(self, input: __BombayInput) -> Self
1083 where
1084 Self: #behavior::AppendSend<__BombayInput, #lane>;
1085 }
1086 });
1087
1088 let action_impl_methods = action_methods
1089 .iter()
1090 .zip(lane_names.iter())
1091 .map(|(method, lane)| {
1092 quote! {
1093 fn #method<__BombayInput>(self, input: __BombayInput) -> Self
1094 where
1095 Self: #behavior::AppendSend<__BombayInput, #lane>,
1096 {
1097 <Self as #behavior::AppendSend<__BombayInput, #lane>>::append_send(
1098 self,
1099 input,
1100 )
1101 }
1102 }
1103 });
1104
1105 let mut actions_impl_generics = product_generics.clone();
1106 actions_impl_generics
1107 .params
1108 .push(parse_quote!(__BombayAddress));
1109 actions_impl_generics.params.push(parse_quote!(__BombayPh));
1110 actions_impl_generics
1111 .params
1112 .push(parse_quote!(__BombayBirth));
1113 actions_impl_generics
1114 .make_where_clause()
1115 .predicates
1116 .push(parse_quote!(__BombayAddress: #behavior::Address));
1117 actions_impl_generics
1118 .make_where_clause()
1119 .predicates
1120 .push(parse_quote!(__BombayBirth: #behavior::BirthMode));
1121 actions_impl_generics
1122 .make_where_clause()
1123 .predicates
1124 .push(parse_quote!(#actor_type: ::core::marker::Sized));
1125 let (actions_impl_generics, _, actions_where_clause) = actions_impl_generics.split_for_impl();
1126
1127 let send_impls = lane_names
1128 .iter()
1129 .zip(field_names.iter())
1130 .zip(field_types.iter())
1131 .map(|((lane, field), field_ty)| {
1132 let mut generics = product_generics.clone();
1133 generics.params.push(parse_quote!(__BombayInput));
1134 generics.make_where_clause().predicates.push(parse_quote!(
1135 #field_ty: #behavior::SendInput<__BombayInput, #behavior::Own>
1136 ));
1137 let (impl_generics, _, where_clause) = generics.split_for_impl();
1138 quote! {
1139 impl #impl_generics #behavior::SendInput<__BombayInput, #lane>
1140 for #name #type_generics #where_clause
1141 {
1142 fn emit(&mut self, input: __BombayInput) {
1143 <#field_ty as #behavior::SendInput<
1144 __BombayInput,
1145 #behavior::Own,
1146 >>::emit(&mut self.#field, input);
1147 }
1148 }
1149 }
1150 });
1151
1152 let mut settlement_generics = product_generics.clone();
1153 for field_ty in &field_types {
1154 settlement_generics
1155 .make_where_clause()
1156 .predicates
1157 .push(parse_quote!(#field_ty: #behavior::SendSettlements));
1158 }
1159 let (settlement_impl_generics, settlement_type_generics, settlement_where_clause) =
1160 settlement_generics.split_for_impl();
1161 let settlement_type = quote!(#settlements_name #settlement_type_generics);
1162 let send_contract = named_send_contract(
1163 &name,
1164 &product_generics,
1165 &field_names,
1166 &field_types,
1167 &settlement_type,
1168 &settlements_name,
1169 behavior,
1170 );
1171 let settled_fields = field_types
1172 .iter()
1173 .map(|field_ty| parse_quote!(<#field_ty as #behavior::SendSettlements>::Settlements))
1174 .collect::<Vec<Type>>();
1175 let settlement_contract = named_settlement_contract(
1176 &settlements_name,
1177 &settlement_type,
1178 &settlement_generics,
1179 &field_names,
1180 &settled_fields,
1181 behavior,
1182 );
1183
1184 let items = quote! {
1185 #(#visibility enum #lane_names {})*
1186
1187 #visibility struct #name #generics {
1188 #(pub #field_names: #field_types,)*
1189 }
1190
1191 #visibility struct #settlements_name #settlement_impl_generics #settlement_where_clause {
1192 #(
1193 pub #field_names: <#field_types as #behavior::SendSettlements>::Settlements,
1194 )*
1195 }
1196
1197 #settlement_contract
1198
1199 #visibility trait #actions_name #action_trait_generics: ::core::marker::Sized {
1202 #(#action_trait_methods)*
1203 }
1204
1205 impl #actions_impl_generics #actions_name #type_generics
1206 for #behavior::Actions<
1207 __BombayAddress,
1208 __BombayPh,
1209 #name #type_generics,
1210 __BombayBirth,
1211 >
1212 #actions_where_clause
1213 {
1214 #(#action_impl_methods)*
1215 }
1216
1217 #send_contract
1218
1219 #(#send_impls)*
1220 };
1221 (quote!(#name #type_generics), items)
1222}
1223
1224fn generate_births(
1225 product: Option<BirthsSpec>,
1226 disposition: Option<CreationSettlementDisposition>,
1227 actor: &Ident,
1228 _addr: &Type,
1229 item: &ItemImpl,
1230 behavior: &TokenStream2,
1231) -> (TokenStream2, TokenStream2) {
1232 let product = match product {
1233 None => return (quote!(#behavior::NoBirths), quote!()),
1234 Some(BirthsSpec::Existing(ty)) => {
1235 if disposition.is_none() {
1236 return (quote!(#ty), quote!());
1237 }
1238 let child = quote!(<#ty as #behavior::BirthMode>::Child);
1239 let birth = match disposition {
1240 Some(CreationSettlementDisposition::ReturnToCreator) => {
1241 quote!(#behavior::Births<#child>)
1242 }
1243 Some(CreationSettlementDisposition::RetainForRetirement) => {
1244 quote!(#behavior::RetirementBirths<#child>)
1245 }
1246 None => unreachable!("a no-birth declaration has no settlement disposition"),
1247 };
1248 return (birth, quote!());
1249 }
1250 Some(BirthsSpec::Generated(product)) => product,
1251 };
1252 let name = format_ident!("{}Children", actor);
1253 let roles_name = format_ident!("{}Child", actor);
1254 let role_names = product
1255 .fields
1256 .iter()
1257 .map(|field| lane_name(&name, &field.name))
1258 .collect::<Vec<_>>();
1259 let role_values = product
1260 .fields
1261 .iter()
1262 .map(|field| format_ident!("{}", pascal_name(&field.name)))
1263 .collect::<Vec<_>>();
1264 let declared_child_types = product
1265 .fields
1266 .iter()
1267 .map(|field| &field.ty)
1268 .collect::<Vec<_>>();
1269 let role_positions = (0..product.fields.len())
1270 .map(|index| {
1271 (0..(product.fields.len() - index - 1)).fold(
1272 quote!(#behavior::ChildHead),
1273 |position, _| quote!(#behavior::ChildTail<#position>),
1274 )
1275 })
1276 .collect::<Vec<_>>();
1277 let product_generics = product_generics(item, &product.fields);
1278 let (_, type_generics, _) = product_generics.split_for_impl();
1279 let generics = &product_generics;
1280 let parent = &item.self_ty;
1281 let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
1282 let choice = match declared_child_types.as_slice() {
1283 [child] => quote!(#child),
1284 children => children.iter().fold(
1285 quote!(#behavior::Never),
1286 |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
1287 ),
1288 };
1289 let birth = match disposition {
1290 Some(CreationSettlementDisposition::ReturnToCreator) => {
1291 quote!(#behavior::Births<#name #type_generics>)
1292 }
1293 Some(CreationSettlementDisposition::RetainForRetirement) => {
1294 quote!(#behavior::RetirementBirths<#name #type_generics>)
1295 }
1296 None => unreachable!("a generated birth product has an explicit settlement disposition"),
1297 };
1298 (
1299 birth,
1300 quote! {
1301 pub type #name #generics = #choice;
1302
1303 #(
1304 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1305 pub struct #role_names;
1306
1307 impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
1308 #parent_where_clause
1309 {
1310 type Child = #declared_child_types;
1311 type Position = #role_positions;
1312 }
1313
1314 impl #parent_impl_generics #behavior::ChildOccurrence<#parent> for #role_names
1315 #parent_where_clause
1316 {
1317 type Resolution = #behavior::DeclaredChildOccurrence;
1318 }
1319 )*
1320
1321 pub struct #roles_name;
1322
1323 #[allow(
1324 non_upper_case_globals,
1325 reason = "generated role values retain the application-authored field spelling"
1326 )]
1327 impl #roles_name {
1328 #(pub const #role_values: #role_names = #role_names;)*
1329 }
1330
1331 },
1332 )
1333}
1334
1335#[proc_macro_attribute]
1341#[allow(
1342 clippy::too_many_lines,
1343 reason = "validation and the one coherent Behavior expansion remain in one entry point"
1344)]
1345pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
1346 let args = parse_macro_input!(args as BehaviorArgs);
1347 let item = parse_macro_input!(item as ItemImpl);
1348
1349 if item.trait_.is_some() {
1350 return Error::new_spanned(
1351 &item,
1352 "#[behavior] applies to an inherent impl, not a trait impl",
1353 )
1354 .to_compile_error()
1355 .into();
1356 }
1357
1358 let init = item.items.iter().find_map(|item| match item {
1359 ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
1360 _ => None,
1361 });
1362 let receive = item.items.iter().find_map(|item| match item {
1363 ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
1364 _ => None,
1365 });
1366 let creations_settled = item.items.iter().find_map(|item| match item {
1367 ImplItem::Fn(method) if method.sig.ident == "creations_settled" => Some(method),
1368 _ => None,
1369 });
1370 let Some(receive) = receive else {
1371 return Error::new_spanned(
1372 &item.self_ty,
1373 "#[behavior] requires a receive(&mut self, from, message) method",
1374 )
1375 .to_compile_error()
1376 .into();
1377 };
1378 if let Err(error) = init
1379 .map_or(Ok(()), validate_receiver)
1380 .and_then(|()| validate_receiver(receive))
1381 {
1382 return error.to_compile_error().into();
1383 }
1384 if let Some(init) = init
1385 && init.sig.inputs.len() != 1
1386 {
1387 return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
1388 .to_compile_error()
1389 .into();
1390 }
1391 if receive.sig.inputs.len() != 3 {
1392 return Error::new_spanned(
1393 &receive.sig,
1394 "receive must accept exactly &mut self, from, and message",
1395 )
1396 .to_compile_error()
1397 .into();
1398 }
1399 match (args.creation_settlements, creations_settled) {
1400 (Some(CreationSettlementDisposition::ReturnToCreator), Some(method)) => {
1401 if let Err(error) = validate_receiver(method) {
1402 return error.to_compile_error().into();
1403 }
1404 if method.sig.inputs.len() != 2 {
1405 return Error::new_spanned(
1406 &method.sig,
1407 "creations_settled must accept exactly &mut self and one CreationsSettled value",
1408 )
1409 .to_compile_error()
1410 .into();
1411 }
1412 }
1413 (Some(CreationSettlementDisposition::ReturnToCreator), None) => {
1414 return Error::new_spanned(
1415 &item.self_ty,
1416 "`creation_settlements = return_to_creator` requires a creations_settled transition",
1417 )
1418 .to_compile_error()
1419 .into();
1420 }
1421 (Some(CreationSettlementDisposition::RetainForRetirement), Some(method)) => {
1422 return Error::new_spanned(
1423 &method.sig,
1424 "retirement-owned creation settlements do not enter a creations_settled transition",
1425 )
1426 .to_compile_error()
1427 .into();
1428 }
1429 (Some(CreationSettlementDisposition::RetainForRetirement), None) | (None, None) => {}
1430 (None, Some(_)) => {}
1431 }
1432
1433 let BehaviorArgs {
1434 addr,
1435 message,
1436 sends,
1437 births,
1438 creation_settlements,
1439 error,
1440 } = args;
1441 let self_ty = &item.self_ty;
1442 let Type::Path(self_path) = self_ty.as_ref() else {
1443 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1444 .to_compile_error()
1445 .into();
1446 };
1447 let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
1448 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
1449 .to_compile_error()
1450 .into();
1451 };
1452 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1453 let behavior = match behavior_crate() {
1454 Ok(behavior) => behavior,
1455 Err(error) => return error.to_compile_error().into(),
1456 };
1457 let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
1458 let initialize = init.map_or_else(
1459 || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
1460 |_| quote!(<#self_ty>::init(self)),
1461 );
1462
1463 let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
1464 let (births_ty, births_items) = generate_births(
1465 births,
1466 creation_settlements,
1467 self_name,
1468 &addr,
1469 &item,
1470 &behavior,
1471 );
1472 let (event_ty, transition) = match creation_settlements {
1473 Some(CreationSettlementDisposition::ReturnToCreator) => {
1474 let child = quote!(<#births_ty as #behavior::BirthMode>::Child);
1475 (
1476 quote!(#behavior::CreationEvent<#addr, #child, #message>),
1477 quote! {
1478 match event {
1479 #behavior::CreationEvent::Settlements(settlements) => {
1480 <#self_ty>::creations_settled(self, settlements)
1481 }
1482 #behavior::CreationEvent::User(event) => {
1483 <#self_ty>::receive(self, event.from, event.message)
1484 }
1485 }
1486 },
1487 )
1488 }
1489 Some(CreationSettlementDisposition::RetainForRetirement) | None => (
1490 quote!(#behavior::User<#addr, #message>),
1491 quote!(<#self_ty>::receive(self, event.from, event.message)),
1492 ),
1493 };
1494
1495 quote! {
1496 #sends_items
1497 #births_items
1498 #item
1499
1500 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1501 type Addr = #addr;
1502 type Msg = #message;
1503 }
1504
1505 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1506 type Protocol = Self;
1507 type Event = #event_ty;
1508 type Sends = #sends_ty;
1509 type Ph = #behavior::Never;
1510 type Error = #error;
1511 type Birth = #births_ty;
1512
1513 fn init(
1514 &mut self,
1515 _: #behavior::InitializationTurn,
1516 ) -> #behavior::BehaviorActed<Self> {
1517 #initialize
1518 }
1519
1520 fn transition(
1521 &mut self,
1522 _: #behavior::ActiveTurn,
1523 event: Self::Event,
1524 ) -> #behavior::BehaviorActed<Self> {
1525 #transition
1526 }
1527 }
1528
1529 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1530 type Base = Self;
1531
1532 fn base(&self) -> &Self {
1533 self
1534 }
1535 }
1536 }
1537 .into()
1538}
1539
1540#[proc_macro_attribute]
1548pub fn pool_worker(args: TokenStream, item: TokenStream) -> TokenStream {
1549 let PoolWorkerArgs { addr, result } = parse_macro_input!(args as PoolWorkerArgs);
1550 let mut item = parse_macro_input!(item as ItemImpl);
1551
1552 if item.trait_.is_some() {
1553 return Error::new_spanned(
1554 &item,
1555 "#[pool_worker] applies to an inherent impl, not a trait impl",
1556 )
1557 .to_compile_error()
1558 .into();
1559 }
1560
1561 let self_ty = item.self_ty.clone();
1562 if !matches!(self_ty.as_ref(), Type::Path(path) if path.qself.is_none()) {
1563 return Error::new_spanned(&self_ty, "#[pool_worker] requires a nominal worker type")
1564 .to_compile_error()
1565 .into();
1566 }
1567
1568 let actors = match actors_crate() {
1569 Ok(actors) => actors,
1570 Err(error) => return error.to_compile_error().into(),
1571 };
1572 let behavior = match behavior_crate() {
1573 Ok(behavior) => behavior,
1574 Err(error) => return error.to_compile_error().into(),
1575 };
1576 let assignment = {
1577 let mut transitions = item.items.iter_mut().filter_map(|item| match item {
1578 ImplItem::Fn(method) if method.sig.ident == "transition" => Some(method),
1579 _ => None,
1580 });
1581 let Some(transition) = transitions.next() else {
1582 return Error::new_spanned(
1583 &self_ty,
1584 "#[pool_worker] requires transition(&mut self, Assignment<Job>)",
1585 )
1586 .to_compile_error()
1587 .into();
1588 };
1589 if let Some(duplicate) = transitions.next() {
1590 return Error::new_spanned(
1591 &duplicate.sig,
1592 "#[pool_worker] accepts exactly one transition method",
1593 )
1594 .to_compile_error()
1595 .into();
1596 }
1597 let assignment = match pool_worker_assignment(transition) {
1598 Ok(assignment) => assignment,
1599 Err(error) => return error.to_compile_error().into(),
1600 };
1601 transition.sig.output = parse_quote!(-> #behavior::BehaviorActed<Self>);
1602 assignment
1603 };
1604 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
1605
1606 quote! {
1607 #item
1608
1609 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
1610 type Addr = #addr;
1611 type Msg = #assignment;
1612 }
1613
1614 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
1615 type Protocol = Self;
1616 type Event = #behavior::User<#addr, #assignment>;
1617 type Sends = #behavior::InterpreterRequests<
1618 #behavior::ReportToParent<#actors::atomic::Completion<#result>>
1619 >;
1620 type Ph = #behavior::Never;
1621 type Error = #behavior::Never;
1622 type Birth = #behavior::NoBirths;
1623
1624 fn transition(
1625 &mut self,
1626 _: #behavior::ActiveTurn,
1627 event: Self::Event,
1628 ) -> #behavior::BehaviorActed<Self> {
1629 <#self_ty>::transition(self, event.message)
1630 }
1631 }
1632
1633 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
1634 type Base = Self;
1635
1636 fn base(&self) -> &Self {
1637 self
1638 }
1639 }
1640 }
1641 .into()
1642}
1643
1644#[cfg(test)]
1645mod tests {
1646 use super::*;
1647
1648 fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
1649 if at == values.len() {
1650 output.push(values.join(", "));
1651 return;
1652 }
1653 for index in at..values.len() {
1654 values.swap(at, index);
1655 permutations(values, at + 1, output);
1656 values.swap(at, index);
1657 }
1658 }
1659
1660 #[test]
1661 fn every_argument_order_parses() {
1662 let mut declarations = [
1663 "addr = u64",
1664 "message = String",
1665 "sends = { first: Vec<u8>, second: Vec<u16> }",
1666 "births = { first: u8, second: u16 }",
1667 "creation_settlements = retain_for_retirement",
1668 "error = String",
1669 ];
1670 let mut cases = Vec::new();
1671 permutations(&mut declarations, 0, &mut cases);
1672 assert_eq!(cases.len(), 720);
1673 for case in cases {
1674 syn::parse_str::<BehaviorArgs>(&case)
1675 .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
1676 }
1677 }
1678
1679 #[test]
1680 fn capability_omission_and_trailing_comma_parse() {
1681 syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
1682 .expect("only required protocol declarations are sufficient");
1683 for declaration in ["NoBirths", "behavior::NoBirths"] {
1684 syn::parse_str::<BehaviorArgs>(&format!(
1685 "message = (), addr = u8, births = {declaration}"
1686 ))
1687 .unwrap_or_else(|error| panic!("explicit no-birth declaration did not parse: {error}"));
1688 }
1689 }
1690
1691 #[test]
1692 fn invalid_declaration_states_are_rejected() {
1693 for case in [
1694 "message = ()",
1695 "addr = u8",
1696 "addr = u8, message = (), addr = u16",
1697 "addr = u8, message = (), unknown = u8",
1698 "addr = u8, message = (), sends = {}",
1699 "addr = u8, message = (), births = {}",
1700 "addr = u8, message = (), births = {}, creation_settlements = retain_for_retirement",
1701 "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
1702 "addr = u8, message = (), births = { same: u8, same: u16 }, creation_settlements = retain_for_retirement",
1703 "addr = u8, message = (), creation_settlements = retain_for_retirement",
1704 "addr = u8, message = (), births = { child: u8 }, creation_settlements = discard",
1705 "addr = u8, message = (), births = { child: u8 }, creation_settlements = retain_for_retirement, creation_settlements = return_to_creator",
1706 ] {
1707 assert!(
1708 syn::parse_str::<BehaviorArgs>(case).is_err(),
1709 "invalid declaration parsed: `{case}`"
1710 );
1711 }
1712 }
1713
1714 #[test]
1715 fn pool_worker_arguments_require_one_address_and_result() {
1716 for valid in [
1717 "addr = u64, result = String",
1718 "result = String, addr = u64,",
1719 ] {
1720 syn::parse_str::<PoolWorkerArgs>(valid)
1721 .unwrap_or_else(|error| panic!("failed to parse `{valid}`: {error}"));
1722 }
1723 for invalid in [
1724 "addr = u64",
1725 "result = String",
1726 "addr = u64, result = String, addr = u32",
1727 "addr = u64, result = String, result = u8",
1728 "addr = u64, result = String, message = u8",
1729 ] {
1730 assert!(
1731 syn::parse_str::<PoolWorkerArgs>(invalid).is_err(),
1732 "invalid pool worker arguments parsed: `{invalid}`"
1733 );
1734 }
1735 }
1736
1737 #[test]
1738 fn pool_worker_transition_requires_the_domain_source_shape() {
1739 let valid: ItemImpl = syn::parse_str(
1740 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) \
1741 -> WorkerActed<Self> { result() } }",
1742 )
1743 .expect("valid inherent worker declaration parses");
1744 let method = valid
1745 .items
1746 .iter()
1747 .find_map(|item| match item {
1748 ImplItem::Fn(method) => Some(method),
1749 _ => None,
1750 })
1751 .expect("fixture declares one method");
1752 let assignment = pool_worker_assignment(method)
1753 .unwrap_or_else(|error| panic!("valid worker transition rejected: {error}"));
1754 assert_eq!(quote!(#assignment).to_string(), "Assignment < Job >");
1755
1756 for invalid in [
1757 "impl Worker { fn transition(self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1758 "impl Worker { async fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Self> { result() } }",
1759 "impl Worker { fn transition(&mut self, job: Job) -> WorkerActed<Self> { result() } }",
1760 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> WorkerActed<Job> { result() } }",
1761 "impl Worker { fn transition(&mut self, assignment: Assignment<Job>) -> Result<(), ()> { result() } }",
1762 ] {
1763 let declaration: ItemImpl = syn::parse_str(invalid)
1764 .unwrap_or_else(|error| panic!("invalid fixture did not parse: {error}"));
1765 let method = declaration
1766 .items
1767 .iter()
1768 .find_map(|item| match item {
1769 ImplItem::Fn(method) => Some(method),
1770 _ => None,
1771 })
1772 .expect("fixture declares one method");
1773 assert!(
1774 pool_worker_assignment(method).is_err(),
1775 "invalid pool worker transition was accepted: `{invalid}`"
1776 );
1777 }
1778 }
1779}