1use std::collections::{BTreeMap, BTreeSet, VecDeque};
2
3use yulang_runtime_ir::{FinalizedBinding as Binding, RuntimeType};
4use yulang_typed_ir as typed_ir;
5
6use crate::{MonomorphizeDiagnostic, MonomorphizeResult};
7
8#[derive(Debug, Default, Clone, PartialEq, Eq)]
9pub struct TypeGraph {
10 next_fresh: usize,
11 slots: BTreeMap<typed_ir::TypeVar, TypeVarBounds>,
12 constraints: Vec<SubtypeConstraint>,
13 cast_order: TypeCastOrder,
14}
15
16impl TypeGraph {
17 pub fn with_cast_order(cast_order: TypeCastOrder) -> Self {
18 Self {
19 cast_order,
20 ..Self::default()
21 }
22 }
23
24 pub fn instantiate_principal(&mut self, binding: &Binding) -> PrincipalInstance {
25 let mut renames = BTreeMap::new();
26 for param in &binding.type_params {
27 let fresh = self.fresh_var(param);
28 self.slots.entry(fresh.clone()).or_default();
29 renames.insert(param.clone(), fresh);
30 }
31 PrincipalInstance {
32 original_binding: binding.name.clone(),
33 principal_type: rename_type(&binding.scheme.body, &renames),
34 type_params: renames
35 .into_iter()
36 .map(|(original, fresh)| PrincipalTypeParam { original, fresh })
37 .collect(),
38 }
39 }
40
41 pub fn collect_runtime_bounds(
42 &mut self,
43 template: &typed_ir::Type,
44 bounds: &RuntimeBounds,
45 ) -> MonomorphizeResult<()> {
46 if let Some(lower) = &bounds.lower {
47 self.collect_runtime(template, lower, BoundSide::Lower)?;
48 }
49 if let Some(upper) = &bounds.upper {
50 self.collect_runtime(template, upper, BoundSide::Upper)?;
51 }
52 self.propagate_constraints()
53 }
54
55 pub fn collect_runtime_lower(
56 &mut self,
57 template: &typed_ir::Type,
58 lower: &RuntimeType,
59 ) -> MonomorphizeResult<()> {
60 self.collect_runtime(template, lower, BoundSide::Lower)?;
61 self.propagate_constraints()
62 }
63
64 pub fn collect_runtime_upper(
65 &mut self,
66 template: &typed_ir::Type,
67 upper: &RuntimeType,
68 ) -> MonomorphizeResult<()> {
69 self.collect_runtime(template, upper, BoundSide::Upper)?;
70 self.propagate_constraints()
71 }
72
73 pub fn fresh_hole(&mut self, prefix: &str) -> typed_ir::Type {
74 let var = self.fresh_var(&typed_ir::TypeVar(prefix.into()));
75 self.slots.entry(var.clone()).or_default();
76 typed_ir::Type::Var(var)
77 }
78
79 pub fn constrain_subtype(
80 &mut self,
81 lower: typed_ir::Type,
82 upper: typed_ir::Type,
83 ) -> MonomorphizeResult<()> {
84 let constraint = SubtypeConstraint { lower, upper };
85 if !self.constraints.contains(&constraint) {
86 self.constraints.push(constraint);
87 }
88 self.propagate_constraints()
89 }
90
91 pub fn default_unbound_lower(
92 &mut self,
93 vars: BTreeSet<typed_ir::TypeVar>,
94 lower: typed_ir::Type,
95 ) -> MonomorphizeResult<()> {
96 for var in vars {
97 let Some(slot) = self.slots.get_mut(&var) else {
98 continue;
99 };
100 if slot.lower.is_none() && slot.upper.is_none() {
101 slot.push_lower(var, lower.clone(), &self.cast_order)?;
102 }
103 }
104 self.propagate_constraints()
105 }
106
107 pub fn solve(self) -> GraphSolution {
108 let type_vars = self
109 .slots
110 .into_iter()
111 .map(|(var, bounds)| {
112 let solution = bounds.solution_ref().cloned();
113 ResolvedTypeVar {
114 var,
115 bounds,
116 solution,
117 }
118 })
119 .collect();
120 GraphSolution { type_vars }
121 }
122
123 pub fn slot(&self, var: &typed_ir::TypeVar) -> Option<&TypeVarBounds> {
124 self.slots.get(var)
125 }
126
127 fn fresh_var(&mut self, source: &typed_ir::TypeVar) -> typed_ir::TypeVar {
128 let fresh = typed_ir::TypeVar(format!("{}#{}", source.0, self.next_fresh));
129 self.next_fresh += 1;
130 fresh
131 }
132
133 fn collect_runtime(
134 &mut self,
135 template: &typed_ir::Type,
136 actual: &RuntimeType,
137 side: BoundSide,
138 ) -> MonomorphizeResult<()> {
139 match actual {
140 RuntimeType::Value(actual) => self.collect_core(template, actual, side),
141 RuntimeType::Fun { param, ret } => {
142 let typed_ir::Type::Fun {
143 param: template_param,
144 param_effect: template_param_effect,
145 ret_effect: template_ret_effect,
146 ret: template_ret,
147 } = template
148 else {
149 return Ok(());
150 };
151 let RuntimeEffectedType {
152 value: actual_param,
153 effect: actual_param_effect,
154 } = split_runtime_effected_type(param);
155 let RuntimeEffectedType {
156 value: actual_ret,
157 effect: actual_ret_effect,
158 } = split_runtime_effected_type(ret);
159 let param_side = side.flip();
160 if !(param_side == BoundSide::Lower && runtime_value_is_top(actual_param)) {
161 self.collect_runtime(template_param, actual_param, param_side)?;
162 }
163 self.collect_runtime_effect(template_param_effect, actual_param_effect, side)?;
164 self.collect_runtime_effect(template_ret_effect, actual_ret_effect, side)?;
165 self.collect_runtime(template_ret, actual_ret, side)
166 }
167 RuntimeType::Thunk { value, .. } => self.collect_runtime(template, value, side),
168 RuntimeType::Unknown => Ok(()),
169 }
170 }
171
172 fn collect_core(
173 &mut self,
174 template: &typed_ir::Type,
175 actual: &typed_ir::Type,
176 side: BoundSide,
177 ) -> MonomorphizeResult<()> {
178 match template {
179 typed_ir::Type::Var(var) => self.record(var.clone(), actual.clone(), side).map(|_| ()),
180 typed_ir::Type::Named { path, args } => {
181 let typed_ir::Type::Named {
182 path: actual_path,
183 args: actual_args,
184 } = actual
185 else {
186 return Ok(());
187 };
188 if path != actual_path || args.len() != actual_args.len() {
189 return Ok(());
190 }
191 for (template, actual) in args.iter().zip(actual_args) {
192 self.collect_type_arg(template, actual, side)?;
193 }
194 Ok(())
195 }
196 typed_ir::Type::Fun {
197 param,
198 param_effect,
199 ret_effect,
200 ret,
201 } => {
202 let typed_ir::Type::Fun {
203 param: actual_param,
204 param_effect: actual_param_effect,
205 ret_effect: actual_ret_effect,
206 ret: actual_ret,
207 } = actual
208 else {
209 return Ok(());
210 };
211 let param_side = side.flip();
212 if !(param_side == BoundSide::Lower
213 && matches!(actual_param.as_ref(), typed_ir::Type::Any))
214 {
215 self.collect_core(param, actual_param, param_side)?;
216 }
217 self.collect_core(param_effect, actual_param_effect, side)?;
218 self.collect_core(ret_effect, actual_ret_effect, side)?;
219 self.collect_core(ret, actual_ret, side)
220 }
221 typed_ir::Type::Tuple(items) => {
222 let typed_ir::Type::Tuple(actual_items) = actual else {
223 return Ok(());
224 };
225 if items.len() != actual_items.len() {
226 return Ok(());
227 }
228 for (template, actual) in items.iter().zip(actual_items) {
229 self.collect_core(template, actual, side)?;
230 }
231 Ok(())
232 }
233 typed_ir::Type::Row { items, tail } => {
234 let typed_ir::Type::Row {
235 items: actual_items,
236 tail: actual_tail,
237 } = actual
238 else {
239 return Ok(());
240 };
241 self.collect_row(items, tail, actual_items, actual_tail, side)
242 }
243 typed_ir::Type::Variant(template) => {
244 let typed_ir::Type::Variant(actual) = actual else {
245 return Ok(());
246 };
247 self.collect_variant(template, actual, side)
248 }
249 typed_ir::Type::Record(template) => {
250 let typed_ir::Type::Record(actual) = actual else {
251 return Ok(());
252 };
253 self.collect_record(template, actual, side)
254 }
255 typed_ir::Type::Unknown
256 | typed_ir::Type::Never
257 | typed_ir::Type::Any
258 | typed_ir::Type::Union(_)
259 | typed_ir::Type::Inter(_)
260 | typed_ir::Type::Recursive { .. } => Ok(()),
261 }
262 }
263
264 fn collect_runtime_effect(
265 &mut self,
266 template: &typed_ir::Type,
267 actual: RuntimeEffectRef<'_>,
268 side: BoundSide,
269 ) -> MonomorphizeResult<()> {
270 match actual {
271 RuntimeEffectRef::Known(actual) => self.collect_core(template, actual, side),
272 RuntimeEffectRef::Pure | RuntimeEffectRef::Unknown => Ok(()),
273 }
274 }
275
276 fn propagate_constraints(&mut self) -> MonomorphizeResult<()> {
277 loop {
278 let mut changed = false;
279 for constraint in self.constraints.clone() {
280 changed |= self.apply_subtype_constraint(constraint.lower, constraint.upper)?;
281 }
282 if !changed {
283 return Ok(());
284 }
285 }
286 }
287
288 fn apply_subtype_constraint(
289 &mut self,
290 lower: typed_ir::Type,
291 upper: typed_ir::Type,
292 ) -> MonomorphizeResult<bool> {
293 self.apply_subtype_constraint_inner(lower, upper, &mut Vec::new())
294 }
295
296 fn apply_subtype_constraint_inner(
297 &mut self,
298 lower: typed_ir::Type,
299 upper: typed_ir::Type,
300 seen: &mut Vec<SubtypeConstraint>,
301 ) -> MonomorphizeResult<bool> {
302 let constraint = SubtypeConstraint {
303 lower: lower.clone(),
304 upper: upper.clone(),
305 };
306 if seen.contains(&constraint) {
307 return Ok(false);
308 }
309 seen.push(constraint);
310 if lower == upper || matches!(upper, typed_ir::Type::Any) {
311 return Ok(false);
312 }
313 match (lower, upper) {
314 (typed_ir::Type::Unknown, _) | (_, typed_ir::Type::Unknown) => Ok(false),
315 (typed_ir::Type::Var(lower), upper) => {
316 let mut changed = false;
317 if let Some(bound) = self
318 .slots
319 .get(&lower)
320 .and_then(|slot| slot.lower.as_ref())
321 .cloned()
322 .filter(|bound| !self.lower_bound_chain_reaches(bound, &lower))
323 {
324 changed |= self.apply_subtype_constraint_inner(bound, upper.clone(), seen)?;
325 }
326 if let typed_ir::Type::Var(upper_var) = &upper
327 && let Some(bound) = self
328 .slots
329 .get(upper_var)
330 .and_then(|slot| slot.upper.as_ref())
331 .cloned()
332 .filter(|bound| !self.upper_bound_chain_reaches(bound, &lower))
333 {
334 changed |= self.apply_subtype_constraint_inner(
335 typed_ir::Type::Var(lower.clone()),
336 bound,
337 seen,
338 )?;
339 }
340 changed |= self.record(lower, upper, BoundSide::Upper)?;
341 Ok(changed)
342 }
343 (lower, typed_ir::Type::Var(upper)) => {
344 let mut changed = false;
345 let lower = self.known_lower_or_self(lower);
346 if let Some(bound) = self
347 .slots
348 .get(&upper)
349 .and_then(|slot| slot.upper.as_ref())
350 .cloned()
351 .filter(|bound| !self.upper_bound_chain_reaches(bound, &upper))
352 {
353 changed |= self.apply_subtype_constraint_inner(lower.clone(), bound, seen)?;
354 }
355 changed |= self.record(upper, lower, BoundSide::Lower)?;
356 Ok(changed)
357 }
358 (
359 typed_ir::Type::Fun {
360 param: lower_param,
361 param_effect: lower_param_effect,
362 ret_effect: lower_ret_effect,
363 ret: lower_ret,
364 },
365 typed_ir::Type::Fun {
366 param: upper_param,
367 param_effect: upper_param_effect,
368 ret_effect: upper_ret_effect,
369 ret: upper_ret,
370 },
371 ) => {
372 let mut changed = false;
373 changed |= self.apply_subtype_constraint_inner(*upper_param, *lower_param, seen)?;
374 changed |= self.apply_subtype_constraint_inner(
375 *lower_param_effect,
376 *upper_param_effect,
377 seen,
378 )?;
379 changed |= self.apply_subtype_constraint_inner(
380 *lower_ret_effect,
381 *upper_ret_effect,
382 seen,
383 )?;
384 changed |= self.apply_subtype_constraint_inner(*lower_ret, *upper_ret, seen)?;
385 Ok(changed)
386 }
387 (
388 typed_ir::Type::Named {
389 path: lower_path,
390 args: lower_args,
391 },
392 typed_ir::Type::Named {
393 path: upper_path,
394 args: upper_args,
395 },
396 ) if lower_path == upper_path && lower_args.len() == upper_args.len() => {
397 let mut changed = false;
398 for (lower, upper) in lower_args.into_iter().zip(upper_args) {
399 changed |= self.constrain_type_arg(lower, upper, seen)?;
400 }
401 Ok(changed)
402 }
403 (typed_ir::Type::Tuple(lower), typed_ir::Type::Tuple(upper))
404 if lower.len() == upper.len() =>
405 {
406 let mut changed = false;
407 for (lower, upper) in lower.into_iter().zip(upper) {
408 changed |= self.apply_subtype_constraint_inner(lower, upper, seen)?;
409 }
410 Ok(changed)
411 }
412 (typed_ir::Type::Union(items), upper) => {
413 let mut changed = false;
414 for item in items {
415 changed |= self.apply_subtype_constraint_inner(item, upper.clone(), seen)?;
416 }
417 Ok(changed)
418 }
419 (lower, typed_ir::Type::Inter(items)) => {
420 let mut changed = false;
421 for item in items {
422 changed |= self.apply_subtype_constraint_inner(lower.clone(), item, seen)?;
423 }
424 Ok(changed)
425 }
426 (
427 typed_ir::Type::Row {
428 items: lower_items,
429 tail: lower_tail,
430 },
431 typed_ir::Type::Row {
432 items: upper_items,
433 tail: upper_tail,
434 },
435 ) => self.constrain_row(lower_items, *lower_tail, upper_items, *upper_tail, seen),
436 (typed_ir::Type::Variant(lower), typed_ir::Type::Variant(upper)) => {
437 self.constrain_variant(lower, upper, seen)
438 }
439 (typed_ir::Type::Record(lower), typed_ir::Type::Record(upper)) => {
440 self.constrain_record(lower, upper, seen)
441 }
442 _ => Ok(false),
443 }
444 }
445
446 fn constrain_type_arg(
447 &mut self,
448 lower: typed_ir::TypeArg,
449 upper: typed_ir::TypeArg,
450 seen: &mut Vec<SubtypeConstraint>,
451 ) -> MonomorphizeResult<bool> {
452 match (lower, upper) {
453 (typed_ir::TypeArg::Type(lower), typed_ir::TypeArg::Type(upper)) => {
454 self.apply_subtype_constraint_inner(lower, upper, seen)
455 }
456 (typed_ir::TypeArg::Type(lower), typed_ir::TypeArg::Bounds(upper)) => {
463 if let Some(upper_upper) = upper.upper {
464 self.apply_subtype_constraint_inner(lower, *upper_upper, seen)
465 } else {
466 Ok(false)
467 }
468 }
469 (typed_ir::TypeArg::Bounds(lower), typed_ir::TypeArg::Type(upper)) => {
473 if let Some(lower_lower) = lower.lower {
474 self.apply_subtype_constraint_inner(*lower_lower, upper, seen)
475 } else {
476 Ok(false)
477 }
478 }
479 (typed_ir::TypeArg::Bounds(lower), typed_ir::TypeArg::Bounds(upper)) => {
480 let mut changed = false;
481 if let (Some(lower), Some(upper)) = (lower.lower, upper.lower) {
482 changed |= self.apply_subtype_constraint_inner(*lower, *upper, seen)?;
483 }
484 if let (Some(lower), Some(upper)) = (lower.upper, upper.upper) {
485 changed |= self.apply_subtype_constraint_inner(*lower, *upper, seen)?;
486 }
487 Ok(changed)
488 }
489 }
490 }
491
492 fn known_lower_or_self(&self, ty: typed_ir::Type) -> typed_ir::Type {
493 let typed_ir::Type::Var(var) = &ty else {
494 return ty;
495 };
496 self.slots
497 .get(var)
498 .and_then(|slot| slot.lower.as_ref())
499 .cloned()
500 .unwrap_or(ty)
501 }
502
503 fn known_upper_or_self(&self, ty: typed_ir::Type) -> typed_ir::Type {
504 let typed_ir::Type::Var(var) = &ty else {
505 return ty;
506 };
507 self.slots
508 .get(var)
509 .and_then(|slot| slot.upper.as_ref())
510 .cloned()
511 .unwrap_or(ty)
512 }
513
514 fn collect_type_arg(
515 &mut self,
516 template: &typed_ir::TypeArg,
517 actual: &typed_ir::TypeArg,
518 side: BoundSide,
519 ) -> MonomorphizeResult<()> {
520 match (template, actual) {
521 (typed_ir::TypeArg::Type(template), typed_ir::TypeArg::Type(actual)) => {
522 self.collect_core(template, actual, side)
523 }
524 (typed_ir::TypeArg::Type(template), typed_ir::TypeArg::Bounds(actual)) => {
525 if let Some(lower) = actual.lower.as_deref() {
526 self.collect_core(template, lower, BoundSide::Lower)?;
527 }
528 if let Some(upper) = actual.upper.as_deref() {
529 self.collect_core(template, upper, BoundSide::Upper)?;
530 }
531 Ok(())
532 }
533 (typed_ir::TypeArg::Bounds(template), typed_ir::TypeArg::Type(actual)) => {
534 if let Some(lower) = template.lower.as_deref() {
535 self.collect_core(lower, actual, side)?;
536 }
537 if let Some(upper) = template.upper.as_deref() {
538 self.collect_core(upper, actual, BoundSide::Upper)?;
539 }
540 Ok(())
541 }
542 (typed_ir::TypeArg::Bounds(template), typed_ir::TypeArg::Bounds(actual)) => {
543 if let (Some(template), Some(actual)) = (&template.lower, &actual.lower) {
544 self.collect_core(template, actual, BoundSide::Lower)?;
545 }
546 if let (Some(template), Some(actual)) = (&template.upper, &actual.upper) {
547 self.collect_core(template, actual, BoundSide::Upper)?;
548 }
549 Ok(())
550 }
551 }
552 }
553
554 fn collect_row(
555 &mut self,
556 template_items: &[typed_ir::Type],
557 template_tail: &typed_ir::Type,
558 actual_items: &[typed_ir::Type],
559 actual_tail: &typed_ir::Type,
560 side: BoundSide,
561 ) -> MonomorphizeResult<()> {
562 let RowResidual {
563 matched,
564 unmatched_left,
565 unmatched_right,
566 } = split_row_items(template_items, actual_items);
567 if matched.is_empty() && !template_items.is_empty() && !actual_items.is_empty() {
568 return Ok(());
569 }
570 for (template, actual) in matched {
571 self.collect_core(template, actual, side)?;
572 }
573 if !unmatched_left.is_empty() {
574 return Ok(());
575 }
576 let residual = effect_row_from_items_and_tail(unmatched_right, actual_tail.clone());
577 self.collect_core(template_tail, &residual, side)
578 }
579
580 fn constrain_row(
581 &mut self,
582 lower_items: Vec<typed_ir::Type>,
583 lower_tail: typed_ir::Type,
584 upper_items: Vec<typed_ir::Type>,
585 upper_tail: typed_ir::Type,
586 seen: &mut Vec<SubtypeConstraint>,
587 ) -> MonomorphizeResult<bool> {
588 let RowResidual {
589 matched,
590 unmatched_left,
591 unmatched_right,
592 } = split_row_items(&lower_items, &upper_items);
593 if matched.is_empty() && !lower_items.is_empty() && !upper_items.is_empty() {
594 return Ok(false);
595 }
596 let mut changed = false;
597 for (lower, upper) in matched {
598 changed |= self.apply_subtype_constraint_inner(lower.clone(), upper.clone(), seen)?;
599 }
600 let lower_residual = effect_row_from_items_and_tail(unmatched_left, lower_tail);
601 let upper_residual = effect_row_from_items_and_tail(unmatched_right, upper_tail);
602 changed |= self.apply_subtype_constraint_inner(lower_residual, upper_residual, seen)?;
603 Ok(changed)
604 }
605
606 fn collect_variant(
607 &mut self,
608 template: &typed_ir::VariantType,
609 actual: &typed_ir::VariantType,
610 side: BoundSide,
611 ) -> MonomorphizeResult<()> {
612 for template_case in &template.cases {
613 let Some(actual_case) = find_variant_case(actual, &template_case.name) else {
614 if actual.tail.is_none() && side == BoundSide::Lower {
615 for payload in &template_case.payloads {
616 self.collect_core(payload, &typed_ir::Type::Never, BoundSide::Lower)?;
617 self.collect_core(payload, &typed_ir::Type::Never, BoundSide::Upper)?;
618 }
619 }
620 continue;
621 };
622 if template_case.payloads.len() != actual_case.payloads.len() {
623 continue;
624 }
625 for (template, actual) in template_case.payloads.iter().zip(&actual_case.payloads) {
626 self.collect_core(template, actual, side)?;
627 }
628 }
629 Ok(())
630 }
631
632 fn collect_record(
633 &mut self,
634 template: &typed_ir::RecordType,
635 actual: &typed_ir::RecordType,
636 side: BoundSide,
637 ) -> MonomorphizeResult<()> {
638 for template_field in &template.fields {
639 let Some(actual_field) = actual
640 .fields
641 .iter()
642 .find(|actual| actual.name == template_field.name)
643 else {
644 continue;
645 };
646 self.collect_core(&template_field.value, &actual_field.value, side)?;
647 }
648 Ok(())
649 }
650
651 fn constrain_variant(
652 &mut self,
653 lower: typed_ir::VariantType,
654 upper: typed_ir::VariantType,
655 seen: &mut Vec<SubtypeConstraint>,
656 ) -> MonomorphizeResult<bool> {
657 let mut changed = false;
658 for lower_case in &lower.cases {
659 let Some(upper_case) = find_variant_case(&upper, &lower_case.name) else {
660 continue;
661 };
662 if lower_case.payloads.len() != upper_case.payloads.len() {
663 continue;
664 }
665 for (lower, upper) in lower_case.payloads.iter().zip(&upper_case.payloads) {
666 changed |=
667 self.apply_subtype_constraint_inner(lower.clone(), upper.clone(), seen)?;
668 }
669 }
670 if lower.tail.is_none() {
671 for upper_case in &upper.cases {
672 if find_variant_case(&lower, &upper_case.name).is_some() {
673 continue;
674 }
675 for payload in &upper_case.payloads {
676 changed |= self.apply_subtype_constraint_inner(
677 typed_ir::Type::Never,
678 payload.clone(),
679 seen,
680 )?;
681 changed |= self.apply_subtype_constraint_inner(
682 payload.clone(),
683 typed_ir::Type::Never,
684 seen,
685 )?;
686 }
687 }
688 }
689 Ok(changed)
690 }
691
692 fn constrain_record(
693 &mut self,
694 lower: typed_ir::RecordType,
695 upper: typed_ir::RecordType,
696 seen: &mut Vec<SubtypeConstraint>,
697 ) -> MonomorphizeResult<bool> {
698 let mut changed = false;
699 for lower_field in &lower.fields {
700 let Some(upper_field) = upper
701 .fields
702 .iter()
703 .find(|upper| upper.name == lower_field.name)
704 else {
705 continue;
706 };
707 changed |= self.apply_subtype_constraint_inner(
708 lower_field.value.clone(),
709 upper_field.value.clone(),
710 seen,
711 )?;
712 }
713 Ok(changed)
714 }
715
716 fn record(
717 &mut self,
718 var: typed_ir::TypeVar,
719 mut ty: typed_ir::Type,
720 side: BoundSide,
721 ) -> MonomorphizeResult<bool> {
722 ty = match side {
723 BoundSide::Lower => self.known_lower_or_self(ty),
724 BoundSide::Upper => self.known_upper_or_self(ty),
725 };
726 ty = normalize_bound_form(&ty);
727 if matches!(ty, typed_ir::Type::Unknown) || is_vacuous_bound(&ty, side) {
728 return Ok(false);
729 }
730 if let typed_ir::Type::Var(other) = &ty
736 && *other == var
737 {
738 return Ok(false);
739 }
740 if let typed_ir::Type::Var(other) = &ty {
741 let cyclic = match side {
742 BoundSide::Lower => self.var_lower_bound_chain_reaches(other, &var),
743 BoundSide::Upper => self.var_upper_bound_chain_reaches(other, &var),
744 };
745 if cyclic {
746 return Ok(false);
747 }
748 }
749 if std::env::var_os("YULANG_TRACE_ANY_BOUNDS").is_some()
750 && side == BoundSide::Lower
751 && matches!(ty, typed_ir::Type::Any)
752 {
753 eprintln!(
754 "TRACE Any lower bound var={var:?}\n{}",
755 std::backtrace::Backtrace::force_capture()
756 );
757 }
758 let slot = self.slots.entry(var.clone()).or_default();
759 let changed = match side {
760 BoundSide::Lower => slot.push_lower(var.clone(), ty, &self.cast_order),
761 BoundSide::Upper => slot.push_upper(var.clone(), ty, &self.cast_order),
762 }?;
763 reject_invalid_top_bottom_bounds(&var, slot)?;
764 Ok(changed)
765 }
766
767 fn lower_bound_chain_reaches(&self, ty: &typed_ir::Type, target: &typed_ir::TypeVar) -> bool {
768 let typed_ir::Type::Var(var) = ty else {
769 return false;
770 };
771 self.var_lower_bound_chain_reaches(var, target)
772 }
773
774 fn upper_bound_chain_reaches(&self, ty: &typed_ir::Type, target: &typed_ir::TypeVar) -> bool {
775 let typed_ir::Type::Var(var) = ty else {
776 return false;
777 };
778 self.var_upper_bound_chain_reaches(var, target)
779 }
780
781 fn var_lower_bound_chain_reaches(
782 &self,
783 start: &typed_ir::TypeVar,
784 target: &typed_ir::TypeVar,
785 ) -> bool {
786 self.var_bound_chain_reaches(start, target, BoundSide::Lower)
787 }
788
789 fn var_upper_bound_chain_reaches(
790 &self,
791 start: &typed_ir::TypeVar,
792 target: &typed_ir::TypeVar,
793 ) -> bool {
794 self.var_bound_chain_reaches(start, target, BoundSide::Upper)
795 }
796
797 fn var_bound_chain_reaches(
798 &self,
799 start: &typed_ir::TypeVar,
800 target: &typed_ir::TypeVar,
801 side: BoundSide,
802 ) -> bool {
803 let mut current = start;
804 let mut seen = Vec::new();
805 loop {
806 if current == target {
807 return true;
808 }
809 if seen.iter().any(|seen| seen == current) {
810 return false;
811 }
812 seen.push(current.clone());
813 let Some(typed_ir::Type::Var(next)) =
814 self.slots.get(current).and_then(|slot| match side {
815 BoundSide::Lower => slot.lower.as_ref(),
816 BoundSide::Upper => slot.upper.as_ref(),
817 })
818 else {
819 return false;
820 };
821 current = next;
822 }
823 }
824}
825
826#[derive(Debug, Default, Clone, PartialEq, Eq)]
827pub struct TypeCastOrder {
828 edges: Vec<TypeCastEdge>,
829}
830
831impl TypeCastOrder {
832 pub fn from_edges(edges: Vec<(typed_ir::Type, typed_ir::Type)>) -> Self {
833 Self {
834 edges: edges
835 .into_iter()
836 .map(|(from, to)| TypeCastEdge { from, to })
837 .collect(),
838 }
839 }
840
841 fn join_lower(&self, left: &typed_ir::Type, right: &typed_ir::Type) -> Option<typed_ir::Type> {
842 let left_to_right = self.can_cast(left, right);
843 let right_to_left = self.can_cast(right, left);
844 match (left_to_right, right_to_left) {
845 (true, false) => Some(right.clone()),
846 (false, true) => Some(left.clone()),
847 _ => None,
848 }
849 }
850
851 fn meet_upper(&self, left: &typed_ir::Type, right: &typed_ir::Type) -> Option<typed_ir::Type> {
852 let left_to_right = self.can_cast(left, right);
853 let right_to_left = self.can_cast(right, left);
854 match (left_to_right, right_to_left) {
855 (true, false) => Some(left.clone()),
856 (false, true) => Some(right.clone()),
857 _ => None,
858 }
859 }
860
861 fn can_cast(&self, from: &typed_ir::Type, to: &typed_ir::Type) -> bool {
862 if lightweight_bounds_equivalent(from, to) {
863 return true;
864 }
865 let mut seen = Vec::new();
866 let mut queue = VecDeque::from([from.clone()]);
867 while let Some(current) = queue.pop_front() {
868 if seen
869 .iter()
870 .any(|seen| lightweight_bounds_equivalent(seen, ¤t))
871 {
872 continue;
873 }
874 seen.push(current.clone());
875 for edge in &self.edges {
876 if !lightweight_bounds_equivalent(&edge.from, ¤t) {
877 continue;
878 }
879 if lightweight_bounds_equivalent(&edge.to, to) {
880 return true;
881 }
882 queue.push_back(edge.to.clone());
883 }
884 }
885 false
886 }
887}
888
889#[derive(Debug, Clone, PartialEq, Eq)]
890struct TypeCastEdge {
891 from: typed_ir::Type,
892 to: typed_ir::Type,
893}
894
895#[derive(Debug, Clone, PartialEq, Eq)]
896struct SubtypeConstraint {
897 lower: typed_ir::Type,
898 upper: typed_ir::Type,
899}
900
901#[derive(Debug, Clone, PartialEq, Eq)]
902pub struct PrincipalInstance {
903 pub original_binding: typed_ir::Path,
904 pub principal_type: typed_ir::Type,
905 pub type_params: Vec<PrincipalTypeParam>,
906}
907
908impl PrincipalInstance {
909 pub fn original_substitutions(
910 &self,
911 solution: &GraphSolution,
912 ) -> Vec<typed_ir::TypeSubstitution> {
913 self.type_params
914 .iter()
915 .filter_map(|param| {
916 solution
917 .solution_for(¶m.fresh)
918 .cloned()
919 .map(|ty| typed_ir::TypeSubstitution {
920 var: param.original.clone(),
921 ty,
922 })
923 })
924 .collect()
925 }
926
927 pub fn effect_only_type_params(&self) -> BTreeSet<typed_ir::TypeVar> {
928 let params = self
929 .type_params
930 .iter()
931 .map(|param| param.fresh.clone())
932 .collect::<BTreeSet<_>>();
933 let mut vars = TypePositionVars::default();
934 collect_type_position_vars(
935 &self.principal_type,
936 TypePosition::Value,
937 ¶ms,
938 &mut vars,
939 );
940 vars.effect
941 .difference(&vars.value)
942 .cloned()
943 .collect::<BTreeSet<_>>()
944 }
945}
946
947#[derive(Debug, Clone, PartialEq, Eq)]
948pub struct PrincipalTypeParam {
949 pub original: typed_ir::TypeVar,
950 pub fresh: typed_ir::TypeVar,
951}
952
953#[derive(Debug, Default, Clone, PartialEq, Eq)]
954pub struct RuntimeBounds {
955 pub lower: Option<RuntimeType>,
956 pub upper: Option<RuntimeType>,
957}
958
959impl RuntimeBounds {
960 pub fn lower(ty: RuntimeType) -> Self {
961 Self {
962 lower: Some(ty),
963 upper: None,
964 }
965 }
966
967 pub fn upper(ty: RuntimeType) -> Self {
968 Self {
969 lower: None,
970 upper: Some(ty),
971 }
972 }
973
974 pub fn exact(ty: RuntimeType) -> Self {
975 Self {
976 lower: Some(ty.clone()),
977 upper: Some(ty),
978 }
979 }
980}
981
982#[derive(Debug, Default, Clone, PartialEq, Eq)]
983pub struct TypeVarBounds {
984 pub lower: Option<typed_ir::Type>,
985 pub upper: Option<typed_ir::Type>,
986}
987
988impl TypeVarBounds {
989 pub fn solution(self) -> Option<typed_ir::Type> {
990 self.lower.or(self.upper)
991 }
992
993 pub fn solution_ref(&self) -> Option<&typed_ir::Type> {
994 self.lower.as_ref().or(self.upper.as_ref())
995 }
996
997 fn push_lower(
998 &mut self,
999 var: typed_ir::TypeVar,
1000 ty: typed_ir::Type,
1001 cast_order: &TypeCastOrder,
1002 ) -> MonomorphizeResult<bool> {
1003 push_bound(&mut self.lower, var, ty, BoundSide::Lower, cast_order)
1004 }
1005
1006 fn push_upper(
1007 &mut self,
1008 var: typed_ir::TypeVar,
1009 ty: typed_ir::Type,
1010 cast_order: &TypeCastOrder,
1011 ) -> MonomorphizeResult<bool> {
1012 push_bound(&mut self.upper, var, ty, BoundSide::Upper, cast_order)
1013 }
1014}
1015
1016#[derive(Debug, Clone, PartialEq, Eq)]
1017pub struct GraphSolution {
1018 pub type_vars: Vec<ResolvedTypeVar>,
1019}
1020
1021#[derive(Debug, Clone, PartialEq, Eq)]
1022pub struct ResolvedTypeVar {
1023 pub var: typed_ir::TypeVar,
1024 pub bounds: TypeVarBounds,
1025 pub solution: Option<typed_ir::Type>,
1026}
1027
1028impl GraphSolution {
1029 pub fn is_complete(&self) -> bool {
1030 self.type_vars.iter().all(|var| var.solution.is_some())
1031 }
1032
1033 pub fn substitutions(&self) -> Vec<typed_ir::TypeSubstitution> {
1034 self.type_vars
1035 .iter()
1036 .filter_map(|var| {
1037 var.solution.clone().map(|ty| typed_ir::TypeSubstitution {
1038 var: var.var.clone(),
1039 ty,
1040 })
1041 })
1042 .collect()
1043 }
1044
1045 pub fn solution_for(&self, var: &typed_ir::TypeVar) -> Option<&typed_ir::Type> {
1046 self.type_vars
1047 .iter()
1048 .find(|resolved| &resolved.var == var)
1049 .and_then(|resolved| resolved.solution.as_ref())
1050 }
1051
1052 pub fn materialize_core(&self, ty: typed_ir::Type) -> typed_ir::Type {
1053 materialize_type(ty, &self.substitutions())
1054 }
1055}
1056
1057pub fn materialize_core_type(
1058 ty: typed_ir::Type,
1059 substitutions: &[typed_ir::TypeSubstitution],
1060) -> typed_ir::Type {
1061 normalize_bound_form(&materialize_type(ty, substitutions))
1062}
1063
1064pub fn materialize_runtime_type(
1065 ty: RuntimeType,
1066 substitutions: &[typed_ir::TypeSubstitution],
1067) -> RuntimeType {
1068 match ty {
1069 RuntimeType::Unknown => RuntimeType::Unknown,
1070 RuntimeType::Value(ty) => {
1071 runtime_type_from_core_value(normalize_bound_form(&materialize_type(ty, substitutions)))
1072 }
1073 RuntimeType::Fun { param, ret } => RuntimeType::Fun {
1074 param: Box::new(materialize_runtime_type(*param, substitutions)),
1075 ret: Box::new(materialize_runtime_type(*ret, substitutions)),
1076 },
1077 RuntimeType::Thunk { effect, value } => RuntimeType::Thunk {
1078 effect: normalize_bound_form(&materialize_type(effect, substitutions)),
1079 value: Box::new(materialize_runtime_type(*value, substitutions)),
1080 },
1081 }
1082}
1083
1084pub(crate) fn runtime_type_from_core_value(ty: typed_ir::Type) -> RuntimeType {
1089 match ty {
1090 typed_ir::Type::Fun {
1091 param,
1092 param_effect,
1093 ret_effect,
1094 ret,
1095 } => RuntimeType::Fun {
1096 param: Box::new(runtime_type_from_core_value_and_effect(
1097 *param,
1098 *param_effect,
1099 )),
1100 ret: Box::new(runtime_type_from_core_value_and_effect(*ret, *ret_effect)),
1101 },
1102 ty => RuntimeType::Value(ty),
1103 }
1104}
1105
1106pub(crate) fn runtime_type_from_core_value_and_effect(
1107 value: typed_ir::Type,
1108 effect: typed_ir::Type,
1109) -> RuntimeType {
1110 let value = runtime_type_from_core_value(value);
1111 if should_thunk_effect(&effect) {
1112 RuntimeType::Thunk {
1113 effect,
1114 value: Box::new(value),
1115 }
1116 } else {
1117 value
1118 }
1119}
1120
1121pub(crate) fn should_thunk_effect(effect: &typed_ir::Type) -> bool {
1122 !effect_is_empty(effect) && !matches!(effect, typed_ir::Type::Unknown)
1123}
1124
1125pub(crate) fn effect_is_empty(effect: &typed_ir::Type) -> bool {
1126 match effect {
1127 typed_ir::Type::Never => true,
1128 typed_ir::Type::Row { items, tail } => items.is_empty() && effect_is_empty(tail),
1129 typed_ir::Type::Recursive { body, .. } => effect_is_empty(body),
1130 _ => false,
1131 }
1132}
1133
1134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1135enum BoundSide {
1136 Lower,
1137 Upper,
1138}
1139
1140impl BoundSide {
1141 fn flip(self) -> Self {
1142 match self {
1143 Self::Lower => Self::Upper,
1144 Self::Upper => Self::Lower,
1145 }
1146 }
1147}
1148
1149struct RuntimeEffectedType<'a> {
1150 value: &'a RuntimeType,
1151 effect: RuntimeEffectRef<'a>,
1152}
1153
1154#[derive(Clone, Copy)]
1155enum RuntimeEffectRef<'a> {
1156 Known(&'a typed_ir::Type),
1157 Pure,
1158 Unknown,
1159}
1160
1161fn runtime_value_is_top(ty: &RuntimeType) -> bool {
1162 matches!(ty, RuntimeType::Value(typed_ir::Type::Any))
1163}
1164
1165fn split_runtime_effected_type(ty: &RuntimeType) -> RuntimeEffectedType<'_> {
1166 match ty {
1167 RuntimeType::Thunk { effect, value } => RuntimeEffectedType {
1168 value,
1169 effect: RuntimeEffectRef::Known(effect),
1170 },
1171 RuntimeType::Unknown => RuntimeEffectedType {
1172 value: ty,
1173 effect: RuntimeEffectRef::Unknown,
1174 },
1175 RuntimeType::Value(_) | RuntimeType::Fun { .. } => RuntimeEffectedType {
1176 value: ty,
1177 effect: RuntimeEffectRef::Pure,
1178 },
1179 }
1180}
1181
1182fn merge_row_bounds(
1199 previous: &typed_ir::Type,
1200 ty: &typed_ir::Type,
1201 side: BoundSide,
1202) -> Option<typed_ir::Type> {
1203 if !is_row_shaped(previous) && !is_row_shaped(ty) {
1207 return None;
1208 }
1209 let (mut prev_items, prev_tail) = flatten_closed_row_or_atom(previous)?;
1210 let (ty_items, ty_tail) = flatten_closed_row_or_atom(ty)?;
1211 let merged_tail = merge_row_tails(&prev_tail, &ty_tail, side)?;
1212 let merged_items = match side {
1213 BoundSide::Lower => {
1214 for item in ty_items {
1215 push_unique_effect_item(&mut prev_items, item);
1216 }
1217 prev_items
1218 }
1219 BoundSide::Upper => {
1220 let mut out = Vec::new();
1221 for item in &prev_items {
1222 if ty_items.iter().any(|other| effect_items_match(item, other)) {
1223 push_unique_effect_item(&mut out, item.clone());
1224 }
1225 }
1226 out
1227 }
1228 };
1229 Some(typed_ir::Type::Row {
1230 items: merged_items,
1231 tail: Box::new(merged_tail),
1232 })
1233}
1234
1235fn merge_row_tails(
1236 previous: &typed_ir::Type,
1237 ty: &typed_ir::Type,
1238 side: BoundSide,
1239) -> Option<typed_ir::Type> {
1240 match (side, previous, ty) {
1241 (_, typed_ir::Type::Never, typed_ir::Type::Never) => Some(typed_ir::Type::Never),
1242 (_, typed_ir::Type::Any, typed_ir::Type::Any) => Some(typed_ir::Type::Any),
1243 (BoundSide::Lower, typed_ir::Type::Any, typed_ir::Type::Never)
1244 | (BoundSide::Lower, typed_ir::Type::Never, typed_ir::Type::Any) => {
1245 Some(typed_ir::Type::Any)
1246 }
1247 (BoundSide::Upper, typed_ir::Type::Any, typed_ir::Type::Never)
1248 | (BoundSide::Upper, typed_ir::Type::Never, typed_ir::Type::Any) => {
1249 Some(typed_ir::Type::Never)
1250 }
1251 _ => None,
1252 }
1253}
1254
1255fn is_row_shaped(ty: &typed_ir::Type) -> bool {
1256 matches!(ty, typed_ir::Type::Row { .. } | typed_ir::Type::Never)
1257}
1258
1259fn flatten_closed_row_or_atom(
1265 ty: &typed_ir::Type,
1266) -> Option<(Vec<typed_ir::Type>, typed_ir::Type)> {
1267 match ty {
1268 typed_ir::Type::Named { .. } => Some((vec![ty.clone()], typed_ir::Type::Never)),
1269 _ => flatten_closed_row(ty),
1270 }
1271}
1272
1273fn flatten_closed_row(ty: &typed_ir::Type) -> Option<(Vec<typed_ir::Type>, typed_ir::Type)> {
1287 match ty {
1288 typed_ir::Type::Never => Some((Vec::new(), typed_ir::Type::Never)),
1289 typed_ir::Type::Row { items, tail } => {
1290 let mut out: Vec<typed_ir::Type> = Vec::new();
1291 for item in items {
1292 push_unique_effect_item(&mut out, item.clone());
1293 }
1294 let mut current_tail: typed_ir::Type = (**tail).clone();
1295 let mut depth = 0usize;
1296 loop {
1297 if depth >= 64 {
1298 return None;
1299 }
1300 depth += 1;
1301 match current_tail {
1302 typed_ir::Type::Row {
1303 items: tail_items,
1304 tail: next_tail,
1305 } => {
1306 for item in tail_items {
1307 push_unique_effect_item(&mut out, item);
1308 }
1309 current_tail = *next_tail;
1310 }
1311 typed_ir::Type::Inter(branches) => {
1312 let collapsed = collapse_equivalent_inter(&branches)?;
1313 current_tail = collapsed;
1314 }
1315 typed_ir::Type::Never => return Some((out, typed_ir::Type::Never)),
1316 other => return Some((out, other)),
1317 }
1318 }
1319 }
1320 _ => None,
1321 }
1322}
1323
1324fn collapse_equivalent_inter(branches: &[typed_ir::Type]) -> Option<typed_ir::Type> {
1329 let (first, rest) = branches.split_first()?;
1330 let (first_items, first_tail) = flatten_closed_row(first)?;
1331 for branch in rest {
1332 let (branch_items, branch_tail) = flatten_closed_row(branch)?;
1333 if !rows_equivalent(&first_items, &first_tail, &branch_items, &branch_tail) {
1334 return None;
1335 }
1336 }
1337 Some(typed_ir::Type::Row {
1338 items: first_items,
1339 tail: Box::new(first_tail),
1340 })
1341}
1342
1343fn intersect_closed_rows(branches: &[typed_ir::Type]) -> Option<typed_ir::Type> {
1344 let (first, rest) = branches.split_first()?;
1345 let (mut items, mut tail) = flatten_closed_row(first)?;
1346 for branch in rest {
1347 let (branch_items, branch_tail) = flatten_closed_row(branch)?;
1348 let mut intersection = Vec::new();
1349 for item in &items {
1350 if branch_items
1351 .iter()
1352 .any(|other| effect_items_match(item, other))
1353 {
1354 push_unique_effect_item(&mut intersection, item.clone());
1355 }
1356 }
1357 items = intersection;
1358 tail = merge_row_tails(&tail, &branch_tail, BoundSide::Upper)?;
1359 }
1360 Some(typed_ir::Type::Row {
1361 items,
1362 tail: Box::new(tail),
1363 })
1364}
1365
1366fn rows_equivalent(
1367 a_items: &[typed_ir::Type],
1368 a_tail: &typed_ir::Type,
1369 b_items: &[typed_ir::Type],
1370 b_tail: &typed_ir::Type,
1371) -> bool {
1372 if a_items.len() != b_items.len() {
1373 return false;
1374 }
1375 if !a_items
1376 .iter()
1377 .all(|item| b_items.iter().any(|other| effect_items_match(item, other)))
1378 {
1379 return false;
1380 }
1381 a_tail == b_tail
1382 || normalize_bound_form_inner(a_tail, true) == normalize_bound_form_inner(b_tail, true)
1383}
1384
1385fn push_unique_effect_item(items: &mut Vec<typed_ir::Type>, item: typed_ir::Type) {
1386 if let typed_ir::Type::Row {
1387 items: row_items,
1388 tail,
1389 } = item
1390 {
1391 if matches!(tail.as_ref(), typed_ir::Type::Never) {
1392 for item in row_items {
1393 push_unique_effect_item(items, item);
1394 }
1395 return;
1396 }
1397 let item = typed_ir::Type::Row {
1398 items: row_items,
1399 tail,
1400 };
1401 push_unique_non_row_effect_item(items, item);
1402 return;
1403 }
1404 push_unique_non_row_effect_item(items, item);
1405}
1406
1407fn push_unique_non_row_effect_item(items: &mut Vec<typed_ir::Type>, item: typed_ir::Type) {
1408 if matches!(item, typed_ir::Type::Never) {
1409 return;
1410 }
1411 if items
1412 .iter()
1413 .any(|existing| effect_items_match(existing, &item))
1414 {
1415 return;
1416 }
1417 items.push(item);
1418}
1419
1420fn effect_items_match(left: &typed_ir::Type, right: &typed_ir::Type) -> bool {
1421 left == right
1422 || normalize_bound_form_inner(left, true) == normalize_bound_form_inner(right, true)
1423}
1424
1425fn push_bound(
1426 slot: &mut Option<typed_ir::Type>,
1427 var: typed_ir::TypeVar,
1428 ty: typed_ir::Type,
1429 side: BoundSide,
1430 cast_order: &TypeCastOrder,
1431) -> MonomorphizeResult<bool> {
1432 if let Some(previous) = slot {
1433 if bounds_are_equivalent(previous, &ty) {
1434 return Ok(false);
1435 }
1436 if let Some(merged) = merge_unknown_bounds(previous, &ty) {
1437 if bounds_are_equivalent(previous, &merged) {
1438 return Ok(false);
1439 }
1440 *previous = merged;
1441 return Ok(true);
1442 }
1443 if let Some(merged) = merge_row_bounds(previous, &ty, side) {
1444 if bounds_are_equivalent(previous, &merged) {
1445 return Ok(false);
1446 }
1447 *previous = merged;
1448 return Ok(true);
1449 }
1450 if let Some(merged) = merge_ordered_bounds(previous, &ty, side, cast_order) {
1451 if bounds_are_equivalent(previous, &merged) {
1452 return Ok(false);
1453 }
1454 *previous = merged;
1455 return Ok(true);
1456 }
1457 if matches!(
1458 (&*previous, &ty),
1459 (typed_ir::Type::Var(_), typed_ir::Type::Var(_))
1460 ) {
1461 return Ok(false);
1462 }
1463 if matches!(ty, typed_ir::Type::Var(_)) && !matches!(previous, typed_ir::Type::Var(_)) {
1464 return Ok(false);
1465 }
1466 if matches!(previous, typed_ir::Type::Var(_)) && !matches!(ty, typed_ir::Type::Var(_)) {
1467 *previous = ty;
1468 return Ok(true);
1469 }
1470 if type_contains_var(previous) || type_contains_var(&ty) {
1471 return Ok(false);
1472 }
1473 return Err(MonomorphizeDiagnostic::ConflictingBounds {
1474 var,
1475 previous: previous.clone(),
1476 next: ty,
1477 });
1478 }
1479 *slot = Some(ty);
1480 Ok(true)
1481}
1482
1483fn reject_invalid_top_bottom_bounds(
1484 var: &typed_ir::TypeVar,
1485 bounds: &TypeVarBounds,
1486) -> MonomorphizeResult<()> {
1487 let (Some(lower), Some(upper)) = (&bounds.lower, &bounds.upper) else {
1488 return Ok(());
1489 };
1490 if matches!(lower, typed_ir::Type::Any)
1491 && !matches!(upper, typed_ir::Type::Any | typed_ir::Type::Never)
1492 {
1493 return Err(MonomorphizeDiagnostic::ConflictingBounds {
1494 var: var.clone(),
1495 previous: lower.clone(),
1496 next: upper.clone(),
1497 });
1498 }
1499 Ok(())
1500}
1501
1502fn merge_ordered_bounds(
1503 previous: &typed_ir::Type,
1504 ty: &typed_ir::Type,
1505 side: BoundSide,
1506 cast_order: &TypeCastOrder,
1507) -> Option<typed_ir::Type> {
1508 match side {
1509 BoundSide::Lower
1510 if matches!(previous, typed_ir::Type::Any) || matches!(ty, typed_ir::Type::Any) =>
1511 {
1512 Some(typed_ir::Type::Any)
1513 }
1514 BoundSide::Lower if union_supertype_contains(previous, ty) => Some(previous.clone()),
1515 BoundSide::Lower if union_supertype_contains(ty, previous) => Some(ty.clone()),
1516 BoundSide::Lower => {
1517 merge_lower_union_bound(previous, ty).or_else(|| cast_order.join_lower(previous, ty))
1518 }
1519 BoundSide::Upper
1520 if matches!(previous, typed_ir::Type::Never) || matches!(ty, typed_ir::Type::Never) =>
1521 {
1522 Some(typed_ir::Type::Never)
1523 }
1524 BoundSide::Upper if bound_subtype(previous, ty) => Some(previous.clone()),
1525 BoundSide::Upper if bound_subtype(ty, previous) => Some(ty.clone()),
1526 BoundSide::Upper => cast_order.meet_upper(previous, ty),
1527 }
1528}
1529
1530fn merge_lower_union_bound(
1531 previous: &typed_ir::Type,
1532 ty: &typed_ir::Type,
1533) -> Option<typed_ir::Type> {
1534 if !matches!(previous, typed_ir::Type::Union(_)) && !matches!(ty, typed_ir::Type::Union(_)) {
1535 return None;
1536 }
1537 let mut items = Vec::new();
1538 append_lower_union_items(&mut items, previous);
1539 append_lower_union_items(&mut items, ty);
1540 Some(normalize_bound_form(&typed_ir::Type::Union(items)))
1541}
1542
1543fn append_lower_union_items(items: &mut Vec<typed_ir::Type>, ty: &typed_ir::Type) {
1544 match ty {
1545 typed_ir::Type::Union(branches) => {
1546 for branch in branches {
1547 push_lower_union_item(items, branch.clone());
1548 }
1549 }
1550 ty => push_lower_union_item(items, ty.clone()),
1551 }
1552}
1553
1554fn push_lower_union_item(items: &mut Vec<typed_ir::Type>, item: typed_ir::Type) {
1555 if items
1556 .iter()
1557 .any(|existing| bounds_are_equivalent(existing, &item))
1558 {
1559 return;
1560 }
1561 items.push(item);
1562}
1563
1564fn bound_subtype(sub: &typed_ir::Type, sup: &typed_ir::Type) -> bool {
1565 if lightweight_bounds_equivalent(sub, sup)
1566 || matches!(sub, typed_ir::Type::Never)
1567 || matches!(sup, typed_ir::Type::Any)
1568 || function_bound_subtype(sub, sup)
1569 || row_bound_subtype(sub, sup)
1570 || union_supertype_contains(sup, sub)
1571 {
1572 return true;
1573 }
1574 match (sub, sup) {
1575 (typed_ir::Type::Union(items), _) => items.iter().all(|item| bound_subtype(item, sup)),
1576 (_, typed_ir::Type::Union(items)) => items.iter().any(|item| bound_subtype(sub, item)),
1577 (typed_ir::Type::Inter(items), _) => items.iter().any(|item| bound_subtype(item, sup)),
1578 (_, typed_ir::Type::Inter(items)) => items.iter().all(|item| bound_subtype(sub, item)),
1579 (typed_ir::Type::Recursive { var, body }, _) if !type_body_mentions_var(body, var) => {
1580 bound_subtype(&normalize_bound_form(body), sup)
1581 }
1582 (_, typed_ir::Type::Recursive { var, body }) if !type_body_mentions_var(body, var) => {
1583 bound_subtype(sub, &normalize_bound_form(body))
1584 }
1585 _ => false,
1586 }
1587}
1588
1589fn function_bound_subtype(sub: &typed_ir::Type, sup: &typed_ir::Type) -> bool {
1590 let (
1591 typed_ir::Type::Fun {
1592 param: sub_param,
1593 param_effect: sub_param_effect,
1594 ret_effect: sub_ret_effect,
1595 ret: sub_ret,
1596 },
1597 typed_ir::Type::Fun {
1598 param: sup_param,
1599 param_effect: sup_param_effect,
1600 ret_effect: sup_ret_effect,
1601 ret: sup_ret,
1602 },
1603 ) = (sub, sup)
1604 else {
1605 return false;
1606 };
1607 bound_subtype(sup_param, sub_param)
1608 && bound_subtype(sub_param_effect, sup_param_effect)
1609 && bound_subtype(sub_ret_effect, sup_ret_effect)
1610 && bound_subtype(sub_ret, sup_ret)
1611}
1612
1613fn union_supertype_contains(sup: &typed_ir::Type, sub: &typed_ir::Type) -> bool {
1614 let typed_ir::Type::Union(items) = sup else {
1615 return false;
1616 };
1617 items.iter().any(|item| {
1618 !matches!(item, typed_ir::Type::Union(_))
1619 && (lightweight_bounds_equivalent(sub, item) || bound_subtype(sub, item))
1620 })
1621}
1622
1623fn row_bound_subtype(sub: &typed_ir::Type, sup: &typed_ir::Type) -> bool {
1624 let Some((sub_items, sub_tail)) = flatten_closed_row(sub) else {
1625 return false;
1626 };
1627 let Some((sup_items, sup_tail)) = flatten_closed_row(sup) else {
1628 return false;
1629 };
1630 let residual = split_row_items(&sub_items, &sup_items);
1631 let items_covered =
1632 residual.unmatched_left.is_empty() || matches!(sup_tail, typed_ir::Type::Any);
1633 items_covered && row_tail_subtype(&sub_tail, &sup_tail)
1634}
1635
1636fn row_tail_subtype(sub: &typed_ir::Type, sup: &typed_ir::Type) -> bool {
1637 sub == sup || matches!(sub, typed_ir::Type::Never) || matches!(sup, typed_ir::Type::Any)
1638}
1639
1640fn lightweight_bounds_equivalent(left: &typed_ir::Type, right: &typed_ir::Type) -> bool {
1641 left == right || core_type_is_unit_value(left) && core_type_is_unit_value(right)
1642}
1643
1644fn merge_unknown_bounds(previous: &typed_ir::Type, ty: &typed_ir::Type) -> Option<typed_ir::Type> {
1645 if !type_contains_unknown(previous) && !type_contains_unknown(ty) {
1646 return None;
1647 }
1648 merge_unknown_bounds_inner(previous, ty)
1649}
1650
1651fn merge_unknown_bounds_inner(
1652 previous: &typed_ir::Type,
1653 ty: &typed_ir::Type,
1654) -> Option<typed_ir::Type> {
1655 match (previous, ty) {
1656 (typed_ir::Type::Unknown, _) => Some(ty.clone()),
1657 (_, typed_ir::Type::Unknown) => Some(previous.clone()),
1658 (
1659 typed_ir::Type::Named {
1660 path: previous_path,
1661 args: previous_args,
1662 },
1663 typed_ir::Type::Named { path, args },
1664 ) if previous_path == path && previous_args.len() == args.len() => {
1665 Some(typed_ir::Type::Named {
1666 path: path.clone(),
1667 args: previous_args
1668 .iter()
1669 .zip(args)
1670 .map(|(previous, arg)| merge_unknown_type_arg_bounds(previous, arg))
1671 .collect::<Option<Vec<_>>>()?,
1672 })
1673 }
1674 (
1675 typed_ir::Type::Fun {
1676 param: previous_param,
1677 param_effect: previous_param_effect,
1678 ret_effect: previous_ret_effect,
1679 ret: previous_ret,
1680 },
1681 typed_ir::Type::Fun {
1682 param,
1683 param_effect,
1684 ret_effect,
1685 ret,
1686 },
1687 ) => Some(typed_ir::Type::Fun {
1688 param: Box::new(merge_unknown_bounds_inner(previous_param, param)?),
1689 param_effect: Box::new(merge_unknown_bounds_inner(
1690 previous_param_effect,
1691 param_effect,
1692 )?),
1693 ret_effect: Box::new(merge_unknown_bounds_inner(previous_ret_effect, ret_effect)?),
1694 ret: Box::new(merge_unknown_bounds_inner(previous_ret, ret)?),
1695 }),
1696 (typed_ir::Type::Tuple(previous), typed_ir::Type::Tuple(items))
1697 if previous.len() == items.len() =>
1698 {
1699 Some(typed_ir::Type::Tuple(
1700 previous
1701 .iter()
1702 .zip(items)
1703 .map(|(previous, item)| merge_unknown_bounds_inner(previous, item))
1704 .collect::<Option<Vec<_>>>()?,
1705 ))
1706 }
1707 (
1708 typed_ir::Type::Row {
1709 items: previous_items,
1710 tail: previous_tail,
1711 },
1712 typed_ir::Type::Row { items, tail },
1713 ) if previous_items.len() == items.len() => Some(typed_ir::Type::Row {
1714 items: previous_items
1715 .iter()
1716 .zip(items)
1717 .map(|(previous, item)| merge_unknown_bounds_inner(previous, item))
1718 .collect::<Option<Vec<_>>>()?,
1719 tail: Box::new(merge_unknown_bounds_inner(previous_tail, tail)?),
1720 }),
1721 _ if previous == ty => Some(previous.clone()),
1722 _ => None,
1723 }
1724}
1725
1726fn merge_unknown_type_arg_bounds(
1727 previous: &typed_ir::TypeArg,
1728 arg: &typed_ir::TypeArg,
1729) -> Option<typed_ir::TypeArg> {
1730 match (previous, arg) {
1731 (typed_ir::TypeArg::Type(previous), typed_ir::TypeArg::Type(arg)) => Some(
1732 typed_ir::TypeArg::Type(merge_unknown_bounds_inner(previous, arg)?),
1733 ),
1734 (typed_ir::TypeArg::Bounds(previous), typed_ir::TypeArg::Bounds(arg)) => {
1735 Some(typed_ir::TypeArg::Bounds(typed_ir::TypeBounds {
1736 lower: merge_optional_unknown_bound(
1737 previous.lower.as_deref(),
1738 arg.lower.as_deref(),
1739 )?,
1740 upper: merge_optional_unknown_bound(
1741 previous.upper.as_deref(),
1742 arg.upper.as_deref(),
1743 )?,
1744 }))
1745 }
1746 _ => None,
1747 }
1748}
1749
1750fn merge_optional_unknown_bound(
1751 previous: Option<&typed_ir::Type>,
1752 ty: Option<&typed_ir::Type>,
1753) -> Option<Option<Box<typed_ir::Type>>> {
1754 match (previous, ty) {
1755 (Some(previous), Some(ty)) => {
1756 Some(Some(Box::new(merge_unknown_bounds_inner(previous, ty)?)))
1757 }
1758 (Some(previous), None) => Some(Some(Box::new(previous.clone()))),
1759 (None, Some(ty)) => Some(Some(Box::new(ty.clone()))),
1760 (None, None) => Some(None),
1761 }
1762}
1763
1764fn type_contains_var(ty: &typed_ir::Type) -> bool {
1765 match ty {
1766 typed_ir::Type::Var(_) => true,
1767 typed_ir::Type::Fun {
1768 param,
1769 param_effect,
1770 ret_effect,
1771 ret,
1772 } => {
1773 type_contains_var(param)
1774 || type_contains_var(param_effect)
1775 || type_contains_var(ret_effect)
1776 || type_contains_var(ret)
1777 }
1778 typed_ir::Type::Named { args, .. } => args.iter().any(type_arg_contains_var),
1779 typed_ir::Type::Tuple(items)
1780 | typed_ir::Type::Union(items)
1781 | typed_ir::Type::Inter(items) => items.iter().any(type_contains_var),
1782 typed_ir::Type::Row { items, tail } => {
1783 items.iter().any(type_contains_var) || type_contains_var(tail)
1784 }
1785 typed_ir::Type::Record(record) => {
1786 record
1787 .fields
1788 .iter()
1789 .any(|field| type_contains_var(&field.value))
1790 || record.spread.as_ref().is_some_and(|spread| match spread {
1791 typed_ir::RecordSpread::Head(ty) | typed_ir::RecordSpread::Tail(ty) => {
1792 type_contains_var(ty)
1793 }
1794 })
1795 }
1796 typed_ir::Type::Variant(variant) => {
1797 variant
1798 .cases
1799 .iter()
1800 .any(|case| case.payloads.iter().any(type_contains_var))
1801 || variant
1802 .tail
1803 .as_ref()
1804 .is_some_and(|tail| type_contains_var(tail))
1805 }
1806 typed_ir::Type::Recursive { body, .. } => type_contains_var(body),
1807 typed_ir::Type::Unknown | typed_ir::Type::Never | typed_ir::Type::Any => false,
1808 }
1809}
1810
1811fn type_arg_contains_var(arg: &typed_ir::TypeArg) -> bool {
1812 match arg {
1813 typed_ir::TypeArg::Type(ty) => type_contains_var(ty),
1814 typed_ir::TypeArg::Bounds(bounds) => {
1815 bounds
1816 .lower
1817 .as_ref()
1818 .is_some_and(|ty| type_contains_var(ty))
1819 || bounds
1820 .upper
1821 .as_ref()
1822 .is_some_and(|ty| type_contains_var(ty))
1823 }
1824 }
1825}
1826
1827fn type_body_mentions_var(ty: &typed_ir::Type, target: &typed_ir::TypeVar) -> bool {
1828 match ty {
1829 typed_ir::Type::Var(var) => var == target,
1830 typed_ir::Type::Fun {
1831 param,
1832 param_effect,
1833 ret_effect,
1834 ret,
1835 } => {
1836 type_body_mentions_var(param, target)
1837 || type_body_mentions_var(param_effect, target)
1838 || type_body_mentions_var(ret_effect, target)
1839 || type_body_mentions_var(ret, target)
1840 }
1841 typed_ir::Type::Named { args, .. } => args
1842 .iter()
1843 .any(|arg| type_arg_body_mentions_var(arg, target)),
1844 typed_ir::Type::Tuple(items)
1845 | typed_ir::Type::Union(items)
1846 | typed_ir::Type::Inter(items) => items
1847 .iter()
1848 .any(|item| type_body_mentions_var(item, target)),
1849 typed_ir::Type::Row { items, tail } => {
1850 items
1851 .iter()
1852 .any(|item| type_body_mentions_var(item, target))
1853 || type_body_mentions_var(tail, target)
1854 }
1855 typed_ir::Type::Record(record) => {
1856 record
1857 .fields
1858 .iter()
1859 .any(|field| type_body_mentions_var(&field.value, target))
1860 || record.spread.as_ref().is_some_and(|spread| match spread {
1861 typed_ir::RecordSpread::Head(ty) | typed_ir::RecordSpread::Tail(ty) => {
1862 type_body_mentions_var(ty, target)
1863 }
1864 })
1865 }
1866 typed_ir::Type::Variant(variant) => {
1867 variant.cases.iter().any(|case| {
1868 case.payloads
1869 .iter()
1870 .any(|ty| type_body_mentions_var(ty, target))
1871 }) || variant
1872 .tail
1873 .as_ref()
1874 .is_some_and(|tail| type_body_mentions_var(tail, target))
1875 }
1876 typed_ir::Type::Recursive { body, .. } => type_body_mentions_var(body, target),
1877 typed_ir::Type::Unknown | typed_ir::Type::Never | typed_ir::Type::Any => false,
1878 }
1879}
1880
1881fn type_arg_body_mentions_var(arg: &typed_ir::TypeArg, target: &typed_ir::TypeVar) -> bool {
1882 match arg {
1883 typed_ir::TypeArg::Type(ty) => type_body_mentions_var(ty, target),
1884 typed_ir::TypeArg::Bounds(bounds) => {
1885 bounds
1886 .lower
1887 .as_ref()
1888 .is_some_and(|ty| type_body_mentions_var(ty, target))
1889 || bounds
1890 .upper
1891 .as_ref()
1892 .is_some_and(|ty| type_body_mentions_var(ty, target))
1893 }
1894 }
1895}
1896
1897fn bounds_are_equivalent(left: &typed_ir::Type, right: &typed_ir::Type) -> bool {
1898 left == right
1899 || core_type_is_unit_value(left) && core_type_is_unit_value(right)
1900 || closed_singleton_row_item(left).is_some_and(|item| item == right)
1901 || closed_singleton_row_item(right).is_some_and(|item| item == left)
1902 || normalize_bound_form(left) == normalize_bound_form(right)
1903}
1904
1905fn core_type_is_unit_value(ty: &typed_ir::Type) -> bool {
1906 match ty {
1907 typed_ir::Type::Tuple(items) => items.is_empty(),
1908 typed_ir::Type::Named { path, args } => {
1909 args.is_empty()
1910 && path
1911 .segments
1912 .last()
1913 .is_some_and(|segment| segment.0 == "unit")
1914 }
1915 _ => false,
1916 }
1917}
1918
1919fn normalize_bound_form(ty: &typed_ir::Type) -> typed_ir::Type {
1920 normalize_bound_form_inner(ty, false)
1921}
1922
1923fn normalize_bound_form_inner(ty: &typed_ir::Type, effect_atom: bool) -> typed_ir::Type {
1924 match ty {
1925 typed_ir::Type::Named { path, args } => typed_ir::Type::Named {
1926 path: path.clone(),
1927 args: if effect_atom && args.iter().any(type_arg_contains_unknown) {
1928 Vec::new()
1929 } else {
1930 args.iter()
1931 .map(|arg| normalize_bound_arg_form(arg, effect_atom))
1932 .collect()
1933 },
1934 },
1935 typed_ir::Type::Fun {
1936 param,
1937 param_effect,
1938 ret_effect,
1939 ret,
1940 } => typed_ir::Type::Fun {
1941 param: Box::new(normalize_bound_form_inner(param, false)),
1942 param_effect: Box::new(normalize_bound_form_inner(param_effect, false)),
1943 ret_effect: Box::new(normalize_bound_form_inner(ret_effect, false)),
1944 ret: Box::new(normalize_bound_form_inner(ret, false)),
1945 },
1946 typed_ir::Type::Tuple(items) => typed_ir::Type::Tuple(
1947 items
1948 .iter()
1949 .map(|item| normalize_bound_form_inner(item, false))
1950 .collect(),
1951 ),
1952 typed_ir::Type::Union(items) => {
1953 let mut normalized = Vec::new();
1954 for item in items
1955 .iter()
1956 .map(|item| normalize_bound_form_inner(item, false))
1957 {
1958 if matches!(item, typed_ir::Type::Any) {
1959 return typed_ir::Type::Any;
1960 }
1961 if matches!(item, typed_ir::Type::Never) {
1962 continue;
1963 }
1964 match item {
1965 typed_ir::Type::Union(items) => {
1966 for item in items {
1967 push_normalized_union_item(&mut normalized, item);
1968 }
1969 }
1970 item => {
1971 push_normalized_union_item(&mut normalized, item);
1972 }
1973 }
1974 }
1975 match normalized.len() {
1976 0 => typed_ir::Type::Never,
1977 1 => normalized.pop().unwrap(),
1978 _ => typed_ir::Type::Union(normalized),
1979 }
1980 }
1981 typed_ir::Type::Inter(items) => {
1982 let mut normalized = Vec::new();
1983 for item in items
1984 .iter()
1985 .map(|item| normalize_bound_form_inner(item, false))
1986 {
1987 if matches!(item, typed_ir::Type::Any) {
1988 continue;
1989 }
1990 if matches!(item, typed_ir::Type::Never) {
1991 return typed_ir::Type::Never;
1992 }
1993 match item {
1994 typed_ir::Type::Inter(items) => {
1995 for item in items {
1996 push_normalized_inter_item(&mut normalized, item);
1997 }
1998 }
1999 item => {
2000 push_normalized_inter_item(&mut normalized, item);
2001 }
2002 }
2003 }
2004 if let Some(row) = intersect_closed_rows(&normalized) {
2005 return row;
2006 }
2007 match normalized.len() {
2008 0 => typed_ir::Type::Any,
2009 1 => normalized.pop().unwrap(),
2010 _ => typed_ir::Type::Inter(normalized),
2011 }
2012 }
2013 typed_ir::Type::Row { items, tail } => {
2014 let (raw_items, raw_tail) = match flatten_closed_row(ty) {
2018 Some(flat) => flat,
2019 None => (items.clone(), (**tail).clone()),
2020 };
2021 let mut normalized_items = Vec::new();
2022 for item in raw_items
2023 .into_iter()
2024 .map(|item| normalize_bound_form_inner(&item, true))
2025 {
2026 push_unique_effect_item(&mut normalized_items, item);
2027 }
2028 normalized_items.sort_by_key(|item| format!("{item:?}"));
2031 let tail = normalize_bound_form_inner(&raw_tail, false);
2032 if normalized_items.is_empty() {
2033 tail
2034 } else {
2035 typed_ir::Type::Row {
2036 items: normalized_items,
2037 tail: Box::new(tail),
2038 }
2039 }
2040 }
2041 typed_ir::Type::Record(record) => typed_ir::Type::Record(typed_ir::RecordType {
2042 fields: record
2043 .fields
2044 .iter()
2045 .map(|field| typed_ir::RecordField {
2046 name: field.name.clone(),
2047 value: normalize_bound_form_inner(&field.value, false),
2048 optional: field.optional,
2049 })
2050 .collect(),
2051 spread: record.spread.as_ref().map(|spread| match spread {
2052 typed_ir::RecordSpread::Head(ty) => {
2053 typed_ir::RecordSpread::Head(Box::new(normalize_bound_form_inner(ty, false)))
2054 }
2055 typed_ir::RecordSpread::Tail(ty) => {
2056 typed_ir::RecordSpread::Tail(Box::new(normalize_bound_form_inner(ty, false)))
2057 }
2058 }),
2059 }),
2060 typed_ir::Type::Variant(variant) => typed_ir::Type::Variant(typed_ir::VariantType {
2061 cases: variant
2062 .cases
2063 .iter()
2064 .map(|case| typed_ir::VariantCase {
2065 name: case.name.clone(),
2066 payloads: case
2067 .payloads
2068 .iter()
2069 .map(|payload| normalize_bound_form_inner(payload, false))
2070 .collect(),
2071 })
2072 .collect(),
2073 tail: variant
2074 .tail
2075 .as_ref()
2076 .map(|tail| Box::new(normalize_bound_form_inner(tail, false))),
2077 }),
2078 typed_ir::Type::Recursive { var, body }
2079 if effect_atom && !type_body_mentions_var(body, var) =>
2080 {
2081 normalize_bound_form_inner(body, effect_atom)
2082 }
2083 typed_ir::Type::Recursive { var, body } => typed_ir::Type::Recursive {
2084 var: var.clone(),
2085 body: Box::new(normalize_bound_form_inner(body, effect_atom)),
2086 },
2087 typed_ir::Type::Var(_)
2088 | typed_ir::Type::Unknown
2089 | typed_ir::Type::Never
2090 | typed_ir::Type::Any => ty.clone(),
2091 }
2092}
2093
2094fn normalize_bound_arg_form(arg: &typed_ir::TypeArg, effect_atom: bool) -> typed_ir::TypeArg {
2095 match arg {
2096 typed_ir::TypeArg::Type(ty) => {
2097 typed_ir::TypeArg::Type(normalize_bound_form_inner(ty, effect_atom))
2098 }
2099 typed_ir::TypeArg::Bounds(bounds) => {
2100 if let Some(lower) = bounds
2101 .lower
2102 .as_deref()
2103 .filter(|lower| !matches!(lower, typed_ir::Type::Never))
2104 {
2105 return typed_ir::TypeArg::Type(normalize_bound_form_inner(lower, effect_atom));
2106 }
2107 if let Some(upper) = bounds
2108 .upper
2109 .as_deref()
2110 .filter(|upper| !matches!(upper, typed_ir::Type::Any))
2111 {
2112 return typed_ir::TypeArg::Type(normalize_bound_form_inner(upper, effect_atom));
2113 }
2114 typed_ir::TypeArg::Bounds(typed_ir::TypeBounds {
2115 lower: bounds
2116 .lower
2117 .as_ref()
2118 .map(|lower| Box::new(normalize_bound_form_inner(lower, effect_atom))),
2119 upper: bounds
2120 .upper
2121 .as_ref()
2122 .map(|upper| Box::new(normalize_bound_form_inner(upper, effect_atom))),
2123 })
2124 }
2125 }
2126}
2127
2128fn push_normalized_union_item(items: &mut Vec<typed_ir::Type>, item: typed_ir::Type) {
2129 if matches!(item, typed_ir::Type::Never) {
2130 return;
2131 }
2132 if matches!(item, typed_ir::Type::Any) {
2133 items.clear();
2134 items.push(item);
2135 return;
2136 }
2137 if items
2138 .iter()
2139 .any(|existing| absorption_subtype(&item, existing))
2140 {
2141 return;
2142 }
2143 items.retain(|existing| !absorption_subtype(existing, &item));
2144 items.push(item);
2145}
2146
2147fn push_normalized_inter_item(items: &mut Vec<typed_ir::Type>, item: typed_ir::Type) {
2148 if matches!(item, typed_ir::Type::Any) {
2149 return;
2150 }
2151 if matches!(item, typed_ir::Type::Never) {
2152 items.clear();
2153 items.push(item);
2154 return;
2155 }
2156 if items
2157 .iter()
2158 .any(|existing| absorption_subtype(existing, &item))
2159 {
2160 return;
2161 }
2162 items.retain(|existing| !absorption_subtype(&item, existing));
2163 items.push(item);
2164}
2165
2166fn absorption_subtype(sub: &typed_ir::Type, sup: &typed_ir::Type) -> bool {
2167 if lightweight_bounds_equivalent(sub, sup)
2168 || closed_singleton_row_item(sub).is_some_and(|item| item == sup)
2169 || closed_singleton_row_item(sup).is_some_and(|item| item == sub)
2170 || matches!(sub, typed_ir::Type::Never)
2171 || matches!(sup, typed_ir::Type::Any)
2172 {
2173 return true;
2174 }
2175 match (sub, sup) {
2176 (typed_ir::Type::Union(items), _) => items.iter().all(|item| absorption_subtype(item, sup)),
2177 (_, typed_ir::Type::Union(items)) => items.iter().any(|item| absorption_subtype(sub, item)),
2178 (typed_ir::Type::Inter(items), _) => items.iter().any(|item| absorption_subtype(item, sup)),
2179 (_, typed_ir::Type::Inter(items)) => items.iter().all(|item| absorption_subtype(sub, item)),
2180 (
2181 typed_ir::Type::Fun {
2182 param: sub_param,
2183 param_effect: sub_param_effect,
2184 ret_effect: sub_ret_effect,
2185 ret: sub_ret,
2186 },
2187 typed_ir::Type::Fun {
2188 param: sup_param,
2189 param_effect: sup_param_effect,
2190 ret_effect: sup_ret_effect,
2191 ret: sup_ret,
2192 },
2193 ) => {
2194 absorption_subtype(sup_param, sub_param)
2195 && absorption_subtype(sub_param_effect, sup_param_effect)
2196 && absorption_subtype(sub_ret_effect, sup_ret_effect)
2197 && absorption_subtype(sub_ret, sup_ret)
2198 }
2199 _ => row_bound_subtype(sub, sup),
2200 }
2201}
2202
2203fn type_arg_contains_unknown(arg: &typed_ir::TypeArg) -> bool {
2204 match arg {
2205 typed_ir::TypeArg::Type(ty) => type_contains_unknown(ty),
2206 typed_ir::TypeArg::Bounds(bounds) => {
2207 bounds
2208 .lower
2209 .as_ref()
2210 .is_some_and(|ty| type_contains_unknown(ty))
2211 || bounds
2212 .upper
2213 .as_ref()
2214 .is_some_and(|ty| type_contains_unknown(ty))
2215 }
2216 }
2217}
2218
2219fn type_contains_unknown(ty: &typed_ir::Type) -> bool {
2220 match ty {
2221 typed_ir::Type::Unknown => true,
2222 typed_ir::Type::Fun {
2223 param,
2224 param_effect,
2225 ret_effect,
2226 ret,
2227 } => {
2228 type_contains_unknown(param)
2229 || type_contains_unknown(param_effect)
2230 || type_contains_unknown(ret_effect)
2231 || type_contains_unknown(ret)
2232 }
2233 typed_ir::Type::Named { args, .. } => args.iter().any(type_arg_contains_unknown),
2234 typed_ir::Type::Tuple(items)
2235 | typed_ir::Type::Union(items)
2236 | typed_ir::Type::Inter(items) => items.iter().any(type_contains_unknown),
2237 typed_ir::Type::Row { items, tail } => {
2238 items.iter().any(type_contains_unknown) || type_contains_unknown(tail)
2239 }
2240 typed_ir::Type::Record(record) => {
2241 record
2242 .fields
2243 .iter()
2244 .any(|field| type_contains_unknown(&field.value))
2245 || record.spread.as_ref().is_some_and(|spread| match spread {
2246 typed_ir::RecordSpread::Head(ty) | typed_ir::RecordSpread::Tail(ty) => {
2247 type_contains_unknown(ty)
2248 }
2249 })
2250 }
2251 typed_ir::Type::Variant(variant) => {
2252 variant
2253 .cases
2254 .iter()
2255 .any(|case| case.payloads.iter().any(type_contains_unknown))
2256 || variant
2257 .tail
2258 .as_ref()
2259 .is_some_and(|tail| type_contains_unknown(tail))
2260 }
2261 typed_ir::Type::Recursive { body, .. } => type_contains_unknown(body),
2262 typed_ir::Type::Var(_) | typed_ir::Type::Never | typed_ir::Type::Any => false,
2263 }
2264}
2265
2266fn closed_singleton_row_item(ty: &typed_ir::Type) -> Option<&typed_ir::Type> {
2267 let typed_ir::Type::Row { items, tail } = ty else {
2268 return None;
2269 };
2270 if items.len() == 1 && matches!(tail.as_ref(), typed_ir::Type::Never) {
2271 items.first()
2272 } else {
2273 None
2274 }
2275}
2276
2277fn is_vacuous_bound(ty: &typed_ir::Type, side: BoundSide) -> bool {
2278 matches!(
2279 (side, ty),
2280 (BoundSide::Lower, typed_ir::Type::Never) | (BoundSide::Upper, typed_ir::Type::Any)
2281 )
2282}
2283
2284struct RowResidual<'a> {
2285 matched: Vec<(&'a typed_ir::Type, &'a typed_ir::Type)>,
2286 unmatched_left: Vec<typed_ir::Type>,
2287 unmatched_right: Vec<typed_ir::Type>,
2288}
2289
2290#[derive(Default)]
2291struct TypePositionVars {
2292 value: BTreeSet<typed_ir::TypeVar>,
2293 effect: BTreeSet<typed_ir::TypeVar>,
2294}
2295
2296#[derive(Clone, Copy)]
2297enum TypePosition {
2298 Value,
2299 Effect,
2300}
2301
2302fn collect_type_position_vars(
2303 ty: &typed_ir::Type,
2304 position: TypePosition,
2305 params: &BTreeSet<typed_ir::TypeVar>,
2306 vars: &mut TypePositionVars,
2307) {
2308 match ty {
2309 typed_ir::Type::Var(var) if params.contains(var) => match position {
2310 TypePosition::Value => {
2311 vars.value.insert(var.clone());
2312 }
2313 TypePosition::Effect => {
2314 vars.effect.insert(var.clone());
2315 }
2316 },
2317 typed_ir::Type::Var(_)
2318 | typed_ir::Type::Unknown
2319 | typed_ir::Type::Never
2320 | typed_ir::Type::Any => {}
2321 typed_ir::Type::Fun {
2322 param,
2323 param_effect,
2324 ret_effect,
2325 ret,
2326 } => {
2327 collect_type_position_vars(param, TypePosition::Value, params, vars);
2328 collect_type_position_vars(param_effect, TypePosition::Effect, params, vars);
2329 collect_type_position_vars(ret_effect, TypePosition::Effect, params, vars);
2330 collect_type_position_vars(ret, TypePosition::Value, params, vars);
2331 }
2332 typed_ir::Type::Named { args, .. } => {
2333 for arg in args {
2334 collect_type_arg_position_vars(arg, params, vars);
2335 }
2336 }
2337 typed_ir::Type::Tuple(items)
2338 | typed_ir::Type::Union(items)
2339 | typed_ir::Type::Inter(items) => {
2340 for item in items {
2341 collect_type_position_vars(item, position, params, vars);
2342 }
2343 }
2344 typed_ir::Type::Row { items, tail } => {
2345 for item in items {
2346 collect_effect_item_position_vars(item, params, vars);
2347 }
2348 collect_type_position_vars(tail, TypePosition::Effect, params, vars);
2349 }
2350 typed_ir::Type::Record(record) => {
2351 for field in &record.fields {
2352 collect_type_position_vars(&field.value, TypePosition::Value, params, vars);
2353 }
2354 if let Some(spread) = &record.spread {
2355 collect_record_spread_position_vars(spread, params, vars);
2356 }
2357 }
2358 typed_ir::Type::Variant(variant) => {
2359 for case in &variant.cases {
2360 for payload in &case.payloads {
2361 collect_type_position_vars(payload, TypePosition::Value, params, vars);
2362 }
2363 }
2364 if let Some(tail) = &variant.tail {
2365 collect_type_position_vars(tail, TypePosition::Value, params, vars);
2366 }
2367 }
2368 typed_ir::Type::Recursive { body, .. } => {
2369 collect_type_position_vars(body, position, params, vars);
2370 }
2371 }
2372}
2373
2374fn collect_effect_item_position_vars(
2375 ty: &typed_ir::Type,
2376 params: &BTreeSet<typed_ir::TypeVar>,
2377 vars: &mut TypePositionVars,
2378) {
2379 match ty {
2380 typed_ir::Type::Named { args, .. } => {
2381 for arg in args {
2382 collect_type_arg_position_vars(arg, params, vars);
2383 }
2384 }
2385 other => collect_type_position_vars(other, TypePosition::Effect, params, vars),
2386 }
2387}
2388
2389fn collect_type_arg_position_vars(
2390 arg: &typed_ir::TypeArg,
2391 params: &BTreeSet<typed_ir::TypeVar>,
2392 vars: &mut TypePositionVars,
2393) {
2394 match arg {
2395 typed_ir::TypeArg::Type(ty) => {
2396 collect_type_position_vars(ty, TypePosition::Value, params, vars);
2397 }
2398 typed_ir::TypeArg::Bounds(bounds) => {
2399 if let Some(lower) = bounds.lower.as_deref() {
2400 collect_type_position_vars(lower, TypePosition::Value, params, vars);
2401 }
2402 if let Some(upper) = bounds.upper.as_deref() {
2403 collect_type_position_vars(upper, TypePosition::Value, params, vars);
2404 }
2405 }
2406 }
2407}
2408
2409fn collect_record_spread_position_vars(
2410 spread: &typed_ir::RecordSpread,
2411 params: &BTreeSet<typed_ir::TypeVar>,
2412 vars: &mut TypePositionVars,
2413) {
2414 match spread {
2415 typed_ir::RecordSpread::Head(ty) | typed_ir::RecordSpread::Tail(ty) => {
2416 collect_type_position_vars(ty, TypePosition::Value, params, vars);
2417 }
2418 }
2419}
2420
2421fn split_row_items<'a>(
2422 left_items: &'a [typed_ir::Type],
2423 right_items: &'a [typed_ir::Type],
2424) -> RowResidual<'a> {
2425 let mut matched_right = vec![false; right_items.len()];
2426 let mut matched = Vec::new();
2427 let mut unmatched_left = Vec::new();
2428
2429 for left in left_items {
2430 let Some((index, right)) = right_items
2431 .iter()
2432 .enumerate()
2433 .find(|(index, right)| !matched_right[*index] && same_effect_head(left, right))
2434 else {
2435 unmatched_left.push(left.clone());
2436 continue;
2437 };
2438 matched_right[index] = true;
2439 matched.push((left, right));
2440 }
2441
2442 let unmatched_right = right_items
2443 .iter()
2444 .enumerate()
2445 .filter_map(|(index, right)| (!matched_right[index]).then_some(right.clone()))
2446 .collect();
2447
2448 RowResidual {
2449 matched,
2450 unmatched_left,
2451 unmatched_right,
2452 }
2453}
2454
2455fn find_variant_case<'a>(
2456 variant: &'a typed_ir::VariantType,
2457 name: &typed_ir::Name,
2458) -> Option<&'a typed_ir::VariantCase> {
2459 variant.cases.iter().find(|case| &case.name == name)
2460}
2461
2462fn same_effect_head(left: &typed_ir::Type, right: &typed_ir::Type) -> bool {
2463 match (left, right) {
2464 (
2465 typed_ir::Type::Named { path, .. },
2466 typed_ir::Type::Named {
2467 path: actual_path, ..
2468 },
2469 ) => effect_paths_match(path, actual_path),
2470 _ => false,
2471 }
2472}
2473
2474fn effect_paths_match(left: &typed_ir::Path, right: &typed_ir::Path) -> bool {
2475 left == right
2476 || qualified_prefix_effect_paths_match(left, right)
2477 || qualified_prefix_effect_paths_match(right, left)
2478}
2479
2480fn qualified_prefix_effect_paths_match(parent: &typed_ir::Path, child: &typed_ir::Path) -> bool {
2481 effect_path_can_match_child_prefix(parent)
2482 && child.segments.len() > parent.segments.len()
2483 && child.segments.starts_with(parent.segments.as_slice())
2484}
2485
2486fn effect_path_can_match_child_prefix(path: &typed_ir::Path) -> bool {
2487 path.segments.len() > 1
2488 || path.segments.first().is_some_and(|name| {
2489 name.0.starts_with('#') || name.0.starts_with('&') && name.0.contains('#')
2490 })
2491}
2492
2493fn effect_row_from_items_and_tail(
2494 items: Vec<typed_ir::Type>,
2495 tail: typed_ir::Type,
2496) -> typed_ir::Type {
2497 if items.is_empty() {
2498 return tail;
2499 }
2500 typed_ir::Type::Row {
2501 items,
2502 tail: Box::new(tail),
2503 }
2504}
2505
2506fn rename_type(
2507 ty: &typed_ir::Type,
2508 renames: &BTreeMap<typed_ir::TypeVar, typed_ir::TypeVar>,
2509) -> typed_ir::Type {
2510 match ty {
2511 typed_ir::Type::Var(var) => renames
2512 .get(var)
2513 .cloned()
2514 .map(typed_ir::Type::Var)
2515 .unwrap_or_else(|| typed_ir::Type::Var(var.clone())),
2516 typed_ir::Type::Fun {
2517 param,
2518 param_effect,
2519 ret_effect,
2520 ret,
2521 } => typed_ir::Type::Fun {
2522 param: Box::new(rename_type(param, renames)),
2523 param_effect: Box::new(rename_type(param_effect, renames)),
2524 ret_effect: Box::new(rename_type(ret_effect, renames)),
2525 ret: Box::new(rename_type(ret, renames)),
2526 },
2527 typed_ir::Type::Tuple(items) => typed_ir::Type::Tuple(
2528 items
2529 .iter()
2530 .map(|item| rename_type(item, renames))
2531 .collect(),
2532 ),
2533 typed_ir::Type::Named { path, args } => typed_ir::Type::Named {
2534 path: path.clone(),
2535 args: args
2536 .iter()
2537 .map(|arg| rename_type_arg(arg, renames))
2538 .collect(),
2539 },
2540 typed_ir::Type::Row { items, tail } => typed_ir::Type::Row {
2541 items: items
2542 .iter()
2543 .map(|item| rename_type(item, renames))
2544 .collect(),
2545 tail: Box::new(rename_type(tail, renames)),
2546 },
2547 typed_ir::Type::Union(items) => typed_ir::Type::Union(
2548 items
2549 .iter()
2550 .map(|item| rename_type(item, renames))
2551 .collect(),
2552 ),
2553 typed_ir::Type::Inter(items) => typed_ir::Type::Inter(
2554 items
2555 .iter()
2556 .map(|item| rename_type(item, renames))
2557 .collect(),
2558 ),
2559 typed_ir::Type::Record(record) => typed_ir::Type::Record(typed_ir::RecordType {
2560 fields: record
2561 .fields
2562 .iter()
2563 .map(|field| typed_ir::RecordField {
2564 name: field.name.clone(),
2565 value: rename_type(&field.value, renames),
2566 optional: field.optional,
2567 })
2568 .collect(),
2569 spread: record
2570 .spread
2571 .as_ref()
2572 .map(|spread| rename_record_spread(spread, renames)),
2573 }),
2574 typed_ir::Type::Variant(variant) => typed_ir::Type::Variant(typed_ir::VariantType {
2575 cases: variant
2576 .cases
2577 .iter()
2578 .map(|case| typed_ir::VariantCase {
2579 name: case.name.clone(),
2580 payloads: case
2581 .payloads
2582 .iter()
2583 .map(|payload| rename_type(payload, renames))
2584 .collect(),
2585 })
2586 .collect(),
2587 tail: variant
2588 .tail
2589 .as_ref()
2590 .map(|tail| Box::new(rename_type(tail, renames))),
2591 }),
2592 typed_ir::Type::Recursive { var, body } => typed_ir::Type::Recursive {
2593 var: renames.get(var).cloned().unwrap_or_else(|| var.clone()),
2594 body: Box::new(rename_type(body, renames)),
2595 },
2596 typed_ir::Type::Unknown => typed_ir::Type::Unknown,
2597 typed_ir::Type::Never => typed_ir::Type::Never,
2598 typed_ir::Type::Any => typed_ir::Type::Any,
2599 }
2600}
2601
2602fn rename_type_arg(
2603 arg: &typed_ir::TypeArg,
2604 renames: &BTreeMap<typed_ir::TypeVar, typed_ir::TypeVar>,
2605) -> typed_ir::TypeArg {
2606 match arg {
2607 typed_ir::TypeArg::Type(ty) => typed_ir::TypeArg::Type(rename_type(ty, renames)),
2608 typed_ir::TypeArg::Bounds(bounds) => typed_ir::TypeArg::Bounds(typed_ir::TypeBounds {
2609 lower: bounds
2610 .lower
2611 .as_ref()
2612 .map(|ty| Box::new(rename_type(ty, renames))),
2613 upper: bounds
2614 .upper
2615 .as_ref()
2616 .map(|ty| Box::new(rename_type(ty, renames))),
2617 }),
2618 }
2619}
2620
2621fn rename_record_spread(
2622 spread: &typed_ir::RecordSpread,
2623 renames: &BTreeMap<typed_ir::TypeVar, typed_ir::TypeVar>,
2624) -> typed_ir::RecordSpread {
2625 match spread {
2626 typed_ir::RecordSpread::Head(ty) => {
2627 typed_ir::RecordSpread::Head(Box::new(rename_type(ty, renames)))
2628 }
2629 typed_ir::RecordSpread::Tail(ty) => {
2630 typed_ir::RecordSpread::Tail(Box::new(rename_type(ty, renames)))
2631 }
2632 }
2633}
2634
2635fn materialize_type(
2636 ty: typed_ir::Type,
2637 substitutions: &[typed_ir::TypeSubstitution],
2638) -> typed_ir::Type {
2639 materialize_type_inner(ty, substitutions, &mut Vec::new())
2640}
2641
2642fn materialize_type_inner(
2643 ty: typed_ir::Type,
2644 substitutions: &[typed_ir::TypeSubstitution],
2645 seen: &mut Vec<typed_ir::TypeVar>,
2646) -> typed_ir::Type {
2647 match ty {
2648 typed_ir::Type::Var(var) => {
2649 if seen.contains(&var) {
2650 return typed_ir::Type::Var(var);
2651 }
2652 let Some(substitution) = substitutions
2653 .iter()
2654 .find(|substitution| substitution.var == var)
2655 else {
2656 return typed_ir::Type::Var(var);
2657 };
2658 if matches!(&substitution.ty, typed_ir::Type::Var(next) if next == &var) {
2659 return typed_ir::Type::Var(var);
2660 }
2661 seen.push(var);
2662 let materialized = materialize_type_inner(substitution.ty.clone(), substitutions, seen);
2663 seen.pop();
2664 materialized
2665 }
2666 typed_ir::Type::Fun {
2667 param,
2668 param_effect,
2669 ret_effect,
2670 ret,
2671 } => typed_ir::Type::Fun {
2672 param: Box::new(materialize_type_inner(*param, substitutions, seen)),
2673 param_effect: Box::new(materialize_type_inner(*param_effect, substitutions, seen)),
2674 ret_effect: Box::new(materialize_type_inner(*ret_effect, substitutions, seen)),
2675 ret: Box::new(materialize_type_inner(*ret, substitutions, seen)),
2676 },
2677 typed_ir::Type::Tuple(items) => typed_ir::Type::Tuple(
2678 items
2679 .into_iter()
2680 .map(|item| materialize_type_inner(item, substitutions, seen))
2681 .collect(),
2682 ),
2683 typed_ir::Type::Named { path, args } => typed_ir::Type::Named {
2684 path,
2685 args: args
2686 .into_iter()
2687 .map(|arg| materialize_type_arg(arg, substitutions, seen))
2688 .collect(),
2689 },
2690 typed_ir::Type::Row { items, tail } => typed_ir::Type::Row {
2691 items: items
2692 .into_iter()
2693 .map(|item| materialize_type_inner(item, substitutions, seen))
2694 .collect(),
2695 tail: Box::new(materialize_type_inner(*tail, substitutions, seen)),
2696 },
2697 typed_ir::Type::Union(items) => typed_ir::Type::Union(
2698 items
2699 .into_iter()
2700 .map(|item| materialize_type_inner(item, substitutions, seen))
2701 .collect(),
2702 ),
2703 typed_ir::Type::Inter(items) => typed_ir::Type::Inter(
2704 items
2705 .into_iter()
2706 .map(|item| materialize_type_inner(item, substitutions, seen))
2707 .collect(),
2708 ),
2709 typed_ir::Type::Record(record) => typed_ir::Type::Record(typed_ir::RecordType {
2710 fields: record
2711 .fields
2712 .into_iter()
2713 .map(|field| typed_ir::RecordField {
2714 name: field.name,
2715 value: materialize_type_inner(field.value, substitutions, seen),
2716 optional: field.optional,
2717 })
2718 .collect(),
2719 spread: record
2720 .spread
2721 .map(|spread| materialize_record_spread(spread, substitutions, seen)),
2722 }),
2723 typed_ir::Type::Variant(variant) => typed_ir::Type::Variant(typed_ir::VariantType {
2724 cases: variant
2725 .cases
2726 .into_iter()
2727 .map(|case| typed_ir::VariantCase {
2728 name: case.name,
2729 payloads: case
2730 .payloads
2731 .into_iter()
2732 .map(|payload| materialize_type_inner(payload, substitutions, seen))
2733 .collect(),
2734 })
2735 .collect(),
2736 tail: variant
2737 .tail
2738 .map(|tail| Box::new(materialize_type_inner(*tail, substitutions, seen))),
2739 }),
2740 typed_ir::Type::Recursive { var, body } => typed_ir::Type::Recursive {
2741 var,
2742 body: Box::new(materialize_type_inner(*body, substitutions, seen)),
2743 },
2744 ty => ty,
2745 }
2746}
2747
2748fn materialize_record_spread(
2749 spread: typed_ir::RecordSpread,
2750 substitutions: &[typed_ir::TypeSubstitution],
2751 seen: &mut Vec<typed_ir::TypeVar>,
2752) -> typed_ir::RecordSpread {
2753 match spread {
2754 typed_ir::RecordSpread::Head(ty) => {
2755 typed_ir::RecordSpread::Head(Box::new(materialize_type_inner(*ty, substitutions, seen)))
2756 }
2757 typed_ir::RecordSpread::Tail(ty) => {
2758 typed_ir::RecordSpread::Tail(Box::new(materialize_type_inner(*ty, substitutions, seen)))
2759 }
2760 }
2761}
2762
2763fn materialize_type_arg(
2764 arg: typed_ir::TypeArg,
2765 substitutions: &[typed_ir::TypeSubstitution],
2766 seen: &mut Vec<typed_ir::TypeVar>,
2767) -> typed_ir::TypeArg {
2768 match arg {
2769 typed_ir::TypeArg::Type(ty) => {
2770 typed_ir::TypeArg::Type(materialize_type_inner(ty, substitutions, seen))
2771 }
2772 typed_ir::TypeArg::Bounds(bounds) => typed_ir::TypeArg::Bounds(typed_ir::TypeBounds {
2773 lower: bounds
2774 .lower
2775 .map(|ty| Box::new(materialize_type_inner(*ty, substitutions, seen))),
2776 upper: bounds
2777 .upper
2778 .map(|ty| Box::new(materialize_type_inner(*ty, substitutions, seen))),
2779 }),
2780 }
2781}
2782
2783#[cfg(test)]
2784mod tests {
2785 use super::*;
2786 use yulang_runtime_ir::{Expr, ExprKind};
2787
2788 #[test]
2789 fn principal_type_is_freshened_into_graph() {
2790 let mut graph = TypeGraph::default();
2791 let instance = graph.instantiate_principal(&id_binding());
2792
2793 assert_eq!(instance.original_binding, path(&["id"]));
2794 assert!(matches!(
2795 instance.principal_type,
2796 typed_ir::Type::Fun { .. }
2797 ));
2798 assert_eq!(
2799 instance.type_params,
2800 vec![PrincipalTypeParam {
2801 original: typed_ir::TypeVar("a".into()),
2802 fresh: typed_ir::TypeVar("a#0".into()),
2803 }]
2804 );
2805 assert!(graph.slot(&typed_ir::TypeVar("a#0".into())).is_some());
2806 }
2807
2808 #[test]
2809 fn graph_solves_fresh_principal_var_from_lower_bound() {
2810 let mut graph = TypeGraph::default();
2811 let instance = graph.instantiate_principal(&id_binding());
2812 let typed_ir::Type::Fun { param, ret, .. } = &instance.principal_type else {
2813 panic!("expected function principal");
2814 };
2815
2816 graph
2817 .collect_runtime_lower(param, &RuntimeType::Value(int_type()))
2818 .unwrap();
2819 let solution = graph.solve();
2820
2821 assert_eq!(solution.materialize_core(ret.as_ref().clone()), int_type());
2822 assert_eq!(
2823 instance.original_substitutions(&solution),
2824 vec![typed_ir::TypeSubstitution {
2825 var: typed_ir::TypeVar("a".into()),
2826 ty: int_type(),
2827 }]
2828 );
2829 assert!(solution.is_complete());
2830 }
2831
2832 #[test]
2833 fn subtype_var_upper_chain_propagates_concrete_upper_bound() {
2834 let mut graph = TypeGraph::default();
2835 let value = typed_ir::TypeVar("value".into());
2836 let result = typed_ir::TypeVar("result".into());
2837
2838 graph
2839 .constrain_subtype(
2840 typed_ir::Type::Var(value.clone()),
2841 typed_ir::Type::Var(result.clone()),
2842 )
2843 .unwrap();
2844 graph
2845 .constrain_subtype(typed_ir::Type::Var(result), int_type())
2846 .unwrap();
2847
2848 let solution = graph.solve();
2849
2850 assert_eq!(solution.solution_for(&value), Some(&int_type()));
2851 }
2852
2853 #[test]
2854 fn subtype_function_result_upper_closes_effect_payload_var() {
2855 let mut graph = TypeGraph::default();
2856 let payload = typed_ir::TypeVar("payload".into());
2857 let result = typed_ir::TypeVar("result".into());
2858 let principal = fun_type_with_effects(
2859 typed_ir::Type::Never,
2860 effect_row(vec![
2861 effect_type_arg("sub", typed_ir::Type::Var(payload.clone())),
2862 effect_type("undet"),
2863 ]),
2864 effect_type("undet"),
2865 typed_ir::Type::Var(payload.clone()),
2866 );
2867 let expected = fun_type_with_effects(
2868 typed_ir::Type::Never,
2869 closed_row(&["sub", "undet"]),
2870 effect_type("undet"),
2871 typed_ir::Type::Var(result.clone()),
2872 );
2873
2874 graph.constrain_subtype(principal, expected).unwrap();
2875 graph
2876 .constrain_subtype(typed_ir::Type::Var(result.clone()), int_type())
2877 .unwrap();
2878
2879 let solution = graph.solve();
2880
2881 assert_eq!(solution.solution_for(&payload), Some(&int_type()));
2882 }
2883
2884 #[test]
2885 fn materialize_core_chases_substitution_chains_without_self_loop() {
2886 let source = typed_ir::TypeVar("source".into());
2887 let intermediate = typed_ir::TypeVar("intermediate".into());
2888 let self_ref = typed_ir::TypeVar("self".into());
2889 let substitutions = vec![
2890 typed_ir::TypeSubstitution {
2891 var: source.clone(),
2892 ty: typed_ir::Type::Var(intermediate.clone()),
2893 },
2894 typed_ir::TypeSubstitution {
2895 var: intermediate,
2896 ty: int_type(),
2897 },
2898 typed_ir::TypeSubstitution {
2899 var: self_ref.clone(),
2900 ty: typed_ir::Type::Var(self_ref.clone()),
2901 },
2902 ];
2903
2904 assert_eq!(
2905 materialize_core_type(typed_ir::Type::Var(source), &substitutions),
2906 int_type()
2907 );
2908 assert_eq!(
2909 materialize_core_type(typed_ir::Type::Var(self_ref.clone()), &substitutions),
2910 typed_ir::Type::Var(self_ref)
2911 );
2912 }
2913
2914 #[test]
2915 fn any_lower_bound_conflicts_with_concrete_upper_bound() {
2916 let mut graph = TypeGraph::default();
2917 let var = typed_ir::TypeVar("a".into());
2918
2919 graph
2920 .constrain_subtype(typed_ir::Type::Any, typed_ir::Type::Var(var.clone()))
2921 .unwrap();
2922 let error = graph
2923 .constrain_subtype(typed_ir::Type::Var(var.clone()), int_type())
2924 .unwrap_err();
2925
2926 assert!(matches!(
2927 error,
2928 MonomorphizeDiagnostic::ConflictingBounds {
2929 var: error_var,
2930 previous: typed_ir::Type::Any,
2931 next,
2932 } if error_var == var && next == int_type()
2933 ));
2934 }
2935
2936 #[test]
2937 fn function_runtime_lower_bound_flips_parameter_polarity() {
2938 let mut graph = TypeGraph::default();
2939 let var = typed_ir::TypeVar("a".into());
2940 let template = fun_type(typed_ir::Type::Var(var.clone()), unit_type());
2941 let actual = fun_type(int_type(), unit_type());
2942
2943 graph
2944 .collect_runtime_lower(&template, &RuntimeType::Value(actual))
2945 .unwrap();
2946
2947 let bounds = graph.slot(&var).expect("parameter var should be tracked");
2948 assert_eq!(bounds.lower, None);
2949 assert_eq!(bounds.upper, Some(int_type()));
2950 }
2951
2952 #[test]
2953 fn function_runtime_lower_bound_does_not_turn_top_parameter_into_solution() {
2954 let mut graph = TypeGraph::default();
2955 let var = typed_ir::TypeVar("a".into());
2956 let template = fun_type(typed_ir::Type::Var(var.clone()), unit_type());
2957 let actual = fun_type(typed_ir::Type::Any, unit_type());
2958
2959 graph
2960 .collect_runtime_lower(&template, &RuntimeType::Value(actual))
2961 .unwrap();
2962
2963 assert_eq!(graph.slot(&var).and_then(TypeVarBounds::solution_ref), None);
2964 }
2965
2966 #[test]
2967 fn graph_joins_subtype_lower_bound_into_union_supertype() {
2968 let mut graph = TypeGraph::default();
2969 let var = typed_ir::TypeVar("effect".into());
2970 let last = closed_row(&["last"]);
2971 let sub = closed_row(&["sub"]);
2972 let either = typed_ir::Type::Union(vec![sub, last.clone()]);
2973
2974 graph
2975 .collect_runtime_bounds(
2976 &typed_ir::Type::Var(var.clone()),
2977 &RuntimeBounds {
2978 lower: Some(RuntimeType::Value(last)),
2979 upper: None,
2980 },
2981 )
2982 .unwrap();
2983 graph
2984 .collect_runtime_bounds(
2985 &typed_ir::Type::Var(var.clone()),
2986 &RuntimeBounds {
2987 lower: Some(RuntimeType::Value(either.clone())),
2988 upper: None,
2989 },
2990 )
2991 .unwrap();
2992
2993 assert_eq!(
2994 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
2995 Some(&either)
2996 );
2997 }
2998
2999 #[test]
3000 fn graph_keeps_existing_union_supertype_lower_bound() {
3001 let mut graph = TypeGraph::default();
3002 let var = typed_ir::TypeVar("effect".into());
3003 let last = closed_row(&["last"]);
3004 let sub = closed_row(&["sub"]);
3005 let either = typed_ir::Type::Union(vec![sub, last.clone()]);
3006
3007 graph
3008 .collect_runtime_bounds(
3009 &typed_ir::Type::Var(var.clone()),
3010 &RuntimeBounds {
3011 lower: Some(RuntimeType::Value(either.clone())),
3012 upper: None,
3013 },
3014 )
3015 .unwrap();
3016 graph
3017 .collect_runtime_bounds(
3018 &typed_ir::Type::Var(var.clone()),
3019 &RuntimeBounds {
3020 lower: Some(RuntimeType::Value(last)),
3021 upper: None,
3022 },
3023 )
3024 .unwrap();
3025
3026 assert_eq!(
3027 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3028 Some(&either)
3029 );
3030 }
3031
3032 #[test]
3033 fn graph_treats_named_unit_and_empty_tuple_bounds_as_equivalent() {
3034 let mut graph = TypeGraph::default();
3035 let var = typed_ir::TypeVar("unitish".into());
3036
3037 graph
3038 .collect_runtime_bounds(
3039 &typed_ir::Type::Var(var.clone()),
3040 &RuntimeBounds {
3041 lower: Some(RuntimeType::Value(unit_type())),
3042 upper: None,
3043 },
3044 )
3045 .unwrap();
3046 graph
3047 .collect_runtime_bounds(
3048 &typed_ir::Type::Var(var.clone()),
3049 &RuntimeBounds {
3050 lower: Some(RuntimeType::Value(typed_ir::Type::Tuple(Vec::new()))),
3051 upper: None,
3052 },
3053 )
3054 .unwrap();
3055
3056 assert_eq!(
3057 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3058 Some(&unit_type())
3059 );
3060 }
3061
3062 #[test]
3063 fn graph_joins_lower_bounds_using_cast_order() {
3064 let small = effect_type("small");
3065 let middle = effect_type("middle");
3066 let large = effect_type("large");
3067 let cast_order = TypeCastOrder::from_edges(vec![
3068 (small.clone(), middle),
3069 (effect_type("middle"), large.clone()),
3070 ]);
3071 let mut graph = TypeGraph::with_cast_order(cast_order);
3072 let var = typed_ir::TypeVar("value".into());
3073
3074 graph
3075 .collect_runtime_bounds(
3076 &typed_ir::Type::Var(var.clone()),
3077 &RuntimeBounds {
3078 lower: Some(RuntimeType::Value(small)),
3079 upper: None,
3080 },
3081 )
3082 .unwrap();
3083 graph
3084 .collect_runtime_bounds(
3085 &typed_ir::Type::Var(var.clone()),
3086 &RuntimeBounds {
3087 lower: Some(RuntimeType::Value(large.clone())),
3088 upper: None,
3089 },
3090 )
3091 .unwrap();
3092
3093 assert_eq!(
3094 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3095 Some(&large)
3096 );
3097 }
3098
3099 #[test]
3100 fn lower_solution_wins_over_upper_solution() {
3101 let mut graph = TypeGraph::default();
3102 let var = typed_ir::TypeVar("a".into());
3103
3104 graph
3105 .collect_runtime_bounds(
3106 &typed_ir::Type::Var(var.clone()),
3107 &RuntimeBounds {
3108 lower: Some(RuntimeType::Value(int_type())),
3109 upper: Some(RuntimeType::Value(number_type())),
3110 },
3111 )
3112 .unwrap();
3113 let solution = graph.solve();
3114
3115 assert_eq!(
3116 solution.substitutions(),
3117 vec![typed_ir::TypeSubstitution {
3118 var,
3119 ty: int_type(),
3120 }]
3121 );
3122 }
3123
3124 #[test]
3125 fn top_and_bottom_are_extremes_not_holes() {
3126 let mut graph = TypeGraph::default();
3127 let var = typed_ir::TypeVar("a".into());
3128
3129 graph
3130 .collect_runtime_bounds(
3131 &typed_ir::Type::Var(var.clone()),
3132 &RuntimeBounds {
3133 lower: Some(RuntimeType::Value(any_type())),
3134 upper: Some(RuntimeType::Value(never_type())),
3135 },
3136 )
3137 .unwrap();
3138
3139 assert_eq!(
3140 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3141 Some(&any_type())
3142 );
3143 }
3144
3145 #[test]
3146 fn bottom_lower_and_top_upper_are_vacuous_bounds() {
3147 let mut graph = TypeGraph::default();
3148 let var = typed_ir::TypeVar("a".into());
3149
3150 graph
3151 .collect_runtime_bounds(
3152 &typed_ir::Type::Var(var.clone()),
3153 &RuntimeBounds {
3154 lower: Some(RuntimeType::Value(never_type())),
3155 upper: Some(RuntimeType::Value(any_type())),
3156 },
3157 )
3158 .unwrap();
3159
3160 assert_eq!(graph.slot(&var).and_then(TypeVarBounds::solution_ref), None);
3161
3162 graph
3163 .collect_runtime_bounds(
3164 &typed_ir::Type::Var(var.clone()),
3165 &RuntimeBounds {
3166 lower: Some(RuntimeType::Value(int_type())),
3167 upper: Some(RuntimeType::Value(any_type())),
3168 },
3169 )
3170 .unwrap();
3171
3172 assert_eq!(
3173 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3174 Some(&int_type())
3175 );
3176 }
3177
3178 #[test]
3179 fn symbolic_bounds_do_not_conflict_before_concrete_bounds_arrive() {
3180 let mut graph = TypeGraph::default();
3181 let var = typed_ir::TypeVar("a".into());
3182 let first = typed_ir::Type::Var(typed_ir::TypeVar("b".into()));
3183 let second = typed_ir::Type::Var(typed_ir::TypeVar("c".into()));
3184
3185 graph
3186 .collect_runtime_bounds(
3187 &typed_ir::Type::Var(var.clone()),
3188 &RuntimeBounds {
3189 lower: Some(RuntimeType::Value(first.clone())),
3190 upper: None,
3191 },
3192 )
3193 .unwrap();
3194 graph
3195 .collect_runtime_bounds(
3196 &typed_ir::Type::Var(var.clone()),
3197 &RuntimeBounds {
3198 lower: Some(RuntimeType::Value(second)),
3199 upper: None,
3200 },
3201 )
3202 .unwrap();
3203
3204 assert_eq!(
3205 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3206 Some(&first)
3207 );
3208 }
3209
3210 #[test]
3211 fn indirect_var_lower_bound_cycle_is_not_chased_forever() {
3212 let mut graph = TypeGraph::default();
3213 let a = typed_ir::TypeVar("a".into());
3214 let b = typed_ir::TypeVar("b".into());
3215
3216 graph
3217 .collect_runtime_lower(
3218 &typed_ir::Type::Var(a.clone()),
3219 &RuntimeType::Value(typed_ir::Type::Var(b.clone())),
3220 )
3221 .unwrap();
3222 graph
3223 .collect_runtime_lower(
3224 &typed_ir::Type::Var(b.clone()),
3225 &RuntimeType::Value(typed_ir::Type::Var(a.clone())),
3226 )
3227 .unwrap();
3228 graph
3229 .constrain_subtype(typed_ir::Type::Var(a.clone()), int_type())
3230 .unwrap();
3231
3232 assert_eq!(
3233 graph.slot(&a).and_then(TypeVarBounds::solution_ref),
3234 Some(&typed_ir::Type::Var(b))
3235 );
3236 }
3237
3238 #[test]
3239 fn graph_solution_keeps_lower_and_upper_after_solving() {
3240 let mut graph = TypeGraph::default();
3241 let var = typed_ir::TypeVar("a".into());
3242
3243 graph
3244 .collect_runtime_bounds(
3245 &typed_ir::Type::Var(var.clone()),
3246 &RuntimeBounds {
3247 lower: Some(RuntimeType::Value(int_type())),
3248 upper: Some(RuntimeType::Value(number_type())),
3249 },
3250 )
3251 .unwrap();
3252 let solution = graph.solve();
3253
3254 assert_eq!(
3255 solution.type_vars,
3256 vec![ResolvedTypeVar {
3257 var,
3258 bounds: TypeVarBounds {
3259 lower: Some(int_type()),
3260 upper: Some(number_type()),
3261 },
3262 solution: Some(int_type()),
3263 }]
3264 );
3265 assert!(solution.is_complete());
3266 }
3267
3268 #[test]
3269 fn subtype_constraints_solve_curried_first_application() {
3270 let mut graph = TypeGraph::default();
3271 let a_var = typed_ir::TypeVar("a".into());
3272 let b_var = typed_ir::TypeVar("b".into());
3273 let a = typed_ir::Type::Var(a_var.clone());
3274 let b = typed_ir::Type::Var(b_var.clone());
3275 graph.slots.entry(a_var.clone()).or_default();
3276 graph.slots.entry(b_var.clone()).or_default();
3277 let r0 = graph.fresh_hole("apply");
3278 let r1 = graph.fresh_hole("apply");
3279
3280 graph
3281 .constrain_subtype(
3282 fun_type(a.clone(), fun_type(b.clone(), a.clone())),
3283 fun_type(int_type(), r0.clone()),
3284 )
3285 .unwrap();
3286 graph
3287 .constrain_subtype(r0, fun_type(bool_type(), r1.clone()))
3288 .unwrap();
3289 let solution = graph.solve();
3290
3291 assert_eq!(solution.solution_for(&a_var), Some(&int_type()));
3292 assert_eq!(solution.solution_for(&b_var), Some(&bool_type()));
3293 assert_eq!(solution.materialize_core(r1), int_type());
3294 assert!(solution.is_complete());
3295 }
3296
3297 #[test]
3298 fn subtype_constraints_propagate_after_later_bounds_arrive() {
3299 let mut graph = TypeGraph::default();
3300 let a_var = typed_ir::TypeVar("a".into());
3301 let b_var = typed_ir::TypeVar("b".into());
3302 graph.slots.entry(a_var.clone()).or_default();
3303 graph.slots.entry(b_var.clone()).or_default();
3304
3305 graph
3306 .constrain_subtype(
3307 typed_ir::Type::Var(a_var.clone()),
3308 typed_ir::Type::Var(b_var.clone()),
3309 )
3310 .unwrap();
3311 graph
3312 .collect_runtime_lower(
3313 &typed_ir::Type::Var(a_var.clone()),
3314 &RuntimeType::Value(int_type()),
3315 )
3316 .unwrap();
3317
3318 let solution = graph.solve();
3319
3320 assert_eq!(solution.solution_for(&a_var), Some(&int_type()));
3321 assert_eq!(solution.solution_for(&b_var), Some(&int_type()));
3322 }
3323
3324 #[test]
3325 fn union_lower_constraint_propagates_upper_bound_to_items() {
3326 let mut graph = TypeGraph::default();
3327 let var = typed_ir::TypeVar("a".into());
3328 graph.slots.entry(var.clone()).or_default();
3329
3330 graph
3331 .constrain_subtype(
3332 typed_ir::Type::Union(vec![typed_ir::Type::Var(var.clone()), int_type()]),
3333 int_type(),
3334 )
3335 .unwrap();
3336 let solution = graph.solve();
3337
3338 assert_eq!(solution.solution_for(&var), Some(&int_type()));
3339 assert!(solution.is_complete());
3340 }
3341
3342 #[test]
3343 fn variant_subtype_solves_present_payloads_and_bottoms_absent_upper_payloads() {
3344 let mut graph = TypeGraph::default();
3345 let ok_var = typed_ir::TypeVar("ok".into());
3346 let err_var = typed_ir::TypeVar("err".into());
3347 graph.slots.entry(ok_var.clone()).or_default();
3348 graph.slots.entry(err_var.clone()).or_default();
3349
3350 graph
3351 .constrain_subtype(
3352 variant_type(&[("ok", vec![int_type()])]),
3353 variant_type(&[
3354 ("ok", vec![typed_ir::Type::Var(ok_var.clone())]),
3355 ("err", vec![typed_ir::Type::Var(err_var.clone())]),
3356 ("pending", Vec::new()),
3357 ]),
3358 )
3359 .unwrap();
3360 let solution = graph.solve();
3361
3362 assert_eq!(solution.solution_for(&ok_var), Some(&int_type()));
3363 assert_eq!(
3364 solution.solution_for(&err_var),
3365 Some(&typed_ir::Type::Never)
3366 );
3367 assert!(solution.is_complete());
3368 }
3369
3370 #[test]
3371 fn record_subtype_solves_field_type_variables() {
3372 let mut graph = TypeGraph::default();
3373 let port_var = typed_ir::TypeVar("port".into());
3374 graph.slots.entry(port_var.clone()).or_default();
3375
3376 graph
3377 .constrain_subtype(
3378 record_type(&[("port", typed_ir::Type::Var(port_var.clone()))]),
3379 record_type(&[("port", int_type())]),
3380 )
3381 .unwrap();
3382 let solution = graph.solve();
3383
3384 assert_eq!(solution.solution_for(&port_var), Some(&int_type()));
3385 assert!(solution.is_complete());
3386 }
3387
3388 #[test]
3389 fn materialized_union_drops_bottom_and_singleton() {
3390 let ty = materialize_core_type(
3391 typed_ir::Type::Union(vec![typed_ir::Type::Never, int_type()]),
3392 &[],
3393 );
3394
3395 assert_eq!(ty, int_type());
3396 }
3397
3398 #[test]
3399 fn normalized_union_absorbs_intersection_subtype() {
3400 let acc = typed_ir::Type::Var(typed_ir::TypeVar("acc".into()));
3401 let branch = typed_ir::Type::Var(typed_ir::TypeVar("branch".into()));
3402 let ty = materialize_core_type(
3403 typed_ir::Type::Union(vec![
3404 acc.clone(),
3405 typed_ir::Type::Inter(vec![acc.clone(), branch]),
3406 ]),
3407 &[],
3408 );
3409
3410 assert_eq!(ty, acc);
3411 }
3412
3413 #[test]
3414 fn normalized_intersection_absorbs_union_supertype() {
3415 let acc = typed_ir::Type::Var(typed_ir::TypeVar("acc".into()));
3416 let branch = typed_ir::Type::Var(typed_ir::TypeVar("branch".into()));
3417 let ty = materialize_core_type(
3418 typed_ir::Type::Inter(vec![
3419 typed_ir::Type::Union(vec![unit_type(), acc.clone(), branch]),
3420 acc.clone(),
3421 ]),
3422 &[],
3423 );
3424
3425 assert_eq!(ty, acc);
3426 }
3427
3428 #[test]
3429 fn runtime_function_thunks_feed_function_effect_slots() {
3430 let mut graph = TypeGraph::default();
3431 let a_var = typed_ir::TypeVar("a".into());
3432 let b_var = typed_ir::TypeVar("b".into());
3433 let param_effect_var = typed_ir::TypeVar("pe".into());
3434 let ret_effect_var = typed_ir::TypeVar("re".into());
3435 let template = fun_type_with_effects(
3436 typed_ir::Type::Var(a_var.clone()),
3437 typed_ir::Type::Var(param_effect_var.clone()),
3438 typed_ir::Type::Var(ret_effect_var.clone()),
3439 typed_ir::Type::Var(b_var.clone()),
3440 );
3441
3442 graph
3443 .collect_runtime_bounds(
3444 &template,
3445 &RuntimeBounds::exact(RuntimeType::Fun {
3446 param: Box::new(RuntimeType::Thunk {
3447 effect: effect_type("arg"),
3448 value: Box::new(RuntimeType::Value(int_type())),
3449 }),
3450 ret: Box::new(RuntimeType::Thunk {
3451 effect: effect_type("ret"),
3452 value: Box::new(RuntimeType::Value(bool_type())),
3453 }),
3454 }),
3455 )
3456 .unwrap();
3457 let solution = graph.solve();
3458
3459 assert_eq!(solution.solution_for(&a_var), Some(&int_type()));
3460 assert_eq!(solution.solution_for(&b_var), Some(&bool_type()));
3461 assert_eq!(
3462 solution.solution_for(¶m_effect_var),
3463 Some(&effect_type("arg"))
3464 );
3465 assert_eq!(
3466 solution.solution_for(&ret_effect_var),
3467 Some(&effect_type("ret"))
3468 );
3469 assert!(solution.is_complete());
3470 }
3471
3472 #[test]
3473 fn singleton_closed_effect_row_bound_matches_its_atom() {
3474 let mut graph = TypeGraph::default();
3475 let var = typed_ir::TypeVar("e".into());
3476 let atom = effect_type("io");
3477 let row = typed_ir::Type::Row {
3478 items: vec![atom.clone()],
3479 tail: Box::new(typed_ir::Type::Never),
3480 };
3481
3482 graph
3483 .collect_runtime_bounds(
3484 &typed_ir::Type::Var(var.clone()),
3485 &RuntimeBounds {
3486 lower: None,
3487 upper: Some(RuntimeType::Value(row.clone())),
3488 },
3489 )
3490 .unwrap();
3491 graph
3492 .collect_runtime_bounds(
3493 &typed_ir::Type::Var(var.clone()),
3494 &RuntimeBounds {
3495 lower: None,
3496 upper: Some(RuntimeType::Value(atom)),
3497 },
3498 )
3499 .unwrap();
3500
3501 assert_eq!(
3502 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3503 Some(&row)
3504 );
3505 }
3506
3507 #[test]
3508 fn empty_effect_row_bound_matches_its_tail() {
3509 let mut graph = TypeGraph::default();
3510 let var = typed_ir::TypeVar("e".into());
3511 let open_top_row = typed_ir::Type::Row {
3512 items: Vec::new(),
3513 tail: Box::new(typed_ir::Type::Any),
3514 };
3515
3516 graph
3517 .collect_runtime_bounds(
3518 &typed_ir::Type::Var(var.clone()),
3519 &RuntimeBounds {
3520 lower: Some(RuntimeType::Value(typed_ir::Type::Any)),
3521 upper: None,
3522 },
3523 )
3524 .unwrap();
3525 graph
3526 .collect_runtime_bounds(
3527 &typed_ir::Type::Var(var.clone()),
3528 &RuntimeBounds {
3529 lower: Some(RuntimeType::Value(open_top_row)),
3530 upper: None,
3531 },
3532 )
3533 .unwrap();
3534
3535 assert_eq!(
3536 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3537 Some(&typed_ir::Type::Any)
3538 );
3539 }
3540
3541 #[test]
3542 fn never_effect_row_item_is_empty() {
3543 let mut graph = TypeGraph::default();
3544 let var = typed_ir::TypeVar("e".into());
3545 let never_item_row = typed_ir::Type::Row {
3546 items: vec![typed_ir::Type::Never],
3547 tail: Box::new(typed_ir::Type::Never),
3548 };
3549
3550 graph
3551 .collect_runtime_bounds(
3552 &typed_ir::Type::Var(var.clone()),
3553 &RuntimeBounds {
3554 lower: Some(RuntimeType::Value(typed_ir::Type::Never)),
3555 upper: None,
3556 },
3557 )
3558 .unwrap();
3559 graph
3560 .collect_runtime_bounds(
3561 &typed_ir::Type::Var(var.clone()),
3562 &RuntimeBounds {
3563 lower: Some(RuntimeType::Value(never_item_row)),
3564 upper: None,
3565 },
3566 )
3567 .unwrap();
3568
3569 assert_eq!(graph.slot(&var).and_then(TypeVarBounds::solution_ref), None);
3570 }
3571
3572 #[test]
3573 fn intersection_bound_drops_top_and_duplicate_rows() {
3574 let mut graph = TypeGraph::default();
3575 let var = typed_ir::TypeVar("e".into());
3576 let row = typed_ir::Type::Row {
3577 items: vec![effect_type("ref_update")],
3578 tail: Box::new(typed_ir::Type::Never),
3579 };
3580 let noisy = typed_ir::Type::Inter(vec![typed_ir::Type::Any, row.clone(), row.clone()]);
3581
3582 graph
3583 .collect_runtime_bounds(
3584 &typed_ir::Type::Var(var.clone()),
3585 &RuntimeBounds {
3586 lower: Some(RuntimeType::Value(row.clone())),
3587 upper: None,
3588 },
3589 )
3590 .unwrap();
3591 graph
3592 .collect_runtime_bounds(
3593 &typed_ir::Type::Var(var.clone()),
3594 &RuntimeBounds {
3595 lower: Some(RuntimeType::Value(noisy)),
3596 upper: None,
3597 },
3598 )
3599 .unwrap();
3600
3601 assert_eq!(
3602 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3603 Some(&row)
3604 );
3605 }
3606
3607 #[test]
3608 fn closed_effect_row_intersection_keeps_common_items() {
3609 let mut graph = TypeGraph::default();
3610 let var = typed_ir::TypeVar("e".into());
3611 let parse = effect_type("parse");
3612 let pick = effect_type("pick");
3613 let parse_and_pick = typed_ir::Type::Row {
3614 items: vec![parse.clone(), pick],
3615 tail: Box::new(typed_ir::Type::Never),
3616 };
3617 let parse_only = typed_ir::Type::Row {
3618 items: vec![parse],
3619 tail: Box::new(typed_ir::Type::Never),
3620 };
3621 let intersection = typed_ir::Type::Inter(vec![parse_and_pick, parse_only.clone()]);
3622
3623 graph
3624 .collect_runtime_bounds(
3625 &typed_ir::Type::Var(var.clone()),
3626 &RuntimeBounds {
3627 lower: None,
3628 upper: Some(RuntimeType::Value(intersection)),
3629 },
3630 )
3631 .unwrap();
3632 graph
3633 .collect_runtime_bounds(
3634 &typed_ir::Type::Var(var.clone()),
3635 &RuntimeBounds {
3636 lower: None,
3637 upper: Some(RuntimeType::Value(parse_only.clone())),
3638 },
3639 )
3640 .unwrap();
3641
3642 assert_eq!(
3643 graph.slot(&var).and_then(|bounds| bounds.upper.as_ref()),
3644 Some(&parse_only)
3645 );
3646 }
3647
3648 #[test]
3649 fn closed_effect_row_subtypes_open_effect_row_intersection() {
3650 let read = effect_type("read");
3651 let write = effect_type("write");
3652 let closed = typed_ir::Type::Row {
3653 items: vec![read.clone(), write.clone()],
3654 tail: Box::new(typed_ir::Type::Never),
3655 };
3656 let read_open = typed_ir::Type::Row {
3657 items: vec![read],
3658 tail: Box::new(typed_ir::Type::Any),
3659 };
3660 let write_open = typed_ir::Type::Row {
3661 items: vec![write],
3662 tail: Box::new(typed_ir::Type::Any),
3663 };
3664 let open_intersection = typed_ir::Type::Inter(vec![read_open, write_open]);
3665
3666 assert!(bound_subtype(&closed, &open_intersection));
3667 }
3668
3669 #[test]
3670 fn upper_bound_prefers_closed_row_below_open_row_intersection() {
3671 let mut graph = TypeGraph::default();
3672 let var = typed_ir::TypeVar("e".into());
3673 let state = effect_type("&s");
3674 let var_effect = effect_type("var");
3675 let closed = typed_ir::Type::Row {
3676 items: vec![state.clone(), var_effect.clone()],
3677 tail: Box::new(typed_ir::Type::Never),
3678 };
3679 let state_open = typed_ir::Type::Row {
3680 items: vec![state],
3681 tail: Box::new(typed_ir::Type::Any),
3682 };
3683 let var_open = typed_ir::Type::Row {
3684 items: vec![var_effect],
3685 tail: Box::new(typed_ir::Type::Any),
3686 };
3687 let recursive_open = typed_ir::Type::Recursive {
3688 var: typed_ir::TypeVar("tail".into()),
3689 body: Box::new(typed_ir::Type::Row {
3690 items: Vec::new(),
3691 tail: Box::new(typed_ir::Type::Any),
3692 }),
3693 };
3694 let open_intersection = typed_ir::Type::Inter(vec![state_open, var_open, recursive_open]);
3695
3696 graph
3697 .collect_runtime_bounds(
3698 &typed_ir::Type::Var(var.clone()),
3699 &RuntimeBounds {
3700 lower: None,
3701 upper: Some(RuntimeType::Value(open_intersection)),
3702 },
3703 )
3704 .unwrap();
3705 graph
3706 .collect_runtime_bounds(
3707 &typed_ir::Type::Var(var.clone()),
3708 &RuntimeBounds {
3709 lower: None,
3710 upper: Some(RuntimeType::Value(closed.clone())),
3711 },
3712 )
3713 .unwrap();
3714
3715 assert_eq!(
3716 graph.slot(&var).and_then(|bounds| bounds.upper.as_ref()),
3717 Some(&closed)
3718 );
3719 }
3720
3721 #[test]
3722 fn closed_effect_row_item_flattens_into_outer_row() {
3723 let mut graph = TypeGraph::default();
3724 let var = typed_ir::TypeVar("e".into());
3725 let nested = typed_ir::Type::Row {
3726 items: vec![typed_ir::Type::Row {
3727 items: vec![effect_type("junction")],
3728 tail: Box::new(typed_ir::Type::Never),
3729 }],
3730 tail: Box::new(typed_ir::Type::Never),
3731 };
3732 let expected = typed_ir::Type::Row {
3733 items: vec![effect_type("junction")],
3734 tail: Box::new(typed_ir::Type::Never),
3735 };
3736
3737 graph
3738 .collect_runtime_bounds(
3739 &typed_ir::Type::Var(var.clone()),
3740 &RuntimeBounds {
3741 lower: Some(RuntimeType::Value(nested)),
3742 upper: None,
3743 },
3744 )
3745 .unwrap();
3746
3747 assert_eq!(
3748 graph.slot(&var).and_then(TypeVarBounds::solution_ref),
3749 Some(&expected)
3750 );
3751 }
3752
3753 #[test]
3754 fn function_upper_bounds_merge_by_variance() {
3755 let mut graph = TypeGraph::default();
3756 let var = typed_ir::TypeVar("f".into());
3757 let precise = fun_type_with_effects(
3758 bool_type(),
3759 typed_ir::Type::Never,
3760 closed_row(&["parse"]),
3761 int_type(),
3762 );
3763 let broad = fun_type_with_effects(
3764 bool_type(),
3765 typed_ir::Type::Any,
3766 typed_ir::Type::Any,
3767 int_type(),
3768 );
3769
3770 graph
3771 .collect_runtime_bounds(
3772 &typed_ir::Type::Var(var.clone()),
3773 &RuntimeBounds {
3774 lower: None,
3775 upper: Some(RuntimeType::Value(broad)),
3776 },
3777 )
3778 .unwrap();
3779 graph
3780 .collect_runtime_bounds(
3781 &typed_ir::Type::Var(var.clone()),
3782 &RuntimeBounds {
3783 lower: None,
3784 upper: Some(RuntimeType::Value(precise.clone())),
3785 },
3786 )
3787 .unwrap();
3788
3789 assert_eq!(
3790 graph.slot(&var).and_then(|bounds| bounds.upper.as_ref()),
3791 Some(&precise)
3792 );
3793 }
3794
3795 #[test]
3796 fn graph_solution_reports_open_type_vars() {
3797 let mut graph = TypeGraph::default();
3798 graph.instantiate_principal(&id_binding());
3799 let solution = graph.solve();
3800
3801 assert!(!solution.is_complete());
3802 }
3803
3804 fn id_binding() -> Binding {
3805 Binding {
3806 name: path(&["id"]),
3807 type_params: vec![typed_ir::TypeVar("a".into())],
3808 scheme: typed_ir::Scheme {
3809 requirements: Vec::new(),
3810 body: typed_ir::Type::Fun {
3811 param: Box::new(typed_ir::Type::Var(typed_ir::TypeVar("a".into()))),
3812 param_effect: Box::new(typed_ir::Type::Never),
3813 ret_effect: Box::new(typed_ir::Type::Never),
3814 ret: Box::new(typed_ir::Type::Var(typed_ir::TypeVar("a".into()))),
3815 },
3816 },
3817 body: Expr::typed(ExprKind::Tuple(Vec::new()), RuntimeType::Unknown),
3818 }
3819 }
3820
3821 fn int_type() -> typed_ir::Type {
3822 typed_ir::Type::Named {
3823 path: path(&["Int"]),
3824 args: Vec::new(),
3825 }
3826 }
3827
3828 fn bool_type() -> typed_ir::Type {
3829 typed_ir::Type::Named {
3830 path: path(&["Bool"]),
3831 args: Vec::new(),
3832 }
3833 }
3834
3835 fn unit_type() -> typed_ir::Type {
3836 typed_ir::Type::Named {
3837 path: path(&["unit"]),
3838 args: Vec::new(),
3839 }
3840 }
3841
3842 fn any_type() -> typed_ir::Type {
3843 typed_ir::Type::Any
3844 }
3845
3846 fn number_type() -> typed_ir::Type {
3847 typed_ir::Type::Named {
3848 path: path(&["Number"]),
3849 args: Vec::new(),
3850 }
3851 }
3852
3853 fn never_type() -> typed_ir::Type {
3854 typed_ir::Type::Never
3855 }
3856
3857 fn variant_type(cases: &[(&str, Vec<typed_ir::Type>)]) -> typed_ir::Type {
3858 typed_ir::Type::Variant(typed_ir::VariantType {
3859 cases: cases
3860 .iter()
3861 .map(|(name, payloads)| typed_ir::VariantCase {
3862 name: typed_ir::Name((*name).to_string()),
3863 payloads: payloads.clone(),
3864 })
3865 .collect(),
3866 tail: None,
3867 })
3868 }
3869
3870 fn record_type(fields: &[(&str, typed_ir::Type)]) -> typed_ir::Type {
3871 typed_ir::Type::Record(typed_ir::RecordType {
3872 fields: fields
3873 .iter()
3874 .map(|(name, value)| typed_ir::RecordField {
3875 name: typed_ir::Name((*name).to_string()),
3876 value: value.clone(),
3877 optional: false,
3878 })
3879 .collect(),
3880 spread: None,
3881 })
3882 }
3883
3884 fn fun_type(param: typed_ir::Type, ret: typed_ir::Type) -> typed_ir::Type {
3885 fun_type_with_effects(param, typed_ir::Type::Never, typed_ir::Type::Never, ret)
3886 }
3887
3888 fn fun_type_with_effects(
3889 param: typed_ir::Type,
3890 param_effect: typed_ir::Type,
3891 ret_effect: typed_ir::Type,
3892 ret: typed_ir::Type,
3893 ) -> typed_ir::Type {
3894 typed_ir::Type::Fun {
3895 param: Box::new(param),
3896 param_effect: Box::new(param_effect),
3897 ret_effect: Box::new(ret_effect),
3898 ret: Box::new(ret),
3899 }
3900 }
3901
3902 fn effect_type(name: &str) -> typed_ir::Type {
3903 typed_ir::Type::Named {
3904 path: path(&[name]),
3905 args: Vec::new(),
3906 }
3907 }
3908
3909 fn effect_type_arg(name: &str, arg: typed_ir::Type) -> typed_ir::Type {
3910 typed_ir::Type::Named {
3911 path: path(&[name]),
3912 args: vec![typed_ir::TypeArg::Type(arg)],
3913 }
3914 }
3915
3916 fn effect_row(items: Vec<typed_ir::Type>) -> typed_ir::Type {
3917 typed_ir::Type::Row {
3918 items,
3919 tail: Box::new(typed_ir::Type::Never),
3920 }
3921 }
3922
3923 fn closed_row(items: &[&str]) -> typed_ir::Type {
3924 effect_row(items.iter().map(|item| effect_type(item)).collect())
3925 }
3926
3927 fn path(segments: &[&str]) -> typed_ir::Path {
3928 typed_ir::Path::new(
3929 segments
3930 .iter()
3931 .map(|segment| typed_ir::Name((*segment).into()))
3932 .collect(),
3933 )
3934 }
3935}