1use crate::{Label, ValType};
18use serde::{Deserialize, Serialize};
19use std::collections::BTreeSet;
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
43pub enum LocalTypeR {
44 End,
46 Send {
48 partner: String,
49 branches: Vec<(Label, Option<ValType>, LocalTypeR)>,
50 },
51 Recv {
53 partner: String,
54 branches: Vec<(Label, Option<ValType>, LocalTypeR)>,
55 },
56 Mu { var: String, body: Box<LocalTypeR> },
58 Var(String),
60}
61
62impl LocalTypeR {
63 fn collect_all_var_names(&self, names: &mut BTreeSet<String>) {
64 match self {
65 LocalTypeR::End => {}
66 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
67 for (_, _, cont) in branches {
68 cont.collect_all_var_names(names);
69 }
70 }
71 LocalTypeR::Mu { var, body } => {
72 names.insert(var.clone());
73 body.collect_all_var_names(names);
74 }
75 LocalTypeR::Var(t) => {
76 names.insert(t.clone());
77 }
78 }
79 }
80
81 fn all_var_names(&self) -> BTreeSet<String> {
82 let mut names = BTreeSet::new();
83 self.collect_all_var_names(&mut names);
84 names
85 }
86
87 fn fresh_var(base: &str, avoid: &BTreeSet<String>) -> String {
88 let mut idx = 0usize;
89 loop {
90 let candidate = format!("{base}_{idx}");
92 if !avoid.contains(&candidate) {
93 return candidate;
94 }
95 idx += 1;
96 }
97 }
98
99 #[must_use]
101 pub fn send(partner: impl Into<String>, label: Label, cont: LocalTypeR) -> Self {
102 LocalTypeR::Send {
103 partner: partner.into(),
104 branches: vec![(label, None, cont)],
105 }
106 }
107
108 #[must_use]
110 pub fn send_choice(
111 partner: impl Into<String>,
112 branches: Vec<(Label, Option<ValType>, LocalTypeR)>,
113 ) -> Self {
114 LocalTypeR::Send {
115 partner: partner.into(),
116 branches,
117 }
118 }
119
120 #[must_use]
122 pub fn recv(partner: impl Into<String>, label: Label, cont: LocalTypeR) -> Self {
123 LocalTypeR::Recv {
124 partner: partner.into(),
125 branches: vec![(label, None, cont)],
126 }
127 }
128
129 #[must_use]
131 pub fn recv_choice(
132 partner: impl Into<String>,
133 branches: Vec<(Label, Option<ValType>, LocalTypeR)>,
134 ) -> Self {
135 LocalTypeR::Recv {
136 partner: partner.into(),
137 branches,
138 }
139 }
140
141 #[must_use]
143 pub fn mu(var: impl Into<String>, body: LocalTypeR) -> Self {
144 LocalTypeR::Mu {
145 var: var.into(),
146 body: Box::new(body),
147 }
148 }
149
150 #[must_use]
152 pub fn var(name: impl Into<String>) -> Self {
153 LocalTypeR::Var(name.into())
154 }
155
156 #[must_use]
160 pub fn free_vars(&self) -> Vec<String> {
161 let mut result = Vec::new();
162 let mut bound = BTreeSet::new();
163 self.collect_free_vars(&mut result, &mut bound);
164 result
165 }
166
167 fn collect_free_vars(&self, free: &mut Vec<String>, bound: &mut BTreeSet<String>) {
168 match self {
169 LocalTypeR::End => {}
170 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
171 for (_, _, cont) in branches {
172 cont.collect_free_vars(free, bound);
173 }
174 }
175 LocalTypeR::Mu { var, body } => {
176 bound.insert(var.clone());
177 body.collect_free_vars(free, bound);
178 bound.remove(var);
179 }
180 LocalTypeR::Var(t) => {
181 if !bound.contains(t) && !free.contains(t) {
182 free.push(t.clone());
183 }
184 }
185 }
186 }
187
188 #[must_use]
192 pub fn substitute(&self, var_name: &str, replacement: &LocalTypeR) -> LocalTypeR {
193 match self {
194 LocalTypeR::End => LocalTypeR::End,
195 LocalTypeR::Send { partner, branches } => {
196 Self::substitute_branching(partner, branches, var_name, replacement, true)
197 }
198 LocalTypeR::Recv { partner, branches } => {
199 Self::substitute_branching(partner, branches, var_name, replacement, false)
200 }
201 LocalTypeR::Mu { var, body } => Self::substitute_mu(var, body, var_name, replacement),
202 LocalTypeR::Var(t) => {
203 if t == var_name {
204 replacement.clone()
205 } else {
206 LocalTypeR::Var(t.clone())
207 }
208 }
209 }
210 }
211
212 fn substitute_branching(
213 partner: &str,
214 branches: &[(Label, Option<ValType>, LocalTypeR)],
215 var_name: &str,
216 replacement: &LocalTypeR,
217 is_send: bool,
218 ) -> LocalTypeR {
219 let substituted = branches
220 .iter()
221 .map(|(l, vt, cont)| {
222 (
223 l.clone(),
224 vt.clone(),
225 cont.substitute(var_name, replacement),
226 )
227 })
228 .collect();
229 if is_send {
230 LocalTypeR::Send {
231 partner: partner.to_string(),
232 branches: substituted,
233 }
234 } else {
235 LocalTypeR::Recv {
236 partner: partner.to_string(),
237 branches: substituted,
238 }
239 }
240 }
241
242 fn substitute_mu(
243 var: &str,
244 body: &LocalTypeR,
245 var_name: &str,
246 replacement: &LocalTypeR,
247 ) -> LocalTypeR {
248 if var == var_name {
249 return LocalTypeR::Mu {
250 var: var.to_string(),
251 body: Box::new(body.clone()),
252 };
253 }
254 let replacement_free = replacement.free_vars();
255 if replacement_free.iter().any(|v| v == var) {
256 let mut avoid = body.all_var_names();
258 avoid.extend(replacement.all_var_names());
259 avoid.insert(var_name.to_string());
260 let fresh = Self::fresh_var(var, &avoid);
261 let renamed_body = body.substitute(var, &LocalTypeR::Var(fresh.clone()));
262 return LocalTypeR::Mu {
263 var: fresh,
264 body: Box::new(renamed_body.substitute(var_name, replacement)),
265 };
266 }
267 LocalTypeR::Mu {
268 var: var.to_string(),
269 body: Box::new(body.substitute(var_name, replacement)),
270 }
271 }
272
273 #[must_use]
277 pub fn unfold(&self) -> LocalTypeR {
278 match self {
279 LocalTypeR::Mu { var, body } => body.substitute(var, self),
280 _ => self.clone(),
281 }
282 }
283
284 #[must_use]
288 pub fn mu_height(&self) -> usize {
289 match self {
290 LocalTypeR::Mu { body, .. } => 1 + body.mu_height(),
291 _ => 0,
292 }
293 }
294
295 #[must_use]
299 pub fn full_unfold(&self) -> LocalTypeR {
300 let mut current = self.clone();
301 let fuel = self.mu_height();
302 for _ in 0..fuel {
303 current = current.unfold();
304 }
305 current
306 }
307
308 #[must_use]
313 pub fn dual(&self) -> LocalTypeR {
314 match self {
315 LocalTypeR::End => LocalTypeR::End,
316 LocalTypeR::Send { partner, branches } => LocalTypeR::Recv {
317 partner: partner.clone(),
318 branches: branches
319 .iter()
320 .map(|(l, vt, cont)| (l.clone(), vt.clone(), cont.dual()))
321 .collect(),
322 },
323 LocalTypeR::Recv { partner, branches } => LocalTypeR::Send {
324 partner: partner.clone(),
325 branches: branches
326 .iter()
327 .map(|(l, vt, cont)| (l.clone(), vt.clone(), cont.dual()))
328 .collect(),
329 },
330 LocalTypeR::Mu { var, body } => LocalTypeR::Mu {
331 var: var.clone(),
332 body: Box::new(body.dual()),
333 },
334 LocalTypeR::Var(t) => LocalTypeR::Var(t.clone()),
335 }
336 }
337
338 #[must_use]
342 pub fn all_vars_bound(&self) -> bool {
343 self.check_vars_bound(&BTreeSet::new())
344 }
345
346 fn check_vars_bound(&self, bound: &BTreeSet<String>) -> bool {
347 match self {
348 LocalTypeR::End => true,
349 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => branches
350 .iter()
351 .all(|(_, _, cont)| cont.check_vars_bound(bound)),
352 LocalTypeR::Mu { var, body } => {
353 let mut new_bound = bound.clone();
354 new_bound.insert(var.clone());
355 body.check_vars_bound(&new_bound)
356 }
357 LocalTypeR::Var(t) => bound.contains(t),
358 }
359 }
360
361 #[must_use]
365 pub fn all_choices_non_empty(&self) -> bool {
366 match self {
367 LocalTypeR::End => true,
368 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
369 !branches.is_empty()
370 && branches
371 .iter()
372 .all(|(_, _, cont)| cont.all_choices_non_empty())
373 }
374 LocalTypeR::Mu { body, .. } => body.all_choices_non_empty(),
375 LocalTypeR::Var(_) => true,
376 }
377 }
378
379 fn branches_have_unique_names(branches: &[(Label, Option<ValType>, LocalTypeR)]) -> bool {
380 let mut seen = BTreeSet::new();
381 branches
382 .iter()
383 .all(|(label, _, _)| seen.insert(label.name.clone()))
384 }
385
386 #[must_use]
388 pub fn unique_branch_labels(&self) -> bool {
389 match self {
390 LocalTypeR::End | LocalTypeR::Var(_) => true,
391 LocalTypeR::Mu { body, .. } => body.unique_branch_labels(),
392 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
393 Self::branches_have_unique_names(branches)
394 && branches
395 .iter()
396 .all(|(_, _, cont)| cont.unique_branch_labels())
397 }
398 }
399 }
400
401 #[must_use]
409 pub fn well_formed(&self) -> bool {
410 self.all_vars_bound()
411 && self.all_choices_non_empty()
412 && self.unique_branch_labels()
413 && self.is_guarded()
414 }
415
416 #[must_use]
420 pub fn reaches_communication(&self) -> bool {
421 match self.full_unfold() {
422 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
423 !branches.is_empty()
424 }
425 LocalTypeR::End | LocalTypeR::Var(_) | LocalTypeR::Mu { .. } => false,
426 }
427 }
428
429 #[must_use]
433 pub fn regular_practical_fragment(&self) -> bool {
434 self.well_formed() && self.reaches_communication()
435 }
436
437 #[must_use]
441 pub fn depth(&self) -> usize {
442 match self {
443 LocalTypeR::End => 0,
444 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
445 1 + branches
446 .iter()
447 .map(|(_, _, t)| t.depth())
448 .max()
449 .unwrap_or(0)
450 }
451 LocalTypeR::Mu { body, .. } => 1 + body.depth(),
452 LocalTypeR::Var(_) => 0,
453 }
454 }
455
456 #[must_use]
460 pub fn is_guarded(&self) -> bool {
461 match self {
462 LocalTypeR::End => true,
463 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
464 branches.iter().all(|(_, _, cont)| cont.is_guarded())
465 }
466 LocalTypeR::Mu { body, .. } => match body.as_ref() {
467 LocalTypeR::Var(_) | LocalTypeR::Mu { .. } => false,
468 _ => body.is_guarded(),
469 },
470 LocalTypeR::Var(_) => true,
471 }
472 }
473
474 #[must_use]
480 pub fn is_var_guarded(&self, var: &str) -> bool {
481 match self {
482 LocalTypeR::End => true,
483 LocalTypeR::Var(w) => w != var,
484 LocalTypeR::Send { .. } | LocalTypeR::Recv { .. } => true,
485 LocalTypeR::Mu { var: t, body, .. } => {
486 if var == t {
487 true
488 } else {
489 body.is_var_guarded(var)
490 }
491 }
492 }
493 }
494
495 #[must_use]
499 pub fn head_labels(&self) -> Vec<String> {
500 match self {
501 LocalTypeR::End | LocalTypeR::Var(_) => vec![],
502 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
503 branches.iter().map(|(l, _, _)| l.name.clone()).collect()
504 }
505 LocalTypeR::Mu { body, .. } => body.head_labels(),
506 }
507 }
508
509 #[must_use]
511 pub fn labels(&self) -> Vec<String> {
512 self.head_labels()
513 }
514
515 #[must_use]
517 pub fn all_labels(&self) -> Vec<String> {
518 let mut labels = BTreeSet::new();
519 self.collect_all_labels(&mut labels);
520 labels.into_iter().collect()
521 }
522
523 fn collect_all_labels(&self, labels: &mut BTreeSet<String>) {
524 match self {
525 LocalTypeR::End | LocalTypeR::Var(_) => {}
526 LocalTypeR::Mu { body, .. } => body.collect_all_labels(labels),
527 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
528 for (label, _, cont) in branches {
529 labels.insert(label.name.clone());
530 cont.collect_all_labels(labels);
531 }
532 }
533 }
534 }
535
536 #[must_use]
540 pub fn partners(&self) -> Vec<String> {
541 let mut result = BTreeSet::new();
542 self.collect_partners(&mut result);
543 result.into_iter().collect()
544 }
545
546 fn collect_partners(&self, partners: &mut BTreeSet<String>) {
547 match self {
548 LocalTypeR::End | LocalTypeR::Var(_) => {}
549 LocalTypeR::Send { partner, branches } | LocalTypeR::Recv { partner, branches } => {
550 partners.insert(partner.clone());
551 for (_, _, cont) in branches {
552 cont.collect_partners(partners);
553 }
554 }
555 LocalTypeR::Mu { body, .. } => body.collect_partners(partners),
556 }
557 }
558
559 #[must_use]
561 pub fn mentions_partner(&self, role: &str) -> bool {
562 self.mentions_partner_inner(role)
563 }
564
565 fn mentions_partner_inner(&self, role: &str) -> bool {
566 match self {
567 LocalTypeR::End | LocalTypeR::Var(_) => false,
568 LocalTypeR::Mu { body, .. } => body.mentions_partner_inner(role),
569 LocalTypeR::Send { partner, branches } | LocalTypeR::Recv { partner, branches } => {
570 partner == role
571 || branches
572 .iter()
573 .any(|(_, _, cont)| cont.mentions_partner_inner(role))
574 }
575 }
576 }
577
578 #[must_use]
580 pub fn is_send(&self) -> bool {
581 matches!(self, LocalTypeR::Send { .. })
582 }
583
584 #[must_use]
586 pub fn is_recv(&self) -> bool {
587 matches!(self, LocalTypeR::Recv { .. })
588 }
589
590 #[must_use]
592 pub fn is_end(&self) -> bool {
593 matches!(self, LocalTypeR::End)
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 use crate::PayloadSort;
601 use assert_matches::assert_matches;
602 use proptest::prelude::*;
603
604 #[test]
605 fn test_simple_local_type() {
606 let lt = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
608 assert!(lt.well_formed());
609 assert_eq!(lt.partners().len(), 1);
610 assert!(lt.mentions_partner("B"));
611 }
612
613 #[test]
614 fn test_recursive_local_type() {
615 let lt = LocalTypeR::mu(
617 "t",
618 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("t")),
619 );
620 assert!(lt.well_formed());
621 assert!(lt.all_vars_bound());
622 }
623
624 #[test]
625 fn test_dual() {
626 let send = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
628 let recv = send.dual();
629
630 assert_matches!(recv, LocalTypeR::Recv { partner, branches } => {
631 assert_eq!(partner, "B");
632 assert_eq!(branches.len(), 1);
633 assert_eq!((branches[0].0).name, "msg");
634 });
635 }
636
637 #[test]
638 fn test_unfold() {
639 let lt = LocalTypeR::mu(
641 "t",
642 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("t")),
643 );
644 let unfolded = lt.unfold();
645
646 assert_matches!(unfolded, LocalTypeR::Send { partner, branches } => {
647 assert_eq!(partner, "B");
648 assert_matches!(branches[0].2, LocalTypeR::Mu { .. });
649 });
650 }
651
652 #[test]
653 fn test_substitute() {
654 let lt = LocalTypeR::var("t");
655 let replacement = LocalTypeR::End;
656 assert_eq!(lt.substitute("t", &replacement), LocalTypeR::End);
657 assert_eq!(lt.substitute("s", &replacement), LocalTypeR::var("t"));
658 }
659
660 #[test]
661 fn test_substitute_avoids_capture() {
662 let lt = LocalTypeR::mu("y", LocalTypeR::var("x"));
663 let result = lt.substitute("x", &LocalTypeR::var("y"));
664
665 assert_matches!(result, LocalTypeR::Mu { var, body } => {
666 assert_ne!(var, "y");
667 assert_matches!(*body, LocalTypeR::Var(ref name) => {
668 assert_eq!(name, "y");
669 });
670 });
671 }
672
673 #[test]
674 fn test_substitute_avoids_capture_with_nested_binders() {
675 let lt = LocalTypeR::mu("y", LocalTypeR::mu("y_0", LocalTypeR::var("x")));
676 let result = lt.substitute("x", &LocalTypeR::var("y"));
677 assert!(result.free_vars().contains(&"y".to_string()));
678 }
679
680 #[test]
681 fn test_unbound_variable() {
682 let lt = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("t"));
684 assert!(!lt.all_vars_bound());
685 assert!(!lt.well_formed());
686 }
687
688 #[test]
689 fn test_guarded() {
690 let unguarded = LocalTypeR::mu("t", LocalTypeR::var("t"));
692 assert!(!unguarded.is_guarded());
693 assert!(!unguarded.well_formed()); let guarded = LocalTypeR::mu(
697 "t",
698 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("t")),
699 );
700 assert!(guarded.is_guarded());
701 assert!(guarded.well_formed()); }
703
704 #[test]
705 fn test_mu_height_and_full_unfold() {
706 let lt = LocalTypeR::mu(
707 "outer",
708 LocalTypeR::mu(
709 "inner",
710 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("inner")),
711 ),
712 );
713 assert_eq!(lt.mu_height(), 2);
714 assert_matches!(lt.full_unfold(), LocalTypeR::Send { partner, branches } => {
715 assert_eq!(partner, "B");
716 assert_eq!(branches.len(), 1);
717 });
718 }
719
720 #[test]
721 fn test_reaches_communication_matches_practical_fragment_intent() {
722 assert!(!LocalTypeR::End.reaches_communication());
723 assert!(!LocalTypeR::End.regular_practical_fragment());
724
725 let send = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
726 assert!(send.reaches_communication());
727 assert!(send.regular_practical_fragment());
728
729 let guarded = LocalTypeR::mu(
730 "t",
731 LocalTypeR::recv("B", Label::new("msg"), LocalTypeR::var("t")),
732 );
733 assert!(guarded.reaches_communication());
734 assert!(guarded.regular_practical_fragment());
735
736 let unguarded = LocalTypeR::mu("t", LocalTypeR::var("t"));
737 assert!(!unguarded.reaches_communication());
738 assert!(!unguarded.regular_practical_fragment());
739 }
740
741 #[test]
742 fn test_free_vars() {
743 let lt = LocalTypeR::mu(
745 "t",
746 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("s")),
747 );
748 let free = lt.free_vars();
749 assert_eq!(free, vec!["s"]);
750 }
751
752 #[test]
753 fn test_choice_with_payload() {
754 let branches = vec![
755 (
756 Label::with_sort("accept", PayloadSort::Bool),
757 None,
758 LocalTypeR::End,
759 ),
760 (
761 Label::with_sort("data", PayloadSort::Nat),
762 None,
763 LocalTypeR::End,
764 ),
765 ];
766 let lt = LocalTypeR::recv_choice("A", branches);
767 assert!(lt.well_formed());
768 assert_eq!(lt.head_labels(), vec!["accept", "data"]);
769 assert_eq!(lt.labels(), vec!["accept", "data"]);
770 }
771
772 #[test]
773 fn test_depth() {
774 let lt = LocalTypeR::send(
775 "B",
776 Label::new("outer"),
777 LocalTypeR::send("C", Label::new("inner"), LocalTypeR::End),
778 );
779 assert_eq!(lt.depth(), 2);
780 }
781
782 #[test]
783 fn test_is_send_recv() {
784 let send = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
785 let recv = LocalTypeR::recv("B", Label::new("msg"), LocalTypeR::End);
786
787 assert!(send.is_send());
788 assert!(!send.is_recv());
789 assert!(recv.is_recv());
790 assert!(!recv.is_send());
791 }
792
793 #[test]
794 fn test_duplicate_branch_labels_not_well_formed() {
795 let lt = LocalTypeR::recv_choice(
796 "A",
797 vec![
798 (Label::new("msg"), None, LocalTypeR::End),
799 (Label::new("msg"), None, LocalTypeR::End),
800 ],
801 );
802 assert!(!lt.unique_branch_labels());
803 assert!(!lt.well_formed());
804 }
805
806 #[test]
807 fn test_partners_are_sorted() {
808 let lt = LocalTypeR::send(
809 "Zed",
810 Label::new("outer"),
811 LocalTypeR::recv(
812 "Alice",
813 Label::new("mid"),
814 LocalTypeR::send("Bob", Label::new("inner"), LocalTypeR::End),
815 ),
816 );
817
818 assert_eq!(lt.partners(), vec!["Alice", "Bob", "Zed"]);
819 }
820
821 #[test]
822 fn test_mentions_partner_direct_traversal_matches_expectation() {
823 let lt = LocalTypeR::mu(
824 "t",
825 LocalTypeR::send(
826 "A",
827 Label::new("x"),
828 LocalTypeR::recv("B", Label::new("y"), LocalTypeR::var("t")),
829 ),
830 );
831 assert!(lt.mentions_partner("A"));
832 assert!(lt.mentions_partner("B"));
833 assert!(!lt.mentions_partner("C"));
834 }
835
836 #[test]
837 fn test_all_labels_collects_nested_labels() {
838 let lt = LocalTypeR::send(
839 "B",
840 Label::new("outer"),
841 LocalTypeR::recv(
842 "A",
843 Label::new("inner"),
844 LocalTypeR::send("C", Label::new("leaf"), LocalTypeR::End),
845 ),
846 );
847 assert_eq!(lt.head_labels(), vec!["outer"]);
848 assert_eq!(lt.all_labels(), vec!["inner", "leaf", "outer"]);
849 }
850
851 fn arb_var_name() -> impl Strategy<Value = String> {
852 prop_oneof![
853 Just("x".to_string()),
854 Just("y".to_string()),
855 Just("z".to_string()),
856 Just("t".to_string()),
857 Just("u".to_string()),
858 ]
859 }
860
861 proptest! {
862 #[test]
863 fn prop_substitute_identity_when_var_absent(var in arb_var_name()) {
864 let lt = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
865 let replacement = LocalTypeR::var("r");
866 prop_assert_eq!(lt.substitute(&var, &replacement), lt);
867 }
868
869 #[test]
870 fn prop_substitute_avoids_capture_simple(
871 binder in arb_var_name(),
872 target in arb_var_name(),
873 ) {
874 prop_assume!(binder != target);
875 let lt = LocalTypeR::mu(&binder, LocalTypeR::var(&target));
876 let replacement = LocalTypeR::var(&binder);
877 let result = lt.substitute(&target, &replacement);
878 prop_assert!(result.free_vars().contains(&binder));
879 }
880 }
881}