1use crate::ast::*;
8use crate::semantics::*;
9use crate::subsume::subsumes;
10use num_bigint::BigInt;
11use std::cell::RefCell;
12use std::collections::{HashMap, HashSet};
13use std::rc::Rc;
14
15#[derive(Clone)]
16pub struct Ty {
17 pub rt: Option<RT>,
18 pub abs: bool,
19}
20pub fn unk() -> Ty {
21 Ty {
22 rt: None,
23 abs: false,
24 }
25}
26pub fn tyv(rt: Option<RT>) -> Ty {
27 Ty { rt, abs: false }
28}
29pub fn prim(name: &str) -> RT {
30 ty(RTk::Prim(name.to_string()))
31}
32fn bool_ty() -> Ty {
33 tyv(Some(prim("bool")))
34}
35
36pub type Report = Rc<dyn Fn(&str, String)>;
37
38#[derive(Clone)]
39pub struct Ctx {
40 pub env: Rc<Env>,
41 pub report: Report,
42 pub vars: HashMap<String, Ty>,
43 pub present: HashSet<String>,
44 pub nonnull: HashSet<String>,
45 pub const_memo: Rc<RefCell<HashMap<String, Ty>>>,
46 pub pos: Rc<RefCell<Option<Rc<Expr>>>>,
48 pub record: Option<Rc<dyn Fn(&Rc<Expr>, &Ty)>>,
50 pub resolve_hook: Option<Rc<dyn Fn(&Rc<Expr>, Option<Target>)>>,
51}
52
53#[derive(Clone)]
55pub struct Target {
56 pub kind: &'static str, pub env: Option<Rc<Env>>,
58 pub name: String,
59}
60pub fn resolve_name(cx: &Ctx, name: &str) -> Option<Target> {
61 if cx.vars.contains_key(name) {
62 return Some(Target {
63 kind: "var",
64 env: None,
65 name: name.to_string(),
66 });
67 }
68 resolve_in(&cx.env, name)
69}
70pub fn resolve_in(env: &Rc<Env>, name: &str) -> Option<Target> {
71 let t = |kind: &'static str| {
72 Some(Target {
73 kind,
74 env: Some(env.clone()),
75 name: name.to_string(),
76 })
77 };
78 if env.consts.borrow().contains_key(name) {
79 return t("const");
80 }
81 if env.funcs.borrow().contains_key(name) {
82 return t("func");
83 }
84 if env.outputs.borrow().iter().any(|(o, _, _)| o == name) {
85 return t("output");
86 }
87 if env.inputs.borrow().contains_key(name) {
88 return t("input");
89 }
90 let im = env.imports.borrow().get(name).cloned();
91 if let Some(im) = im {
92 return resolve_in(&im.env, &im.name).or(Some(Target {
93 kind: "export",
94 env: Some(im.env.clone()),
95 name: im.name.clone(),
96 }));
97 }
98 if env.namespaces.borrow().contains_key(name) {
99 return t("namespace");
100 }
101 if env.type_asts.borrow().contains_key(name) {
102 return t("type");
103 }
104 if env.diags.borrow().contains_key(name) {
105 return t("diagnostic");
106 }
107 None
108}
109impl Ctx {
110 pub fn report(&self, code: &str, msg: String) {
111 (self.report)(code, msg)
112 }
113 pub fn child(&self) -> Ctx {
114 self.clone()
115 }
116 pub fn with_env(&self, env: Rc<Env>) -> Ctx {
117 let mut c = self.clone();
118 c.env = env;
119 c
120 }
121}
122pub fn make_ctx(env: Rc<Env>, report: Report) -> Ctx {
123 Ctx {
124 env,
125 report,
126 vars: HashMap::new(),
127 present: HashSet::new(),
128 nonnull: HashSet::new(),
129 const_memo: Rc::new(RefCell::new(HashMap::new())),
130 pos: Rc::new(RefCell::new(None)),
131 record: None,
132 resolve_hook: None,
133 }
134}
135
136pub fn js_typeof(v: &Value) -> &'static str {
138 match v {
139 Value::Bool(_) => "boolean",
140 Value::Int(_) => "bigint",
141 Value::Float(_) => "number",
142 Value::Str(_) => "string",
143 _ => "object",
144 }
145}
146pub fn js_str(v: &Value) -> String {
147 match v {
148 Value::Null => "null".into(),
149 Value::Bool(b) => {
150 if *b {
151 "true".into()
152 } else {
153 "false".into()
154 }
155 }
156 Value::Int(i) => i.to_string(),
157 Value::Float(f) => js_num_str(*f),
158 Value::Str(s) => s.clone(),
159 other => format!("{other:?}"),
160 }
161}
162pub fn tag(rt: &RT) -> &'static str {
163 match &rt.k {
164 RTk::Prim(_) => "prim",
165 RTk::Lit(_) => "lit",
166 RTk::Range { .. } => "range",
167 RTk::Pattern { .. } => "pattern",
168 RTk::Arr { .. } => "arr",
169 RTk::Map { .. } => "map",
170 RTk::Union(_) => "union",
171 RTk::IsectN(_) => "isectN",
172 RTk::Rec(_) => "rec",
173 RTk::Pred { .. } => "pred",
174 RTk::Ref(_) => "ref",
175 RTk::Quantity(_) => "quantity",
176 RTk::Func { .. } => "func",
177 RTk::Any => "any",
178 }
179}
180fn ret_of(rt: &RT) -> Option<RT> {
182 if matches!(rt.k, RTk::Any) {
183 None
184 } else {
185 Some(rt.clone())
186 }
187}
188pub fn member_ty(m: &Member) -> Option<RT> {
189 match &m.conj {
190 Some(c) => Some(ty(RTk::IsectN(c.clone()))),
191 None => m.ty.clone(),
192 }
193}
194fn find_member<'a>(members: &'a [Member], name: &str) -> Option<&'a Member> {
195 members.iter().find(|m| m.name == name)
196}
197
198fn is_null_lit(t: &RT) -> bool {
200 matches!(&t.k, RTk::Lit(Value::Null)) || matches!(&t.k, RTk::Prim(n) if n == "null")
201}
202pub fn has_null(rt: Option<&RT>) -> bool {
203 match rt {
204 None => false,
205 Some(t) => {
206 is_null_lit(t)
207 || matches!(&t.k, RTk::Union(arms) if arms.iter().any(|a| has_null(Some(a))))
208 }
209 }
210}
211fn strip_null(rt: &RT) -> RT {
212 if let RTk::Union(arms) = &rt.k {
213 let kept: Vec<RT> = arms.iter().filter(|a| !is_null_lit(a)).cloned().collect();
214 return if kept.len() == 1 {
215 kept[0].clone()
216 } else {
217 ty(RTk::Union(kept))
218 };
219 }
220 rt.clone()
221}
222fn same_rt(a: &RT, b: &RT) -> bool {
223 if Rc::ptr_eq(a, b) {
224 return true;
225 }
226 match (&a.k, &b.k) {
227 (RTk::Prim(x), RTk::Prim(y)) => x == y,
228 (RTk::Lit(x), RTk::Lit(y)) => js_typeof(x) == js_typeof(y) && value_eq(x, y),
229 _ => false,
230 }
231}
232pub fn mk_union(arms: Vec<Option<RT>>) -> Option<RT> {
233 if arms.iter().any(|a| a.is_none()) {
234 return None;
235 }
236 let mut flat: Vec<RT> = vec![];
237 for a in arms.into_iter().flatten() {
238 match &a.k {
239 RTk::Union(xs) => flat.extend(xs.iter().cloned()),
240 _ => flat.push(a),
241 }
242 }
243 let mut uniq: Vec<RT> = vec![];
244 for a in flat {
245 if !uniq.iter().any(|b| same_rt(&a, b)) {
246 uniq.push(a);
247 }
248 }
249 Some(if uniq.len() == 1 {
250 uniq[0].clone()
251 } else {
252 ty(RTk::Union(uniq))
253 })
254}
255pub fn num_kind(rt: Option<&RT>) -> Option<String> {
256 let rt = rt?;
257 match &rt.k {
258 RTk::Prim(n) => {
259 if ["int", "float", "string", "bool"].contains(&n.as_str()) {
260 Some(n.clone())
261 } else {
262 None
263 }
264 }
265 RTk::Lit(v) => match v {
266 Value::Bool(_) => Some("bool".into()),
267 Value::Int(_) => Some("int".into()),
268 Value::Float(_) => Some("float".into()),
269 Value::Str(_) => Some("string".into()),
270 _ => None,
271 },
272 RTk::Range { base, .. } => Some(base.clone()),
273 RTk::Pattern { .. } => Some("string".into()),
274 RTk::Pred { base, .. } => num_kind(Some(base)),
275 RTk::Quantity(_) => Some("quantity".into()),
276 RTk::Union(arms) => {
277 let ks: Vec<Option<String>> = arms.iter().map(|a| num_kind(Some(a))).collect();
278 match ks.first() {
279 Some(Some(k0)) if ks.iter().all(|k| k.as_ref() == Some(k0)) => Some(k0.clone()),
280 _ => None,
281 }
282 }
283 _ => None,
284 }
285}
286fn is_boolish(rt: Option<&RT>) -> bool {
287 rt.is_none() || num_kind(rt).as_deref() == Some("bool")
288}
289fn arm_of(rt: Option<&RT>, t: &str) -> Option<RT> {
292 let rt = rt?;
293 match &rt.k {
294 RTk::Ref(target) if t != "ref" => return arm_of(Some(target), t),
295 RTk::Pred { base, .. } => return arm_of(Some(base), t),
296 _ => {}
297 }
298 if tag(rt) == t {
299 return Some(rt.clone());
300 }
301 if let RTk::IsectN(arms) = &rt.k {
302 for x in arms {
303 if let Some(v) = arm_of(Some(x), t) {
304 return Some(v);
305 }
306 }
307 }
308 if let RTk::Union(arms) = &rt.k {
309 let sub: Vec<RT> = arms.iter().filter_map(|x| arm_of(Some(x), t)).collect();
310 if sub.len() == arms.len() && !sub.is_empty() {
311 if t == "arr" {
312 let elems: Vec<RT> = sub
313 .iter()
314 .filter_map(|a| {
315 if let RTk::Arr { elem, .. } = &a.k {
316 Some(elem.clone())
317 } else {
318 None
319 }
320 })
321 .collect();
322 return Some(ty(RTk::Arr {
323 elem: ty(RTk::Union(elems)),
324 lo: None,
325 hi: None,
326 }));
327 }
328 return Some(sub[0].clone());
329 }
330 }
331 None
332}
333
334pub fn path_key(e: &Expr) -> Option<String> {
336 match e {
337 Expr::Name(n) => Some(n.clone()),
338 Expr::Ctx(n) => Some(n.clone()),
339 Expr::Paren(x) => path_key(x),
340 Expr::Member { x, name, safe } => {
341 if *safe {
342 return None;
343 }
344 path_key(x).map(|b| format!("{b}.{name}"))
345 }
346 Expr::Index { x, i } => {
347 let b = path_key(x)?;
348 match &**i {
349 Expr::Lit(v) => Some(format!("{b}[{}]", js_str(v))),
350 Expr::Name(n) => Some(format!("{b}[{n}]")),
351 _ => None,
352 }
353 }
354 _ => None,
355 }
356}
357#[derive(Default)]
358pub struct Guards {
359 pub present: Vec<String>,
360 pub nonnull: Vec<String>,
361}
362fn merge(mut a: Guards, b: Guards) -> Guards {
363 a.present.extend(b.present);
364 a.nonnull.extend(b.nonnull);
365 a
366}
367pub fn guards_of(e: &Expr, polarity: bool) -> Guards {
368 match e {
369 Expr::Paren(x) => guards_of(x, polarity),
370 Expr::Un { op, x } => {
371 if op == "!" {
372 guards_of(x, !polarity)
373 } else {
374 Guards::default()
375 }
376 }
377 Expr::Bin { op, l, r } => {
378 if op == "&&" && polarity {
379 return merge(guards_of(l, true), guards_of(r, true));
380 }
381 if op == "||" && !polarity {
382 return merge(guards_of(l, false), guards_of(r, false));
383 }
384 if op == "in" && polarity {
385 let Some(b) = path_key(r) else {
386 return Guards::default();
387 };
388 return match &**l {
389 Expr::Lit(Value::Str(s)) => Guards {
390 present: vec![format!("{b}.{s}"), format!("{b}[{s}]")],
391 nonnull: vec![],
392 },
393 Expr::Name(n) => Guards {
394 present: vec![format!("{b}[{n}]")],
395 nonnull: vec![],
396 },
397 _ => Guards::default(),
398 };
399 }
400 let null_side = if matches!(&**l, Expr::Lit(Value::Null)) {
401 Some(r)
402 } else if matches!(&**r, Expr::Lit(Value::Null)) {
403 Some(l)
404 } else {
405 None
406 };
407 if let Some(side) = null_side {
408 if let Some(p) = path_key(side) {
409 if (op == "!=" && polarity) || (op == "==" && !polarity) {
410 return Guards {
411 present: vec![],
412 nonnull: vec![p],
413 };
414 }
415 }
416 }
417 Guards::default()
418 }
419 _ => Guards::default(),
420 }
421}
422fn name_bound(cx: &Ctx, n: &str) -> bool {
425 cx.vars.contains_key(n)
426 || cx.env.consts.borrow().contains_key(n)
427 || cx.env.funcs.borrow().contains_key(n)
428 || cx.env.type_asts.borrow().contains_key(n)
429 || cx.env.inputs.borrow().contains_key(n)
430 || cx.env.outputs.borrow().iter().any(|(o, _, _)| o == n)
431}
432pub fn apply_guards(cx: &Ctx, g: Guards) -> Ctx {
433 let mut c2 = cx.child();
434 c2.present.extend(g.present);
435 c2.nonnull.extend(g.nonnull);
436 c2
437}
438
439#[derive(Clone, Copy)]
442pub enum StdRet {
443 Unknown,
444 Int,
445 Bool,
446 Str,
447 Float,
448 ArrStr,
449 PredFn,
450}
451pub const STD: &[(&str, usize, StdRet)] = &[
453 ("array.count", 1, StdRet::Int),
454 ("array.all", 2, StdRet::Bool),
455 ("array.any", 2, StdRet::Bool),
456 ("array.filter", 2, StdRet::Unknown),
457 ("array.all_distinct", 1, StdRet::Bool),
458 ("array.sum", 1, StdRet::Unknown),
459 ("array.fold", 3, StdRet::Unknown),
460 ("map.keys", 1, StdRet::ArrStr),
461 ("map.values", 1, StdRet::Unknown),
462 ("string.length", 1, StdRet::Int),
463 ("string.of", 1, StdRet::Str),
464 ("string.join", 2, StdRet::Str),
465 ("string.starts_with", 2, StdRet::Bool),
466 ("string.ends_with", 2, StdRet::Bool),
467 ("string.contains", 2, StdRet::Bool),
468 ("string.split", 2, StdRet::ArrStr),
469 ("map.entries", 1, StdRet::Unknown),
470 ("ref.path", 1, StdRet::Str),
471 ("math.abs", 1, StdRet::Unknown),
472 ("math.min", 2, StdRet::Unknown),
473 ("math.max", 2, StdRet::Unknown),
474 ("math.clog2", 1, StdRet::Int),
475 ("math.floor", 1, StdRet::Int),
476 ("math.ceil", 1, StdRet::Int),
477 ("math.round", 1, StdRet::Int),
478 ("int.of", 1, StdRet::Int),
479 ("int.at_least", 1, StdRet::PredFn),
480 ("int.at_most", 1, StdRet::PredFn),
481 ("float.of", 1, StdRet::Float),
482 ("object.merge", 2, StdRet::Unknown),
483];
484pub fn std_names() -> impl Iterator<Item = &'static str> {
486 STD.iter().map(|e| e.0)
487}
488fn std_sig(name: &str) -> Option<(usize, Option<RT>)> {
489 let e = STD.iter().find(|e| e.0 == name)?;
490 let ret = match e.2 {
491 StdRet::Unknown => None,
492 StdRet::Int => Some(prim("int")),
493 StdRet::Bool => Some(prim("bool")),
494 StdRet::Str => Some(prim("string")),
495 StdRet::Float => Some(prim("float")),
496 StdRet::ArrStr => Some(ty(RTk::Arr {
497 elem: prim("string"),
498 lo: None,
499 hi: None,
500 })),
501 StdRet::PredFn => Some(ty(RTk::Func {
502 params: vec![prim("int")],
503 ret: prim("bool"),
504 })),
505 };
506 Some((e.1, ret))
507}
508
509pub fn type_text(rt: Option<&RT>) -> String {
513 let Some(rt) = rt else {
514 return "unknown".into();
515 };
516 let lit = |v: &Value| match v {
517 Value::Str(s) => crate::semantics::json_str(s),
518 other => js_str(other),
519 };
520 let is_null_arm = |a: &RT| {
521 matches!(&a.k, RTk::Prim(n) if n == "null") || matches!(&a.k, RTk::Lit(Value::Null))
522 };
523 match &rt.k {
524 RTk::Any => "any".into(),
525 RTk::Prim(n) => n.clone(),
526 RTk::Lit(v) => lit(v),
527 RTk::Range { lo, hi, excl, .. } => {
528 format!("{}..{}{}", lit(lo), if *excl { "<" } else { "" }, lit(hi))
529 }
530 RTk::Pattern { src, .. } => format!("/{src}/"),
531 RTk::Quantity(dim) => format!("quantity<{dim}>"),
532 RTk::Ref(t) => format!("ref<{}>", type_text(Some(t))),
533 RTk::Map { key, val } => format!("map<{}, {}>", type_text(Some(key)), type_text(Some(val))),
534 RTk::Arr { elem, lo, hi } => {
535 let b = if lo.is_some() || hi.is_some() {
536 format!(
537 "[{}..{}]",
538 lo.map(|x| x.to_string()).unwrap_or_default(),
539 hi.map(|x| x.to_string()).unwrap_or_default()
540 )
541 } else {
542 "[]".into()
543 };
544 let e = type_text(Some(elem));
545 let wrap = matches!(
547 &elem.k,
548 RTk::Union(_) | RTk::Func { .. } | RTk::Pred { .. } | RTk::Range { .. }
549 );
550 format!("{}{b}", if wrap { format!("({e})") } else { e })
551 }
552 RTk::Union(arms) => {
553 let nn: Vec<&RT> = arms.iter().filter(|a| !is_null_arm(a)).collect();
554 if nn.len() + 1 == arms.len() && nn.len() == 1 {
555 return format!("{}?", type_text(Some(nn[0])));
556 }
557 arms.iter()
558 .map(|a| type_text(Some(a)))
559 .collect::<Vec<_>>()
560 .join(" | ")
561 }
562 RTk::Pred { base, .. } => format!("{} where …", type_text(Some(base))),
563 RTk::Func { params, ret } => format!(
564 "({}) => {}",
565 params
566 .iter()
567 .map(|p| type_text(Some(p)))
568 .collect::<Vec<_>>()
569 .join(", "),
570 type_text(Some(ret))
571 ),
572 RTk::Rec(r) => {
573 if let Some(n) = rt.name.borrow().as_ref() {
574 if !n.starts_with('{') {
575 return n.clone();
576 }
577 }
578 let ms: Vec<String> = r
579 .members
580 .borrow()
581 .iter()
582 .map(|m| {
583 format!(
584 "{}{}{}{}",
585 m.name,
586 if m.kind == MKind::Opt { "?" } else { "" },
587 m.ty.as_ref()
588 .map(|t| format!(": {}", type_text(Some(t))))
589 .unwrap_or_default(),
590 if matches!(m.kind, MKind::Der | MKind::Dflt) {
591 " = …"
592 } else {
593 ""
594 }
595 )
596 })
597 .collect();
598 let open = if r.open.get() {
599 if ms.is_empty() {
600 "..."
601 } else {
602 ", ..."
603 }
604 } else {
605 ""
606 };
607 format!("{{ {}{open} }}", ms.join(", "))
608 }
609 RTk::IsectN(_) => "?".into(),
610 }
611}
612pub fn std_path(e: &Expr) -> Option<String> {
613 match e {
614 Expr::Member {
615 x,
616 name,
617 safe: false,
618 } => {
619 let b = std_path(x)?;
620 Some(if b.is_empty() {
621 name.clone()
622 } else {
623 format!("{b}.{name}")
624 })
625 }
626 Expr::Name(n) if n == "std" => Some(String::new()),
627 _ => None,
628 }
629}
630
631pub fn try_resolve(env: &Rc<Env>, ast: Option<&TypeAst>) -> Option<RT> {
633 env.resolve(ast?, None).ok()
634}
635pub fn named(name: &str) -> TypeAst {
636 TypeAst::Named {
637 name: name.to_string(),
638 args: vec![],
639 preds: None,
640 ext: None,
641 loc: None,
642 }
643}
644
645pub fn require_val(cx: &Ctx, e: &Expr, ty: Ty, what: &str) -> Ty {
646 if ty.abs {
647 let k = path_key(e);
648 if !k.map(|k| cx.present.contains(&k)).unwrap_or(false) {
649 cx.report(
650 "E4050",
651 format!("maybe-absent expression consumed {what} (use ?. / ?? or an `in` guard)"),
652 );
653 }
654 }
655 ty
656}
657
658pub fn infer(cx: &Ctx, e: &Rc<Expr>) -> Ty {
659 let prev = cx.pos.replace(Some(e.clone()));
660 let t = infer0(cx, e);
661 if let Some(r) = &cx.record {
662 r(e, &t);
663 }
664 if let Some(h) = &cx.resolve_hook {
665 if let Expr::Name(n) = &**e {
666 h(e, resolve_name(cx, n));
667 }
668 }
669 *cx.pos.borrow_mut() = prev;
670 t
671}
672fn infer0(cx: &Ctx, e: &Rc<Expr>) -> Ty {
673 match &**e {
674 Expr::Lit(v) => tyv(Some(ty(RTk::Lit(v.clone())))),
675 Expr::Pattern(src) => match pattern_error(src) {
676 Some(bad) => {
677 cx.report("E4119", format!("malformed pattern /{src}/: {bad}"));
678 unk()
679 }
680 None => match compile_pattern(src) {
681 Ok(re) => tyv(Some(ty(RTk::Pattern {
682 src: src.clone(),
683 re,
684 }))),
685 Err(_) => unk(),
686 },
687 },
688 Expr::UnitLit { unit, .. } => match cx.env.unit_info(unit) {
689 Ok((key, _)) => tyv(Some(ty(RTk::Quantity(key)))),
690 Err(msg) => {
691 cx.report("E4073", msg);
692 unk()
693 }
694 },
695 Expr::Template(parts) => {
696 for p in parts {
697 if let TPart::Expr(x) = p {
698 require_val(cx, x, infer(cx, x), "in a template");
699 }
700 }
701 tyv(Some(prim("string")))
702 }
703 Expr::Name(name) => {
704 if let Some(t) = cx.vars.get(name) {
705 return t.clone();
706 }
707 let env = &cx.env;
708 if env.consts.borrow().contains_key(name) {
709 return const_ty(cx, name);
710 }
711 if env.funcs.borrow().contains_key(name) {
712 return tyv(Some(func_rt(cx, name)));
713 }
714 if name == "std" {
715 return unk();
716 }
717 let out = env
718 .outputs
719 .borrow()
720 .iter()
721 .find(|(o, _, _)| o == name)
722 .map(|(_, t, _)| t.clone());
723 if let Some(t) = out {
724 return tyv(try_resolve(env, Some(&t)));
725 }
726 let inp = env.inputs.borrow().get(name).map(|(t, _)| t.clone());
727 if let Some(t) = inp {
728 return tyv(try_resolve(env, Some(&t)));
729 }
730 let im = env.imports.borrow().get(name).cloned();
731 if let Some(im) = im {
732 return imported_ty(cx, &im);
733 }
734 if env.namespaces.borrow().contains_key(name) {
735 cx.report("E3008", format!("namespace name {name} used as a value"));
736 return unk();
737 }
738 if env.type_asts.borrow().contains_key(name) {
739 cx.report(
740 "E3008",
741 format!("type/namespace name {name} used as a value"),
742 );
743 return unk();
744 }
745 cx.report("E3003", format!("unknown name {name}"));
746 unk()
747 }
748 Expr::Ctx(n) => cx.vars.get(n).cloned().unwrap_or_else(unk),
749 Expr::Referrers { ty: tn, .. } => {
750 let rt = try_resolve(&cx.env, Some(&named(tn)));
751 match &rt {
752 None => cx.report("E4091", format!("$referrers: unknown record type {tn}")),
753 Some(r) if !is_rec(r) => {
754 cx.report("E4091", format!("$referrers: {tn} is not a record type"))
755 }
756 _ => {}
757 }
758 tyv(rt.filter(is_rec).map(|r| {
759 ty(RTk::Arr {
760 elem: ty(RTk::Ref(r)),
761 lo: None,
762 hi: None,
763 })
764 }))
765 }
766 Expr::Obj(entries) => {
767 for (_, v) in entries {
768 require_val(cx, v, infer(cx, v), "as a construction member");
769 }
770 unk() }
772 Expr::Arr(items) => {
773 let ts: Vec<Option<RT>> = items
774 .iter()
775 .map(|(spread, x)| {
776 let t = require_val(cx, x, infer(cx, x), "as an array element");
777 if *spread {
778 t.rt.and_then(|r| {
779 if let RTk::Arr { elem, .. } = &r.k {
780 Some(elem.clone())
781 } else {
782 None
783 }
784 })
785 } else {
786 t.rt
787 }
788 })
789 .collect();
790 tyv(mk_union(ts).map(|elem| {
791 ty(RTk::Arr {
792 elem,
793 lo: None,
794 hi: None,
795 })
796 }))
797 }
798 Expr::Comp { head, clauses } => {
799 let c2 = bind_clauses(cx, clauses);
800 let h = require_val(&c2, head, infer(&c2, head), "as a comprehension element");
801 tyv(h.rt.map(|elem| {
802 ty(RTk::Arr {
803 elem,
804 lo: None,
805 hi: None,
806 })
807 }))
808 }
809 Expr::MapComp { key, val, clauses } => {
810 let c2 = bind_clauses(cx, clauses);
811 let k = require_val(&c2, key, infer(&c2, key), "as a map key");
812 if k.rt.is_some() && num_kind(k.rt.as_ref()).as_deref() != Some("string") {
813 cx.report("E4001", "map-comprehension key is not a string".into());
814 }
815 let v = require_val(&c2, val, infer(&c2, val), "as a map value");
816 tyv(v.rt.map(|val| {
817 ty(RTk::Map {
818 key: prim("string"),
819 val,
820 })
821 }))
822 }
823 Expr::Bin { .. } => infer_bin(cx, e),
824 Expr::Un { op, x } => {
825 let t = require_val(cx, x, infer(cx, x), &format!("as `{op}` operand"));
826 if op == "!" {
827 if t.rt.is_some() && !is_boolish(t.rt.as_ref()) {
828 cx.report("E4071", "`!` on a non-bool operand".into());
829 }
830 return bool_ty();
831 }
832 if op == "~" {
833 if t.rt.is_some() && num_kind(t.rt.as_ref()).as_deref() != Some("int") {
834 cx.report("E4071", "`~` on a non-int operand".into());
835 }
836 return tyv(Some(prim("int")));
837 }
838 let k = num_kind(t.rt.as_ref());
839 if t.rt.is_some()
840 && !matches!(k.as_deref(), Some("int") | Some("float") | Some("quantity"))
841 {
842 cx.report("E4071", "unary `-` on a non-numeric operand".into());
843 }
844 tyv(match k.as_deref() {
845 Some("int") => Some(prim("int")),
846 Some("float") => Some(prim("float")),
847 _ => None,
848 })
849 }
850 Expr::Paren(x) => infer(cx, x),
851 Expr::If { c, t, f } => {
852 let ct = require_val(cx, c, infer(cx, c), "as a condition");
853 if ct.rt.is_some() && !is_boolish(ct.rt.as_ref()) {
854 cx.report("E4001", "`if` condition is not bool".into());
855 }
856 let tt = infer(&apply_guards(cx, guards_of(c, true)), t);
857 let ft = infer(&apply_guards(cx, guards_of(c, false)), f);
858 Ty {
859 rt: mk_union(vec![tt.rt, ft.rt]),
860 abs: tt.abs || ft.abs,
861 }
862 }
863 Expr::Lambda { params, body } => {
864 let mut c2 = cx.child();
865 for p in params {
866 if name_bound(&c2, p) {
867 cx.report(
868 "E3019",
869 format!("lambda parameter {p} shadows an enclosing name"),
870 );
871 }
872 c2.vars.insert(p.clone(), unk());
873 }
874 infer(&c2, body);
875 unk()
876 }
877 Expr::Call { .. } => infer_call(cx, e),
878 Expr::Member { .. } => infer_member(cx, e),
879 Expr::Index { x, .. } => {
880 let b = require_val(cx, x, infer(cx, x), "for indexing");
881 index_core(cx, b, e)
882 }
883 Expr::With { base, patch } => {
884 let b = require_val(cx, base, infer(cx, base), "as `with` base");
885 let brt = match &b.rt {
886 Some(r) => match &r.k {
887 RTk::Ref(t) => Some(t.clone()),
888 _ => Some(r.clone()),
889 },
890 None => None,
891 };
892 if let Some(r) = &brt {
893 if !is_rec(r) {
894 cx.report("E4080", "`with` on a non-record base".into());
895 return unk();
896 }
897 }
898 if let (Expr::Obj(entries), Some(r)) = (&**patch, &brt) {
899 let RTk::Rec(rec) = &r.k else { unreachable!() };
900 let members = rec.members.borrow();
901 for (k, _) in entries {
902 match find_member(&members, k) {
903 None if !rec.open.get() => {
904 cx.report("E4080", format!("`with` updates unknown member {k}"))
905 }
906 Some(m) if m.kind == MKind::Der => {
907 cx.report("E4080", format!("`with` updates derived member {k}"))
908 }
909 _ => {}
910 }
911 }
912 }
913 if let Expr::Obj(entries) = &**patch {
914 for (_, v) in entries {
915 require_val(cx, v, infer(cx, v), "as a `with` update");
916 }
917 } else {
918 infer(cx, patch);
919 }
920 tyv(brt)
921 }
922 Expr::Match { .. } => infer_match(cx, e, None),
923 }
924}
925
926fn bind_clauses(cx: &Ctx, clauses: &[ForClause]) -> Ctx {
927 let mut c2 = cx.child();
928 for cl in clauses {
929 let vt = iter_var_ty(&c2, &cl.iter);
930 if name_bound(&c2, &cl.v) {
931 cx.report(
932 "E3019",
933 format!("comprehension variable {} shadows an enclosing name", cl.v),
934 );
935 }
936 c2.vars.insert(cl.v.clone(), vt);
937 for f in &cl.filters {
938 require_val(&c2, f, infer(&c2, f), "as a filter");
939 c2 = apply_guards(&c2, guards_of(f, true));
940 }
941 }
942 c2
943}
944
945fn iter_var_ty(cx: &Ctx, it: &Rc<Expr>) -> Ty {
946 let t = require_val(cx, it, infer(cx, it), "as an iterable");
947 if let Expr::Bin { op, l, r } = &**it {
948 if op == ".." || op == "..<" {
949 let lo = if let Expr::Lit(v) = &**l {
950 Some(v.clone())
951 } else {
952 None
953 };
954 let hi = if let Expr::Lit(v) = &**r {
955 Some(v.clone())
956 } else {
957 None
958 };
959 if matches!(lo, Some(Value::Float(_))) || matches!(hi, Some(Value::Float(_))) {
960 cx.report("E4115", "comprehension over a float range".into());
961 return unk();
962 }
963 if let (Some(lo), Some(hi)) = (lo, hi) {
964 return tyv(Some(ty(RTk::Range {
965 lo,
966 hi,
967 excl: op == "..<",
968 base: "int".into(),
969 })));
970 }
971 return tyv(Some(prim("int")));
972 }
973 }
974 let Some(rt) = &t.rt else { return unk() };
975 if let Some(a) = arm_of(Some(rt), "arr") {
976 if let RTk::Arr { elem, .. } = &a.k {
977 return tyv(Some(elem.clone()));
978 }
979 }
980 let what = if arm_of(Some(rt), "map").is_some() {
981 "map (use std.map.keys/values)"
982 } else {
983 "value"
984 };
985 cx.report("E4115", format!("comprehension over a non-iterable {what}"));
986 unk()
987}
988
989fn q_dim(rt: Option<&RT>) -> Option<String> {
990 match rt.map(|r| &r.k) {
991 Some(RTk::Quantity(d)) => Some(d.clone()),
992 Some(RTk::Pred { base, .. }) => match &base.k {
993 RTk::Quantity(d) => Some(d.clone()),
994 _ => None,
995 },
996 _ => None,
997 }
998}
999
1000fn infer_bin(cx: &Ctx, e: &Rc<Expr>) -> Ty {
1001 let Expr::Bin { op, l, r } = &**e else {
1002 return unk();
1003 };
1004 let op = op.as_str();
1005 if op == "|>" {
1006 let call = match &**r {
1008 Expr::Call { fun, args } => {
1009 let mut a = vec![l.clone()];
1010 a.extend(args.iter().cloned());
1011 Expr::Call {
1012 fun: fun.clone(),
1013 args: a,
1014 }
1015 }
1016 _ => Expr::Call {
1017 fun: r.clone(),
1018 args: vec![l.clone()],
1019 },
1020 };
1021 return infer_call(cx, &Rc::new(call));
1022 }
1023 if op == "??" {
1024 let lt = infer(cx, l); let rt = require_val(cx, r, infer(cx, r), "as `??` fallback");
1026 return tyv(match (lt.rt, rt.rt) {
1027 (Some(a), Some(b)) => mk_union(vec![Some(strip_null(&a)), Some(b)]),
1028 _ => None,
1029 });
1030 }
1031 if op == "&&" || op == "||" {
1032 let lt = require_val(cx, l, infer(cx, l), &format!("as `{op}` operand"));
1033 if lt.rt.is_some() && !is_boolish(lt.rt.as_ref()) {
1034 cx.report("E4071", format!("`{op}` on a non-bool operand"));
1035 }
1036 let c2 = apply_guards(cx, guards_of(l, op == "&&"));
1037 let rt = require_val(&c2, r, infer(&c2, r), &format!("as `{op}` operand"));
1038 if rt.rt.is_some() && !is_boolish(rt.rt.as_ref()) {
1039 cx.report("E4071", format!("`{op}` on a non-bool operand"));
1040 }
1041 return bool_ty();
1042 }
1043 if op == "in" {
1044 require_val(cx, l, infer(cx, l), "as `in` key");
1045 let rt = require_val(cx, r, infer(cx, r), "as `in` container");
1046 let rrt = rt.rt.map(|x| match &x.k {
1047 RTk::Ref(t) => t.clone(),
1048 _ => x,
1049 });
1050 if let (Some(rr), Expr::Lit(Value::Str(key))) = (&rrt, &**l) {
1051 if let RTk::Rec(rec) = &rr.k {
1052 let members = rec.members.borrow();
1053 match find_member(&members, key) {
1054 Some(m) if m.kind != MKind::Opt => cx.report(
1055 "E4054",
1056 format!("`in` on member {key}, which is not optional"),
1057 ),
1058 None if !rec.open.get() => cx.report(
1059 "E4054",
1060 format!("`in` on undeclared member {key} of a closed record"),
1061 ),
1062 _ => {}
1063 }
1064 }
1065 }
1066 return bool_ty();
1067 }
1068 if op == ".." || op == "..<" {
1069 require_val(cx, l, infer(cx, l), "as a range endpoint");
1070 require_val(cx, r, infer(cx, r), "as a range endpoint");
1071 return unk(); }
1073 let lt = require_val(cx, l, infer(cx, l), &format!("as `{op}` operand"));
1074 let rt = require_val(cx, r, infer(cx, r), &format!("as `{op}` operand"));
1075 if op == "matches" {
1076 if lt.rt.is_some() && num_kind(lt.rt.as_ref()).as_deref() != Some("string") {
1077 cx.report("E4071", "`matches` needs a string left operand".into());
1078 }
1079 return bool_ty();
1080 }
1081 if op == "==" || op == "!=" {
1082 return bool_ty();
1083 }
1084 let lk = num_kind(lt.rt.as_ref());
1085 let rk = num_kind(rt.rt.as_ref());
1086 let cmp = ["<", "<=", ">", ">="].contains(&op);
1087 let (lks, rks) = (lk.as_deref(), rk.as_deref());
1088 if lks == Some("quantity") || rks == Some("quantity") {
1089 if op == "+" || op == "-" || cmp {
1092 if lt.rt.is_some() && rt.rt.is_some() {
1093 if lks != Some("quantity") || rks != Some("quantity") {
1094 let other = if lks == Some("quantity") { rks } else { lks };
1095 cx.report(
1096 "E4071",
1097 format!("`{op}` mixes quantity and {}", other.unwrap_or("null")),
1098 );
1099 } else {
1100 let (a, b) = (q_dim(lt.rt.as_ref()), q_dim(rt.rt.as_ref()));
1101 if let (Some(a), Some(b)) = (&a, &b) {
1102 if a != b {
1103 let one = |s: &str| {
1104 if s.is_empty() {
1105 "1".to_string()
1106 } else {
1107 s.to_string()
1108 }
1109 };
1110 cx.report(
1111 "E4072",
1112 format!(
1113 "`{op}` on quantities of different dimensions ({} vs {})",
1114 one(a),
1115 one(b)
1116 ),
1117 );
1118 }
1119 }
1120 }
1121 }
1122 return if cmp {
1123 bool_ty()
1124 } else {
1125 tyv(if lks == Some("quantity") {
1126 lt.rt.clone()
1127 } else {
1128 rt.rt.clone()
1129 })
1130 };
1131 }
1132 if op == "*" || op == "/" {
1133 let (Some(_), Some(_)) = (<.rt, &rt.rt) else {
1134 return unk();
1135 };
1136 let (lv, rv) = (q_dim(lt.rt.as_ref()), q_dim(rt.rt.as_ref()));
1137 let numeric = |k: Option<&str>| matches!(k, Some("int") | Some("float"));
1138 if (lv.is_none() && !numeric(lks)) || (rv.is_none() && !numeric(rks)) {
1139 cx.report("E4071", format!("`{op}` on a non-numeric operand"));
1140 return unk();
1141 }
1142 let key = key_of_vec(&vec_combine(
1143 &lv.as_deref().map(vec_of_key).unwrap_or_default(),
1144 &rv.as_deref().map(vec_of_key).unwrap_or_default(),
1145 if op == "*" { 1 } else { -1 },
1146 ));
1147 return tyv(Some(if key.is_empty() {
1148 prim("float")
1149 } else {
1150 ty(RTk::Quantity(key))
1151 }));
1152 }
1153 cx.report("E4071", format!("`{op}` on quantity operands"));
1154 return unk();
1155 }
1156 if let (Some(_), Some(_), Some(a), Some(b)) = (<.rt, &rt.rt, lks, rks) {
1157 if a != b {
1158 cx.report("E4071", format!("`{op}` mixes {a} and {b} operands"));
1159 }
1160 }
1161 if cmp {
1162 return bool_ty();
1163 }
1164 if ["&", "^", "<<", ">>"].contains(&op) {
1165 if (lt.rt.is_some() && lks != Some("int")) || (rt.rt.is_some() && rks != Some("int")) {
1166 cx.report("E4071", format!("`{op}` on non-int operands"));
1167 }
1168 return tyv(Some(prim("int")));
1169 }
1170 if op == "|" {
1171 if (lt.rt.is_some() && lks != Some("int")) || (rt.rt.is_some() && rks != Some("int")) {
1173 cx.report("E4071", "`|` on non-int operands".into());
1174 }
1175 return tyv(Some(prim("int")));
1176 }
1177 if op == "+" && lks == Some("string") && rks == Some("string") {
1179 return tyv(Some(prim("string")));
1180 }
1181 if let (Some(lrt), Some(rrt), Some(a), Some(_)) = (<.rt, &rt.rt, lks, rks) {
1182 if !["int", "float", "quantity"].contains(&a) {
1183 cx.report("E4071", format!("`{op}` on {a} operands"));
1184 }
1185 if a == "int" && ["+", "-", "*"].contains(&op) {
1186 if let (Some(x), Some(y)) = (as_ival(lrt), as_ival(rrt)) {
1189 let cands: Vec<BigInt> = match op {
1190 "+" => vec![&x.0 + &y.0, &x.1 + &y.1],
1191 "-" => vec![&x.0 - &y.1, &x.1 - &y.0],
1192 _ => vec![&x.0 * &y.0, &x.0 * &y.1, &x.1 * &y.0, &x.1 * &y.1],
1193 };
1194 let lo = cands.iter().min().unwrap().clone();
1195 let hi = cands.iter().max().unwrap().clone();
1196 return tyv(Some(ty(RTk::Range {
1197 lo: Value::Int(lo),
1198 hi: Value::Int(hi),
1199 excl: false,
1200 base: "int".into(),
1201 })));
1202 }
1203 }
1204 return tyv(if a == "int" || a == "float" {
1205 Some(prim(a))
1206 } else {
1207 None
1208 });
1209 }
1210 unk()
1211}
1212
1213fn as_ival(rt: &RT) -> Option<(BigInt, BigInt)> {
1214 match &rt.k {
1215 RTk::Lit(Value::Int(i)) => Some((i.clone(), i.clone())),
1216 RTk::Range {
1217 lo: Value::Int(lo),
1218 hi: Value::Int(hi),
1219 excl,
1220 base,
1221 } if base == "int" => Some((lo.clone(), if *excl { hi - 1 } else { hi.clone() })),
1222 RTk::Union(arms) => {
1223 let ivs: Vec<Option<(BigInt, BigInt)>> = arms.iter().map(as_ival).collect();
1224 if !ivs.is_empty() && ivs.iter().all(|v| v.is_some()) {
1225 let ivs: Vec<(BigInt, BigInt)> = ivs.into_iter().flatten().collect();
1226 let lo = ivs.iter().map(|v| v.0.clone()).min().unwrap();
1227 let hi = ivs.iter().map(|v| v.1.clone()).max().unwrap();
1228 return Some((lo, hi));
1229 }
1230 None
1231 }
1232 RTk::Pred { base, .. } => as_ival(base),
1233 _ => None,
1234 }
1235}
1236
1237fn index_core(cx: &Ctx, b: Ty, e: &Rc<Expr>) -> Ty {
1238 let Expr::Index { i, .. } = &**e else {
1239 return unk();
1240 };
1241 let it = require_val(cx, i, infer(cx, i), "as an index");
1242 let Some(brt) = &b.rt else { return unk() };
1243 if let Some(a) = arm_of(Some(brt), "arr") {
1244 if it.rt.is_some() && num_kind(it.rt.as_ref()).as_deref() != Some("int") {
1245 cx.report("E4071", "array index is not an int".into());
1246 }
1247 if let RTk::Arr { elem, .. } = &a.k {
1248 return tyv(Some(elem.clone()));
1249 }
1250 }
1251 if let Some(m) = arm_of(Some(brt), "map") {
1252 let k = path_key(e);
1253 if let RTk::Map { val, .. } = &m.k {
1254 return Ty {
1255 rt: Some(val.clone()),
1256 abs: !k.map(|k| cx.present.contains(&k)).unwrap_or(false),
1257 };
1258 }
1259 }
1260 if arm_of(Some(brt), "rec").is_some() {
1261 return unk(); }
1263 cx.report("E4071", "indexing a non-collection".into());
1264 unk()
1265}
1266
1267fn imported_ty(cx: &Ctx, ex: &Export) -> Ty {
1269 let t = &ex.env;
1270 let name = &ex.name;
1271 let c = t.consts.borrow().get(name).cloned();
1272 if let Some(c) = c {
1273 return match try_resolve(t, c.ty.as_ref()) {
1276 Some(a) => tyv(Some(a)),
1277 None => infer(&make_ctx(t.clone(), Rc::new(|_, _| {})), &c.expr),
1278 };
1279 }
1280 let f = t.funcs.borrow().get(name).cloned();
1281 if let Some(f) = f {
1282 return tyv(Some(func_rt_of(t, &f)));
1283 }
1284 let out = t
1285 .outputs
1286 .borrow()
1287 .iter()
1288 .find(|(o, _, _)| o == name)
1289 .map(|(_, ty, _)| ty.clone());
1290 if let Some(o) = out {
1291 return tyv(try_resolve(t, Some(&o)));
1292 }
1293 let inp = t.inputs.borrow().get(name).map(|(ty, _)| ty.clone());
1294 if let Some(i) = inp {
1295 return tyv(try_resolve(t, Some(&i)));
1296 }
1297 if t.type_asts.borrow().contains_key(name) {
1298 cx.report("E3008", format!("type name {name} used as a value"));
1299 return unk();
1300 }
1301 unk()
1302}
1303
1304fn infer_member(cx: &Ctx, e: &Rc<Expr>) -> Ty {
1305 let Expr::Member { x, name, safe } = &**e else {
1306 return unk();
1307 };
1308 if std_path(e).is_some() {
1309 return unk(); }
1311 if let Expr::Name(xn) = &**x {
1312 if !cx.vars.contains_key(xn) {
1313 let ns = cx.env.namespaces.borrow().get(xn).map(|(_, ex)| ex.clone());
1314 if let Some(exports) = ns {
1315 let ex = exports.borrow().get(name).cloned();
1316 let Some(ex) = ex else {
1317 cx.report("E3005", format!("namespace {xn} has no export {name}"));
1318 return unk();
1319 };
1320 return imported_ty(cx, &ex);
1321 }
1322 }
1323 }
1324 let b = infer(cx, x);
1325 let key = path_key(x);
1326 if !*safe {
1327 let present = key
1328 .as_ref()
1329 .map(|k| cx.present.contains(k))
1330 .unwrap_or(false);
1331 let nonnull = key
1332 .as_ref()
1333 .map(|k| cx.nonnull.contains(k))
1334 .unwrap_or(false);
1335 if b.abs && !present {
1336 cx.report(
1337 "E4050",
1338 "member access on a maybe-absent expression (use ?. or an `in` guard)".into(),
1339 );
1340 }
1341 if has_null(b.rt.as_ref()) && !nonnull {
1342 cx.report(
1343 "E4051",
1344 format!("member .{name} on a possibly-null expression without ?."),
1345 );
1346 }
1347 }
1348 member_core(cx, b, e)
1349}
1350
1351fn member_core(cx: &Ctx, b: Ty, e: &Rc<Expr>) -> Ty {
1352 let Expr::Member { name, safe, .. } = &**e else {
1353 return unk();
1354 };
1355 let mut brt = b.rt.as_ref().map(strip_null);
1356 if let Some(RTk::Ref(t)) = brt.as_ref().map(|r| &r.k) {
1357 brt = Some(t.clone());
1358 }
1359 if let Some(RTk::Pred { base, .. }) = brt.as_ref().map(|r| &r.k) {
1360 brt = Some(base.clone());
1361 }
1362 if let Some(r) = &brt {
1363 if matches!(r.k, RTk::IsectN(_)) {
1364 brt = arm_of(Some(r), "rec")
1365 .or_else(|| arm_of(Some(r), "map"))
1366 .or_else(|| Some(r.clone()));
1367 }
1368 }
1369 let mk_abs = |t: Ty| {
1370 if *safe {
1371 Ty {
1372 rt: t.rt,
1373 abs: true,
1374 }
1375 } else {
1376 t
1377 }
1378 };
1379 let Some(brt) = brt else { return mk_abs(unk()) };
1380 match &brt.k {
1381 RTk::Rec(rec) => {
1382 let members = rec.members.borrow();
1383 let Some(m) = find_member(&members, name) else {
1384 if !rec.open.get() {
1385 cx.report(
1386 "E4003",
1387 format!(
1388 "member {name} is not declared on {}",
1389 brt.name
1390 .borrow()
1391 .clone()
1392 .unwrap_or_else(|| "this record".into())
1393 ),
1394 );
1395 }
1396 return mk_abs(unk());
1397 };
1398 let rt = member_ty(m);
1399 let present = path_key(e)
1400 .map(|k| cx.present.contains(&k))
1401 .unwrap_or(false);
1402 Ty {
1403 rt,
1404 abs: *safe || (m.kind == MKind::Opt && !present),
1405 }
1406 }
1407 RTk::Map { val, .. } => {
1408 let present = path_key(e)
1409 .map(|k| cx.present.contains(&k))
1410 .unwrap_or(false);
1411 Ty {
1412 rt: Some(val.clone()),
1413 abs: *safe || !present,
1414 }
1415 }
1416 RTk::Union(arms) => {
1417 let parts: Vec<Option<RT>> = arms
1418 .iter()
1419 .map(|a| match &a.k {
1420 RTk::Rec(rec) => {
1421 find_member(&rec.members.borrow(), name).and_then(|m| m.ty.clone())
1422 }
1423 _ => None,
1424 })
1425 .collect();
1426 mk_abs(tyv(mk_union(parts)))
1427 }
1428 RTk::Quantity(_) if name == "value" || name == "unit" => {
1429 mk_abs(tyv(Some(prim(if name == "value" {
1430 "float"
1431 } else {
1432 "string"
1433 }))))
1434 }
1435 _ => mk_abs(unk()),
1436 }
1437}
1438
1439fn func_rt_of(env: &Rc<Env>, f: &FuncEntry) -> RT {
1440 ty(RTk::Func {
1441 params: f
1442 .params
1443 .iter()
1444 .map(|p| try_resolve(env, p.ty.as_ref()).unwrap_or_else(|| ty(RTk::Any)))
1445 .collect(),
1446 ret: try_resolve(env, f.ret.as_ref()).unwrap_or_else(|| ty(RTk::Any)),
1447 })
1448}
1449fn func_rt(cx: &Ctx, name: &str) -> RT {
1450 let f = cx.env.funcs.borrow().get(name).cloned().unwrap();
1451 func_rt_of(&cx.env, &f)
1452}
1453fn const_ty(cx: &Ctx, name: &str) -> Ty {
1454 if let Some(t) = cx.const_memo.borrow().get(name) {
1455 return t.clone();
1456 }
1457 cx.const_memo.borrow_mut().insert(name.to_string(), unk()); let c = cx.env.consts.borrow().get(name).cloned().unwrap();
1459 let anno = try_resolve(&cx.env, c.ty.as_ref());
1460 let t = match anno {
1461 Some(a) => tyv(Some(a)),
1462 None => infer(&make_ctx(cx.env.clone(), Rc::new(|_, _| {})), &c.expr), };
1464 cx.const_memo
1465 .borrow_mut()
1466 .insert(name.to_string(), t.clone());
1467 t
1468}
1469
1470fn infer_call(cx: &Ctx, e: &Rc<Expr>) -> Ty {
1471 let Expr::Call { fun, args } = &**e else {
1472 return unk();
1473 };
1474 if let Some(sp) = std_path(fun) {
1475 let sig = std_sig(&sp);
1476 if sig.is_none() {
1477 cx.report(
1478 "E3003",
1479 format!("std.{sp} does not exist (§13.1: names not listed do not exist)"),
1480 );
1481 }
1482 if let Some((arity, _)) = &sig {
1483 if args.len() != *arity {
1484 cx.report(
1485 "E4062",
1486 format!("std.{sp} expects {arity} argument(s), got {}", args.len()),
1487 );
1488 }
1489 }
1490 for a in args {
1491 if matches!(&**a, Expr::Lambda { .. }) {
1492 infer(cx, a);
1493 continue;
1494 }
1495 require_val(cx, a, infer(cx, a), "as an argument");
1496 }
1497 return tyv(sig.and_then(|(_, r)| r));
1498 }
1499 let f = infer(cx, fun);
1500 let frt = f.rt.filter(|r| matches!(r.k, RTk::Func { .. }));
1501 let (params, ret): (Vec<RT>, Option<RT>) = match frt.as_ref().map(|r| &r.k) {
1502 Some(RTk::Func { params, ret }) => (params.clone(), ret_of(ret)),
1503 _ => (vec![], None),
1504 };
1505 if frt.is_some() && args.len() != params.len() {
1506 cx.report(
1507 "E4062",
1508 format!(
1509 "call expects {} argument(s), got {}",
1510 params.len(),
1511 args.len()
1512 ),
1513 );
1514 }
1515 for (i, a) in args.iter().enumerate() {
1516 let expected: Option<RT> =
1517 if frt.is_some() && i < params.len() && !matches!(params[i].k, RTk::Any) {
1518 Some(params[i].clone())
1519 } else {
1520 None
1521 };
1522 if let (Expr::Lambda { .. }, Some(ex)) = (&**a, &expected) {
1523 if matches!(ex.k, RTk::Func { .. }) {
1524 check_lambda(cx, a, ex);
1525 continue;
1526 }
1527 }
1528 if matches!(&**a, Expr::Lambda { .. }) {
1529 infer(cx, a);
1530 continue;
1531 }
1532 let at = require_val(cx, a, infer(cx, a), "as an argument");
1533 if let (Some(art), Some(ex)) = (&at.rt, &expected) {
1534 if !subsumes(&cx.env, art, ex) && !deferrable(art, ex) {
1535 cx.report(
1536 "E4001",
1537 format!("argument {} is not assignable to its parameter", i + 1),
1538 );
1539 }
1540 }
1541 }
1542 tyv(if frt.is_some() { ret } else { None })
1543}
1544
1545fn check_lambda(cx: &Ctx, e: &Rc<Expr>, expected: &RT) {
1546 let (Expr::Lambda { params, body }, RTk::Func { params: eps, ret }) = (&**e, &expected.k)
1547 else {
1548 return;
1549 };
1550 if params.len() != eps.len() {
1551 cx.report(
1552 "E4062",
1553 "lambda arity differs from expected function type".into(),
1554 );
1555 return;
1556 }
1557 let mut c2 = cx.child();
1558 for (i, p) in params.iter().enumerate() {
1559 if name_bound(&c2, p) {
1560 cx.report(
1561 "E3019",
1562 format!("lambda parameter {p} shadows an enclosing name"),
1563 );
1564 }
1565 c2.vars.insert(
1566 p.clone(),
1567 tyv(if matches!(eps[i].k, RTk::Any) {
1568 None
1569 } else {
1570 Some(eps[i].clone())
1571 }),
1572 );
1573 }
1574 let b = require_val(&c2, body, infer(&c2, body), "as a lambda result");
1575 if let (Some(brt), Some(r)) = (&b.rt, ret_of(ret)) {
1576 if !subsumes(&cx.env, brt, &r) && !deferrable(brt, &r) {
1577 cx.report(
1578 "E4001",
1579 "lambda body is not assignable to the expected result type".into(),
1580 );
1581 }
1582 }
1583}
1584
1585fn infer_match(cx: &Ctx, e: &Rc<Expr>, expected: Option<&RT>) -> Ty {
1587 let Expr::Match { subject, arms } = &**e else {
1588 return unk();
1589 };
1590 let s = require_val(cx, subject, infer(cx, subject), "as a match subject");
1591 let mut variants: Option<Vec<RT>> = None;
1592 if let Some(srt0) = &s.rt {
1593 let srt = strip_null(srt0);
1594 if let RTk::Union(vs) = &srt.k {
1595 let mut v = vs.clone();
1596 if has_null(Some(srt0)) {
1597 v.push(ty(RTk::Lit(Value::Null)));
1598 }
1599 variants = Some(v);
1600 } else {
1601 cx.report(
1602 "E4103",
1603 "`match` subject is not a discriminable union".into(),
1604 );
1605 }
1606 }
1607 let mut covered: HashSet<usize> = HashSet::new();
1608 let mut catch_alls = 0;
1609 let mut results: Vec<Option<RT>> = vec![];
1610 for arm in arms {
1611 let mut c2 = cx.child();
1612 if name_bound(&c2, &arm.v) {
1613 cx.report(
1614 "E3019",
1615 format!("match binding {} shadows an enclosing name", arm.v),
1616 );
1617 }
1618 let mut arm_ty: Option<RT> = None;
1619 if let Some(t) = &arm.ty {
1620 arm_ty = try_resolve(&cx.env, Some(t));
1621 if arm_ty.is_none() {
1622 cx.report("E3003", "unknown type in match arm".into());
1623 }
1624 if let (Some(vs), Some(at)) = (&variants, &arm_ty) {
1625 for (i, v) in vs.iter().enumerate() {
1626 if subsumes(&cx.env, v, at) {
1627 if covered.contains(&i) {
1628 cx.report("E4100", "match arms overlap on a variant".into());
1629 }
1630 covered.insert(i);
1631 }
1632 }
1633 }
1634 } else {
1635 catch_alls += 1;
1636 if let Some(vs) = &variants {
1637 let rest: Vec<Option<RT>> = vs
1638 .iter()
1639 .enumerate()
1640 .filter(|(i, _)| !covered.contains(i))
1641 .map(|(_, v)| Some(v.clone()))
1642 .collect();
1643 if rest.is_empty() {
1644 cx.report(
1645 "E4102",
1646 "match catch-all is dead (typed arms are exhaustive)".into(),
1647 );
1648 }
1649 arm_ty = mk_union(rest);
1650 }
1651 }
1652 c2.vars.insert(arm.v.clone(), tyv(arm_ty));
1653 let bt = match expected {
1654 Some(ex) => check_expr(&c2, &arm.body, Some(ex)),
1655 None => infer(&c2, &arm.body),
1656 };
1657 let b = require_val(&c2, &arm.body, bt, "as a match result");
1658 results.push(b.rt);
1659 }
1660 if catch_alls > 1 {
1661 cx.report("E4100", "more than one match catch-all arm".into());
1662 }
1663 if let Some(vs) = &variants {
1664 if catch_alls == 0 && covered.len() < vs.len() {
1665 cx.report(
1666 "E4101",
1667 "`match` is not exhaustive over the subject union".into(),
1668 );
1669 }
1670 }
1671 tyv(mk_union(results))
1672}
1673
1674fn place_ty(cx: &Ctx, e: &Rc<Expr>) -> Ty {
1679 match &**e {
1680 Expr::Paren(x) => place_ty(cx, x),
1681 Expr::Member { x, name, .. } => {
1682 let base = place_ty(cx, x);
1683 let rec = base.rt.as_ref().map(|t| match &t.k {
1686 RTk::Ref(target) => target.clone(),
1687 _ => t.clone(),
1688 });
1689 if let Some(rec) = rec {
1690 if let RTk::Rec(r) = &rec.k {
1691 if r.members
1692 .borrow()
1693 .iter()
1694 .any(|m| m.name == *name && m.hidden)
1695 {
1696 cx.report("E4093", format!("`ref` position navigates hidden member {name} — not part of the value (§7.5)"));
1697 }
1698 }
1699 }
1700 member_core(cx, base, e)
1701 }
1702 Expr::Index { x, .. } => index_core(cx, place_ty(cx, x), e),
1703 Expr::If { c, t, f } => {
1704 let ct = require_val(cx, c, infer(cx, c), "as a condition");
1707 if ct.rt.is_some() && !is_boolish(ct.rt.as_ref()) {
1708 cx.report("E4001", "`if` condition is not bool".into());
1709 }
1710 let tt = place_ty(&apply_guards(cx, guards_of(c, true)), t);
1711 let ft = place_ty(&apply_guards(cx, guards_of(c, false)), f);
1712 Ty {
1713 rt: mk_union(vec![tt.rt, ft.rt]),
1714 abs: tt.abs || ft.abs,
1715 }
1716 }
1717 Expr::Name(n) if !cx.vars.contains_key(n) && cx.env.consts.borrow().contains_key(n) => {
1718 cx.report(
1720 "E4093",
1721 format!(
1722 "`ref` position navigates module const {n} — not a root-derived place (§7.5)"
1723 ),
1724 );
1725 infer(cx, e)
1726 }
1727 _ => infer(cx, e),
1728 }
1729}
1730
1731pub fn check_expr(cx: &Ctx, e: &Rc<Expr>, expected: Option<&RT>) -> Ty {
1732 let Some(expected) = expected else {
1733 return infer(cx, e);
1734 };
1735 if let RTk::Ref(_) = &expected.k {
1736 place_ty(cx, e);
1737 return tyv(Some(expected.clone())); }
1739 if let RTk::Pred { base, .. } = &expected.k {
1740 return check_expr(cx, e, Some(base));
1741 }
1742 if let RTk::IsectN(arms) = &expected.k {
1743 if matches!(
1744 &**e,
1745 Expr::Obj(_) | Expr::Arr(_) | Expr::Comp { .. } | Expr::MapComp { .. }
1746 ) {
1747 for arm in arms {
1748 check_expr(cx, e, Some(arm)); }
1750 return tyv(Some(expected.clone()));
1751 }
1752 }
1753 match (&**e, &expected.k) {
1754 (Expr::Comp { head, clauses }, RTk::Arr { elem, .. }) => {
1755 let c2 = bind_clauses(cx, clauses);
1756 check_expr(&c2, head, Some(elem));
1757 return tyv(Some(expected.clone()));
1758 }
1759 (Expr::MapComp { key, val, clauses }, RTk::Map { val: ev, .. }) => {
1760 let c2 = bind_clauses(cx, clauses);
1761 let k = require_val(&c2, key, infer(&c2, key), "as a map key");
1762 if k.rt.is_some() && num_kind(k.rt.as_ref()).as_deref() != Some("string") {
1763 cx.report("E4001", "map-comprehension key is not a string".into());
1764 }
1765 check_expr(&c2, val, Some(ev));
1766 return tyv(Some(expected.clone()));
1767 }
1768 (Expr::Paren(x), _) => return check_expr(cx, x, Some(expected)),
1769 (Expr::If { c, t, f }, _) => {
1770 let ct = require_val(cx, c, infer(cx, c), "as a condition");
1771 if ct.rt.is_some() && !is_boolish(ct.rt.as_ref()) {
1772 cx.report("E4001", "`if` condition is not bool".into());
1773 }
1774 check_expr(&apply_guards(cx, guards_of(c, true)), t, Some(expected));
1775 check_expr(&apply_guards(cx, guards_of(c, false)), f, Some(expected));
1776 return tyv(Some(expected.clone()));
1777 }
1778 (Expr::Match { .. }, _) => return infer_match(cx, e, Some(expected)),
1779 (Expr::Obj(entries), RTk::Rec(rec)) => {
1780 let members = rec.members.borrow().clone();
1782 let mut cx_r = cx.child();
1783 for m in &members {
1784 cx_r.vars.insert(
1785 m.name.clone(),
1786 Ty {
1787 rt: member_ty(m),
1788 abs: m.kind == MKind::Opt,
1789 },
1790 );
1791 }
1792 for (k, v) in entries {
1793 let Some(m) = find_member(&members, k) else {
1794 if !rec.open.get() {
1795 cx.report(
1796 "E4003",
1797 format!(
1798 "member {k} is not declared on {}",
1799 expected
1800 .name
1801 .borrow()
1802 .clone()
1803 .unwrap_or_else(|| "the record".into())
1804 ),
1805 );
1806 }
1807 require_val(&cx_r, v, infer(&cx_r, v), "as a construction member");
1808 continue;
1809 };
1810 let mt = member_ty(m);
1811 let t = check_expr(&cx_r, v, mt.as_ref());
1812 require_val(&cx_r, v, t, "as a construction member");
1813 }
1814 for m in &members {
1815 if m.kind == MKind::Req && !entries.iter().any(|(k, _)| *k == m.name) {
1816 cx.report(
1817 "E4002",
1818 format!("required member {} missing in the construction", m.name),
1819 );
1820 }
1821 }
1822 return tyv(Some(expected.clone()));
1823 }
1824 (Expr::Obj(entries), RTk::Map { val, .. }) => {
1825 for (_, v) in entries {
1826 let t = check_expr(cx, v, Some(val));
1827 require_val(cx, v, t, "as a map value");
1828 }
1829 return tyv(Some(expected.clone()));
1830 }
1831 (Expr::Obj(_), RTk::Union(_)) => {
1832 infer(cx, e);
1833 return tyv(Some(expected.clone())); }
1835 (Expr::Obj(_), _) => {
1836 infer(cx, e);
1837 cx.report(
1838 "E4001",
1839 format!("object literal where {} is expected", tag(expected)),
1840 );
1841 return tyv(Some(expected.clone()));
1842 }
1843 (Expr::Arr(items), RTk::Arr { elem, .. }) => {
1844 for (spread, x) in items {
1845 if *spread {
1846 require_val(cx, x, infer(cx, x), "as a spread");
1847 continue;
1848 }
1849 let t = check_expr(cx, x, Some(elem));
1850 require_val(cx, x, t, "as an array element");
1851 }
1852 return tyv(Some(expected.clone()));
1853 }
1854 (Expr::Lambda { .. }, RTk::Func { .. }) => {
1855 check_lambda(cx, e, expected);
1856 return tyv(Some(expected.clone()));
1857 }
1858 _ => {}
1859 }
1860 let t = require_val(cx, e, infer(cx, e), "as a value");
1861 if let Some(rt) = &t.rt {
1862 if !subsumes(&cx.env, rt, expected) && !deferrable(rt, expected) {
1863 cx.report(
1864 "E4001",
1865 "expression type does not satisfy the expected type".into(),
1866 );
1867 }
1868 }
1869 t
1870}
1871
1872fn deferrable(s: &RT, t: &RT) -> bool {
1877 let Some(k) = num_kind(Some(s)) else {
1878 return false;
1879 };
1880 match &t.k {
1881 RTk::Pattern { .. } => k == "string",
1882 RTk::Range { base, .. } => &k == base,
1883 RTk::Lit(_) => Some(k) == num_kind(Some(t)),
1884 RTk::Union(arms) => arms.iter().any(|a| deferrable(s, a)),
1885 RTk::Pred { base, .. } => deferrable(s, base),
1886 _ => false,
1887 }
1888}