1use proc_macro::TokenStream;
7use proc_macro_crate::{FoundCrate, crate_name};
8use proc_macro2::{Span, TokenStream as TokenStream2};
9use quote::{format_ident, quote};
10use syn::parse::{Parse, ParseStream};
11use syn::visit::Visit;
12use syn::{
13 Error, FnArg, GenericParam, Generics, Ident, ImplItem, ItemImpl, Result, ReturnType, Token,
14 Type, braced, parse_macro_input, parse_quote,
15};
16
17fn crate_path(found: FoundCrate) -> TokenStream2 {
18 match found {
19 FoundCrate::Itself => quote!(crate),
20 FoundCrate::Name(name) => {
21 let name = syn::Ident::new(&name, Span::call_site());
22 quote!(::#name)
23 }
24 }
25}
26
27fn facade_crate_path(found: FoundCrate) -> TokenStream2 {
28 match found {
29 FoundCrate::Itself => quote!(crate),
30 FoundCrate::Name(name) => {
31 let name = if name == "bombay_rs" {
37 "bombay".to_owned()
38 } else {
39 name
40 };
41 let name = syn::Ident::new(&name, Span::call_site());
42 quote!(::#name)
43 }
44 }
45}
46
47fn behavior_crate() -> Result<TokenStream2> {
48 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
49 return Ok(quote!(::behavior));
53 }
54 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-rs") {
55 return if std::env::var("CARGO_CRATE_NAME").as_deref() == Ok("bombay") {
60 Ok(quote!(crate::behavior))
61 } else {
62 Ok(quote!(::bombay::behavior))
63 };
64 }
65 if let Ok(found) = crate_name("bombay-behavior") {
66 return Ok(crate_path(found));
67 }
68 if let Ok(found) = crate_name("bombay-rs") {
69 let bombay = facade_crate_path(found);
70 return Ok(quote!(#bombay::behavior));
71 }
72 Err(Error::new(
73 Span::call_site(),
74 "could not resolve `bombay-behavior` directly or through `bombay-rs`",
75 ))
76}
77
78#[cfg(test)]
79mod crate_resolution_tests {
80 use super::*;
81
82 #[test]
83 fn facade_default_library_name_and_explicit_rename_resolve_distinctly() {
84 assert_eq!(
85 facade_crate_path(FoundCrate::Name("bombay_rs".to_owned())).to_string(),
86 ":: bombay"
87 );
88 assert_eq!(
89 facade_crate_path(FoundCrate::Name("runtime".to_owned())).to_string(),
90 ":: runtime"
91 );
92 }
93}
94
95struct NamedField {
96 name: Ident,
97 ty: Type,
98}
99
100struct NamedProduct {
101 fields: Vec<NamedField>,
102}
103
104enum SendsSpec {
105 Existing(Type),
106 Generated(NamedProduct),
107}
108
109enum BirthsSpec {
110 Existing(Type),
111 Generated(NamedProduct),
112}
113
114fn parse_product(input: ParseStream) -> Result<NamedProduct> {
115 let content;
116 braced!(content in input);
117 let mut fields = Vec::new();
118 let mut names = std::collections::BTreeSet::new();
119 while !content.is_empty() {
120 let name: Ident = content.parse()?;
121 if !names.insert(name.to_string()) {
122 return Err(Error::new_spanned(name, "duplicate product lane"));
123 }
124 content.parse::<Token![:]>()?;
125 fields.push(NamedField {
126 name,
127 ty: content.parse()?,
128 });
129 if content.peek(Token![,]) {
130 content.parse::<Token![,]>()?;
131 } else if !content.is_empty() {
132 return Err(content.error("expected `,` between product lanes"));
133 }
134 }
135 if fields.is_empty() {
136 return Err(input.error("empty products must be omitted"));
137 }
138 Ok(NamedProduct { fields })
139}
140
141struct BehaviorArgs {
142 addr: Type,
143 message: Type,
144 sends: Option<SendsSpec>,
145 births: Option<BirthsSpec>,
146 error: Option<Type>,
147}
148
149impl Parse for BehaviorArgs {
150 fn parse(input: ParseStream) -> Result<Self> {
151 let mut addr = None;
152 let mut message = None;
153 let mut sends = None;
154 let mut births = None;
155 let mut error = None;
156 while !input.is_empty() {
157 let key: Ident = input.parse()?;
158 input.parse::<Token![=]>()?;
159 match key.to_string().as_str() {
160 "addr" if addr.is_none() => addr = Some(input.parse()?),
161 "message" if message.is_none() => message = Some(input.parse()?),
162 "sends" if sends.is_none() => {
163 sends = Some(if input.peek(syn::token::Brace) {
164 SendsSpec::Generated(parse_product(input)?)
165 } else {
166 SendsSpec::Existing(input.parse()?)
167 });
168 }
169 "births" if births.is_none() => {
170 births = Some(if input.peek(syn::token::Brace) {
171 BirthsSpec::Generated(parse_product(input)?)
172 } else {
173 BirthsSpec::Existing(input.parse()?)
174 });
175 }
176 "error" if error.is_none() => error = Some(input.parse()?),
177 "addr" | "message" | "sends" | "births" | "error" => {
178 return Err(Error::new_spanned(key, "duplicate behavior argument"));
179 }
180 _ => return Err(Error::new_spanned(key, "unknown behavior argument")),
181 }
182 if input.peek(Token![,]) {
183 input.parse::<Token![,]>()?;
184 } else if !input.is_empty() {
185 return Err(input.error("expected `,` between behavior arguments"));
186 }
187 }
188 Ok(Self {
189 addr: addr.ok_or_else(|| input.error("missing `addr`"))?,
190 message: message.ok_or_else(|| input.error("missing `message`"))?,
191 sends,
192 births,
193 error,
194 })
195 }
196}
197
198fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
199 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
200 return Err(Error::new_spanned(
201 &method.sig,
202 "behavior methods must begin with &mut self",
203 ));
204 };
205 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
206 return Err(Error::new_spanned(
207 receiver,
208 "behavior methods must begin with &mut self",
209 ));
210 }
211 if method.sig.constness.is_some()
212 || method.sig.asyncness.is_some()
213 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
214 || !method.sig.generics.params.is_empty()
215 {
216 return Err(Error::new_spanned(
217 &method.sig,
218 "behavior init and receive methods must be synchronous, safe, and non-generic",
219 ));
220 }
221 if matches!(method.sig.output, ReturnType::Default) {
222 return Err(Error::new_spanned(
223 &method.sig,
224 "behavior methods must declare their complete Actions result type",
225 ));
226 }
227 Ok(())
228}
229
230fn pascal_name(field: &Ident) -> String {
231 field
232 .to_string()
233 .split('_')
234 .filter(|part| !part.is_empty())
235 .map(|part| {
236 let mut chars = part.chars();
237 chars.next().map_or_else(String::new, |first| {
238 first.to_uppercase().chain(chars).collect()
239 })
240 })
241 .collect::<String>()
242}
243
244fn lane_name(product: &Ident, field: &Ident) -> Ident {
245 let lane = pascal_name(field);
246 format_ident!("{}{}", product, lane)
247}
248
249#[derive(Default)]
250struct GenericUses {
251 identifiers: std::collections::BTreeSet<String>,
252 lifetimes: std::collections::BTreeSet<String>,
253}
254
255impl<'ast> Visit<'ast> for GenericUses {
256 fn visit_ident(&mut self, ident: &'ast Ident) {
257 self.identifiers.insert(ident.to_string());
258 }
259
260 fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
261 self.lifetimes.insert(lifetime.ident.to_string());
262 }
263}
264
265fn product_generics(item: &ItemImpl, fields: &[NamedField]) -> Generics {
266 let mut uses = GenericUses::default();
267 for field in fields {
268 uses.visit_type(&field.ty);
269 }
270 let mut generics = item.generics.clone();
271 generics.params = generics
272 .params
273 .into_iter()
274 .filter(|parameter| match parameter {
275 GenericParam::Lifetime(parameter) => uses
276 .lifetimes
277 .contains(¶meter.lifetime.ident.to_string()),
278 GenericParam::Type(parameter) => {
279 uses.identifiers.contains(¶meter.ident.to_string())
280 }
281 GenericParam::Const(parameter) => {
282 uses.identifiers.contains(¶meter.ident.to_string())
283 }
284 })
285 .collect();
286 generics.where_clause = None;
287 generics
288}
289
290#[allow(
291 clippy::too_many_lines,
292 reason = "the generated nominal product keeps every structural trait implementation adjacent"
293)]
294fn generate_sends(
295 product: Option<SendsSpec>,
296 actor: &Ident,
297 item: &ItemImpl,
298 behavior: &TokenStream2,
299) -> (TokenStream2, TokenStream2) {
300 let product = match product {
301 None => return (quote!(#behavior::NoSends), quote!()),
302 Some(SendsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
303 Some(SendsSpec::Generated(product)) => product,
304 };
305 let name = format_ident!("{}Sends", actor);
306 let field_names: Vec<_> = product.fields.iter().map(|field| &field.name).collect();
307 let field_types: Vec<_> = product.fields.iter().map(|field| &field.ty).collect();
308 let lane_names: Vec<_> = field_names
309 .iter()
310 .map(|field| lane_name(&name, field))
311 .collect();
312 let actions_name = format_ident!("{}Actions", actor);
313 let action_methods: Vec<_> = field_names
314 .iter()
315 .map(|field| {
316 let field = field.to_string();
317 format_ident!("send_{}", field.trim_start_matches("r#"))
318 })
319 .collect();
320 let product_generics = product_generics(item, &product.fields);
321 let (_, type_generics, _) = product_generics.split_for_impl();
322 let generics = &product_generics;
323 let actor_type = &item.self_ty;
324 let mut action_trait_generics = product_generics.clone();
325 action_trait_generics
326 .make_where_clause()
327 .predicates
328 .push(parse_quote!(#actor_type: ::core::marker::Sized));
329
330 let action_trait_methods =
331 action_methods
332 .iter()
333 .zip(lane_names.iter())
334 .map(|(method, lane)| {
335 quote! {
336 #[must_use]
338 fn #method<__BombayInput>(self, input: __BombayInput) -> Self
339 where
340 Self: #behavior::AppendSend<__BombayInput, #lane>;
341 }
342 });
343
344 let action_impl_methods = action_methods
345 .iter()
346 .zip(lane_names.iter())
347 .map(|(method, lane)| {
348 quote! {
349 fn #method<__BombayInput>(self, input: __BombayInput) -> Self
350 where
351 Self: #behavior::AppendSend<__BombayInput, #lane>,
352 {
353 <Self as #behavior::AppendSend<__BombayInput, #lane>>::append_send(
354 self,
355 input,
356 )
357 }
358 }
359 });
360
361 let mut actions_impl_generics = product_generics.clone();
362 actions_impl_generics
363 .params
364 .push(parse_quote!(__BombayAddress));
365 actions_impl_generics.params.push(parse_quote!(__BombayPh));
366 actions_impl_generics
367 .params
368 .push(parse_quote!(__BombayBirth));
369 actions_impl_generics
370 .make_where_clause()
371 .predicates
372 .push(parse_quote!(__BombayAddress: #behavior::Address));
373 actions_impl_generics
374 .make_where_clause()
375 .predicates
376 .push(parse_quote!(__BombayBirth: #behavior::BirthMode));
377 actions_impl_generics
378 .make_where_clause()
379 .predicates
380 .push(parse_quote!(#actor_type: ::core::marker::Sized));
381 let (actions_impl_generics, _, actions_where_clause) = actions_impl_generics.split_for_impl();
382
383 let send_impls = lane_names
384 .iter()
385 .zip(field_names.iter())
386 .zip(field_types.iter())
387 .map(|((lane, field), field_ty)| {
388 let mut generics = product_generics.clone();
389 generics.params.push(parse_quote!(__BombayInput));
390 generics.make_where_clause().predicates.push(parse_quote!(
391 #field_ty: #behavior::SendInput<__BombayInput, #behavior::Own>
392 ));
393 let (impl_generics, _, where_clause) = generics.split_for_impl();
394 quote! {
395 impl #impl_generics #behavior::SendInput<__BombayInput, #lane>
396 for #name #type_generics #where_clause
397 {
398 fn emit(&mut self, input: __BombayInput) {
399 <#field_ty as #behavior::SendInput<
400 __BombayInput,
401 #behavior::Own,
402 >>::emit(&mut self.#field, input);
403 }
404 }
405 }
406 });
407
408 let mut effects_generics = product_generics.clone();
409 for field_ty in &field_types {
410 effects_generics
411 .make_where_clause()
412 .predicates
413 .push(parse_quote!(#field_ty: #behavior::SendEffects));
414 }
415 let (effects_impl_generics, _, effects_where_clause) = effects_generics.split_for_impl();
416
417 let mut lawful_generics = product_generics.clone();
418 lawful_generics.params.push(parse_quote!(__BombayEvent));
419 for field_ty in &field_types {
420 lawful_generics
421 .make_where_clause()
422 .predicates
423 .push(parse_quote!(#field_ty: #behavior::SendsFor<__BombayEvent>));
424 }
425 let (lawful_impl_generics, _, lawful_where_clause) = lawful_generics.split_for_impl();
426
427 let mut interpret_generics = product_generics.clone();
428 interpret_generics
429 .params
430 .push(parse_quote!(__BombayInterpreter));
431 interpret_generics
432 .params
433 .push(parse_quote!(__BombayRootEvent));
434 interpret_generics.params.push(parse_quote!(__BombayPath));
435 interpret_generics
436 .make_where_clause()
437 .predicates
438 .push(parse_quote!(__BombayInterpreter: #behavior::SendInterpreter));
439 for field_ty in &field_types {
440 interpret_generics
441 .make_where_clause()
442 .predicates
443 .push(parse_quote!(
444 #field_ty: #behavior::InterpretSends<
445 __BombayInterpreter,
446 __BombayRootEvent,
447 __BombayPath,
448 >
449 ));
450 }
451 interpret_generics
452 .make_where_clause()
453 .predicates
454 .push(parse_quote!(#name #type_generics: ::core::marker::Send));
455 let (interpret_impl_generics, _, interpret_where_clause) = interpret_generics.split_for_impl();
456
457 let items = quote! {
458 #(pub enum #lane_names {})*
459
460 pub struct #name #generics {
461 #(pub #field_names: #field_types,)*
462 }
463
464 pub trait #actions_name #action_trait_generics: ::core::marker::Sized {
467 #(#action_trait_methods)*
468 }
469
470 impl #actions_impl_generics #actions_name #type_generics
471 for #behavior::Actions<
472 __BombayAddress,
473 __BombayPh,
474 #name #type_generics,
475 __BombayBirth,
476 >
477 #actions_where_clause
478 {
479 #(#action_impl_methods)*
480 }
481
482 impl #effects_impl_generics #behavior::SendEffects for #name #type_generics
483 #effects_where_clause
484 {
485 fn empty() -> Self {
486 Self {
487 #(#field_names: <#field_types as #behavior::SendEffects>::empty(),)*
488 }
489 }
490
491 fn append(&mut self, other: Self) {
492 #(
493 <#field_types as #behavior::SendEffects>::append(
494 &mut self.#field_names,
495 other.#field_names,
496 );
497 )*
498 }
499 }
500
501 impl #lawful_impl_generics #behavior::SendsFor<__BombayEvent>
502 for #name #type_generics #lawful_where_clause
503 {}
504
505 #(#send_impls)*
506
507 impl #interpret_impl_generics
508 #behavior::InterpretSends<__BombayInterpreter, __BombayRootEvent, __BombayPath>
509 for #name #type_generics #interpret_where_clause
510 {
511 fn interpret(
512 self,
513 interpreter: &mut __BombayInterpreter,
514 ) -> impl ::core::future::Future<
515 Output = ::core::result::Result<(), __BombayInterpreter::Error>,
516 > + ::core::marker::Send {
517 async move {
518 #(
519 <#field_types as #behavior::InterpretSends<
520 __BombayInterpreter,
521 __BombayRootEvent,
522 __BombayPath,
523 >>::interpret(self.#field_names, interpreter).await?;
524 )*
525 ::core::result::Result::Ok(())
526 }
527 }
528 }
529 };
530 (quote!(#name #type_generics), items)
531}
532
533fn generate_births(
534 product: Option<BirthsSpec>,
535 actor: &Ident,
536 addr: &Type,
537 item: &ItemImpl,
538 behavior: &TokenStream2,
539) -> (TokenStream2, TokenStream2) {
540 let product = match product {
541 None => return (quote!(#behavior::NoBirths), quote!()),
542 Some(BirthsSpec::Existing(ty)) => return (quote!(#ty), quote!()),
543 Some(BirthsSpec::Generated(product)) => product,
544 };
545 let name = format_ident!("{}Children", actor);
546 let roles_name = format_ident!("{}Child", actor);
547 let routes_name = format_ident!("{}ChildrenRoutes", actor);
548 let field_names = product
549 .fields
550 .iter()
551 .map(|field| &field.name)
552 .collect::<Vec<_>>();
553 let role_names = product
554 .fields
555 .iter()
556 .map(|field| lane_name(&name, &field.name))
557 .collect::<Vec<_>>();
558 let role_values = product
559 .fields
560 .iter()
561 .map(|field| format_ident!("{}", pascal_name(&field.name)))
562 .collect::<Vec<_>>();
563 let declared_child_types = product
564 .fields
565 .iter()
566 .map(|field| &field.ty)
567 .collect::<Vec<_>>();
568 let role_positions = (0..product.fields.len())
569 .map(|index| {
570 (0..(product.fields.len() - index - 1)).fold(
571 quote!(#behavior::ChildHead),
572 |position, _| quote!(#behavior::ChildTail<#position>),
573 )
574 })
575 .collect::<Vec<_>>();
576 let child_types = product.fields.iter().map(|field| &field.ty);
577 let product_generics = product_generics(item, &product.fields);
578 let (impl_generics, type_generics, where_clause) = product_generics.split_for_impl();
579 let generics = &product_generics;
580 let parent = &item.self_ty;
581 let (parent_impl_generics, _, parent_where_clause) = item.generics.split_for_impl();
582 let choice = child_types.fold(
583 quote!(#behavior::Never),
584 |tail, child| quote!(#behavior::ChildChoice<#child, #tail>),
585 );
586 (
587 quote!(#behavior::Births<#name #type_generics>),
588 quote! {
589 pub type #name #generics = #choice;
590
591 #(
592 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
593 pub struct #role_names;
594
595 impl #parent_impl_generics #behavior::ChildRole<#parent> for #role_names
596 #parent_where_clause
597 {
598 type Child = #declared_child_types;
599 type Position = #role_positions;
600 }
601 )*
602
603 pub struct #roles_name;
604
605 #[allow(non_upper_case_globals)]
606 impl #roles_name {
607 #(pub const #role_values: #role_names = #role_names;)*
608 }
609
610 pub struct #routes_name #generics {
611 #(
612 pub #field_names: #behavior::ChildRoute<#declared_child_types, #role_names>,
613 )*
614 }
615
616 impl #impl_generics #routes_name #type_generics #where_clause {
617 #[must_use]
618 pub fn new(
619 #(#field_names: <#addr as #behavior::Address>::Nonce,)*
620 ) -> Self {
621 Self {
622 #(
623 #field_names: #behavior::ChildRoute::new(#field_names),
624 )*
625 }
626 }
627 }
628 },
629 )
630}
631
632#[proc_macro_attribute]
638#[allow(
639 clippy::too_many_lines,
640 reason = "validation and the one coherent Behavior expansion remain in one entry point"
641)]
642pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
643 let args = parse_macro_input!(args as BehaviorArgs);
644 let item = parse_macro_input!(item as ItemImpl);
645
646 if item.trait_.is_some() {
647 return Error::new_spanned(
648 &item,
649 "#[behavior] applies to an inherent impl, not a trait impl",
650 )
651 .to_compile_error()
652 .into();
653 }
654
655 let init = item.items.iter().find_map(|item| match item {
656 ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
657 _ => None,
658 });
659 let receive = item.items.iter().find_map(|item| match item {
660 ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
661 _ => None,
662 });
663 let Some(receive) = receive else {
664 return Error::new_spanned(
665 &item.self_ty,
666 "#[behavior] requires a receive(&mut self, from, message) method",
667 )
668 .to_compile_error()
669 .into();
670 };
671 if let Err(error) = init
672 .map_or(Ok(()), validate_receiver)
673 .and_then(|()| validate_receiver(receive))
674 {
675 return error.to_compile_error().into();
676 }
677 if let Some(init) = init
678 && init.sig.inputs.len() != 1
679 {
680 return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
681 .to_compile_error()
682 .into();
683 }
684 if receive.sig.inputs.len() != 3 {
685 return Error::new_spanned(
686 &receive.sig,
687 "receive must accept exactly &mut self, from, and message",
688 )
689 .to_compile_error()
690 .into();
691 }
692
693 let BehaviorArgs {
694 addr,
695 message,
696 sends,
697 births,
698 error,
699 } = args;
700 let self_ty = &item.self_ty;
701 let Type::Path(self_path) = self_ty.as_ref() else {
702 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
703 .to_compile_error()
704 .into();
705 };
706 let Some(self_name) = self_path.path.segments.last().map(|segment| &segment.ident) else {
707 return Error::new_spanned(self_ty, "#[behavior] requires a nominal actor type")
708 .to_compile_error()
709 .into();
710 };
711 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
712 let behavior = match behavior_crate() {
713 Ok(behavior) => behavior,
714 Err(error) => return error.to_compile_error().into(),
715 };
716 let error = error.map_or_else(|| quote!(#behavior::Never), |error| quote!(#error));
717 let initialize = init.map_or_else(
718 || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
719 |_| quote!(<#self_ty>::init(self)),
720 );
721
722 let (sends_ty, sends_items) = generate_sends(sends, self_name, &item, &behavior);
723 let (births_ty, births_items) = generate_births(births, self_name, &addr, &item, &behavior);
724
725 quote! {
726 #sends_items
727 #births_items
728 #item
729
730 impl #impl_generics #behavior::Protocol for #self_ty #where_clause {
731 type Addr = #addr;
732 type Msg = #message;
733 }
734
735 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
736 type Protocol = Self;
737 type Event = #behavior::User<#addr, #message>;
738 type Sends = #sends_ty;
739 type Ph = #behavior::Never;
740 type Error = #error;
741 type Birth = #births_ty;
742
743 fn init(
744 &mut self,
745 _: #behavior::InitializationTurn,
746 ) -> #behavior::BehaviorActed<Self> {
747 #initialize
748 }
749
750 fn transition(
751 &mut self,
752 _: #behavior::ActiveTurn,
753 event: Self::Event,
754 ) -> #behavior::BehaviorActed<Self> {
755 <#self_ty>::receive(self, event.from, event.message)
756 }
757 }
758
759 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
760 type Base = Self;
761
762 fn base(&self) -> &Self {
763 self
764 }
765 }
766 }
767 .into()
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773
774 fn permutations(values: &mut [&str], at: usize, output: &mut Vec<String>) {
775 if at == values.len() {
776 output.push(values.join(", "));
777 return;
778 }
779 for index in at..values.len() {
780 values.swap(at, index);
781 permutations(values, at + 1, output);
782 values.swap(at, index);
783 }
784 }
785
786 #[test]
787 fn every_argument_order_parses() {
788 let mut declarations = [
789 "addr = u64",
790 "message = String",
791 "sends = { first: Vec<u8>, second: Vec<u16> }",
792 "births = { first: u8, second: u16 }",
793 "error = String",
794 ];
795 let mut cases = Vec::new();
796 permutations(&mut declarations, 0, &mut cases);
797 assert_eq!(cases.len(), 120);
798 for case in cases {
799 syn::parse_str::<BehaviorArgs>(&case)
800 .unwrap_or_else(|error| panic!("failed to parse `{case}`: {error}"));
801 }
802 }
803
804 #[test]
805 fn capability_omission_and_trailing_comma_parse() {
806 syn::parse_str::<BehaviorArgs>("message = (), addr = u8,")
807 .expect("only required protocol declarations are sufficient");
808 }
809
810 #[test]
811 fn invalid_declaration_states_are_rejected() {
812 for case in [
813 "message = ()",
814 "addr = u8",
815 "addr = u8, message = (), addr = u16",
816 "addr = u8, message = (), unknown = u8",
817 "addr = u8, message = (), sends = {}",
818 "addr = u8, message = (), births = {}",
819 "addr = u8, message = (), sends = { same: Vec<u8>, same: Vec<u8> }",
820 "addr = u8, message = (), births = { same: u8, same: u16 }",
821 ] {
822 assert!(
823 syn::parse_str::<BehaviorArgs>(case).is_err(),
824 "invalid declaration parsed: `{case}`"
825 );
826 }
827 }
828}