tinymist_query/analysis/
tyck.rs

1//! Type checking on source file
2
3use 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
26/// Owns all type-check state for one stable dependency component.
27struct 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    /// Returns the type variable shared by every member of the component for
64    /// an in-component declaration. The variable is created by whichever
65    /// member first references the declaration.
66    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        // This mirrors dependency-first DFS while remaining independent of
131        // which worker requested the component. Every member writes to the
132        // same pre-created component variables, and no member reads another
133        // member's TypeInfo candidate.
134        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
148/// Type checks every member of a stable component in one standardized
149/// environment and returns only completed results.
150pub(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/// Type checking at the source unit level.
160#[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    // Retrieve expression information for the source.
169    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    // A binder remains live while the current flow value can still be the function input.
220    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            // todo: record decl types
349            // let should_record = matches!(root.kind(), SyntaxKind::FuncCall).then(||
350            // root.span());
351            // if let Some(s) = should_record {
352            //     self.info.witness_at_least(s, w.clone());
353            // }
354
355            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        // todo: rest expressions
382        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        // Binder discovery is monotonic: the first full visit records every
453        // binder reachable from this interned type, regardless of later scopes.
454        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(&param.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(&param.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            // Rebound inputs keep call inference, but never retain a live caller flow variable.
670            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(&param.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                // strict constraint on upper bound
977                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                // Constrain field access `base.field` by constraining `base` with a record type
1002                // that contains the field. This enables propagating expected types back into
1003                // dictionary literals, e.g. `(cjk: "")` from `fonts.cjk` used as `text(font:
1004                // ...)`.
1005                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                    // if !sl.is_detached() {
1027                    //     self.info.witness_at_most(*sl, rhs.clone());
1028                    // }
1029                    // if !sr.is_detached() {
1030                    //     self.info.witness_at_least(*sr, lhs.clone());
1031                    // }
1032                }
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                // empty array is also a constructing dict but we can safely ignore it during
1046                // type checking, since no fields are added yet.
1047                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                // todo: more information could be extracted from unary constraint structure
1108                // e.g. type(l) == type(r)
1109                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                // if !lhs.1.is_detached() {
1134                //     self.info.witness_at_most(lhs.1, rhs.clone());
1135                // }
1136            }
1137            (lhs, Ty::Value(rhs)) => {
1138                crate::log_debug_ct!("constrain value {lhs:?} ⪯ {rhs:?}");
1139                // if !rhs.1.is_detached() {
1140                //     self.info.witness_at_least(rhs.1, lhs.clone());
1141                // }
1142            }
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        // `Any` is the identity of an upper-bound intersection and carries no contract fact.
1179        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        // Preserve the input contract before the body-flow variable is overwritten.
1378        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(&param.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            // todo: remove not element of
1475            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        // todo: instantiataion
1483        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        // let mut definite = definite.clone();
1783        // for p in &possibles {
1784        //     definite = definite.join(p);
1785        // }
1786
1787        // crate::log_debug_ct!("possibles: {:?} {:?}", definite, possibles);
1788
1789        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            // todo: check possibles
1892            (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            // todo: mystery flow none
1897            // todo: possible some style (auto)
1898            (Ty::Builtin(ty), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Builtin(ty),
1899            (Ty::Builtin(..), _) => self.definite = Ty::undef(),
1900            // todo: value join
1901            (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}