1use 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#[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 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 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 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 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 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 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 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 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 }
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 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 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 }
437 (lhs, Ty::Value(rhs)) => {
438 crate::log_debug_ct!("constrain value {lhs:?} ⪯ {rhs:?}");
439 }
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 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 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 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 (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 (Ty::Builtin(ty), Ty::Builtin(BuiltinTy::None)) => self.definite = Ty::Builtin(ty),
605 (Ty::Builtin(..), _) => self.definite = Ty::undef(),
606 (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}