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