1use crate::Label;
16use serde::{Deserialize, Serialize};
17use std::collections::BTreeSet;
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
36pub enum PayloadSort {
37 #[default]
39 Unit,
40 Nat,
42 Bool,
44 String,
46 Real,
48 Vector(usize),
50 Prod(Box<PayloadSort>, Box<PayloadSort>),
52}
53
54impl PayloadSort {
55 #[must_use]
57 pub fn prod(left: PayloadSort, right: PayloadSort) -> Self {
58 PayloadSort::Prod(Box::new(left), Box::new(right))
59 }
60
61 #[must_use]
63 pub fn vector(n: usize) -> Self {
64 PayloadSort::Vector(n)
65 }
66
67 #[must_use]
69 pub fn is_simple(&self) -> bool {
70 !matches!(self, PayloadSort::Prod(_, _) | PayloadSort::Vector(_))
71 }
72}
73
74impl std::fmt::Display for PayloadSort {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 match self {
77 PayloadSort::Unit => write!(f, "Unit"),
78 PayloadSort::Nat => write!(f, "Nat"),
79 PayloadSort::Bool => write!(f, "Bool"),
80 PayloadSort::String => write!(f, "String"),
81 PayloadSort::Real => write!(f, "Real"),
82 PayloadSort::Vector(n) => write!(f, "Vector({})", n),
83 PayloadSort::Prod(l, r) => write!(f, "({} × {})", l, r),
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
119pub enum GlobalType {
120 End,
122 Comm {
124 sender: String,
125 receiver: String,
126 branches: Vec<(Label, GlobalType)>,
127 },
128 Mu { var: String, body: Box<GlobalType> },
130 Var(String),
132}
133
134impl GlobalType {
135 fn collect_all_var_names(&self, names: &mut BTreeSet<String>) {
136 match self {
137 GlobalType::End => {}
138 GlobalType::Comm { branches, .. } => {
139 for (_, cont) in branches {
140 cont.collect_all_var_names(names);
141 }
142 }
143 GlobalType::Mu { var, body } => {
144 names.insert(var.clone());
145 body.collect_all_var_names(names);
146 }
147 GlobalType::Var(t) => {
148 names.insert(t.clone());
149 }
150 }
151 }
152
153 fn all_var_names(&self) -> BTreeSet<String> {
154 let mut names = BTreeSet::new();
155 self.collect_all_var_names(&mut names);
156 names
157 }
158
159 fn fresh_var(base: &str, avoid: &BTreeSet<String>) -> String {
160 let mut idx = 0usize;
161 loop {
162 let candidate = format!("{base}_{idx}");
164 if !avoid.contains(&candidate) {
165 return candidate;
166 }
167 idx += 1;
168 }
169 }
170
171 #[must_use]
173 pub fn send(
174 sender: impl Into<String>,
175 receiver: impl Into<String>,
176 label: Label,
177 cont: GlobalType,
178 ) -> Self {
179 GlobalType::Comm {
180 sender: sender.into(),
181 receiver: receiver.into(),
182 branches: vec![(label, cont)],
183 }
184 }
185
186 #[must_use]
188 pub fn comm(
189 sender: impl Into<String>,
190 receiver: impl Into<String>,
191 branches: Vec<(Label, GlobalType)>,
192 ) -> Self {
193 GlobalType::Comm {
194 sender: sender.into(),
195 receiver: receiver.into(),
196 branches,
197 }
198 }
199
200 #[must_use]
202 pub fn mu(var: impl Into<String>, body: GlobalType) -> Self {
203 GlobalType::Mu {
204 var: var.into(),
205 body: Box::new(body),
206 }
207 }
208
209 #[must_use]
211 pub fn var(name: impl Into<String>) -> Self {
212 GlobalType::Var(name.into())
213 }
214
215 #[must_use]
219 pub fn roles(&self) -> Vec<String> {
220 let mut result = BTreeSet::new();
221 self.collect_roles(&mut result);
222 result.into_iter().collect()
223 }
224
225 fn collect_roles(&self, roles: &mut BTreeSet<String>) {
226 match self {
227 GlobalType::End => {}
228 GlobalType::Comm {
229 sender,
230 receiver,
231 branches,
232 } => {
233 roles.insert(sender.clone());
234 roles.insert(receiver.clone());
235 for (_, cont) in branches {
236 cont.collect_roles(roles);
237 }
238 }
239 GlobalType::Mu { body, .. } => body.collect_roles(roles),
240 GlobalType::Var(_) => {}
241 }
242 }
243
244 #[must_use]
248 pub fn free_vars(&self) -> Vec<String> {
249 let mut result = Vec::new();
250 let mut bound = BTreeSet::new();
251 self.collect_free_vars(&mut result, &mut bound);
252 result
253 }
254
255 fn collect_free_vars(&self, free: &mut Vec<String>, bound: &mut BTreeSet<String>) {
256 match self {
257 GlobalType::End => {}
258 GlobalType::Comm { branches, .. } => {
259 for (_, cont) in branches {
260 cont.collect_free_vars(free, bound);
261 }
262 }
263 GlobalType::Mu { var, body } => {
264 bound.insert(var.clone());
265 body.collect_free_vars(free, bound);
266 bound.remove(var);
267 }
268 GlobalType::Var(t) => {
269 if !bound.contains(t) && !free.contains(t) {
270 free.push(t.clone());
271 }
272 }
273 }
274 }
275
276 #[must_use]
280 pub fn substitute(&self, var_name: &str, replacement: &GlobalType) -> GlobalType {
281 match self {
282 GlobalType::End => GlobalType::End,
283 GlobalType::Comm {
284 sender,
285 receiver,
286 branches,
287 } => GlobalType::Comm {
288 sender: sender.clone(),
289 receiver: receiver.clone(),
290 branches: branches
291 .iter()
292 .map(|(l, cont)| (l.clone(), cont.substitute(var_name, replacement)))
293 .collect(),
294 },
295 GlobalType::Mu { var, body } => {
296 if var == var_name {
297 GlobalType::Mu {
299 var: var.clone(),
300 body: body.clone(),
301 }
302 } else {
303 let replacement_free = replacement.free_vars();
304 if replacement_free.iter().any(|v| v == var) {
305 let mut avoid = body.all_var_names();
307 avoid.extend(replacement.all_var_names());
308 avoid.insert(var_name.to_string());
309 let fresh = Self::fresh_var(var, &avoid);
310 let renamed_body = body.substitute(var, &GlobalType::Var(fresh.clone()));
311 return GlobalType::Mu {
312 var: fresh,
313 body: Box::new(renamed_body.substitute(var_name, replacement)),
314 };
315 }
316 GlobalType::Mu {
317 var: var.clone(),
318 body: Box::new(body.substitute(var_name, replacement)),
319 }
320 }
321 }
322 GlobalType::Var(t) => {
323 if t == var_name {
324 replacement.clone()
325 } else {
326 GlobalType::Var(t.clone())
327 }
328 }
329 }
330 }
331
332 #[must_use]
336 pub fn unfold(&self) -> GlobalType {
337 match self {
338 GlobalType::Mu { var, body } => body.substitute(var, self),
339 _ => self.clone(),
340 }
341 }
342
343 #[must_use]
347 pub fn all_vars_bound(&self) -> bool {
348 self.check_vars_bound(&BTreeSet::new())
349 }
350
351 fn check_vars_bound(&self, bound: &BTreeSet<String>) -> bool {
352 match self {
353 GlobalType::End => true,
354 GlobalType::Comm { branches, .. } => branches
355 .iter()
356 .all(|(_, cont)| cont.check_vars_bound(bound)),
357 GlobalType::Mu { var, body } => {
358 let mut new_bound = bound.clone();
359 new_bound.insert(var.clone());
360 body.check_vars_bound(&new_bound)
361 }
362 GlobalType::Var(t) => bound.contains(t),
363 }
364 }
365
366 #[must_use]
370 pub fn all_comms_non_empty(&self) -> bool {
371 match self {
372 GlobalType::End => true,
373 GlobalType::Comm { branches, .. } => {
374 !branches.is_empty() && branches.iter().all(|(_, cont)| cont.all_comms_non_empty())
375 }
376 GlobalType::Mu { body, .. } => body.all_comms_non_empty(),
377 GlobalType::Var(_) => true,
378 }
379 }
380
381 fn branches_have_unique_names(branches: &[(Label, GlobalType)]) -> bool {
382 let mut seen = BTreeSet::new();
383 branches
384 .iter()
385 .all(|(label, _)| seen.insert(label.name.clone()))
386 }
387
388 #[must_use]
390 pub fn unique_branch_labels(&self) -> bool {
391 match self {
392 GlobalType::End | GlobalType::Var(_) => true,
393 GlobalType::Mu { body, .. } => body.unique_branch_labels(),
394 GlobalType::Comm { branches, .. } => {
395 Self::branches_have_unique_names(branches)
396 && branches.iter().all(|(_, cont)| cont.unique_branch_labels())
397 }
398 }
399 }
400
401 #[must_use]
405 pub fn no_self_comm(&self) -> bool {
406 match self {
407 GlobalType::End => true,
408 GlobalType::Comm {
409 sender,
410 receiver,
411 branches,
412 } => sender != receiver && branches.iter().all(|(_, cont)| cont.no_self_comm()),
413 GlobalType::Mu { body, .. } => body.no_self_comm(),
414 GlobalType::Var(_) => true,
415 }
416 }
417
418 #[must_use]
427 pub fn well_formed(&self) -> bool {
428 self.all_vars_bound()
429 && self.all_comms_non_empty()
430 && self.unique_branch_labels()
431 && self.no_self_comm()
432 && self.is_guarded()
433 }
434
435 #[must_use]
439 pub fn mentions_role(&self, role: &str) -> bool {
440 self.mentions_role_inner(role)
441 }
442
443 fn mentions_role_inner(&self, role: &str) -> bool {
444 match self {
445 GlobalType::End | GlobalType::Var(_) => false,
446 GlobalType::Mu { body, .. } => body.mentions_role_inner(role),
447 GlobalType::Comm {
448 sender,
449 receiver,
450 branches,
451 } => {
452 sender == role
453 || receiver == role
454 || branches
455 .iter()
456 .any(|(_, cont)| cont.mentions_role_inner(role))
457 }
458 }
459 }
460
461 #[must_use]
465 pub fn depth(&self) -> usize {
466 match self {
467 GlobalType::End => 0,
468 GlobalType::Comm { branches, .. } => {
469 1 + branches.iter().map(|(_, g)| g.depth()).max().unwrap_or(0)
470 }
471 GlobalType::Mu { body, .. } => 1 + body.depth(),
472 GlobalType::Var(_) => 0,
473 }
474 }
475
476 #[must_use]
480 pub fn is_guarded(&self) -> bool {
481 match self {
482 GlobalType::End => true,
483 GlobalType::Comm { branches, .. } => branches.iter().all(|(_, cont)| cont.is_guarded()),
484 GlobalType::Mu { body, .. } => match body.as_ref() {
485 GlobalType::Var(_) | GlobalType::Mu { .. } => false,
486 _ => body.is_guarded(),
487 },
488 GlobalType::Var(_) => true,
489 }
490 }
491
492 #[must_use]
498 pub fn consume(&self, sender: &str, receiver: &str, label: &str) -> Option<GlobalType> {
499 self.consume_with_fuel(sender, receiver, label, self.depth() + 1)
500 }
501
502 fn consume_with_fuel(
503 &self,
504 sender: &str,
505 receiver: &str,
506 label: &str,
507 fuel: usize,
508 ) -> Option<GlobalType> {
509 if fuel == 0 {
510 return None;
511 }
512 match self {
513 GlobalType::Comm {
514 sender: s,
515 receiver: r,
516 branches,
517 } => {
518 if s == sender && r == receiver {
519 branches
520 .iter()
521 .find(|(l, _)| l.name == label)
522 .map(|(_, cont)| cont.clone())
523 } else {
524 None
525 }
526 }
527 GlobalType::Mu { var, body } => {
528 body.substitute(var, self)
530 .consume_with_fuel(sender, receiver, label, fuel - 1)
531 }
532 _ => None,
533 }
534 }
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540 use assert_matches::assert_matches;
541 use proptest::prelude::*;
542
543 #[test]
544 fn test_simple_protocol() {
545 let g = GlobalType::send("A", "B", Label::new("hello"), GlobalType::End);
547 assert!(g.well_formed());
548 assert_eq!(g.roles().len(), 2);
549 assert!(g.mentions_role("A"));
550 assert!(g.mentions_role("B"));
551 }
552
553 #[test]
554 fn test_recursive_protocol() {
555 let g = GlobalType::mu(
557 "t",
558 GlobalType::send("A", "B", Label::new("msg"), GlobalType::var("t")),
559 );
560 assert!(g.well_formed());
561 assert!(g.all_vars_bound());
562 }
563
564 #[test]
565 fn test_unbound_variable() {
566 let g = GlobalType::send("A", "B", Label::new("msg"), GlobalType::var("t"));
568 assert!(!g.all_vars_bound());
569 assert!(!g.well_formed());
570 }
571
572 #[test]
573 fn test_self_communication() {
574 let g = GlobalType::send("A", "A", Label::new("msg"), GlobalType::End);
576 assert!(!g.no_self_comm());
577 assert!(!g.well_formed());
578 }
579
580 #[test]
581 fn test_unfold() {
582 let g = GlobalType::mu(
584 "t",
585 GlobalType::send("A", "B", Label::new("msg"), GlobalType::var("t")),
586 );
587 let unfolded = g.unfold();
588 assert_matches!(unfolded, GlobalType::Comm { sender, receiver, branches } => {
589 assert_eq!(sender, "A");
590 assert_eq!(receiver, "B");
591 assert_eq!(branches.len(), 1);
592 assert_matches!(branches[0].1, GlobalType::Mu { .. });
594 });
595 }
596
597 #[test]
598 fn test_substitute() {
599 let g = GlobalType::var("t");
600 let replacement = GlobalType::End;
601 assert_eq!(g.substitute("t", &replacement), GlobalType::End);
602 assert_eq!(g.substitute("s", &replacement), GlobalType::var("t"));
603 }
604
605 #[test]
606 fn test_substitute_avoids_capture() {
607 let g = GlobalType::mu("y", GlobalType::var("x"));
609 let result = g.substitute("x", &GlobalType::var("y"));
610
611 assert_matches!(result, GlobalType::Mu { var, body } => {
612 assert_ne!(var, "y");
613 assert_matches!(*body, GlobalType::Var(ref name) => {
614 assert_eq!(name, "y");
615 });
616 });
617 }
618
619 #[test]
620 fn test_substitute_avoids_capture_with_nested_binders() {
621 let g = GlobalType::mu("y", GlobalType::mu("y_0", GlobalType::var("x")));
623 let result = g.substitute("x", &GlobalType::var("y"));
624 assert!(result.free_vars().contains(&"y".to_string()));
625 }
626
627 #[test]
628 fn test_consume() {
629 let g = GlobalType::comm(
630 "A",
631 "B",
632 vec![
633 (Label::new("accept"), GlobalType::End),
634 (Label::new("reject"), GlobalType::End),
635 ],
636 );
637
638 assert_eq!(g.consume("A", "B", "accept"), Some(GlobalType::End));
639 assert_eq!(g.consume("A", "B", "reject"), Some(GlobalType::End));
640 assert_eq!(g.consume("A", "B", "unknown"), None);
641 assert_eq!(g.consume("B", "A", "accept"), None);
642 }
643
644 #[test]
645 fn test_consume_unguarded_recursion_returns_none() {
646 let unguarded = GlobalType::mu("t", GlobalType::var("t"));
647 assert_eq!(unguarded.consume("A", "B", "msg"), None);
648 }
649
650 #[test]
651 fn test_consume_guarded_recursion_still_works() {
652 let g = GlobalType::mu(
653 "t",
654 GlobalType::send("A", "B", Label::new("msg"), GlobalType::var("t")),
655 );
656 let out = g.consume("A", "B", "msg");
657 assert!(matches!(out, Some(GlobalType::Mu { .. })));
658 }
659
660 #[test]
661 fn test_payload_sort() {
662 let sort = PayloadSort::prod(PayloadSort::Nat, PayloadSort::Bool);
663 assert!(!sort.is_simple());
664
665 let label = Label::with_sort("data", sort);
666 assert_eq!(label.name, "data");
667 }
668
669 #[test]
670 fn test_guarded() {
671 let unguarded = GlobalType::mu("t", GlobalType::var("t"));
673 assert!(!unguarded.is_guarded());
674 assert!(!unguarded.well_formed()); let guarded = GlobalType::mu(
678 "t",
679 GlobalType::send("A", "B", Label::new("msg"), GlobalType::var("t")),
680 );
681 assert!(guarded.is_guarded());
682 assert!(guarded.well_formed()); }
684
685 #[test]
686 fn test_duplicate_branch_labels_not_well_formed() {
687 let g = GlobalType::comm(
688 "A",
689 "B",
690 vec![
691 (Label::new("msg"), GlobalType::End),
692 (Label::new("msg"), GlobalType::End),
693 ],
694 );
695 assert!(!g.unique_branch_labels());
696 assert!(!g.well_formed());
697 }
698
699 #[test]
700 fn test_roles_are_sorted() {
701 let g = GlobalType::comm(
702 "Zed",
703 "Alice",
704 vec![(
705 Label::new("start"),
706 GlobalType::send("Bob", "Carol", Label::new("next"), GlobalType::End),
707 )],
708 );
709
710 assert_eq!(g.roles(), vec!["Alice", "Bob", "Carol", "Zed"]);
711 }
712
713 #[test]
714 fn test_mentions_role_direct_traversal_matches_expectation() {
715 let g = GlobalType::mu(
716 "t",
717 GlobalType::comm(
718 "A",
719 "B",
720 vec![(
721 Label::new("x"),
722 GlobalType::send("B", "C", Label::new("y"), GlobalType::var("t")),
723 )],
724 ),
725 );
726 assert!(g.mentions_role("A"));
727 assert!(g.mentions_role("B"));
728 assert!(g.mentions_role("C"));
729 assert!(!g.mentions_role("D"));
730 }
731
732 fn arb_var_name() -> impl Strategy<Value = String> {
733 prop_oneof![
734 Just("x".to_string()),
735 Just("y".to_string()),
736 Just("z".to_string()),
737 Just("t".to_string()),
738 Just("u".to_string()),
739 ]
740 }
741
742 proptest! {
743 #[test]
744 fn prop_substitute_identity_when_var_absent(var in arb_var_name()) {
745 let g = GlobalType::send("A", "B", Label::new("msg"), GlobalType::End);
746 let replacement = GlobalType::var("r");
747 prop_assert_eq!(g.substitute(&var, &replacement), g);
748 }
749
750 #[test]
751 fn prop_substitute_avoids_capture_simple(
752 binder in arb_var_name(),
753 target in arb_var_name(),
754 ) {
755 prop_assume!(binder != target);
756 let g = GlobalType::mu(&binder, GlobalType::var(&target));
757 let replacement = GlobalType::var(&binder);
758 let result = g.substitute(&target, &replacement);
759 prop_assert!(result.free_vars().contains(&binder));
760 }
761 }
762}