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 all_labels(&self) -> Vec<String> {
512 let mut labels = BTreeSet::new();
513 self.collect_all_labels(&mut labels);
514 labels.into_iter().collect()
515 }
516
517 fn collect_all_labels(&self, labels: &mut BTreeSet<String>) {
518 match self {
519 LocalTypeR::End | LocalTypeR::Var(_) => {}
520 LocalTypeR::Mu { body, .. } => body.collect_all_labels(labels),
521 LocalTypeR::Send { branches, .. } | LocalTypeR::Recv { branches, .. } => {
522 for (label, _, cont) in branches {
523 labels.insert(label.name.clone());
524 cont.collect_all_labels(labels);
525 }
526 }
527 }
528 }
529
530 #[must_use]
534 pub fn partners(&self) -> Vec<String> {
535 let mut result = BTreeSet::new();
536 self.collect_partners(&mut result);
537 result.into_iter().collect()
538 }
539
540 fn collect_partners(&self, partners: &mut BTreeSet<String>) {
541 match self {
542 LocalTypeR::End | LocalTypeR::Var(_) => {}
543 LocalTypeR::Send { partner, branches } | LocalTypeR::Recv { partner, branches } => {
544 partners.insert(partner.clone());
545 for (_, _, cont) in branches {
546 cont.collect_partners(partners);
547 }
548 }
549 LocalTypeR::Mu { body, .. } => body.collect_partners(partners),
550 }
551 }
552
553 #[must_use]
555 pub fn mentions_partner(&self, role: &str) -> bool {
556 self.mentions_partner_inner(role)
557 }
558
559 fn mentions_partner_inner(&self, role: &str) -> bool {
560 match self {
561 LocalTypeR::End | LocalTypeR::Var(_) => false,
562 LocalTypeR::Mu { body, .. } => body.mentions_partner_inner(role),
563 LocalTypeR::Send { partner, branches } | LocalTypeR::Recv { partner, branches } => {
564 partner == role
565 || branches
566 .iter()
567 .any(|(_, _, cont)| cont.mentions_partner_inner(role))
568 }
569 }
570 }
571
572 #[must_use]
574 pub fn is_send(&self) -> bool {
575 matches!(self, LocalTypeR::Send { .. })
576 }
577
578 #[must_use]
580 pub fn is_recv(&self) -> bool {
581 matches!(self, LocalTypeR::Recv { .. })
582 }
583
584 #[must_use]
586 pub fn is_end(&self) -> bool {
587 matches!(self, LocalTypeR::End)
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594 use crate::PayloadSort;
595 use assert_matches::assert_matches;
596 use proptest::prelude::*;
597
598 #[test]
599 fn test_simple_local_type() {
600 let lt = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
602 assert!(lt.well_formed());
603 assert_eq!(lt.partners().len(), 1);
604 assert!(lt.mentions_partner("B"));
605 }
606
607 #[test]
608 fn test_recursive_local_type() {
609 let lt = LocalTypeR::mu(
611 "t",
612 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("t")),
613 );
614 assert!(lt.well_formed());
615 assert!(lt.all_vars_bound());
616 }
617
618 #[test]
619 fn test_dual() {
620 let send = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
622 let recv = send.dual();
623
624 assert_matches!(recv, LocalTypeR::Recv { partner, branches } => {
625 assert_eq!(partner, "B");
626 assert_eq!(branches.len(), 1);
627 assert_eq!((branches[0].0).name, "msg");
628 });
629 }
630
631 #[test]
632 fn test_unfold() {
633 let lt = LocalTypeR::mu(
635 "t",
636 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("t")),
637 );
638 let unfolded = lt.unfold();
639
640 assert_matches!(unfolded, LocalTypeR::Send { partner, branches } => {
641 assert_eq!(partner, "B");
642 assert_matches!(branches[0].2, LocalTypeR::Mu { .. });
643 });
644 }
645
646 #[test]
647 fn test_substitute() {
648 let lt = LocalTypeR::var("t");
649 let replacement = LocalTypeR::End;
650 assert_eq!(lt.substitute("t", &replacement), LocalTypeR::End);
651 assert_eq!(lt.substitute("s", &replacement), LocalTypeR::var("t"));
652 }
653
654 #[test]
655 fn test_substitute_avoids_capture() {
656 let lt = LocalTypeR::mu("y", LocalTypeR::var("x"));
657 let result = lt.substitute("x", &LocalTypeR::var("y"));
658
659 assert_matches!(result, LocalTypeR::Mu { var, body } => {
660 assert_ne!(var, "y");
661 assert_matches!(*body, LocalTypeR::Var(ref name) => {
662 assert_eq!(name, "y");
663 });
664 });
665 }
666
667 #[test]
668 fn test_substitute_avoids_capture_with_nested_binders() {
669 let lt = LocalTypeR::mu("y", LocalTypeR::mu("y_0", LocalTypeR::var("x")));
670 let result = lt.substitute("x", &LocalTypeR::var("y"));
671 assert!(result.free_vars().contains(&"y".to_string()));
672 }
673
674 #[test]
675 fn test_unbound_variable() {
676 let lt = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("t"));
678 assert!(!lt.all_vars_bound());
679 assert!(!lt.well_formed());
680 }
681
682 #[test]
683 fn test_guarded() {
684 let unguarded = LocalTypeR::mu("t", LocalTypeR::var("t"));
686 assert!(!unguarded.is_guarded());
687 assert!(!unguarded.well_formed()); let guarded = LocalTypeR::mu(
691 "t",
692 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("t")),
693 );
694 assert!(guarded.is_guarded());
695 assert!(guarded.well_formed()); }
697
698 #[test]
699 fn test_mu_height_and_full_unfold() {
700 let lt = LocalTypeR::mu(
701 "outer",
702 LocalTypeR::mu(
703 "inner",
704 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("inner")),
705 ),
706 );
707 assert_eq!(lt.mu_height(), 2);
708 assert_matches!(lt.full_unfold(), LocalTypeR::Send { partner, branches } => {
709 assert_eq!(partner, "B");
710 assert_eq!(branches.len(), 1);
711 });
712 }
713
714 #[test]
715 fn test_reaches_communication_matches_practical_fragment_intent() {
716 assert!(!LocalTypeR::End.reaches_communication());
717 assert!(!LocalTypeR::End.regular_practical_fragment());
718
719 let send = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
720 assert!(send.reaches_communication());
721 assert!(send.regular_practical_fragment());
722
723 let guarded = LocalTypeR::mu(
724 "t",
725 LocalTypeR::recv("B", Label::new("msg"), LocalTypeR::var("t")),
726 );
727 assert!(guarded.reaches_communication());
728 assert!(guarded.regular_practical_fragment());
729
730 let unguarded = LocalTypeR::mu("t", LocalTypeR::var("t"));
731 assert!(!unguarded.reaches_communication());
732 assert!(!unguarded.regular_practical_fragment());
733 }
734
735 #[test]
736 fn test_free_vars() {
737 let lt = LocalTypeR::mu(
739 "t",
740 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::var("s")),
741 );
742 let free = lt.free_vars();
743 assert_eq!(free, vec!["s"]);
744 }
745
746 #[test]
747 fn test_choice_with_payload() {
748 let branches = vec![
749 (
750 Label::with_sort("accept", PayloadSort::Bool),
751 None,
752 LocalTypeR::End,
753 ),
754 (
755 Label::with_sort("data", PayloadSort::Nat),
756 None,
757 LocalTypeR::End,
758 ),
759 ];
760 let lt = LocalTypeR::recv_choice("A", branches);
761 assert!(lt.well_formed());
762 assert_eq!(lt.head_labels(), vec!["accept", "data"]);
763 }
764
765 #[test]
766 fn test_depth() {
767 let lt = LocalTypeR::send(
768 "B",
769 Label::new("outer"),
770 LocalTypeR::send("C", Label::new("inner"), LocalTypeR::End),
771 );
772 assert_eq!(lt.depth(), 2);
773 }
774
775 #[test]
776 fn test_is_send_recv() {
777 let send = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
778 let recv = LocalTypeR::recv("B", Label::new("msg"), LocalTypeR::End);
779
780 assert!(send.is_send());
781 assert!(!send.is_recv());
782 assert!(recv.is_recv());
783 assert!(!recv.is_send());
784 }
785
786 #[test]
787 fn test_duplicate_branch_labels_not_well_formed() {
788 let lt = LocalTypeR::recv_choice(
789 "A",
790 vec![
791 (Label::new("msg"), None, LocalTypeR::End),
792 (Label::new("msg"), None, LocalTypeR::End),
793 ],
794 );
795 assert!(!lt.unique_branch_labels());
796 assert!(!lt.well_formed());
797 }
798
799 #[test]
800 fn test_partners_are_sorted() {
801 let lt = LocalTypeR::send(
802 "Zed",
803 Label::new("outer"),
804 LocalTypeR::recv(
805 "Alice",
806 Label::new("mid"),
807 LocalTypeR::send("Bob", Label::new("inner"), LocalTypeR::End),
808 ),
809 );
810
811 assert_eq!(lt.partners(), vec!["Alice", "Bob", "Zed"]);
812 }
813
814 #[test]
815 fn test_mentions_partner_direct_traversal_matches_expectation() {
816 let lt = LocalTypeR::mu(
817 "t",
818 LocalTypeR::send(
819 "A",
820 Label::new("x"),
821 LocalTypeR::recv("B", Label::new("y"), LocalTypeR::var("t")),
822 ),
823 );
824 assert!(lt.mentions_partner("A"));
825 assert!(lt.mentions_partner("B"));
826 assert!(!lt.mentions_partner("C"));
827 }
828
829 #[test]
830 fn test_all_labels_collects_nested_labels() {
831 let lt = LocalTypeR::send(
832 "B",
833 Label::new("outer"),
834 LocalTypeR::recv(
835 "A",
836 Label::new("inner"),
837 LocalTypeR::send("C", Label::new("leaf"), LocalTypeR::End),
838 ),
839 );
840 assert_eq!(lt.head_labels(), vec!["outer"]);
841 assert_eq!(lt.all_labels(), vec!["inner", "leaf", "outer"]);
842 }
843
844 fn arb_var_name() -> impl Strategy<Value = String> {
845 prop_oneof![
846 Just("x".to_string()),
847 Just("y".to_string()),
848 Just("z".to_string()),
849 Just("t".to_string()),
850 Just("u".to_string()),
851 ]
852 }
853
854 proptest! {
855 #[test]
856 fn prop_substitute_identity_when_var_absent(var in arb_var_name()) {
857 let lt = LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End);
858 let replacement = LocalTypeR::var("r");
859 prop_assert_eq!(lt.substitute(&var, &replacement), lt);
860 }
861
862 #[test]
863 fn prop_substitute_avoids_capture_simple(
864 binder in arb_var_name(),
865 target in arb_var_name(),
866 ) {
867 prop_assume!(binder != target);
868 let lt = LocalTypeR::mu(&binder, LocalTypeR::var(&target));
869 let replacement = LocalTypeR::var(&binder);
870 let result = lt.substitute(&target, &replacement);
871 prop_assert!(result.free_vars().contains(&binder));
872 }
873 }
874}