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