1#![allow(unused)]
2
3use ecow::EcoVec;
4
5use crate::syntax::UnaryOp;
6use crate::{syntax::DeclExpr, ty::prelude::*};
7
8#[derive(Default)]
10struct CompactTy {
11 equiv_vars: HashSet<DefId>,
12 primitives: HashSet<Ty>,
13 recursives: HashMap<DefId, CompactTy>,
14 signatures: Vec<Interned<SigTy>>,
15
16 is_final: bool,
17}
18
19#[allow(clippy::mutable_key_type)]
20fn collect_input_type_vars(ty: &Ty, vars: &mut FxHashSet<DeclExpr>, traversed: &mut FxHashSet<Ty>) {
21 if !traversed.insert(ty.clone()) {
22 return;
23 }
24
25 match ty {
26 Ty::Var(var) => {
27 vars.insert(var.def.clone());
28 }
29 Ty::Func(_) | Ty::With(_) => {}
30 Ty::Param(param) => collect_input_type_vars(¶m.ty, vars, traversed),
31 Ty::Union(types) | Ty::Tuple(types) => {
32 for ty in types.iter() {
33 collect_input_type_vars(ty, vars, traversed);
34 }
35 }
36 Ty::Let(bounds) => {
37 for ty in bounds.lbs.iter().chain(&bounds.ubs) {
38 collect_input_type_vars(ty, vars, traversed);
39 }
40 }
41 Ty::Dict(record) => {
42 for ty in record.types.iter() {
43 collect_input_type_vars(ty, vars, traversed);
44 }
45 }
46 Ty::Array(elem) => collect_input_type_vars(elem, vars, traversed),
47 Ty::Args(sig) | Ty::Pattern(sig) => {
48 for input in sig.inputs() {
49 collect_input_type_vars(input, vars, traversed);
50 }
51 }
52 Ty::Select(select) => collect_input_type_vars(&select.ty, vars, traversed),
53 Ty::Unary(unary) => collect_input_type_vars(&unary.lhs, vars, traversed),
54 Ty::Binary(binary) => {
55 let [lhs, rhs] = binary.operands();
56 collect_input_type_vars(lhs, vars, traversed);
57 collect_input_type_vars(rhs, vars, traversed);
58 }
59 Ty::If(if_ty) => {
60 collect_input_type_vars(&if_ty.cond, vars, traversed);
61 collect_input_type_vars(&if_ty.then, vars, traversed);
62 collect_input_type_vars(&if_ty.else_, vars, traversed);
63 }
64 Ty::Any | Ty::Boolean(_) | Ty::Builtin(_) | Ty::Value(_) => {}
65 }
66}
67
68impl TypeInfo {
69 pub fn simplify(&self, ty: Ty, principal: bool) -> Ty {
71 let mut cache = self.cano_cache.lock();
72 let cache = &mut *cache;
73 let mut signature_binders = FxHashSet::default();
74
75 cache.transform_cache.clear();
76 cache.cano_local_cache.clear();
77 cache.positives.clear();
78 cache.negatives.clear();
79
80 let mut worker = TypeSimplifier {
81 principal,
82 vars: &self.vars,
83 cano_cache: &mut cache.cano_cache,
84 transform_cache: &mut cache.transform_cache,
85 cano_local_cache: &mut cache.cano_local_cache,
86 analyze_cache: FxHashSet::default(),
87 input_var_cache: FxHashSet::default(),
88
89 positives: &mut cache.positives,
90 negatives: &mut cache.negatives,
91 };
92
93 worker.simplify(ty, principal, &mut signature_binders)
94 }
95}
96
97struct TypeSimplifier<'a, 'b> {
99 principal: bool,
100
101 vars: &'a FxHashMap<DeclExpr, TypeVarBounds>,
102
103 cano_cache: &'b mut FxHashMap<(Ty, bool), Ty>,
104 transform_cache: &'b mut FxHashMap<(Ty, bool), Ty>,
105 cano_local_cache: &'b mut FxHashMap<(DeclExpr, bool), Ty>,
106 analyze_cache: FxHashSet<(Ty, bool)>,
107 input_var_cache: FxHashSet<Ty>,
108 negatives: &'b mut FxHashSet<DeclExpr>,
109 positives: &'b mut FxHashSet<DeclExpr>,
110}
111
112impl TypeSimplifier<'_, '_> {
113 fn simplify(
115 &mut self,
116 ty: Ty,
117 principal: bool,
118 signature_binders: &mut FxHashSet<DeclExpr>,
119 ) -> Ty {
120 if let Some(cano) = self.cano_cache.get(&(ty.clone(), principal)) {
121 return cano.clone();
122 }
123
124 self.analyze(&ty, true, signature_binders);
125 let cano = self.transform(&ty, true, signature_binders);
126 self.cano_cache.insert((ty, principal), cano.clone());
127 cano
128 }
129
130 fn analyze(&mut self, ty: &Ty, pol: bool, signature_binders: &mut FxHashSet<DeclExpr>) {
132 if !self.analyze_cache.insert((ty.clone(), pol)) {
133 return;
134 }
135
136 match ty {
137 Ty::Var(var) => {
138 if self.principal && signature_binders.contains(&var.def) {
139 return;
140 }
141 let Some(w) = self.vars.get(&var.def) else {
142 return;
143 };
144
145 let inserted = if pol {
146 self.positives.insert(var.def.clone())
147 } else {
148 self.negatives.insert(var.def.clone())
149 };
150 if !inserted {
151 return;
152 }
153
154 match &w.bounds {
155 FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
156 let bounds = w.read();
157 if pol {
158 for lb in bounds.lbs.iter() {
159 self.analyze(lb, pol, signature_binders);
160 }
161 } else {
162 for ub in bounds.ubs.iter() {
163 self.analyze(ub, pol, signature_binders);
164 }
165 }
166 }
167 }
168 }
169 Ty::Func(func) => {
170 if self.principal {
171 for input in func.inputs() {
172 collect_input_type_vars(
173 input,
174 signature_binders,
175 &mut self.input_var_cache,
176 );
177 }
178 }
179 for input_ty in func.inputs() {
180 self.analyze(input_ty, !pol, signature_binders);
181 }
182 if let Some(ret_ty) = &func.body {
183 self.analyze(ret_ty, pol, signature_binders);
184 }
185 }
186 Ty::Dict(record) => {
187 for member in record.types.iter() {
188 self.analyze(member, pol, signature_binders);
189 }
190 }
191 Ty::Tuple(elems) => {
192 for elem in elems.iter() {
193 self.analyze(elem, pol, signature_binders);
194 }
195 }
196 Ty::Array(arr) => {
197 self.analyze(arr, pol, signature_binders);
198 }
199 Ty::With(with) => {
200 self.analyze(&with.sig, pol, signature_binders);
201 for input in with.with.inputs() {
202 self.analyze(input, pol, signature_binders);
203 }
204 }
205 Ty::Args(args) => {
206 for input in args.inputs() {
207 self.analyze(input, pol, signature_binders);
208 }
209 }
210 Ty::Pattern(pat) => {
211 if self.principal {
212 for input in pat.inputs() {
213 collect_input_type_vars(
214 input,
215 signature_binders,
216 &mut self.input_var_cache,
217 );
218 }
219 }
220 for input in pat.inputs() {
221 self.analyze(input, pol, signature_binders);
222 }
223 }
224 Ty::Unary(unary) => self.analyze(&unary.lhs, pol, signature_binders),
225 Ty::Binary(binary) => {
226 let [lhs, rhs] = binary.operands();
227 self.analyze(lhs, pol, signature_binders);
228 self.analyze(rhs, pol, signature_binders);
229 }
230 Ty::If(if_expr) => {
231 self.analyze(&if_expr.cond, pol, signature_binders);
232 self.analyze(&if_expr.then, pol, signature_binders);
233 self.analyze(&if_expr.else_, pol, signature_binders);
234 }
235 Ty::Union(types) => {
236 for ty in types.iter() {
237 self.analyze(ty, pol, signature_binders);
238 }
239 }
240 Ty::Select(select) => {
241 self.analyze(&select.ty, pol, signature_binders);
242 }
243 Ty::Let(bounds) => {
244 for lb in bounds.lbs.iter() {
245 self.analyze(lb, !pol, signature_binders);
246 }
247 for ub in bounds.ubs.iter() {
248 self.analyze(ub, pol, signature_binders);
249 }
250 }
251 Ty::Param(param) => {
252 self.analyze(¶m.ty, pol, signature_binders);
253 }
254 Ty::Value(_v) => {}
255 Ty::Any => {}
256 Ty::Boolean(_) => {}
257 Ty::Builtin(_) => {}
258 }
259 }
260
261 fn transform(&mut self, ty: &Ty, pol: bool, signature_binders: &FxHashSet<DeclExpr>) -> Ty {
263 let cache_key = (ty.clone(), pol);
264 if let Some(cano) = self.transform_cache.get(&cache_key) {
265 return cano.clone();
266 }
267
268 let cano = match ty {
269 Ty::Let(bounds) => self.transform_let(
270 bounds.lbs.iter(),
271 bounds.ubs.iter(),
272 None,
273 pol,
274 signature_binders,
275 ),
276 Ty::Var(var) => {
277 if self.principal && signature_binders.contains(&var.def) {
278 return Ty::Var(var.clone());
279 }
280 let Some(bounds) = self.vars.get(&var.def) else {
281 return Ty::Var(var.clone());
282 };
283 if let Some(cano) = self
284 .cano_local_cache
285 .get(&(var.def.clone(), self.principal))
286 {
287 return cano.clone();
288 }
289 self.cano_local_cache
291 .insert((var.def.clone(), self.principal), Ty::Any);
292
293 let res = match &bounds.bounds {
294 FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
295 let w = w.read();
296
297 self.transform_let(
298 w.lbs.iter(),
299 w.ubs.iter(),
300 Some(&var.def),
301 pol,
302 signature_binders,
303 )
304 }
305 };
306
307 self.cano_local_cache
308 .insert((var.def.clone(), self.principal), res.clone());
309
310 res
311 }
312 Ty::Func(func) => Ty::Func(self.transform_sig(func, pol, signature_binders)),
313 Ty::Dict(record) => {
314 let mut mutated = record.as_ref().clone();
315 mutated.types = self.transform_seq(&mutated.types, pol, signature_binders);
316
317 Ty::Dict(mutated.into())
318 }
319 Ty::Tuple(tup) => self.transform_tuple(tup, pol, signature_binders),
320 Ty::Array(arr) => Ty::Array(self.transform(arr, pol, signature_binders).into()),
321 Ty::With(with) => {
322 let sig = self.transform(&with.sig, pol, signature_binders).into();
323 let mutated = self.transform_sig(&with.with, !pol, signature_binders);
325
326 Ty::With(SigWithTy::new(sig, mutated))
327 }
328 Ty::Args(args) => Ty::Args(self.transform_sig(args, !pol, signature_binders)),
331 Ty::Pattern(pat) => Ty::Pattern(self.transform_sig(pat, !pol, signature_binders)),
332 Ty::Unary(unary) => self.transform_unary(unary, pol, signature_binders),
333 Ty::Binary(binary) => {
334 let [lhs, rhs] = binary.operands();
335 let lhs = self.transform(lhs, pol, signature_binders);
336 let rhs = self.transform(rhs, pol, signature_binders);
337
338 Ty::Binary(TypeBinary::new(binary.op, lhs, rhs))
339 }
340 Ty::If(if_ty) => Ty::If(IfTy::new(
341 self.transform(&if_ty.cond, pol, signature_binders).into(),
342 self.transform(&if_ty.then, pol, signature_binders).into(),
343 self.transform(&if_ty.else_, pol, signature_binders).into(),
344 )),
345 Ty::Union(types) => {
346 let seq = types
347 .iter()
348 .map(|ty| self.transform(ty, pol, signature_binders));
349 let seq_no_any = seq.filter(|ty| !matches!(ty, Ty::Any));
350 let seq = seq_no_any.collect::<Vec<_>>();
351 Ty::from_types(seq.into_iter())
352 }
353 Ty::Param(param) => {
354 let mut param = param.as_ref().clone();
355 param.ty = self.transform(¶m.ty, pol, signature_binders);
356
357 Ty::Param(param.into())
358 }
359 Ty::Select(sel) => {
360 let mut sel = sel.as_ref().clone();
361 sel.ty = self.transform(&sel.ty, pol, signature_binders).into();
362
363 Ty::Select(sel.into())
364 }
365
366 Ty::Value(ins_ty) => Ty::Value(ins_ty.clone()),
367 Ty::Any => Ty::Any,
368 Ty::Boolean(truthiness) => Ty::Boolean(*truthiness),
369 Ty::Builtin(ty) => Ty::Builtin(ty.clone()),
370 };
371
372 self.transform_cache.insert(cache_key, cano.clone());
373 cano
374 }
375
376 fn transform_seq(
378 &mut self,
379 types: &[Ty],
380 pol: bool,
381 signature_binders: &FxHashSet<DeclExpr>,
382 ) -> Interned<Vec<Ty>> {
383 let seq = types
384 .iter()
385 .map(|ty| self.transform(ty, pol, signature_binders));
386 seq.collect::<Vec<_>>().into()
387 }
388
389 #[allow(clippy::mutable_key_type)]
391 fn transform_let<'a>(
392 &mut self,
393 lbs_iter: impl ExactSizeIterator<Item = &'a Ty>,
394 ubs_iter: impl ExactSizeIterator<Item = &'a Ty>,
395 decl: Option<&DeclExpr>,
396 pol: bool,
397 signature_binders: &FxHashSet<DeclExpr>,
398 ) -> Ty {
399 let mut lbs = HashSet::with_capacity(lbs_iter.len());
400 let mut ubs = HashSet::with_capacity(ubs_iter.len());
401
402 crate::log_debug_ct!("transform let [principal={}]", self.principal);
403
404 if !self.principal || ((pol) && !decl.is_some_and(|decl| self.negatives.contains(decl))) {
405 for lb in lbs_iter {
406 lbs.insert(self.transform(lb, pol, signature_binders));
407 }
408 }
409 if !self.principal || ((!pol) && !decl.is_some_and(|decl| self.positives.contains(decl))) {
410 for ub in ubs_iter {
411 ubs.insert(self.transform(ub, !pol, signature_binders));
412 }
413 }
414
415 if ubs.is_empty() {
416 if lbs.len() == 1 {
417 return lbs.into_iter().next().unwrap();
418 }
419 if lbs.is_empty() {
420 return Ty::Any;
421 }
422 } else if lbs.is_empty() && ubs.len() == 1 {
423 return ubs.into_iter().next().unwrap();
424 }
425
426 let mut lbs: Vec<_> = lbs.into_iter().collect();
428 lbs.sort();
429 let mut ubs: Vec<_> = ubs.into_iter().collect();
430 ubs.sort();
431
432 Ty::Let(TypeBounds { lbs, ubs }.into())
433 }
434
435 fn transform_tuple(
436 &mut self,
437 tup: &[Ty],
438 pol: bool,
439 signature_binders: &FxHashSet<DeclExpr>,
440 ) -> Ty {
441 let mut types = Vec::with_capacity(tup.len());
442
443 for elem in tup.iter() {
444 let elem = self.transform(elem, pol, signature_binders);
445 if !Self::push_spread_tuple_elements(&mut types, &elem) {
446 types.push(elem);
447 }
448 }
449
450 Ty::Tuple(types.into())
451 }
452
453 fn push_spread_tuple_elements(types: &mut Vec<Ty>, ty: &Ty) -> bool {
454 let Ty::Unary(unary) = ty else {
455 return false;
456 };
457 if unary.op != UnaryOp::Spread {
458 return false;
459 }
460
461 match &unary.lhs {
462 Ty::Tuple(elems) => {
463 types.extend(elems.iter().cloned());
464 true
465 }
466 Ty::Args(args) => {
467 types.extend(args.positional_params().cloned());
468 if let Some(rest) = args.rest_param()
469 && !Self::push_spread_tuple_elements(
470 types,
471 &Ty::Unary(TypeUnary::new(UnaryOp::Spread, rest.clone())),
472 )
473 {
474 types.push(Ty::Unary(TypeUnary::new(UnaryOp::Spread, rest.clone())));
475 }
476 true
477 }
478 _ => false,
479 }
480 }
481
482 fn transform_unary(
483 &mut self,
484 unary: &TypeUnary,
485 pol: bool,
486 signature_binders: &FxHashSet<DeclExpr>,
487 ) -> Ty {
488 let lhs = self.transform(&unary.lhs, pol, signature_binders);
489 if unary.op == UnaryOp::ElementOf
490 && let Some(elem) = Self::known_element_type(&lhs)
491 {
492 return elem;
493 }
494
495 Ty::Unary(TypeUnary::new(unary.op, lhs))
496 }
497
498 fn known_element_type(ty: &Ty) -> Option<Ty> {
499 match ty {
500 Ty::Array(elem) => Some(elem.as_ref().clone()),
501 Ty::Tuple(elems) => Self::known_tuple_element_type(elems),
502 Ty::Args(args) => Self::known_args_element_type(args),
503 Ty::Let(bounds) => Self::known_element_types(bounds.lbs.iter()),
504 Ty::Union(types) => Self::known_element_types(types.iter()),
505 _ => None,
506 }
507 }
508
509 fn known_element_types<'a>(types: impl Iterator<Item = &'a Ty>) -> Option<Ty> {
510 let types = types
511 .filter_map(Self::known_element_type)
512 .collect::<Vec<_>>();
513 (!types.is_empty()).then(|| Ty::from_types(types.into_iter()))
514 }
515
516 fn known_tuple_element_type(elems: &[Ty]) -> Option<Ty> {
517 let mut types = vec![];
518 for elem in elems {
519 if let Ty::Unary(unary) = elem
520 && unary.op == UnaryOp::Spread
521 {
522 if let Some(elem) = Self::known_element_type(&unary.lhs) {
523 types.push(elem);
524 }
525 continue;
526 }
527
528 types.push(elem.clone());
529 }
530
531 (!types.is_empty()).then(|| Ty::from_types(types.into_iter()))
532 }
533
534 fn known_args_element_type(args: &ArgsTy) -> Option<Ty> {
535 let mut types = args.positional_params().cloned().collect::<Vec<_>>();
536 if let Some(rest) = args.rest_param()
537 && let Some(elem) = Self::known_element_type(rest)
538 {
539 types.push(elem);
540 }
541
542 (!types.is_empty()).then(|| Ty::from_types(types.into_iter()))
543 }
544
545 fn transform_sig(
547 &mut self,
548 sig: &SigTy,
549 pol: bool,
550 signature_binders: &FxHashSet<DeclExpr>,
551 ) -> Interned<SigTy> {
552 let mut sig = sig.clone();
553 sig.inputs = self.transform_seq(&sig.inputs, !pol, signature_binders);
554 if let Some(ret) = &sig.body {
555 sig.body = Some(self.transform(ret, pol, signature_binders));
556 }
557
558 sig.into()
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566 use crate::syntax::Decl;
567
568 #[test]
570 fn test_simplify_sort() {
571 fn ch(it: &str) -> Ty {
572 Ty::Value(InsTy::new(Value::Str(it.into())))
573 }
574
575 fn val(it: Value) -> Ty {
576 Ty::Value(InsTy::new(it))
577 }
578
579 fn test_sort_ty(mut tys: Vec<Ty>) {
580 tys.sort();
581 }
582
583 let abcdef = vec![ch("a"), ch("b"), ch("c"), ch("d"), ch("e"), ch("f")];
584
585 let mut res = vec![];
586 res.extend(abcdef.clone());
587 res.extend(abcdef.clone());
588 res.extend(abcdef.clone());
589 res.extend(vec![ch("c"), val(Value::None), ch("a")]);
590
591 test_sort_ty(res);
592 }
593
594 #[test]
595 #[allow(clippy::mutable_key_type)]
596 fn test_analyze_memoizes_shared_type_dag() {
597 const DEPTH: usize = 16;
598
599 let mut shared = Ty::Any;
600 for _ in 0..DEPTH {
601 shared = Ty::Tuple(vec![shared.clone(), shared].into());
602 }
603
604 let info = TypeInfo::default();
605 let mut cano_cache = FxHashMap::default();
606 let mut transform_cache = FxHashMap::default();
607 let mut cano_local_cache = FxHashMap::default();
608 let mut positives = FxHashSet::default();
609 let mut negatives = FxHashSet::default();
610 let mut worker = TypeSimplifier {
611 principal: true,
612 vars: &info.vars,
613 cano_cache: &mut cano_cache,
614 transform_cache: &mut transform_cache,
615 cano_local_cache: &mut cano_local_cache,
616 analyze_cache: FxHashSet::default(),
617 input_var_cache: FxHashSet::default(),
618 positives: &mut positives,
619 negatives: &mut negatives,
620 };
621 let mut signature_binders = FxHashSet::default();
622
623 worker.analyze(&shared, true, &mut signature_binders);
624
625 assert_eq!(worker.analyze_cache.len(), DEPTH + 1);
626 }
627
628 fn var(name: &str) -> TypeVarBounds {
629 TypeVarBounds::new(
630 TypeVar {
631 name: name.into(),
632 def: Decl::lit(name).into(),
633 },
634 DynTypeBounds::default(),
635 )
636 }
637
638 fn recursive_fun(root: &Interned<TypeVar>, depth: usize) -> Ty {
639 let mut body = Ty::Var(root.clone());
640 for _ in 0..depth {
641 body = Ty::Func(SigTy::unary(Ty::Any, body));
642 }
643 body
644 }
645
646 #[test]
647 fn test_recursive_cycle_union_is_not_aligned_like_simple_sub() {
648 let mut info = TypeInfo::default();
649
650 let one = var("one");
651 let two = var("two");
652
653 let one_ty = one.as_type();
654 let two_ty = two.as_type();
655
656 info.vars.insert(one.var.def.clone(), one.clone());
657 info.vars.insert(two.var.def.clone(), two.clone());
658
659 info.vars
660 .get(&one.var.def)
661 .unwrap()
662 .bounds
663 .bounds()
664 .write()
665 .lbs
666 .insert_mut(recursive_fun(&one.var, 1));
667 info.vars
668 .get(&two.var.def)
669 .unwrap()
670 .bounds
671 .bounds()
672 .write()
673 .lbs
674 .insert_mut(recursive_fun(&two.var, 2));
675
676 let merged = Ty::from_types([one_ty, two_ty].into_iter());
677 let simplified = info.simplify(merged, true);
678 assert_eq!(
679 format!("{simplified:?}"),
680 "((Any) => Any | (Any) => (Any) => Any)"
681 );
682 }
683
684 #[test]
685 fn test_simplify_populates_top_level_cache() {
686 let mut info = TypeInfo::default();
687 let one = var("one");
688 let one_ty = one.as_type();
689 info.vars.insert(one.var.def.clone(), one.clone());
690 info.vars
691 .get(&one.var.def)
692 .unwrap()
693 .bounds
694 .bounds()
695 .write()
696 .lbs
697 .insert_mut(recursive_fun(&one.var, 1));
698
699 let _ = info.simplify(one_ty.clone(), true);
700 let first_cache_len = info.cano_cache.lock().cano_cache.len();
701 let _ = info.simplify(one_ty, true);
702 let second_cache_len = info.cano_cache.lock().cano_cache.len();
703 assert!(
704 first_cache_len > 0,
705 "simplify should memoize the top-level result"
706 );
707 assert_eq!(first_cache_len, second_cache_len);
708 }
709
710 #[test]
711 fn test_signature_inputs_are_principal_binders() {
712 let binder = TypeVar::new("body".into(), Decl::lit("body").into());
713 let binder_ty = Ty::Var(binder.clone());
714 let content = Ty::Builtin(BuiltinTy::Content(None));
715 let sig = SigTy::unary(binder_ty.clone(), binder_ty);
716
717 let sig_ty = Ty::Func(sig);
718 let mut dynamic_bounds = DynTypeBounds::default();
719 dynamic_bounds.ubs.insert_mut(content);
720 let mut info = TypeInfo::default();
721 info.vars.insert(
722 binder.def.clone(),
723 TypeVarBounds::new(binder.as_ref().clone(), dynamic_bounds),
724 );
725 assert_eq!(
726 format!("{:?}", info.simplify(sig_ty.clone(), false)),
727 "(Content) => Content"
728 );
729 assert_eq!(
730 format!("{:?}", info.simplify(sig_ty, true)),
731 "(@body) => @body"
732 );
733 assert_eq!(info.vars.len(), 1);
734 }
735
736 #[test]
737 fn test_principal_simplify_preserves_unused_signature_binder() {
738 let binder = TypeVar::new("body".into(), Decl::lit("body").into());
739 let binder_ty = Ty::Var(binder.clone());
740 let sig = SigTy::unary(binder_ty, Ty::Builtin(BuiltinTy::Color));
741
742 let mut dynamic_bounds = DynTypeBounds::default();
743 dynamic_bounds
744 .ubs
745 .insert_mut(Ty::Builtin(BuiltinTy::Content(None)));
746 let mut info = TypeInfo::default();
747 info.vars.insert(
748 binder.def.clone(),
749 TypeVarBounds::new(binder.as_ref().clone(), dynamic_bounds),
750 );
751
752 assert_eq!(
753 format!("{:?}", info.simplify(Ty::Func(sig), true)),
754 "(@body) => Color"
755 );
756 }
757}