1use std::{collections::hash_map::Entry, sync::Arc};
4
5use rustc_hash::{FxHashMap, FxHashSet};
6use tinymist_derive::BindTyCtx;
7
8use super::{
9 BuiltinTy, DependencyAdmission, DynTypeBounds, FlowVarKind, SharedContext, TyCtxMut, TypeInfo,
10 TypeVar, TypeVarBounds, prelude::*,
11};
12use crate::{
13 docs::UntypedDefDocs,
14 syntax::{Decl, DeclExpr, Expr, ExprInfo, UnaryOp},
15 ty::*,
16};
17
18mod apply;
19mod docs;
20mod select;
21mod syntax;
22
23pub(crate) use apply::*;
24pub(crate) use select::*;
25
26struct TypeEnv {
28 ctx: Arc<SharedContext>,
29 component: FxHashSet<TypstFileId>,
30 exprs: Arc<FxHashMap<TypstFileId, ExprInfo>>,
31 results: FxHashMap<TypstFileId, Arc<TypeInfo>>,
32 component_vars: FxHashMap<DeclExpr, TypeVarBounds>,
33}
34
35impl TypeEnv {
36 fn new(
37 ctx: Arc<SharedContext>,
38 component: impl IntoIterator<Item = TypstFileId>,
39 exprs: Arc<FxHashMap<TypstFileId, ExprInfo>>,
40 results: FxHashMap<TypstFileId, Arc<TypeInfo>>,
41 ) -> Self {
42 let component: FxHashSet<_> = component.into_iter().collect();
43 assert!(
44 exprs.len() == component.len() && exprs.keys().all(|fid| component.contains(fid)),
45 "expression results must cover the complete type-check component"
46 );
47 assert!(
48 results.is_empty()
49 || (results.len() == component.len()
50 && results.keys().all(|fid| component.contains(fid))),
51 "previous type results must cover the complete component or be empty"
52 );
53
54 Self {
55 ctx,
56 component,
57 exprs,
58 results,
59 component_vars: FxHashMap::default(),
60 }
61 }
62
63 fn component_var(&mut self, decl: &DeclExpr) -> Option<TypeVarBounds> {
67 let fid = decl.file_id()?;
68 if !self.component.contains(&fid) {
69 return None;
70 }
71
72 Some(
73 self.component_vars
74 .entry(decl.clone())
75 .or_insert_with(|| {
76 TypeVarBounds::new(
77 TypeVar {
78 name: decl.name().clone(),
79 def: decl.clone(),
80 },
81 DynTypeBounds::default(),
82 )
83 })
84 .clone(),
85 )
86 }
87
88 fn expr_info(&mut self, importer: TypstFileId, fid: TypstFileId) -> Option<ExprInfo> {
89 if let Some(cached) = self.exprs.get(&fid) {
90 return Some(cached.clone());
91 }
92
93 match self.ctx.dependency_admission(importer, fid) {
94 DependencyAdmission::Reachable => self.ctx.expr_stage_by_id(fid),
95 DependencyAdmission::SameComponent => {
96 unreachable!("same-component expression is missing from TypeEnv")
97 }
98 DependencyAdmission::Unresolved | DependencyAdmission::Rejected => None,
99 }
100 }
101
102 fn external_type_info(&mut self, importer: TypstFileId, ei: ExprInfo) -> Option<Arc<TypeInfo>> {
103 let ctx = self.ctx.clone();
104 if self.component.contains(&ei.fid) {
105 panic!(
106 "same-component type lookup for {:?} bypassed shared component variables",
107 ei.fid
108 );
109 }
110
111 if ctx.dependency_admission(importer, ei.fid) != DependencyAdmission::Reachable {
112 return None;
113 }
114 let source = ctx
115 .source_by_id(ei.fid)
116 .expect("an imported expression result must have a source");
117 Some(ctx.type_check(&source))
118 }
119
120 fn check_all(mut self, members: &[TypstFileId]) -> FxHashMap<TypstFileId, Arc<TypeInfo>> {
121 if self.results.len() == self.component.len() {
122 return self.results;
123 }
124
125 for fid in members {
126 let guard = self.ctx.query_stat(*fid, "type_check");
127 guard.miss();
128 }
129
130 for fid in members.iter().rev() {
135 let ei = self
136 .exprs
137 .get(fid)
138 .unwrap_or_else(|| panic!("component expression batch is missing {fid:?}"))
139 .clone();
140 let type_info = type_check(ei, &mut self);
141 self.results.insert(*fid, type_info);
142 }
143
144 self.results
145 }
146}
147
148pub(super) fn type_check_component(
151 ctx: Arc<SharedContext>,
152 members: &[TypstFileId],
153 exprs: Arc<FxHashMap<TypstFileId, ExprInfo>>,
154 previous: FxHashMap<TypstFileId, Arc<TypeInfo>>,
155) -> FxHashMap<TypstFileId, Arc<TypeInfo>> {
156 TypeEnv::new(ctx, members.iter().copied(), exprs, previous).check_all(members)
157}
158
159#[typst_macros::time(span = ei.source.root().span())]
161fn type_check(ei: ExprInfo, env: &mut TypeEnv) -> Arc<TypeInfo> {
162 let ctx = env.ctx.clone();
163 let mut info = TypeInfo::default();
164 info.valid = true;
165 info.fid = Some(ei.fid);
166 info.revision = ei.revision;
167
168 let root = ei.root.clone();
170
171 let mut checker = TypeChecker {
172 ctx,
173 ei,
174 info,
175 env,
176 call_cache: Default::default(),
177 module_exports: Default::default(),
178 checking_module_exports: Default::default(),
179 overwritten_vars: Default::default(),
180 live_input_vars: Default::default(),
181 input_contract_bounds: Default::default(),
182 };
183
184 let type_check_start = tinymist_std::time::Instant::now();
185
186 checker.check(&root);
187
188 let exports = checker
189 .ei
190 .exports
191 .clone()
192 .into_iter()
193 .map(|(k, v)| (k.clone(), checker.check(v)))
194 .collect();
195 checker.info.exports = exports;
196
197 let elapsed = type_check_start.elapsed();
198 crate::log_debug_ct!("Type checking on {:?} took {elapsed:?}", checker.ei.fid);
199
200 Arc::new(checker.info)
201}
202
203type CallCacheDesc = (
204 Interned<SigTy>,
205 Interned<SigTy>,
206 Option<Vec<Interned<SigTy>>>,
207);
208
209pub(crate) struct TypeChecker<'a> {
210 ctx: Arc<SharedContext>,
211 ei: ExprInfo,
212
213 info: TypeInfo,
214 module_exports: FxHashMap<(TypstFileId, Interned<str>), Option<Ty>>,
215 checking_module_exports: FxHashSet<(TypstFileId, Interned<str>)>,
216
217 call_cache: FxHashSet<CallCacheDesc>,
218 overwritten_vars: FxHashSet<DeclExpr>,
219 live_input_vars: FxHashSet<DeclExpr>,
221 input_contract_bounds: FxHashMap<DeclExpr, DynTypeBounds>,
222
223 env: &'a mut TypeEnv,
224}
225
226impl TyCtx for TypeChecker<'_> {
227 fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<DynTypeBounds> {
228 self.info.global_bounds(var, pol)
229 }
230
231 fn local_bind_of(&self, var: &Interned<TypeVar>) -> Option<Ty> {
232 self.info.local_bind_of(var)
233 }
234}
235
236impl TyCtxMut for TypeChecker<'_> {
237 type Snap = <TypeInfo as TyCtxMut>::Snap;
238
239 fn start_scope(&mut self) -> Self::Snap {
240 self.info.start_scope()
241 }
242
243 fn end_scope(&mut self, snap: Self::Snap) {
244 self.info.end_scope(snap)
245 }
246
247 fn bind_local(&mut self, var: &Interned<TypeVar>, ty: Ty) {
248 self.info.bind_local(var, ty);
249 }
250
251 fn type_of_func(&mut self, func: &Func) -> Option<Interned<SigTy>> {
252 Some(self.ctx.type_of_func(func.clone()).type_sig())
253 }
254
255 fn type_of_value(&mut self, val: &Value) -> Ty {
256 self.ctx.type_of_value(val)
257 }
258
259 fn check_module_item(&mut self, fid: TypstFileId, name: &StrRef) -> Option<Ty> {
260 let key = (fid, name.clone());
261 if let Some(cached) = self.module_exports.get(&key) {
262 return cached.clone();
263 }
264
265 if !self.checking_module_exports.insert(key.clone()) {
266 return None;
267 }
268
269 let result = self
270 .env
271 .expr_info(self.ei.fid, fid)
272 .and_then(|ei| ei.exports.get(name).map(|expr| self.check(expr)));
273
274 self.checking_module_exports.remove(&key);
275 self.module_exports.insert(key, result.clone());
276 result
277 }
278}
279
280impl TypeChecker<'_> {
281 fn check(&mut self, expr: &Expr) -> Ty {
282 self.check_syntax(expr).unwrap_or(Ty::undef())
283 }
284
285 fn copy_doc_vars(
286 &mut self,
287 fr: &TypeVarBounds,
288 var: &Interned<TypeVar>,
289 base: &Interned<Decl>,
290 ) -> Ty {
291 let mut gen_var = var.as_ref().clone();
292 let encoded = Interned::new(Decl::docs(base.clone(), var.clone()));
293 gen_var.def = encoded.clone();
294 crate::log_debug_ct!("copy var {fr:?} as {encoded:?}");
295 let bounds = TypeVarBounds::new(gen_var, fr.bounds.bounds().read().clone());
296 let var = bounds.as_type();
297 self.info.vars.insert(encoded, bounds);
298 var
299 }
300
301 fn get_var(&mut self, decl: &DeclExpr) -> Interned<TypeVar> {
302 crate::log_debug_ct!("get_var {decl:?}");
303 let mut imported_docs = None;
304 let mut imported_input_bounds = vec![];
305 let component_var = self.env.component_var(decl);
306 let var = match self.info.vars.entry(decl.clone()) {
307 Entry::Occupied(entry) => entry.get().var.clone(),
308 Entry::Vacant(entry) => {
309 if let Some(bounds) = component_var {
310 let var = bounds.var.clone();
311 entry.insert(bounds);
312 var
313 } else {
314 let name = decl.name().clone();
315 let init = Self::external_var_bounds(self.env, self.ei.fid, decl, &name)
316 .map(|(bounds, docs, input_bounds)| {
317 imported_docs = docs;
318 imported_input_bounds = input_bounds;
319 bounds
320 })
321 .unwrap_or_default();
322 let bounds = TypeVarBounds::new(
323 TypeVar {
324 name,
325 def: decl.clone(),
326 },
327 init,
328 );
329 let var = bounds.var.clone();
330 entry.insert(bounds);
331 var
332 }
333 }
334 };
335
336 if let Some(docs) = imported_docs {
337 self.info.var_docs.entry(decl.clone()).or_insert(docs);
338 }
339 for bounds in imported_input_bounds {
340 self.info
341 .vars
342 .entry(bounds.var.def.clone())
343 .or_insert(bounds);
344 }
345
346 let s = decl.span();
347 if !s.is_detached() {
348 TypeInfo::witness_(s, Ty::Var(var.clone()), &mut self.info.mapping);
356 }
357 var
358 }
359
360 fn external_var_bounds(
361 env: &mut TypeEnv,
362 current_fid: TypstFileId,
363 decl: &DeclExpr,
364 name: &Interned<str>,
365 ) -> Option<(
366 DynTypeBounds,
367 Option<Arc<UntypedDefDocs>>,
368 Vec<TypeVarBounds>,
369 )> {
370 let fid = decl.file_id()?;
371 if fid == current_fid {
372 return None;
373 }
374
375 crate::log_debug_ct!("import_ty {name} from {fid:?}");
376
377 let ext_def_use_info = env.expr_info(current_fid, fid)?;
378 let ext_type_info = env.external_type_info(current_fid, ext_def_use_info.clone())?;
379 let ext_def = ext_def_use_info.exports.get(name)?;
380
381 let Expr::Decl(decl) = ext_def else {
383 return None;
384 };
385
386 let ext_ty = ext_type_info.vars.get(decl)?.as_type();
387 let docs = ext_type_info.var_docs.get(decl).cloned();
388
389 let def = ext_type_info.simplify(ext_ty, true);
390 let input_bounds = Self::copy_external_input_bounds(&ext_type_info, &def);
391 let mut bounds = DynTypeBounds::default();
392 bounds.lbs.insert_mut(def);
393 Some((bounds, docs, input_bounds))
394 }
395
396 #[allow(clippy::mutable_key_type)]
397 fn copy_external_input_bounds(type_info: &TypeInfo, ty: &Ty) -> Vec<TypeVarBounds> {
398 let mut pending = vec![];
399 let mut seen = FxHashSet::default();
400 let mut traversed = FxHashSet::default();
401 Self::collect_signature_input_binders(
402 ty,
403 &FxHashSet::default(),
404 &mut seen,
405 &mut traversed,
406 &mut pending,
407 );
408
409 let mut copied = vec![];
410 let mut idx = 0;
411 while idx < pending.len() {
412 let (binder, escaped) = pending[idx].clone();
413 idx += 1;
414
415 let Some(source) = type_info.vars.get(&binder.def) else {
416 continue;
417 };
418 let source_is_weak = matches!(&source.bounds, FlowVarKind::Weak(_));
419 let bounds = source.bounds.bounds().read().freeze();
420
421 for bound in bounds.lbs.iter().chain(&bounds.ubs) {
422 Self::collect_signature_input_binders(
423 bound,
424 &escaped,
425 &mut seen,
426 &mut traversed,
427 &mut pending,
428 );
429 }
430
431 let mut closer = FunctionResultantCloser::new(&type_info.vars, escaped);
432 let bounds = closer.close_bounds(&bounds);
433 let mut imported =
434 TypeVarBounds::new(binder.as_ref().clone(), DynTypeBounds::from(bounds));
435 if source_is_weak {
436 imported.weaken();
437 }
438 copied.push(imported);
439 }
440
441 copied
442 }
443
444 #[allow(clippy::mutable_key_type)]
445 fn collect_signature_input_binders(
446 ty: &Ty,
447 escaped: &FxHashSet<DeclExpr>,
448 seen: &mut FxHashSet<DeclExpr>,
449 traversed: &mut FxHashSet<Ty>,
450 binders: &mut Vec<(Interned<TypeVar>, FxHashSet<DeclExpr>)>,
451 ) {
452 if !traversed.insert(ty.clone()) {
455 return;
456 }
457
458 match ty {
459 Ty::Func(sig) => {
460 let mut direct = vec![];
461 let mut direct_seen = FxHashSet::default();
462 let mut direct_traversed = FxHashSet::default();
463 for input in sig.inputs() {
464 Self::collect_input_binders(
465 input,
466 &mut direct_seen,
467 &mut direct_traversed,
468 &mut direct,
469 );
470 }
471
472 let mut scope = escaped.clone();
473 for binder in direct {
474 if seen.insert(binder.def.clone()) {
475 binders.push((binder.clone(), scope.clone()));
476 }
477 scope.insert(binder.def.clone());
478 }
479
480 for input in sig.inputs() {
481 Self::collect_signature_input_binders(input, &scope, seen, traversed, binders);
482 }
483 if let Some(body) = &sig.body {
484 Self::collect_signature_input_binders(body, &scope, seen, traversed, binders);
485 }
486 }
487 Ty::With(with) => {
488 Self::collect_signature_input_binders(&with.sig, escaped, seen, traversed, binders);
489 for input in with.with.inputs() {
490 Self::collect_signature_input_binders(input, escaped, seen, traversed, binders);
491 }
492 if let Some(body) = &with.with.body {
493 Self::collect_signature_input_binders(body, escaped, seen, traversed, binders);
494 }
495 }
496 Ty::Args(sig) | Ty::Pattern(sig) => {
497 for input in sig.inputs() {
498 Self::collect_signature_input_binders(input, escaped, seen, traversed, binders);
499 }
500 if let Some(body) = &sig.body {
501 Self::collect_signature_input_binders(body, escaped, seen, traversed, binders);
502 }
503 }
504 Ty::Param(param) => {
505 Self::collect_signature_input_binders(¶m.ty, escaped, seen, traversed, binders)
506 }
507 Ty::Union(types) | Ty::Tuple(types) => {
508 for ty in types.iter() {
509 Self::collect_signature_input_binders(ty, escaped, seen, traversed, binders);
510 }
511 }
512 Ty::Let(bounds) => {
513 for ty in bounds.lbs.iter().chain(&bounds.ubs) {
514 Self::collect_signature_input_binders(ty, escaped, seen, traversed, binders);
515 }
516 }
517 Ty::Dict(record) => {
518 for ty in record.types.iter() {
519 Self::collect_signature_input_binders(ty, escaped, seen, traversed, binders);
520 }
521 }
522 Ty::Array(elem) => {
523 Self::collect_signature_input_binders(elem, escaped, seen, traversed, binders)
524 }
525 Ty::Select(select) => {
526 Self::collect_signature_input_binders(&select.ty, escaped, seen, traversed, binders)
527 }
528 Ty::Unary(unary) => {
529 Self::collect_signature_input_binders(&unary.lhs, escaped, seen, traversed, binders)
530 }
531 Ty::Binary(binary) => {
532 let [lhs, rhs] = binary.operands();
533 Self::collect_signature_input_binders(lhs, escaped, seen, traversed, binders);
534 Self::collect_signature_input_binders(rhs, escaped, seen, traversed, binders);
535 }
536 Ty::If(if_ty) => {
537 Self::collect_signature_input_binders(
538 &if_ty.cond,
539 escaped,
540 seen,
541 traversed,
542 binders,
543 );
544 Self::collect_signature_input_binders(
545 &if_ty.then,
546 escaped,
547 seen,
548 traversed,
549 binders,
550 );
551 Self::collect_signature_input_binders(
552 &if_ty.else_,
553 escaped,
554 seen,
555 traversed,
556 binders,
557 );
558 }
559 Ty::Var(_) | Ty::Any | Ty::Boolean(_) | Ty::Builtin(_) | Ty::Value(_) => {}
560 }
561 }
562
563 #[allow(clippy::mutable_key_type)]
564 fn collect_input_binders(
565 ty: &Ty,
566 seen: &mut FxHashSet<DeclExpr>,
567 traversed: &mut FxHashSet<Ty>,
568 binders: &mut Vec<Interned<TypeVar>>,
569 ) {
570 if !traversed.insert(ty.clone()) {
571 return;
572 }
573
574 match ty {
575 Ty::Var(var) => {
576 if seen.insert(var.def.clone()) {
577 binders.push(var.clone());
578 }
579 }
580 Ty::Func(_) | Ty::With(_) => {}
581 Ty::Param(param) => Self::collect_input_binders(¶m.ty, seen, traversed, binders),
582 Ty::Union(types) | Ty::Tuple(types) => {
583 for ty in types.iter() {
584 Self::collect_input_binders(ty, seen, traversed, binders);
585 }
586 }
587 Ty::Let(bounds) => {
588 for ty in bounds.lbs.iter().chain(&bounds.ubs) {
589 Self::collect_input_binders(ty, seen, traversed, binders);
590 }
591 }
592 Ty::Dict(record) => {
593 for ty in record.types.iter() {
594 Self::collect_input_binders(ty, seen, traversed, binders);
595 }
596 }
597 Ty::Array(elem) => Self::collect_input_binders(elem, seen, traversed, binders),
598 Ty::Args(sig) | Ty::Pattern(sig) => {
599 for input in sig.inputs() {
600 Self::collect_input_binders(input, seen, traversed, binders);
601 }
602 }
603 Ty::Select(select) => Self::collect_input_binders(&select.ty, seen, traversed, binders),
604 Ty::Unary(unary) => Self::collect_input_binders(&unary.lhs, seen, traversed, binders),
605 Ty::Binary(binary) => {
606 let [lhs, rhs] = binary.operands();
607 Self::collect_input_binders(lhs, seen, traversed, binders);
608 Self::collect_input_binders(rhs, seen, traversed, binders);
609 }
610 Ty::If(if_ty) => {
611 Self::collect_input_binders(&if_ty.cond, seen, traversed, binders);
612 Self::collect_input_binders(&if_ty.then, seen, traversed, binders);
613 Self::collect_input_binders(&if_ty.else_, seen, traversed, binders);
614 }
615 Ty::Any | Ty::Boolean(_) | Ty::Builtin(_) | Ty::Value(_) => {}
616 }
617 }
618
619 fn constrain_sig_inputs(
620 &mut self,
621 sig: &Interned<SigTy>,
622 args: &Interned<SigTy>,
623 with: Option<&Vec<Interned<SigTy>>>,
624 ) {
625 let call_desc = (sig.clone(), args.clone(), with.cloned());
626 if !self.call_cache.insert(call_desc) {
627 return;
628 }
629
630 let rest_bind = Self::rest_arg_bind(sig, args, with);
631
632 for (arg_recv, arg_ins) in sig.matches(args, with) {
633 if rest_bind.as_ref().is_some_and(
634 |(rest_var, _)| matches!(arg_recv, Ty::Var(var) if var.def == rest_var.def),
635 ) {
636 continue;
637 }
638 if matches!(arg_recv, Ty::Var(var) if !self.info.vars.contains_key(&var.def)) {
639 continue;
640 }
641
642 self.constrain_sig_input(arg_ins, arg_recv);
643 }
644
645 if let Some((rest_var, rest_ty)) = rest_bind
646 && self.info.vars.contains_key(&rest_var.def)
647 {
648 self.constrain_sig_input(&rest_ty, &Ty::Var(rest_var));
649 }
650 }
651
652 fn constrain_sig_input(&mut self, actual: &Ty, input: &Ty) {
653 let Ty::Var(input_var) = input else {
654 self.constrain(actual, input);
655 return;
656 };
657
658 let is_external = input_var
659 .def
660 .file_id()
661 .is_some_and(|fid| !Self::same_file_id(fid, self.ei.fid));
662 if is_external {
663 let contract = self.info.simplify(input.clone(), false);
664 self.constrain(actual, &contract);
665 return;
666 }
667
668 if matches!(input_var.def.as_ref(), Decl::Docs(..)) {
669 let input_bounds = self
671 .info
672 .vars
673 .get(&input_var.def)
674 .map(|input| input.bounds.bounds().read().freeze())
675 .unwrap_or_default();
676 let has_contract = !input_bounds.lbs.is_empty() || !input_bounds.ubs.is_empty();
677 if has_contract {
678 let contract = self.info.simplify(Ty::Let(input_bounds.into()), false);
679 let actual_snapshot = self.close_lower_snapshot(actual);
680 if !matches!(actual_snapshot, Ty::Any) {
681 self.constrain(&actual_snapshot, input);
682 }
683 self.constrain(actual, &contract);
684 }
685 return;
686 }
687
688 self.constrain(actual, input);
689 }
690
691 fn close_lower_snapshot(&self, ty: &Ty) -> Ty {
692 let bounds = match ty {
693 Ty::Var(var) => {
694 let Some(bounds) = self.info.vars.get(&var.def) else {
695 return Ty::Any;
696 };
697 let bounds = bounds.bounds.bounds().read();
698 TypeBounds {
699 lbs: bounds.lbs.iter().cloned().collect(),
700 ubs: vec![],
701 }
702 }
703 Ty::Let(bounds) => TypeBounds {
704 lbs: bounds.lbs.clone(),
705 ubs: vec![],
706 },
707 ty => TypeBounds {
708 lbs: vec![ty.clone()],
709 ubs: vec![],
710 },
711 };
712 if bounds.lbs.is_empty() {
713 return Ty::Any;
714 }
715
716 let mut closer = FunctionResultantCloser::new(&self.info.vars, FxHashSet::default());
717 let mut bounds = closer.close_bounds(&bounds);
718 if bounds.ubs.is_empty() && bounds.lbs.len() == 1 {
719 return bounds.lbs.pop().unwrap();
720 }
721 Ty::Let(bounds.into())
722 }
723
724 fn same_file_id(left: TypstFileId, right: TypstFileId) -> bool {
725 left.root() == right.root() && left.vpath() == right.vpath()
726 }
727
728 #[allow(clippy::mutable_key_type)]
729 fn snapshot_function_input_bounds(&self, sig: &SigTy) -> Vec<(Interned<TypeVar>, TypeBounds)> {
730 let mut seen = FxHashSet::default();
731 let mut traversed = FxHashSet::default();
732 let mut binders = vec![];
733 for input in sig.inputs() {
734 Self::collect_input_binders(input, &mut seen, &mut traversed, &mut binders);
735 }
736
737 binders
738 .into_iter()
739 .map(|binder| {
740 let bounds = self
741 .info
742 .vars
743 .get(&binder.def)
744 .map(|bounds| bounds.bounds.bounds().read().freeze())
745 .unwrap_or_default();
746 (binder, bounds)
747 })
748 .collect()
749 }
750
751 fn close_function_resultant_type(
752 &self,
753 body: Ty,
754 input_bounds: &[(Interned<TypeVar>, TypeBounds)],
755 escaped: &FxHashSet<DeclExpr>,
756 ) -> Ty {
757 let mut resultant_params = escaped.clone();
758 resultant_params.extend(
759 input_bounds
760 .iter()
761 .map(|(binder, _)| &binder.def)
762 .filter(|def| !self.overwritten_vars.contains(*def))
763 .cloned(),
764 );
765
766 let mut closer = FunctionResultantCloser::new(&self.info.vars, resultant_params);
767
768 closer.mutate(&body, true).unwrap_or(body)
769 }
770
771 fn rebind_overwritten_function_inputs(
772 &mut self,
773 mut sig: SigTy,
774 input_bounds: Vec<(Interned<TypeVar>, TypeBounds)>,
775 escaped: &FxHashSet<DeclExpr>,
776 ) -> SigTy {
777 let mut scope = escaped.clone();
778 let mut replacements: Vec<(Interned<TypeVar>, Interned<TypeVar>)> = Vec::new();
779
780 for (binder, mut bounds) in input_bounds {
781 if !self.overwritten_vars.contains(&binder.def) {
782 scope.insert(binder.def.clone());
783 continue;
784 }
785 if let Some(input_bounds) = self.input_contract_bounds.get(&binder.def) {
786 let input_bounds = input_bounds.freeze();
787 bounds.lbs.extend(input_bounds.lbs);
788 bounds.ubs.extend(input_bounds.ubs);
789 }
790 bounds.lbs.sort();
791 bounds.lbs.dedup();
792 bounds.ubs.sort();
793 bounds.ubs.dedup();
794
795 let mut closer = FunctionResultantCloser::new(&self.info.vars, scope.clone());
796 let mut bounds = closer.close_bounds(&bounds);
797 bounds.lbs.sort();
798 bounds.lbs.dedup();
799 bounds.ubs.sort();
800 bounds.ubs.dedup();
801
802 for (original, replacement) in &replacements {
803 for bound in bounds.lbs.iter_mut().chain(&mut bounds.ubs) {
804 *bound =
805 Self::replace_var(bound.clone(), original, Ty::Var(replacement.clone()));
806 }
807 }
808
809 let fresh_def: DeclExpr = Decl::docs(binder.def.clone(), binder.clone()).into();
810 let fresh = TypeVar {
811 name: binder.name.clone(),
812 def: fresh_def.clone(),
813 };
814 let fresh = TypeVarBounds::new(fresh, DynTypeBounds::from(bounds));
815 let fresh_var = fresh.var.clone();
816 self.info.vars.insert(fresh_def, fresh);
817 replacements.push((binder.clone(), fresh_var));
818 scope.insert(binder.def.clone());
819 }
820
821 if !replacements.is_empty() {
822 let mut inputs = sig.inputs.as_ref().clone();
823 for input in &mut inputs {
824 for (original, replacement) in &replacements {
825 *input =
826 Self::replace_var(input.clone(), original, Ty::Var(replacement.clone()));
827 }
828 }
829 sig.inputs = inputs.into();
830 }
831
832 sig
833 }
834
835 fn rest_arg_bind(
836 sig: &Interned<SigTy>,
837 args: &Interned<SigTy>,
838 with: Option<&Vec<Interned<SigTy>>>,
839 ) -> Option<(Interned<TypeVar>, Ty)> {
840 let Ty::Var(rest_var) = sig.rest_param()? else {
841 return None;
842 };
843
844 let fixed_pos = sig.positional_params().len();
845 let rest_pos = with
846 .into_iter()
847 .flat_map(|withs| withs.iter().rev())
848 .flat_map(|with| with.positional_params())
849 .chain(args.positional_params())
850 .skip(fixed_pos)
851 .cloned()
852 .collect::<Vec<_>>();
853
854 let rest_named = args
855 .named_params()
856 .filter(|(name, _)| sig.named(name).is_none())
857 .map(|(name, ty)| (name.clone(), ty.clone()))
858 .collect::<Vec<_>>();
859
860 let rest = args.rest_param().cloned();
861 let rest_args = ArgsTy::new(rest_pos.into_iter(), rest_named, None, rest, None);
862
863 Some((rest_var.clone(), Ty::Args(rest_args.into())))
864 }
865
866 fn collect_type_vars(
867 ty: &Ty,
868 vars: &mut FxHashSet<DeclExpr>,
869 binders: &mut Vec<Interned<TypeVar>>,
870 ) {
871 match ty {
872 Ty::Var(var) => {
873 if vars.insert(var.def.clone()) {
874 binders.push(var.clone());
875 }
876 }
877 Ty::Param(param) => Self::collect_type_vars(¶m.ty, vars, binders),
878 Ty::Union(types) | Ty::Tuple(types) => {
879 for ty in types.iter() {
880 Self::collect_type_vars(ty, vars, binders);
881 }
882 }
883 Ty::Let(bounds) => {
884 for ty in bounds.lbs.iter().chain(bounds.ubs.iter()) {
885 Self::collect_type_vars(ty, vars, binders);
886 }
887 }
888 Ty::Dict(record) => {
889 for ty in record.types.iter() {
890 Self::collect_type_vars(ty, vars, binders);
891 }
892 }
893 Ty::Array(elem) => Self::collect_type_vars(elem, vars, binders),
894 Ty::Func(sig) | Ty::Args(sig) | Ty::Pattern(sig) => {
895 for ty in sig.inputs() {
896 Self::collect_type_vars(ty, vars, binders);
897 }
898 if let Some(body) = &sig.body {
899 Self::collect_type_vars(body, vars, binders);
900 }
901 }
902 Ty::With(with) => {
903 Self::collect_type_vars(&with.sig, vars, binders);
904 for ty in with.with.inputs() {
905 Self::collect_type_vars(ty, vars, binders);
906 }
907 if let Some(body) = &with.with.body {
908 Self::collect_type_vars(body, vars, binders);
909 }
910 }
911 Ty::Select(sel) => Self::collect_type_vars(&sel.ty, vars, binders),
912 Ty::Unary(unary) => Self::collect_type_vars(&unary.lhs, vars, binders),
913 Ty::Binary(binary) => {
914 let [lhs, rhs] = binary.operands();
915 Self::collect_type_vars(lhs, vars, binders);
916 Self::collect_type_vars(rhs, vars, binders);
917 }
918 Ty::If(if_ty) => {
919 Self::collect_type_vars(&if_ty.cond, vars, binders);
920 Self::collect_type_vars(&if_ty.then, vars, binders);
921 Self::collect_type_vars(&if_ty.else_, vars, binders);
922 }
923 Ty::Any | Ty::Boolean(_) | Ty::Builtin(_) | Ty::Value(_) => {}
924 }
925 }
926
927 fn constrain(&mut self, lhs: &Ty, rhs: &Ty) {
928 static FLOW_STROKE_DICT_TYPE: LazyLock<Ty> =
929 LazyLock::new(|| Ty::Dict(FLOW_STROKE_DICT.clone()));
930 static FLOW_MARGIN_DICT_TYPE: LazyLock<Ty> =
931 LazyLock::new(|| Ty::Dict(FLOW_MARGIN_DICT.clone()));
932 static FLOW_INSET_DICT_TYPE: LazyLock<Ty> =
933 LazyLock::new(|| Ty::Dict(FLOW_INSET_DICT.clone()));
934 static FLOW_OUTSET_DICT_TYPE: LazyLock<Ty> =
935 LazyLock::new(|| Ty::Dict(FLOW_OUTSET_DICT.clone()));
936 static FLOW_RADIUS_DICT_TYPE: LazyLock<Ty> =
937 LazyLock::new(|| Ty::Dict(FLOW_RADIUS_DICT.clone()));
938 static FLOW_TEXT_FONT_DICT_TYPE: LazyLock<Ty> =
939 LazyLock::new(|| Ty::Dict(FLOW_TEXT_FONT_DICT.clone()));
940
941 fn type_value_instance(ty: &Ty) -> Option<Ty> {
942 match ty {
943 Ty::Builtin(ty @ BuiltinTy::Type(..)) => Some(Ty::Builtin(ty.clone())),
944 Ty::Value(val) => match val.val {
945 Value::Type(ty) => Some(Ty::Builtin(BuiltinTy::Type(ty))),
946 _ => None,
947 },
948 _ => None,
949 }
950 }
951
952 if lhs == rhs {
953 return;
954 }
955
956 match (lhs, rhs) {
957 (Ty::Var(v), Ty::Var(w)) => {
958 if v.def == w.def {
959 return;
960 }
961 let Some(rhs) = self.info.vars.get(&w.def) else {
962 return;
963 };
964 match &rhs.bounds {
965 FlowVarKind::Strong(bounds) | FlowVarKind::Weak(bounds) => {
966 bounds.write().lbs.insert_mut(Ty::Var(v.clone()));
967 }
968 }
969 self.record_input_lower_bound(&w.def, Ty::Var(v.clone()));
970 }
971 (Ty::Var(v), rhs) => {
972 crate::log_debug_ct!("constrain var {v:?} ⪯ {rhs:?}");
973 let Some(w) = self.info.vars.get_mut(&v.def) else {
974 return;
975 };
976 let bound = rhs.clone();
978 match &w.bounds {
979 FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
980 let mut w = w.write();
981 w.ubs.insert_mut(bound.clone());
982 }
983 }
984 self.record_input_upper_bound(&v.def, bound);
985 }
986 (lhs, Ty::Var(v)) => {
987 let Some(w) = self.info.vars.get(&v.def) else {
988 return;
989 };
990 let bound = self.weaken_constraint(lhs, &w.bounds);
991 crate::log_debug_ct!("constrain var {v:?} ⪰ {bound:?}");
992 match &w.bounds {
993 FlowVarKind::Strong(v) | FlowVarKind::Weak(v) => {
994 let mut v = v.write();
995 v.lbs.insert_mut(bound.clone());
996 }
997 };
998 self.record_input_lower_bound(&v.def, bound);
999 }
1000 (Ty::Select(sel), rhs) => {
1001 let dict = Ty::Dict(RecordTy::new(vec![(sel.select.clone(), rhs.clone())]));
1006 self.constrain(sel.ty.as_ref(), &dict);
1007 }
1008 (Ty::Array(lhs), Ty::Array(rhs)) => {
1009 self.constrain(lhs, rhs);
1010 }
1011 (Ty::Tuple(lhs), Ty::Array(rhs)) => {
1012 for lhs in lhs.iter() {
1013 self.constrain(lhs, rhs);
1014 }
1015 }
1016 (Ty::Tuple(lhs), Ty::Tuple(rhs)) => {
1017 self.constrain_tuple_positions(lhs, rhs.iter());
1018 }
1019 (Ty::Tuple(lhs), Ty::Pattern(rhs)) => {
1020 self.constrain_tuple_positions(lhs, rhs.positional_params());
1021 }
1022 (Ty::Dict(lhs), Ty::Dict(rhs)) => {
1023 for (key, lhs, rhs) in lhs.common_iface_fields(rhs) {
1024 crate::log_debug_ct!("constrain record item {key} {lhs:?} ⪯ {rhs:?}");
1025 self.constrain(lhs, rhs);
1026 }
1033 }
1034 (Ty::Union(types), rhs) => {
1035 for ty in types.iter() {
1036 self.constrain(ty, rhs);
1037 }
1038 }
1039 (lhs, Ty::Union(types)) => {
1040 for ty in types.iter() {
1041 self.constrain(lhs, ty);
1042 }
1043 }
1044 (lhs, Ty::Builtin(BuiltinTy::Stroke)) => {
1045 if lhs.is_dict() {
1048 self.constrain(lhs, &FLOW_STROKE_DICT_TYPE);
1049 }
1050 }
1051 (Ty::Builtin(BuiltinTy::Stroke), rhs) => {
1052 if rhs.is_dict() {
1053 self.constrain(&FLOW_STROKE_DICT_TYPE, rhs);
1054 }
1055 }
1056 (lhs, Ty::Builtin(BuiltinTy::Margin)) => {
1057 if lhs.is_dict() {
1058 self.constrain(lhs, &FLOW_MARGIN_DICT_TYPE);
1059 }
1060 }
1061 (Ty::Builtin(BuiltinTy::Margin), rhs) => {
1062 if rhs.is_dict() {
1063 self.constrain(&FLOW_MARGIN_DICT_TYPE, rhs);
1064 }
1065 }
1066 (lhs, Ty::Builtin(BuiltinTy::Inset)) => {
1067 if lhs.is_dict() {
1068 self.constrain(lhs, &FLOW_INSET_DICT_TYPE);
1069 }
1070 }
1071 (Ty::Builtin(BuiltinTy::Inset), rhs) => {
1072 if rhs.is_dict() {
1073 self.constrain(&FLOW_INSET_DICT_TYPE, rhs);
1074 }
1075 }
1076 (lhs, Ty::Builtin(BuiltinTy::Outset)) => {
1077 if lhs.is_dict() {
1078 self.constrain(lhs, &FLOW_OUTSET_DICT_TYPE);
1079 }
1080 }
1081 (Ty::Builtin(BuiltinTy::Outset), rhs) => {
1082 if rhs.is_dict() {
1083 self.constrain(&FLOW_OUTSET_DICT_TYPE, rhs);
1084 }
1085 }
1086 (lhs, Ty::Builtin(BuiltinTy::Radius)) => {
1087 if lhs.is_dict() {
1088 self.constrain(lhs, &FLOW_RADIUS_DICT_TYPE);
1089 }
1090 }
1091 (Ty::Builtin(BuiltinTy::Radius), rhs) => {
1092 if rhs.is_dict() {
1093 self.constrain(&FLOW_RADIUS_DICT_TYPE, rhs);
1094 }
1095 }
1096 (lhs, Ty::Builtin(BuiltinTy::TextFont)) => {
1097 if lhs.is_dict() {
1098 self.constrain(lhs, &FLOW_TEXT_FONT_DICT_TYPE);
1099 }
1100 }
1101 (Ty::Builtin(BuiltinTy::TextFont), rhs) => {
1102 if rhs.is_dict() {
1103 self.constrain(&FLOW_TEXT_FONT_DICT_TYPE, rhs);
1104 }
1105 }
1106 (Ty::Unary(lhs), Ty::Unary(rhs)) if lhs.op == rhs.op => {
1107 self.constrain(&lhs.lhs, &rhs.lhs);
1110 }
1111 (Ty::Unary(lhs), rhs) if lhs.op == UnaryOp::TypeOf => {
1112 if let Some(rhs) = type_value_instance(rhs) {
1113 crate::log_debug_ct!("constrain type of {lhs:?} ⪯ {rhs:?}");
1114 self.constrain(&lhs.lhs, &rhs);
1115 }
1116 }
1117 (lhs, Ty::Unary(rhs)) if rhs.op == UnaryOp::TypeOf => {
1118 if let Some(lhs) = type_value_instance(lhs) {
1119 crate::log_debug_ct!(
1120 "constrain type of {lhs:?} ⪯ {rhs:?} {:?}",
1121 matches!(lhs, Ty::Builtin(..)),
1122 );
1123 self.constrain(&lhs, &rhs.lhs);
1124 }
1125 }
1126 (Ty::Func(lhs), Ty::Func(rhs)) => {
1127 crate::log_debug_ct!("constrain func {lhs:?} ⪯ {rhs:?}");
1128 self.constrain_sig_inputs(lhs, rhs, None);
1129 }
1130 (Ty::Value(lhs), rhs) => {
1131 crate::log_debug_ct!("constrain value {lhs:?} ⪯ {rhs:?}");
1132 let _ = TypeInfo::witness_at_most;
1133 }
1137 (lhs, Ty::Value(rhs)) => {
1138 crate::log_debug_ct!("constrain value {lhs:?} ⪯ {rhs:?}");
1139 }
1143 _ => {
1144 crate::log_debug_ct!("constrain {lhs:?} ⪯ {rhs:?}");
1145 }
1146 }
1147 }
1148
1149 fn constrain_tuple_positions<'a>(
1150 &mut self,
1151 lhs: &[Ty],
1152 rhs: impl ExactSizeIterator<Item = &'a Ty>,
1153 ) {
1154 for (idx, rhs) in rhs.enumerate() {
1155 let mut any = false;
1156 for lhs in self.tuple_pos_candidates(lhs, idx) {
1157 any = true;
1158 self.constrain(&lhs, rhs);
1159 }
1160
1161 if !any && let Some(spread) = self.tuple_open_spread(lhs, idx) {
1162 self.constrain(spread, &Ty::Array(rhs.clone().into()));
1163 }
1164 }
1165 }
1166
1167 fn record_input_lower_bound(&mut self, def: &DeclExpr, bound: Ty) {
1168 if self.live_input_vars.contains(def) {
1169 self.input_contract_bounds
1170 .entry(def.clone())
1171 .or_default()
1172 .lbs
1173 .insert_mut(bound);
1174 }
1175 }
1176
1177 fn record_input_upper_bound(&mut self, def: &DeclExpr, bound: Ty) {
1178 if !matches!(bound, Ty::Any) && self.live_input_vars.contains(def) {
1180 self.input_contract_bounds
1181 .entry(def.clone())
1182 .or_default()
1183 .ubs
1184 .insert_mut(bound);
1185 }
1186 }
1187
1188 fn tuple_pos_candidates(&self, elems: &[Ty], idx: usize) -> Vec<Ty> {
1189 let mut pos = 0;
1190 let mut candidates = vec![];
1191
1192 for elem in elems {
1193 if let Some(spread) = Self::spread_operand(elem) {
1194 let spread_idx = idx.saturating_sub(pos);
1195 self.collect_spread_pos_candidates(spread, spread_idx, &mut candidates);
1196 if !candidates.is_empty() {
1197 return candidates;
1198 }
1199
1200 if let Some(len) = self.fixed_spread_len(spread) {
1201 pos += len;
1202 continue;
1203 }
1204
1205 if idx >= pos {
1206 return candidates;
1207 }
1208 } else if pos == idx {
1209 candidates.push(elem.clone());
1210 return candidates;
1211 } else {
1212 pos += 1;
1213 }
1214 }
1215
1216 candidates
1217 }
1218
1219 fn tuple_open_spread<'a>(&self, elems: &'a [Ty], idx: usize) -> Option<&'a Ty> {
1220 let mut pos = 0;
1221
1222 for elem in elems {
1223 if let Some(spread) = Self::spread_operand(elem) {
1224 if idx >= pos {
1225 if let Some(len) = self.fixed_spread_len(spread) {
1226 if idx < pos + len {
1227 return Some(spread);
1228 }
1229 pos += len;
1230 continue;
1231 }
1232 return Some(spread);
1233 }
1234 } else if pos == idx {
1235 return None;
1236 } else {
1237 pos += 1;
1238 }
1239 }
1240
1241 None
1242 }
1243
1244 fn spread_operand(ty: &Ty) -> Option<&Ty> {
1245 let Ty::Unary(unary) = ty else {
1246 return None;
1247 };
1248 (unary.op == UnaryOp::Spread).then_some(&unary.lhs)
1249 }
1250
1251 fn fixed_spread_len(&self, ty: &Ty) -> Option<usize> {
1252 match ty {
1253 Ty::Tuple(elems) => Some(elems.len()),
1254 Ty::Args(args) if args.rest_param().is_none() => Some(args.positional_params().len()),
1255 Ty::Var(var) => {
1256 let bounds = self.info.vars.get(&var.def)?;
1257 let lbs = bounds.bounds.bounds().read().lbs.clone();
1258 let mut len = None;
1259 for lb in lbs.iter() {
1260 let next = self.fixed_spread_len(lb)?;
1261 if len.is_some_and(|prev| prev != next) {
1262 return None;
1263 }
1264 len = Some(next);
1265 }
1266 len
1267 }
1268 Ty::Let(bounds) => {
1269 let mut len = None;
1270 for lb in bounds.lbs.iter() {
1271 let next = self.fixed_spread_len(lb)?;
1272 if len.is_some_and(|prev| prev != next) {
1273 return None;
1274 }
1275 len = Some(next);
1276 }
1277 len
1278 }
1279 _ => None,
1280 }
1281 }
1282
1283 fn collect_spread_pos_candidates(&self, ty: &Ty, idx: usize, candidates: &mut Vec<Ty>) {
1284 match ty {
1285 Ty::Array(elem) => candidates.push(elem.as_ref().clone()),
1286 Ty::Tuple(elems) => {
1287 if let Some(elem) = elems.get(idx) {
1288 candidates.push(elem.clone());
1289 }
1290 }
1291 Ty::Args(args) => {
1292 if let Some(elem) = args.pos_or_rest(idx) {
1293 candidates.push(elem);
1294 }
1295 }
1296 Ty::Var(var) => {
1297 if let Some(bounds) = self.info.vars.get(&var.def) {
1298 let lbs = bounds.bounds.bounds().read().lbs.clone();
1299 for lb in lbs.iter() {
1300 self.collect_spread_pos_candidates(lb, idx, candidates);
1301 }
1302 }
1303 }
1304 Ty::Let(bounds) => {
1305 for lb in bounds.lbs.iter() {
1306 self.collect_spread_pos_candidates(lb, idx, candidates);
1307 }
1308 }
1309 _ => {}
1310 }
1311 }
1312
1313 fn check_comparable(&self, lhs: &Ty, rhs: &Ty) {
1314 let _ = lhs;
1315 let _ = rhs;
1316 }
1317
1318 fn check_assignable(&self, lhs: &Ty, rhs: &Ty) {
1319 let _ = lhs;
1320 let _ = rhs;
1321 }
1322
1323 fn constrain_assignment(&mut self, lhs: &Ty, rhs: &Ty) {
1324 match lhs {
1325 Ty::Var(var) => {
1326 if !self.assign_var(var, rhs) {
1327 self.possible_ever_be(lhs, rhs);
1328 }
1329 }
1330 Ty::Tuple(_) | Ty::Pattern(_) => self.constrain(rhs, lhs),
1331 Ty::Union(types) => {
1332 for lhs in types.iter() {
1333 self.constrain_assignment(lhs, rhs);
1334 }
1335 }
1336 Ty::Let(bounds) => {
1337 for lhs in bounds.lbs.iter().chain(bounds.ubs.iter()) {
1338 self.constrain_assignment(lhs, rhs);
1339 }
1340 }
1341 _ => self.possible_ever_be(lhs, rhs),
1342 }
1343
1344 let mut vars = FxHashSet::default();
1345 let mut binders = vec![];
1346 Self::collect_type_vars(lhs, &mut vars, &mut binders);
1347 for binder in binders {
1348 self.live_input_vars.remove(&binder.def);
1349 }
1350 }
1351
1352 fn assign_var(&mut self, var: &Interned<TypeVar>, rhs: &Ty) -> bool {
1353 if !Self::assignment_rhs_overwrites(rhs) {
1354 return false;
1355 }
1356
1357 let rhs_mentions_var = match Self::type_contains_var(rhs, &var.def) {
1358 Some(rhs_mentions_var) => rhs_mentions_var,
1359 None => return false,
1360 };
1361
1362 let rhs = if rhs_mentions_var {
1363 let mut snapshot = self.shallow_lower_bound(Ty::Var(var.clone()));
1364 if !matches!(Self::type_contains_var(&snapshot, &var.def), Some(false)) {
1365 snapshot = Ty::Any;
1366 }
1367
1368 let rhs = Self::replace_var(rhs.clone(), var, snapshot);
1369 if !matches!(Self::type_contains_var(&rhs, &var.def), Some(false)) {
1370 return false;
1371 }
1372 rhs
1373 } else {
1374 rhs.clone()
1375 };
1376 let rhs = self.shallow_lower_bound(rhs);
1377 if self.live_input_vars.contains(&var.def)
1379 && let Some(bounds) = self.info.vars.get(&var.def)
1380 {
1381 let bounds = bounds.bounds.bounds().read().freeze();
1382 let input = self
1383 .input_contract_bounds
1384 .entry(var.def.clone())
1385 .or_default();
1386 for bound in bounds.lbs {
1387 input.lbs.insert_mut(bound);
1388 }
1389 for bound in bounds.ubs {
1390 input.ubs.insert_mut(bound);
1391 }
1392 }
1393 let Some(bounds) = self.info.vars.get_mut(&var.def) else {
1394 return false;
1395 };
1396 self.overwritten_vars.insert(var.def.clone());
1397 let mut bounds = bounds.bounds.bounds().write();
1398 bounds.lbs = [rhs].into_iter().collect();
1399 true
1400 }
1401
1402 fn assignment_rhs_overwrites(rhs: &Ty) -> bool {
1403 !matches!(rhs, Ty::Any | Ty::Select(_) | Ty::Binary(_))
1404 }
1405
1406 fn replace_var(ty: Ty, var: &Interned<TypeVar>, with: Ty) -> Ty {
1407 let mut replacer = VarReplacer {
1408 def: var.def.clone(),
1409 with,
1410 };
1411 ty.mutate(true, &mut replacer).unwrap_or(ty)
1412 }
1413
1414 fn type_contains_var(ty: &Ty, def: &DeclExpr) -> Option<bool> {
1415 const NODE_BUDGET: usize = 4096;
1416
1417 let mut stack = vec![ty];
1418 let mut visited = 0usize;
1419 while let Some(ty) = stack.pop() {
1420 visited += 1;
1421 if visited > NODE_BUDGET {
1422 return None;
1423 }
1424
1425 match ty {
1426 Ty::Var(var) if var.def == *def => return Some(true),
1427 Ty::Param(param) => stack.push(¶m.ty),
1428 Ty::Union(types) | Ty::Tuple(types) => stack.extend(types.iter()),
1429 Ty::Let(bounds) => {
1430 stack.extend(bounds.lbs.iter());
1431 stack.extend(bounds.ubs.iter());
1432 }
1433 Ty::Dict(record) => stack.extend(record.types.iter()),
1434 Ty::Array(elem) => stack.push(elem),
1435 Ty::Func(sig) | Ty::Args(sig) | Ty::Pattern(sig) => {
1436 stack.extend(sig.inputs());
1437 if let Some(body) = &sig.body {
1438 stack.push(body);
1439 }
1440 }
1441 Ty::With(with) => {
1442 stack.push(&with.sig);
1443 stack.extend(with.with.inputs());
1444 if let Some(body) = &with.with.body {
1445 stack.push(body);
1446 }
1447 }
1448 Ty::Select(sel) => stack.push(&sel.ty),
1449 Ty::Unary(unary) => stack.push(&unary.lhs),
1450 Ty::Binary(binary) => {
1451 let [lhs, rhs] = binary.operands();
1452 stack.push(lhs);
1453 stack.push(rhs);
1454 }
1455 Ty::If(if_ty) => {
1456 stack.push(&if_ty.cond);
1457 stack.push(&if_ty.then);
1458 stack.push(&if_ty.else_);
1459 }
1460 Ty::Var(_) | Ty::Any | Ty::Boolean(_) | Ty::Builtin(_) | Ty::Value(_) => {}
1461 }
1462 }
1463
1464 Some(false)
1465 }
1466
1467 fn check_containing(&mut self, container: &Ty, elem: &Ty, expected_in: bool) {
1468 let rhs = if expected_in {
1469 match container {
1470 Ty::Tuple(elements) => Ty::Union(elements.clone()),
1471 _ => Ty::Unary(TypeUnary::new(UnaryOp::ElementOf, container.clone())),
1472 }
1473 } else {
1474 Ty::Unary(TypeUnary::new(UnaryOp::NotElementOf, container.clone()))
1476 };
1477
1478 self.constrain(elem, &rhs);
1479 }
1480
1481 fn possible_ever_be(&mut self, lhs: &Ty, rhs: &Ty) {
1482 match rhs {
1484 Ty::Builtin(..) | Ty::Value(..) | Ty::Boolean(..) => {
1485 self.constrain(rhs, lhs);
1486 }
1487 _ => {}
1488 }
1489 }
1490
1491 fn weaken(&mut self, v: &Ty) {
1492 match v {
1493 Ty::Var(v) => {
1494 let w = self.info.vars.get_mut(&v.def).unwrap();
1495 w.weaken();
1496 }
1497 Ty::Any | Ty::Boolean(_) | Ty::Builtin(_) | Ty::Value(_) => {}
1498 Ty::Param(v) => {
1499 self.weaken(&v.ty);
1500 }
1501 Ty::Func(v) | Ty::Args(v) | Ty::Pattern(v) => {
1502 for ty in v.inputs() {
1503 self.weaken(ty);
1504 }
1505 }
1506 Ty::With(v) => {
1507 self.weaken(&v.sig);
1508 for ty in v.with.inputs() {
1509 self.weaken(ty);
1510 }
1511 }
1512 Ty::Dict(v) => {
1513 for (_, ty) in v.interface() {
1514 self.weaken(ty);
1515 }
1516 }
1517 Ty::Array(v) => {
1518 self.weaken(v);
1519 }
1520 Ty::Tuple(v) => {
1521 for ty in v.iter() {
1522 self.weaken(ty);
1523 }
1524 }
1525 Ty::Select(v) => {
1526 self.weaken(&v.ty);
1527 }
1528 Ty::Unary(v) => {
1529 self.weaken(&v.lhs);
1530 }
1531 Ty::Binary(v) => {
1532 let [lhs, rhs] = v.operands();
1533 self.weaken(lhs);
1534 self.weaken(rhs);
1535 }
1536 Ty::If(v) => {
1537 self.weaken(&v.cond);
1538 self.weaken(&v.then);
1539 self.weaken(&v.else_);
1540 }
1541 Ty::Union(v) => {
1542 for ty in v.iter() {
1543 self.weaken(ty);
1544 }
1545 }
1546 Ty::Let(v) => {
1547 for ty in v.lbs.iter() {
1548 self.weaken(ty);
1549 }
1550 for ty in v.ubs.iter() {
1551 self.weaken(ty);
1552 }
1553 }
1554 }
1555 }
1556
1557 fn weaken_constraint(&self, term: &Ty, kind: &FlowVarKind) -> Ty {
1558 if matches!(kind, FlowVarKind::Strong(_)) {
1559 return term.clone();
1560 }
1561
1562 if let Ty::Value(ins_ty) = term {
1563 return BuiltinTy::from_value(&ins_ty.val);
1564 }
1565
1566 term.clone()
1567 }
1568}
1569
1570struct ControlSplit {
1571 normal: Option<Ty>,
1572 returns: Vec<Ty>,
1573 terminal: bool,
1574}
1575
1576#[derive(Clone, Eq, Hash, PartialEq)]
1577enum FunctionResultantEnvironmentTransition {
1578 Visiting(DeclExpr),
1579 Params(Vec<DeclExpr>),
1580}
1581
1582struct FunctionResultantCloser<'a> {
1583 vars: &'a FxHashMap<DeclExpr, TypeVarBounds>,
1584 params: FxHashSet<DeclExpr>,
1585 visiting: FxHashSet<DeclExpr>,
1586 visited: usize,
1587 environment: usize,
1588 environments: FxHashMap<(usize, FunctionResultantEnvironmentTransition), usize>,
1589 memo: FxHashMap<(usize, Ty, bool), Option<Ty>>,
1590}
1591
1592impl<'a> FunctionResultantCloser<'a> {
1593 const NODE_BUDGET: usize = 4096;
1594
1595 fn new(vars: &'a FxHashMap<DeclExpr, TypeVarBounds>, params: FxHashSet<DeclExpr>) -> Self {
1596 Self {
1597 vars,
1598 params,
1599 visiting: FxHashSet::default(),
1600 visited: 0,
1601 environment: 0,
1602 environments: FxHashMap::default(),
1603 memo: FxHashMap::default(),
1604 }
1605 }
1606
1607 fn enter_environment(&mut self, transition: FunctionResultantEnvironmentTransition) -> usize {
1608 let parent = self.environment;
1609 let key = (parent, transition);
1610 let environment = if let Some(environment) = self.environments.get(&key) {
1611 *environment
1612 } else {
1613 let environment = self.environments.len() + 1;
1614 self.environments.insert(key, environment);
1615 environment
1616 };
1617 self.environment = environment;
1618 parent
1619 }
1620
1621 #[allow(clippy::mutable_key_type)]
1622 fn close_scoped_sig(&mut self, sig: &SigTy, pol: bool) -> Option<SigTy> {
1623 let mut seen = FxHashSet::default();
1624 let mut traversed = FxHashSet::default();
1625 let mut binders = vec![];
1626 for input in sig.inputs() {
1627 TypeChecker::collect_input_binders(input, &mut seen, &mut traversed, &mut binders);
1628 }
1629 let inserted = binders
1630 .into_iter()
1631 .filter_map(|binder| {
1632 self.params
1633 .insert(binder.def.clone())
1634 .then_some(binder.def.clone())
1635 })
1636 .collect::<Vec<_>>();
1637 let parent_environment = (!inserted.is_empty()).then(|| {
1638 self.enter_environment(FunctionResultantEnvironmentTransition::Params(
1639 inserted.clone(),
1640 ))
1641 });
1642
1643 let inputs = self.mutate_vec(&sig.inputs, pol);
1644 let body = self.mutate_option(sig.body.as_ref(), pol);
1645
1646 for def in &inserted {
1647 self.params.remove(def);
1648 }
1649 if let Some(parent_environment) = parent_environment {
1650 self.environment = parent_environment;
1651 }
1652 if inputs.is_none() && body.is_none() {
1653 return None;
1654 }
1655
1656 let mut sig = sig.clone();
1657 if let Some(inputs) = inputs {
1658 sig.inputs = inputs;
1659 }
1660 if let Some(body) = body {
1661 sig.body = body;
1662 }
1663 Some(sig)
1664 }
1665
1666 fn lower_bounds_of(&mut self, var: &Interned<TypeVar>) -> Option<Ty> {
1667 self.visited += 1;
1668 if self.visited > Self::NODE_BUDGET {
1669 return Some(Ty::Any);
1670 }
1671
1672 if self.params.contains(&var.def) {
1673 return None;
1674 }
1675 if !self.visiting.insert(var.def.clone()) {
1676 return Some(Ty::Any);
1677 }
1678 let parent_environment = self.enter_environment(
1679 FunctionResultantEnvironmentTransition::Visiting(var.def.clone()),
1680 );
1681
1682 let bounds = self
1683 .vars
1684 .get(&var.def)
1685 .map(|bounds| bounds.bounds.bounds().read().freeze());
1686 let result = match bounds {
1687 Some(bounds) if !bounds.lbs.is_empty() || !bounds.ubs.is_empty() => {
1688 Some(Ty::Let(Interned::new(self.close_bounds(&bounds))))
1689 }
1690 _ => Some(Ty::Any),
1691 };
1692
1693 self.environment = parent_environment;
1694 self.visiting.remove(&var.def);
1695 result
1696 }
1697
1698 fn close_bounds(&mut self, bounds: &TypeBounds) -> TypeBounds {
1699 let lbs = bounds
1700 .lbs
1701 .iter()
1702 .map(|bound| self.mutate(bound, false).unwrap_or_else(|| bound.clone()))
1703 .collect();
1704 let ubs = bounds
1705 .ubs
1706 .iter()
1707 .map(|bound| self.mutate(bound, true).unwrap_or_else(|| bound.clone()))
1708 .collect();
1709 TypeBounds { lbs, ubs }
1710 }
1711}
1712
1713impl TyMutator for FunctionResultantCloser<'_> {
1714 fn mutate(&mut self, ty: &Ty, pol: bool) -> Option<Ty> {
1715 let key = (self.environment, ty.clone(), pol);
1716 if let Some(result) = self.memo.get(&key) {
1717 return result.clone();
1718 }
1719
1720 let result = match ty {
1721 Ty::Var(var) => self.lower_bounds_of(var),
1722 Ty::Let(bounds) => Some(Ty::Let(Interned::new(self.close_bounds(bounds)))),
1723 Ty::Func(sig) => self
1724 .close_scoped_sig(sig, pol)
1725 .map(|sig| Ty::Func(Interned::new(sig))),
1726 Ty::Pattern(sig) => self
1727 .close_scoped_sig(sig, pol)
1728 .map(|sig| Ty::Pattern(Interned::new(sig))),
1729 _ => self.mutate_rec(ty, pol),
1730 };
1731 self.memo.insert(key, result.clone());
1732 result
1733 }
1734}
1735
1736struct Joiner {
1737 break_or_continue_or_return: bool,
1738 definite: Ty,
1739 possibles: Vec<Ty>,
1740 returns: Vec<Ty>,
1741}
1742impl Joiner {
1743 fn finalize(self) -> Ty {
1744 crate::log_debug_ct!(
1745 "join: {:?} {:?} returns {:?}",
1746 self.possibles,
1747 self.definite,
1748 self.returns
1749 );
1750
1751 let normal = Self::finalize_normal(self.definite, self.possibles);
1752 if self.returns.is_empty() {
1753 if self.break_or_continue_or_return {
1754 return Ty::Builtin(BuiltinTy::Never);
1755 }
1756 return normal;
1757 }
1758
1759 let returned = Self::finalize_types(self.returns);
1760 if self.break_or_continue_or_return {
1761 return Ty::Unary(TypeUnary::new(UnaryOp::Return, returned));
1762 }
1763
1764 if Self::is_none_like(&normal) {
1765 return Ty::Any;
1766 }
1767 if normal == returned {
1768 return normal;
1769 }
1770
1771 Ty::from_types([normal, returned].into_iter())
1772 }
1773
1774 fn finalize_normal(definite: Ty, possibles: Vec<Ty>) -> Ty {
1775 if possibles.is_empty() {
1776 return definite;
1777 }
1778 if possibles.len() == 1 {
1779 return possibles.into_iter().next().unwrap();
1780 }
1781
1782 Ty::Any
1790 }
1791
1792 fn finalize_types(types: Vec<Ty>) -> Ty {
1793 if types.len() == 1 {
1794 return types.into_iter().next().unwrap();
1795 }
1796
1797 Ty::from_types(types.into_iter())
1798 }
1799
1800 fn is_none_like(ty: &Ty) -> bool {
1801 matches!(
1802 ty,
1803 Ty::Builtin(
1804 BuiltinTy::Space | BuiltinTy::None | BuiltinTy::Clause | BuiltinTy::FlowNone
1805 )
1806 )
1807 }
1808
1809 fn split_control(child: Ty) -> ControlSplit {
1810 match child {
1811 Ty::Unary(unary) if unary.op == UnaryOp::Return => ControlSplit {
1812 normal: None,
1813 returns: vec![unary.lhs.clone()],
1814 terminal: true,
1815 },
1816 Ty::Builtin(BuiltinTy::Break | BuiltinTy::Continue | BuiltinTy::Never) => {
1817 ControlSplit {
1818 normal: None,
1819 returns: vec![],
1820 terminal: true,
1821 }
1822 }
1823 Ty::If(if_ty) => {
1824 let then = Self::split_control(if_ty.then.as_ref().clone());
1825 let else_ = Self::split_control(if_ty.else_.as_ref().clone());
1826
1827 let mut returns = then.returns;
1828 returns.extend(else_.returns);
1829
1830 let then_normal = then.normal.filter(|ty| !Self::is_none_like(ty));
1831 let else_normal = else_.normal.filter(|ty| !Self::is_none_like(ty));
1832 let normal = if then_normal.is_none() && else_normal.is_none() {
1833 None
1834 } else {
1835 Some(Ty::If(IfTy::new(
1836 if_ty.cond.clone(),
1837 then_normal.unwrap_or(Ty::Builtin(BuiltinTy::None)).into(),
1838 else_normal.unwrap_or(Ty::Builtin(BuiltinTy::None)).into(),
1839 )))
1840 };
1841
1842 ControlSplit {
1843 normal,
1844 returns,
1845 terminal: then.terminal && else_.terminal,
1846 }
1847 }
1848 normal if Self::is_none_like(&normal) => ControlSplit {
1849 normal: None,
1850 returns: vec![],
1851 terminal: false,
1852 },
1853 normal => ControlSplit {
1854 normal: Some(normal),
1855 returns: vec![],
1856 terminal: false,
1857 },
1858 }
1859 }
1860
1861 fn join(&mut self, child: Ty) {
1862 if self.break_or_continue_or_return {
1863 return;
1864 }
1865
1866 let ControlSplit {
1867 normal,
1868 returns,
1869 terminal,
1870 } = Self::split_control(child);
1871
1872 self.returns.extend(returns);
1873 if let Some(normal) = normal {
1874 self.join_normal(normal);
1875 }
1876 if terminal {
1877 self.break_or_continue_or_return = true;
1878 }
1879 }
1880
1881 fn join_normal(&mut self, child: Ty) {
1882 if matches!(self.definite, Ty::Any) && !matches!(child, Ty::Any) {
1883 self.definite = Ty::Builtin(BuiltinTy::None);
1884 }
1885
1886 match (child, &self.definite) {
1887 (Ty::Builtin(BuiltinTy::Space | BuiltinTy::None), _) => {}
1888 (Ty::Builtin(BuiltinTy::Clause | BuiltinTy::FlowNone), _) => {}
1889 (Ty::Any, _) => self.definite = Ty::Any,
1890 (Ty::Var(var), _) => self.possibles.push(Ty::Var(var)),
1891 (Ty::Array(arr), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Array(arr),
1893 (Ty::Array(..), _) => self.definite = Ty::undef(),
1894 (Ty::Tuple(elems), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Tuple(elems),
1895 (Ty::Tuple(..), _) => self.definite = Ty::undef(),
1896 (Ty::Builtin(ty), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Builtin(ty),
1899 (Ty::Builtin(..), _) => self.definite = Ty::undef(),
1900 (Ty::Value(ins_ty), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Value(ins_ty),
1902 (Ty::Value(..), _) => self.definite = Ty::undef(),
1903 (Ty::Func(func), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Func(func),
1904 (Ty::Func(..), _) => self.definite = Ty::undef(),
1905 (Ty::Dict(dict), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Dict(dict),
1906 (Ty::Dict(..), _) => self.definite = Ty::undef(),
1907 (Ty::With(with), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::With(with),
1908 (Ty::With(..), _) => self.definite = Ty::undef(),
1909 (Ty::Args(args), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Args(args),
1910 (Ty::Args(..), _) => self.definite = Ty::undef(),
1911 (Ty::Pattern(pat), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Pattern(pat),
1912 (Ty::Pattern(..), _) => self.definite = Ty::undef(),
1913 (Ty::Select(sel), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Select(sel),
1914 (Ty::Select(..), _) => self.definite = Ty::undef(),
1915 (Ty::Unary(unary), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Unary(unary),
1916 (Ty::Unary(..), _) => self.definite = Ty::undef(),
1917 (Ty::Binary(binary), Ty::Builtin(BuiltinTy::None)) => {
1918 self.definite = Ty::Binary(binary)
1919 }
1920 (Ty::Binary(..), _) => self.definite = Ty::undef(),
1921 (Ty::If(if_ty), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::If(if_ty),
1922 (Ty::If(..), _) => self.definite = Ty::undef(),
1923 (Ty::Union(types), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Union(types),
1924 (Ty::Union(..), _) => self.definite = Ty::undef(),
1925 (Ty::Let(bounds), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Let(bounds),
1926 (Ty::Let(..), _) => self.definite = Ty::undef(),
1927 (Ty::Param(param), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Param(param),
1928 (Ty::Param(..), _) => self.definite = Ty::undef(),
1929 (Ty::Boolean(b), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Boolean(b),
1930 (Ty::Boolean(..), _) => self.definite = Ty::undef(),
1931 }
1932 }
1933}
1934impl Default for Joiner {
1935 fn default() -> Self {
1936 Self {
1937 break_or_continue_or_return: false,
1938 definite: Ty::Builtin(BuiltinTy::None),
1939 possibles: Vec::new(),
1940 returns: Vec::new(),
1941 }
1942 }
1943}
1944
1945struct VarReplacer {
1946 def: DeclExpr,
1947 with: Ty,
1948}
1949
1950impl TyMutator for VarReplacer {
1951 fn mutate(&mut self, ty: &Ty, pol: bool) -> Option<Ty> {
1952 if let Ty::Var(var) = ty
1953 && var.def == self.def
1954 {
1955 return Some(self.with.clone());
1956 }
1957
1958 self.mutate_rec(ty, pol)
1959 }
1960}
1961
1962#[cfg(test)]
1963mod function_resultant_closer_tests {
1964 use super::*;
1965
1966 #[test]
1967 #[allow(clippy::mutable_key_type)]
1968 fn signature_binder_collection_visits_shared_type_once() {
1969 const DEPTH: usize = 16;
1970
1971 let def: DeclExpr = Decl::lit("input").into();
1972 let var = TypeVar::new("input".into(), def);
1973 let var_ty = Ty::Var(var);
1974 let mut shared = Ty::Func(SigTy::unary(var_ty.clone(), var_ty));
1975 for _ in 0..DEPTH {
1976 shared = Ty::Tuple(vec![shared.clone(), shared].into());
1977 }
1978
1979 let mut seen = FxHashSet::default();
1980 let mut traversed = FxHashSet::default();
1981 let mut binders = vec![];
1982 TypeChecker::collect_signature_input_binders(
1983 &shared,
1984 &FxHashSet::default(),
1985 &mut seen,
1986 &mut traversed,
1987 &mut binders,
1988 );
1989
1990 assert_eq!(binders.len(), 1);
1991 assert!(binders[0].1.is_empty());
1992 assert_eq!(traversed.len(), DEPTH + 2);
1993 }
1994
1995 #[test]
1996 fn memo_isolated_by_signature_binder_scope() {
1997 let def: DeclExpr = Decl::lit("input").into();
1998 let var = TypeVar::new("input".into(), def.clone());
1999 let var_ty = Ty::Var(var.clone());
2000 let bound = Ty::Boolean(Some(true));
2001
2002 let mut var_bounds = DynTypeBounds::default();
2003 var_bounds.lbs.insert_mut(bound.clone());
2004 let mut vars = FxHashMap::default();
2005 vars.insert(def, TypeVarBounds::new(var.as_ref().clone(), var_bounds));
2006
2007 let scoped = Ty::Func(SigTy::unary(var_ty.clone(), var_ty.clone()));
2008 let body = Ty::Tuple(vec![var_ty.clone(), scoped.clone(), var_ty.clone()].into());
2009 let mut closer = FunctionResultantCloser::new(&vars, FxHashSet::default());
2010
2011 let closed = closer.mutate(&body, true).expect("outer variables close");
2012 let Ty::Tuple(items) = closed else {
2013 panic!("expected tuple resultant");
2014 };
2015 let expected = Ty::Let(
2016 TypeBounds {
2017 lbs: vec![bound],
2018 ubs: vec![],
2019 }
2020 .into(),
2021 );
2022
2023 assert_eq!(items.as_ref(), &[expected.clone(), scoped, expected]);
2024 assert_eq!(closer.visited, 2);
2025 }
2026}