tinymist_query/analysis/
tyck.rs

1//! Type checking on source file
2
3use std::sync::OnceLock;
4
5use rustc_hash::{FxHashMap, FxHashSet};
6use tinymist_derive::BindTyCtx;
7
8use super::{
9    BuiltinTy, DynTypeBounds, FlowVarKind, SharedContext, TyCtxMut, TypeInfo, TypeVar,
10    TypeVarBounds, prelude::*,
11};
12use crate::{
13    syntax::{Decl, DeclExpr, Expr, ExprInfo, UnaryOp},
14    ty::*,
15};
16
17mod apply;
18mod docs;
19mod select;
20mod syntax;
21
22pub(crate) use apply::*;
23pub(crate) use select::*;
24
25#[derive(Default)]
26pub struct TypeEnv {
27    visiting: FxHashMap<TypstFileId, Arc<TypeInfo>>,
28    exprs: FxHashMap<TypstFileId, Option<ExprInfo>>,
29}
30
31/// Type checking at the source unit level.
32#[typst_macros::time(span = ei.source.root().span())]
33pub(crate) fn type_check(
34    ctx: Arc<SharedContext>,
35    ei: ExprInfo,
36    env: &mut TypeEnv,
37) -> Arc<TypeInfo> {
38    let mut info = TypeInfo::default();
39    info.valid = true;
40    info.fid = Some(ei.fid);
41    info.revision = ei.revision;
42
43    env.visiting.insert(ei.fid, Arc::new(TypeInfo::default()));
44
45    // Retrieve expression information for the source.
46    let root = ei.root.clone();
47
48    let mut checker = TypeChecker {
49        ctx,
50        ei,
51        info,
52        env,
53        call_cache: Default::default(),
54        module_exports: Default::default(),
55    };
56
57    let type_check_start = tinymist_std::time::Instant::now();
58
59    checker.check(&root);
60
61    let exports = checker
62        .ei
63        .exports
64        .clone()
65        .into_iter()
66        .map(|(k, v)| (k.clone(), checker.check(v)))
67        .collect();
68    checker.info.exports = exports;
69
70    let elapsed = type_check_start.elapsed();
71    crate::log_debug_ct!("Type checking on {:?} took {elapsed:?}", checker.ei.fid);
72
73    checker.env.visiting.remove(&checker.ei.fid);
74
75    Arc::new(checker.info)
76}
77
78type CallCacheDesc = (
79    Interned<SigTy>,
80    Interned<SigTy>,
81    Option<Vec<Interned<SigTy>>>,
82);
83
84pub(crate) struct TypeChecker<'a> {
85    ctx: Arc<SharedContext>,
86    ei: ExprInfo,
87
88    info: TypeInfo,
89    module_exports: FxHashMap<(TypstFileId, Interned<str>), OnceLock<Option<Ty>>>,
90
91    call_cache: FxHashSet<CallCacheDesc>,
92
93    env: &'a mut TypeEnv,
94}
95
96impl TyCtx for TypeChecker<'_> {
97    fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<DynTypeBounds> {
98        self.info.global_bounds(var, pol)
99    }
100
101    fn local_bind_of(&self, var: &Interned<TypeVar>) -> Option<Ty> {
102        self.info.local_bind_of(var)
103    }
104}
105
106impl TyCtxMut for TypeChecker<'_> {
107    type Snap = <TypeInfo as TyCtxMut>::Snap;
108
109    fn start_scope(&mut self) -> Self::Snap {
110        self.info.start_scope()
111    }
112
113    fn end_scope(&mut self, snap: Self::Snap) {
114        self.info.end_scope(snap)
115    }
116
117    fn bind_local(&mut self, var: &Interned<TypeVar>, ty: Ty) {
118        self.info.bind_local(var, ty);
119    }
120
121    fn type_of_func(&mut self, func: &Func) -> Option<Interned<SigTy>> {
122        Some(self.ctx.type_of_func(func.clone()).type_sig())
123    }
124
125    fn type_of_value(&mut self, val: &Value) -> Ty {
126        self.ctx.type_of_value(val)
127    }
128
129    fn check_module_item(&mut self, fid: TypstFileId, name: &StrRef) -> Option<Ty> {
130        self.module_exports
131            .entry((fid, name.clone()))
132            .or_default()
133            .clone()
134            .get_or_init(|| {
135                let ei = self
136                    .env
137                    .exprs
138                    .entry(fid)
139                    .or_insert_with(|| self.ctx.expr_stage_by_id(fid))
140                    .clone()?;
141
142                Some(self.check(ei.exports.get(name)?))
143            })
144            .clone()
145    }
146}
147
148impl TypeChecker<'_> {
149    fn check(&mut self, expr: &Expr) -> Ty {
150        self.check_syntax(expr).unwrap_or(Ty::undef())
151    }
152
153    fn copy_doc_vars(
154        &mut self,
155        fr: &TypeVarBounds,
156        var: &Interned<TypeVar>,
157        base: &Interned<Decl>,
158    ) -> Ty {
159        let mut gen_var = var.as_ref().clone();
160        let encoded = Interned::new(Decl::docs(base.clone(), var.clone()));
161        gen_var.def = encoded.clone();
162        crate::log_debug_ct!("copy var {fr:?} as {encoded:?}");
163        let bounds = TypeVarBounds::new(gen_var, fr.bounds.bounds().read().clone());
164        let var = bounds.as_type();
165        self.info.vars.insert(encoded, bounds);
166        var
167    }
168
169    fn get_var(&mut self, decl: &DeclExpr) -> Interned<TypeVar> {
170        crate::log_debug_ct!("get_var {decl:?}");
171        let entry = self.info.vars.entry(decl.clone()).or_insert_with(|| {
172            let name = decl.name().clone();
173            let decl = decl.clone();
174
175            // Check External variables
176            let init = decl.file_id().and_then(|fid| {
177                if fid == self.ei.fid {
178                    return None;
179                }
180
181                crate::log_debug_ct!("import_ty {name} from {fid:?}");
182
183                let ext_def_use_info = self.ctx.expr_stage_by_id(fid)?;
184                let source = &ext_def_use_info.source;
185                // todo: check types in cycle
186                let ext_type_info = if let Some(scheme) = self.env.visiting.get(&source.id()) {
187                    scheme.clone()
188                } else {
189                    self.ctx.clone().type_check_(source, self.env)
190                };
191                let ext_def = ext_def_use_info.exports.get(&name)?;
192
193                // todo: rest expressions
194                let def = match ext_def {
195                    Expr::Decl(decl) => {
196                        let ext_ty = ext_type_info.vars.get(decl)?.as_type();
197                        if let Some(ext_docs) = ext_type_info.var_docs.get(decl) {
198                            self.info.var_docs.insert(decl.clone(), ext_docs.clone());
199                        }
200
201                        ext_type_info.simplify(ext_ty, false)
202                    }
203                    _ => return None,
204                };
205
206                Some(ext_type_info.to_bounds(def))
207            });
208
209            TypeVarBounds::new(TypeVar { name, def: decl }, init.unwrap_or_default())
210        });
211
212        let var = entry.var.clone();
213
214        let s = decl.span();
215        if !s.is_detached() {
216            // todo: record decl types
217            // let should_record = matches!(root.kind(), SyntaxKind::FuncCall).then(||
218            // root.span());
219            // if let Some(s) = should_record {
220            //     self.info.witness_at_least(s, w.clone());
221            // }
222
223            TypeInfo::witness_(s, Ty::Var(var.clone()), &mut self.info.mapping);
224        }
225        var
226    }
227
228    fn constrain_sig_inputs(
229        &mut self,
230        sig: &Interned<SigTy>,
231        args: &Interned<SigTy>,
232        with: Option<&Vec<Interned<SigTy>>>,
233    ) {
234        let call_desc = (sig.clone(), args.clone(), with.cloned());
235        if !self.call_cache.insert(call_desc) {
236            return;
237        }
238
239        for (arg_recv, arg_ins) in sig.matches(args, with) {
240            self.constrain(arg_ins, arg_recv);
241        }
242    }
243
244    fn constrain(&mut self, lhs: &Ty, rhs: &Ty) {
245        static FLOW_STROKE_DICT_TYPE: LazyLock<Ty> =
246            LazyLock::new(|| Ty::Dict(FLOW_STROKE_DICT.clone()));
247        static FLOW_MARGIN_DICT_TYPE: LazyLock<Ty> =
248            LazyLock::new(|| Ty::Dict(FLOW_MARGIN_DICT.clone()));
249        static FLOW_INSET_DICT_TYPE: LazyLock<Ty> =
250            LazyLock::new(|| Ty::Dict(FLOW_INSET_DICT.clone()));
251        static FLOW_OUTSET_DICT_TYPE: LazyLock<Ty> =
252            LazyLock::new(|| Ty::Dict(FLOW_OUTSET_DICT.clone()));
253        static FLOW_RADIUS_DICT_TYPE: LazyLock<Ty> =
254            LazyLock::new(|| Ty::Dict(FLOW_RADIUS_DICT.clone()));
255        static FLOW_TEXT_FONT_DICT_TYPE: LazyLock<Ty> =
256            LazyLock::new(|| Ty::Dict(FLOW_TEXT_FONT_DICT.clone()));
257
258        fn is_ty(ty: &Ty) -> bool {
259            match ty {
260                Ty::Builtin(BuiltinTy::Type(..)) => true,
261                Ty::Value(val) => matches!(val.val, Value::Type(..)),
262                _ => false,
263            }
264        }
265
266        if lhs == rhs {
267            return;
268        }
269
270        match (lhs, rhs) {
271            (Ty::Var(v), Ty::Var(w)) => {
272                if v.def == w.def {
273                    return;
274                }
275
276                // todo: merge
277
278                let _ = v.def;
279                let _ = w.def;
280            }
281            (Ty::Var(v), rhs) => {
282                crate::log_debug_ct!("constrain var {v:?} ⪯ {rhs:?}");
283                let w = self.info.vars.get_mut(&v.def).unwrap();
284                // strict constraint on upper bound
285                let bound = rhs.clone();
286                match &w.bounds {
287                    FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
288                        let mut w = w.write();
289                        w.ubs.insert_mut(bound);
290                    }
291                }
292            }
293            (lhs, Ty::Var(v)) => {
294                let w = self.info.vars.get(&v.def).unwrap();
295                let bound = self.weaken_constraint(lhs, &w.bounds);
296                crate::log_debug_ct!("constrain var {v:?} ⪰ {bound:?}");
297                match &w.bounds {
298                    FlowVarKind::Strong(v) | FlowVarKind::Weak(v) => {
299                        let mut v = v.write();
300                        v.lbs.insert_mut(bound);
301                    }
302                };
303            }
304            (Ty::Select(sel), rhs) => {
305                // Constrain field access `base.field` by constraining `base` with a record type
306                // that contains the field. This enables propagating expected types back into
307                // dictionary literals, e.g. `(cjk: "")` from `fonts.cjk` used as `text(font:
308                // ...)`.
309                let dict = Ty::Dict(RecordTy::new(vec![(sel.select.clone(), rhs.clone())]));
310                self.constrain(sel.ty.as_ref(), &dict);
311            }
312            (Ty::Array(lhs), Ty::Array(rhs)) => {
313                self.constrain(lhs, rhs);
314            }
315            (Ty::Tuple(lhs), Ty::Array(rhs)) => {
316                for lhs in lhs.iter() {
317                    self.constrain(lhs, rhs);
318                }
319            }
320            (Ty::Tuple(lhs), Ty::Tuple(rhs)) => {
321                for (lhs, rhs) in lhs.iter().zip(rhs.iter()) {
322                    self.constrain(lhs, rhs);
323                }
324            }
325            (Ty::Dict(lhs), Ty::Dict(rhs)) => {
326                for (key, lhs, rhs) in lhs.common_iface_fields(rhs) {
327                    crate::log_debug_ct!("constrain record item {key} {lhs:?} ⪯ {rhs:?}");
328                    self.constrain(lhs, rhs);
329                    // if !sl.is_detached() {
330                    //     self.info.witness_at_most(*sl, rhs.clone());
331                    // }
332                    // if !sr.is_detached() {
333                    //     self.info.witness_at_least(*sr, lhs.clone());
334                    // }
335                }
336            }
337            (Ty::Union(types), rhs) => {
338                for ty in types.iter() {
339                    self.constrain(ty, rhs);
340                }
341            }
342            (lhs, Ty::Union(types)) => {
343                for ty in types.iter() {
344                    self.constrain(lhs, ty);
345                }
346            }
347            (lhs, Ty::Builtin(BuiltinTy::Stroke)) => {
348                // empty array is also a constructing dict but we can safely ignore it during
349                // type checking, since no fields are added yet.
350                if lhs.is_dict() {
351                    self.constrain(lhs, &FLOW_STROKE_DICT_TYPE);
352                }
353            }
354            (Ty::Builtin(BuiltinTy::Stroke), rhs) => {
355                if rhs.is_dict() {
356                    self.constrain(&FLOW_STROKE_DICT_TYPE, rhs);
357                }
358            }
359            (lhs, Ty::Builtin(BuiltinTy::Margin)) => {
360                if lhs.is_dict() {
361                    self.constrain(lhs, &FLOW_MARGIN_DICT_TYPE);
362                }
363            }
364            (Ty::Builtin(BuiltinTy::Margin), rhs) => {
365                if rhs.is_dict() {
366                    self.constrain(&FLOW_MARGIN_DICT_TYPE, rhs);
367                }
368            }
369            (lhs, Ty::Builtin(BuiltinTy::Inset)) => {
370                if lhs.is_dict() {
371                    self.constrain(lhs, &FLOW_INSET_DICT_TYPE);
372                }
373            }
374            (Ty::Builtin(BuiltinTy::Inset), rhs) => {
375                if rhs.is_dict() {
376                    self.constrain(&FLOW_INSET_DICT_TYPE, rhs);
377                }
378            }
379            (lhs, Ty::Builtin(BuiltinTy::Outset)) => {
380                if lhs.is_dict() {
381                    self.constrain(lhs, &FLOW_OUTSET_DICT_TYPE);
382                }
383            }
384            (Ty::Builtin(BuiltinTy::Outset), rhs) => {
385                if rhs.is_dict() {
386                    self.constrain(&FLOW_OUTSET_DICT_TYPE, rhs);
387                }
388            }
389            (lhs, Ty::Builtin(BuiltinTy::Radius)) => {
390                if lhs.is_dict() {
391                    self.constrain(lhs, &FLOW_RADIUS_DICT_TYPE);
392                }
393            }
394            (Ty::Builtin(BuiltinTy::Radius), rhs) => {
395                if rhs.is_dict() {
396                    self.constrain(&FLOW_RADIUS_DICT_TYPE, rhs);
397                }
398            }
399            (lhs, Ty::Builtin(BuiltinTy::TextFont)) => {
400                if lhs.is_dict() {
401                    self.constrain(lhs, &FLOW_TEXT_FONT_DICT_TYPE);
402                }
403            }
404            (Ty::Builtin(BuiltinTy::TextFont), rhs) => {
405                if rhs.is_dict() {
406                    self.constrain(&FLOW_TEXT_FONT_DICT_TYPE, rhs);
407                }
408            }
409            (Ty::Unary(lhs), Ty::Unary(rhs)) if lhs.op == rhs.op => {
410                // todo: more information could be extracted from unary constraint structure
411                // e.g. type(l) == type(r)
412                self.constrain(&lhs.lhs, &rhs.lhs);
413            }
414            (Ty::Unary(lhs), rhs) if lhs.op == UnaryOp::TypeOf && is_ty(rhs) => {
415                crate::log_debug_ct!("constrain type of {lhs:?} ⪯ {rhs:?}");
416
417                self.constrain(&lhs.lhs, rhs);
418            }
419            (lhs, Ty::Unary(rhs)) if rhs.op == UnaryOp::TypeOf && is_ty(lhs) => {
420                crate::log_debug_ct!(
421                    "constrain type of {lhs:?} ⪯ {rhs:?} {:?}",
422                    matches!(lhs, Ty::Builtin(..)),
423                );
424                self.constrain(lhs, &rhs.lhs);
425            }
426            (Ty::Func(lhs), Ty::Func(rhs)) => {
427                crate::log_debug_ct!("constrain func {lhs:?} ⪯ {rhs:?}");
428                self.constrain_sig_inputs(lhs, rhs, None);
429            }
430            (Ty::Value(lhs), rhs) => {
431                crate::log_debug_ct!("constrain value {lhs:?} ⪯ {rhs:?}");
432                let _ = TypeInfo::witness_at_most;
433                // if !lhs.1.is_detached() {
434                //     self.info.witness_at_most(lhs.1, rhs.clone());
435                // }
436            }
437            (lhs, Ty::Value(rhs)) => {
438                crate::log_debug_ct!("constrain value {lhs:?} ⪯ {rhs:?}");
439                // if !rhs.1.is_detached() {
440                //     self.info.witness_at_least(rhs.1, lhs.clone());
441                // }
442            }
443            _ => {
444                crate::log_debug_ct!("constrain {lhs:?} ⪯ {rhs:?}");
445            }
446        }
447    }
448
449    fn check_comparable(&self, lhs: &Ty, rhs: &Ty) {
450        let _ = lhs;
451        let _ = rhs;
452    }
453
454    fn check_assignable(&self, lhs: &Ty, rhs: &Ty) {
455        let _ = lhs;
456        let _ = rhs;
457    }
458
459    fn check_containing(&mut self, container: &Ty, elem: &Ty, expected_in: bool) {
460        let rhs = if expected_in {
461            match container {
462                Ty::Tuple(elements) => Ty::Union(elements.clone()),
463                _ => Ty::Unary(TypeUnary::new(UnaryOp::ElementOf, container.clone())),
464            }
465        } else {
466            // todo: remove not element of
467            Ty::Unary(TypeUnary::new(UnaryOp::NotElementOf, container.clone()))
468        };
469
470        self.constrain(elem, &rhs);
471    }
472
473    fn possible_ever_be(&mut self, lhs: &Ty, rhs: &Ty) {
474        // todo: instantiataion
475        match rhs {
476            Ty::Builtin(..) | Ty::Value(..) | Ty::Boolean(..) => {
477                self.constrain(rhs, lhs);
478            }
479            _ => {}
480        }
481    }
482
483    fn weaken(&mut self, v: &Ty) {
484        match v {
485            Ty::Var(v) => {
486                let w = self.info.vars.get_mut(&v.def).unwrap();
487                w.weaken();
488            }
489            Ty::Any | Ty::Boolean(_) | Ty::Builtin(_) | Ty::Value(_) => {}
490            Ty::Param(v) => {
491                self.weaken(&v.ty);
492            }
493            Ty::Func(v) | Ty::Args(v) | Ty::Pattern(v) => {
494                for ty in v.inputs() {
495                    self.weaken(ty);
496                }
497            }
498            Ty::With(v) => {
499                self.weaken(&v.sig);
500                for ty in v.with.inputs() {
501                    self.weaken(ty);
502                }
503            }
504            Ty::Dict(v) => {
505                for (_, ty) in v.interface() {
506                    self.weaken(ty);
507                }
508            }
509            Ty::Array(v) => {
510                self.weaken(v);
511            }
512            Ty::Tuple(v) => {
513                for ty in v.iter() {
514                    self.weaken(ty);
515                }
516            }
517            Ty::Select(v) => {
518                self.weaken(&v.ty);
519            }
520            Ty::Unary(v) => {
521                self.weaken(&v.lhs);
522            }
523            Ty::Binary(v) => {
524                let [lhs, rhs] = v.operands();
525                self.weaken(lhs);
526                self.weaken(rhs);
527            }
528            Ty::If(v) => {
529                self.weaken(&v.cond);
530                self.weaken(&v.then);
531                self.weaken(&v.else_);
532            }
533            Ty::Union(v) => {
534                for ty in v.iter() {
535                    self.weaken(ty);
536                }
537            }
538            Ty::Let(v) => {
539                for ty in v.lbs.iter() {
540                    self.weaken(ty);
541                }
542                for ty in v.ubs.iter() {
543                    self.weaken(ty);
544                }
545            }
546        }
547    }
548
549    fn weaken_constraint(&self, term: &Ty, kind: &FlowVarKind) -> Ty {
550        if matches!(kind, FlowVarKind::Strong(_)) {
551            return term.clone();
552        }
553
554        if let Ty::Value(ins_ty) = term {
555            return BuiltinTy::from_value(&ins_ty.val);
556        }
557
558        term.clone()
559    }
560}
561
562struct Joiner {
563    break_or_continue_or_return: bool,
564    definite: Ty,
565    possibles: Vec<Ty>,
566}
567impl Joiner {
568    fn finalize(self) -> Ty {
569        crate::log_debug_ct!("join: {:?} {:?}", self.possibles, self.definite);
570        if self.possibles.is_empty() {
571            return self.definite;
572        }
573        if self.possibles.len() == 1 {
574            return self.possibles.into_iter().next().unwrap();
575        }
576
577        // let mut definite = self.definite.clone();
578        // for p in &self.possibles {
579        //     definite = definite.join(p);
580        // }
581
582        // crate::log_debug_ct!("possibles: {:?} {:?}", self.definite, self.possibles);
583
584        Ty::Any
585    }
586
587    fn join(&mut self, child: Ty) {
588        if self.break_or_continue_or_return {
589            return;
590        }
591
592        match (child, &self.definite) {
593            (Ty::Builtin(BuiltinTy::Space | BuiltinTy::None), _) => {}
594            (Ty::Builtin(BuiltinTy::Clause | BuiltinTy::FlowNone), _) => {}
595            (Ty::Any, _) | (_, Ty::Any) => {}
596            (Ty::Var(var), _) => self.possibles.push(Ty::Var(var)),
597            // todo: check possibles
598            (Ty::Array(arr), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Array(arr),
599            (Ty::Array(..), _) => self.definite = Ty::undef(),
600            (Ty::Tuple(elems), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Tuple(elems),
601            (Ty::Tuple(..), _) => self.definite = Ty::undef(),
602            // todo: mystery flow none
603            // todo: possible some style (auto)
604            (Ty::Builtin(ty), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Builtin(ty),
605            (Ty::Builtin(..), _) => self.definite = Ty::undef(),
606            // todo: value join
607            (Ty::Value(ins_ty), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Value(ins_ty),
608            (Ty::Value(..), _) => self.definite = Ty::undef(),
609            (Ty::Func(func), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Func(func),
610            (Ty::Func(..), _) => self.definite = Ty::undef(),
611            (Ty::Dict(dict), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Dict(dict),
612            (Ty::Dict(..), _) => self.definite = Ty::undef(),
613            (Ty::With(with), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::With(with),
614            (Ty::With(..), _) => self.definite = Ty::undef(),
615            (Ty::Args(args), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Args(args),
616            (Ty::Args(..), _) => self.definite = Ty::undef(),
617            (Ty::Pattern(pat), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Pattern(pat),
618            (Ty::Pattern(..), _) => self.definite = Ty::undef(),
619            (Ty::Select(sel), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Select(sel),
620            (Ty::Select(..), _) => self.definite = Ty::undef(),
621            (Ty::Unary(unary), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Unary(unary),
622            (Ty::Unary(..), _) => self.definite = Ty::undef(),
623            (Ty::Binary(binary), Ty::Builtin(BuiltinTy::None)) => {
624                self.definite = Ty::Binary(binary)
625            }
626            (Ty::Binary(..), _) => self.definite = Ty::undef(),
627            (Ty::If(if_ty), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::If(if_ty),
628            (Ty::If(..), _) => self.definite = Ty::undef(),
629            (Ty::Union(types), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Union(types),
630            (Ty::Union(..), _) => self.definite = Ty::undef(),
631            (Ty::Let(bounds), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Let(bounds),
632            (Ty::Let(..), _) => self.definite = Ty::undef(),
633            (Ty::Param(param), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Param(param),
634            (Ty::Param(..), _) => self.definite = Ty::undef(),
635            (Ty::Boolean(b), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Boolean(b),
636            (Ty::Boolean(..), _) => self.definite = Ty::undef(),
637        }
638    }
639}
640impl Default for Joiner {
641    fn default() -> Self {
642        Self {
643            break_or_continue_or_return: false,
644            definite: Ty::Builtin(BuiltinTy::None),
645            possibles: Vec::new(),
646        }
647    }
648}