1use crate::ast::*;
14use crate::engine::Engine;
15use crate::infer::Ty;
16use crate::infer::*;
17use crate::semantics::*;
18use crate::subsume::{structurally_empty, subsumes};
19use std::cell::{Cell, RefCell};
20use std::collections::{HashMap, HashSet};
21use std::rc::Rc;
22
23fn walk_expr_tree(e: &Rc<Expr>, into_types: bool, f: &mut dyn FnMut(&Rc<Expr>)) {
28 f(e);
29 let mut go = |x: &Rc<Expr>| walk_expr_tree(x, into_types, f);
30 match &**e {
31 Expr::Lit(_)
32 | Expr::UnitLit { .. }
33 | Expr::Name(_)
34 | Expr::Ctx(_)
35 | Expr::Referrers { .. }
36 | Expr::Pattern(_) => {}
37 Expr::Template(parts) => {
38 for p in parts {
39 if let TPart::Expr(x) = p {
40 go(x);
41 }
42 }
43 }
44 Expr::Obj(entries) => entries.iter().for_each(|(_, v)| go(v)),
45 Expr::Arr(items) => items.iter().for_each(|(_, v)| go(v)),
46 Expr::Comp { head, clauses } => {
47 go(head);
48 for c in clauses {
49 go(&c.iter);
50 c.filters.iter().for_each(&mut go);
51 }
52 }
53 Expr::MapComp { key, val, clauses } => {
54 go(key);
55 go(val);
56 for c in clauses {
57 go(&c.iter);
58 c.filters.iter().for_each(&mut go);
59 }
60 }
61 Expr::Bin { l, r, .. } => {
62 go(l);
63 go(r);
64 }
65 Expr::Un { x, .. } | Expr::Paren(x) => go(x),
66 Expr::If { c, t, f: ff } => {
67 go(c);
68 go(t);
69 go(ff);
70 }
71 Expr::Lambda { body, .. } => go(body),
72 Expr::Call { fun, args } => {
73 go(fun);
74 args.iter().for_each(&mut go);
75 }
76 Expr::Member { x, .. } => go(x),
77 Expr::Index { x, i } => {
78 go(x);
79 go(i);
80 }
81 Expr::With { base, patch } => {
82 go(base);
83 go(patch);
84 }
85 Expr::Match { subject, arms } => {
86 go(subject);
87 for a in arms {
88 if into_types {
89 if let Some(t) = &a.ty {
90 walk_type_exprs(t, into_types, f);
91 }
92 }
93 walk_expr_tree(&a.body, into_types, f);
94 }
95 }
96 }
97}
98fn walk_type_exprs(t: &TypeAst, into_types: bool, f: &mut dyn FnMut(&Rc<Expr>)) {
100 match t {
101 TypeAst::Prim { .. }
102 | TypeAst::Lit { .. }
103 | TypeAst::Range { .. }
104 | TypeAst::Pattern { .. } => {}
105 TypeAst::Record { members, .. } => members
106 .iter()
107 .for_each(|m| walk_member_exprs(m, into_types, f)),
108 TypeAst::Map { key, val, .. } => {
109 walk_type_exprs(key, into_types, f);
110 walk_type_exprs(val, into_types, f);
111 }
112 TypeAst::Array { elem, .. } => walk_type_exprs(elem, into_types, f),
113 TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => {
114 arms.iter().for_each(|a| walk_type_exprs(a, into_types, f))
115 }
116 TypeAst::Func { params, ret, .. } => {
117 params
118 .iter()
119 .for_each(|p| walk_type_exprs(p, into_types, f));
120 walk_type_exprs(ret, into_types, f);
121 }
122 TypeAst::Named {
123 args, preds, ext, ..
124 } => {
125 args.iter().for_each(|a| walk_type_exprs(a, into_types, f));
126 if let Some(ps) = preds {
127 ps.iter().for_each(|p| walk_expr_tree(p, into_types, f));
128 }
129 if let Some(x) = ext {
130 walk_type_exprs(x, into_types, f);
131 }
132 }
133 }
134}
135fn walk_member_exprs(m: &MemberAst, into_types: bool, f: &mut dyn FnMut(&Rc<Expr>)) {
136 match m {
137 MemberAst::Value { ty, dflt, .. } => {
138 walk_type_exprs(ty, into_types, f);
139 if let Some(d) = dflt {
140 walk_expr_tree(d, into_types, f);
141 }
142 }
143 MemberAst::Derived { ty, expr, .. } => {
144 if let Some(t) = ty {
145 walk_type_exprs(t, into_types, f);
146 }
147 walk_expr_tree(expr, into_types, f);
148 }
149 MemberAst::Context { ty, .. } => walk_type_exprs(ty, into_types, f),
150 MemberAst::Assert { cond, tail, .. } => {
151 walk_expr_tree(cond, into_types, f);
152 match tail {
153 Some(Tail::Inline { template, .. }) => {
154 for p in template {
155 if let TPart::Expr(x) = p {
156 walk_expr_tree(x, into_types, f);
157 }
158 }
159 }
160 Some(Tail::Ref { args, .. }) => {
161 args.iter().for_each(|a| walk_expr_tree(a, into_types, f))
162 }
163 None => {}
164 }
165 }
166 MemberAst::When { cond, body, .. } => {
167 walk_expr_tree(cond, into_types, f);
168 body.iter()
169 .for_each(|b| walk_member_exprs(b, into_types, f));
170 }
171 }
172}
173
174fn str_shaped(k: &RT) -> bool {
175 match &k.k {
176 RTk::Prim(n) => n == "string",
177 RTk::Pattern { .. } => true,
178 RTk::Lit(Value::Str(_)) => true,
179 RTk::Union(arms) => arms.iter().all(str_shaped),
180 RTk::Pred { base, .. } => str_shaped(base),
181 _ => false,
182 }
183}
184fn kind_name(k: MKind) -> &'static str {
185 match k {
186 MKind::Req => "req",
187 MKind::Opt => "opt",
188 MKind::Dflt => "dflt",
189 MKind::Der => "der",
190 }
191}
192
193pub struct CheckHooks {
195 pub record: Option<Rc<dyn Fn(&Rc<Expr>, &Ty)>>,
196 pub resolve_hook: Option<Rc<dyn Fn(&Rc<Expr>, Option<Target>)>>,
197}
198
199pub fn check_module(
200 decls: &[Decl],
201 linked: Option<Rc<Env>>,
202 hooks: Option<&CheckHooks>,
203) -> Vec<Diag> {
204 let out: Rc<RefCell<Vec<Diag>>> = Rc::new(RefCell::new(vec![]));
205 let pos: Rc<RefCell<Option<Rc<Expr>>>> = Rc::new(RefCell::new(None));
208 let cur_decl: Rc<Cell<Option<Loc>>> = Rc::new(Cell::new(None));
209 let report: Report = {
210 let out = out.clone();
211 let pos = pos.clone();
212 let cur = cur_decl.clone();
213 Rc::new(move |code: &str, message: String| {
214 let loc = pos.borrow().as_ref().and_then(expr_loc).or(cur.get());
215 let mut d = Diag::error(message, String::new(), Some(code));
216 d.loc = loc;
217 out.borrow_mut().push(d)
218 })
219 };
220 let rep = |code: &str, message: String| report(code, message);
221
222 let env = match &linked {
223 Some(e) => e.clone(),
224 None => {
225 let e = Env::new();
226 e.load(decls);
227 e
228 }
229 };
230 let _eng = if env.const_eval.borrow().is_none() {
232 Some(Engine::new(env.clone()))
233 } else {
234 None
235 };
236 *env.const_diag_sink.borrow_mut() = Some(out.clone()); for n in env.duplicates.borrow().iter() {
238 rep("E3001", format!("duplicate name {n} in module"));
239 }
240 out.borrow_mut().extend(env.finalize_unit_space()); let tparams: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
244 let is_input = |n: &str| env.inputs.borrow().contains_key(n);
245 let is_output = |n: &str| env.outputs.borrow().iter().any(|(o, _, _)| o == n);
246 let check_endpoint = |v: Option<&Value>, where_: &str| {
247 let Some(Value::Str(v)) = v else { return };
248 if tparams.borrow().contains(v) || v.contains('.') {
249 return;
250 }
251 if is_input(v) || is_output(v) {
252 rep(
253 "E4021",
254 format!("non-constant {where_}: {v} is an input/output, not a module const"),
255 );
256 } else if !env.consts.borrow().contains_key(v) {
257 rep("E3003", format!("unknown name {v} in a {where_}"));
258 }
259 };
260 let const_violation = |e: &Rc<Expr>| -> Option<String> {
261 let mut found: Option<String> = None;
262 walk_expr_tree(e, true, &mut |x| {
263 if found.is_some() {
264 return;
265 }
266 found = match &**x {
267 Expr::Ctx(n) => Some(format!("context variable {n}")),
268 Expr::Referrers { .. } => Some("$referrers".into()),
269 Expr::Name(n) if is_input(n) => Some(format!("input {n}")),
270 Expr::Name(n) if is_output(n) => Some(format!("output {n}")),
271 _ => None,
272 };
273 });
274 found
275 };
276
277 let is_bool_op = |e: &Expr| matches!(e, Expr::Bin { op, .. } if op == "&&" || op == "||");
279 let walk_expr = |e: &Rc<Expr>| {
280 walk_expr_tree(e, true, &mut |x| {
281 if let Expr::Bin { op, l, r } = &**x {
282 if op == "??" && (is_bool_op(l) || is_bool_op(r)) {
283 rep(
284 "E4052",
285 "`??` mixed with `&&`/`||` without parentheses".into(),
286 );
287 }
288 }
289 });
290 };
291
292 fn ctx_uses(m: &MemberAst, used: &mut Vec<String>) {
294 let mut scan = |e: &Rc<Expr>| {
295 walk_expr_tree(e, false, &mut |x| {
296 if let Expr::Ctx(n) = &**x {
297 if ["$parent", "$root", "$key"].contains(&n.as_str()) && !used.contains(n) {
298 used.push(n.clone());
299 }
300 }
301 });
302 };
303 match m {
304 MemberAst::Value { dflt: Some(d), .. } => scan(d),
305 MemberAst::Derived { expr, .. } => scan(expr),
306 MemberAst::Assert { cond, .. } => scan(cond),
307 MemberAst::When { cond, body, .. } => {
308 scan(cond);
309 for b in body {
310 ctx_uses(b, used);
311 }
312 }
313 _ => {}
314 }
315 }
316 let check_record_ctx = |members: &[MemberAst], depth: i32, decl_name: Option<&str>| {
317 let declared: Vec<(&String, &TypeAst)> = members
318 .iter()
319 .filter_map(|m| {
320 if let MemberAst::Context { variable, ty, .. } = m {
321 Some((variable, ty))
322 } else {
323 None
324 }
325 })
326 .collect();
327 for (v, t) in &declared {
328 let is_ref = matches!(t, TypeAst::Named { name, .. } if name == "ref");
329 if (v.as_str() == "$parent" || v.as_str() == "$root") && !is_ref {
330 rep(
331 "E4094",
332 format!(
333 "{v} declaration must be ref<...> ({})",
334 decl_name.unwrap_or("anonymous")
335 ),
336 );
337 }
338 if v.as_str() == "$key" && is_ref {
339 rep(
340 "E4094",
341 "$key declares a plain value type, not ref<...>".into(),
342 );
343 }
344 }
345 if depth > 1 {
346 return; }
348 let mut used: Vec<String> = vec![];
349 for m in members {
350 ctx_uses(m, &mut used);
351 }
352 for u in used {
353 if !declared.iter().any(|(v, _)| **v == u) {
354 rep(
355 "E4094",
356 format!(
357 "{u} used without a context declaration in {}",
358 decl_name.unwrap_or("anonymous type")
359 ),
360 );
361 }
362 }
363 };
364
365 let check_extension = |name: &str, args: &[TypeAst], ext: &TypeAst, decl_name: Option<&str>| {
367 let Ok(base) = env.resolve(
368 &TypeAst::Named {
369 name: name.to_string(),
370 args: args.to_vec(),
371 preds: None,
372 ext: None,
373 loc: None,
374 },
375 None,
376 ) else {
377 return;
378 }; let RTk::Rec(brec) = &base.k else {
380 rep("E4031", format!("extending non-record type {name}"));
381 return;
382 };
383 let TypeAst::Record {
384 members: ext_members,
385 ..
386 } = ext
387 else {
388 return;
389 };
390 let bmembers = brec.members.borrow();
391 for om in ext_members {
392 let (oname, o_kind, o_type): (&str, &str, Option<&TypeAst>) = match om {
393 MemberAst::Assert { .. } | MemberAst::When { .. } | MemberAst::Context { .. } => {
394 continue
395 }
396 MemberAst::Derived {
397 name, ty, hidden, ..
398 } => (name, if *hidden { "hidden" } else { "der" }, ty.as_ref()),
399 MemberAst::Value {
400 name,
401 opt,
402 ty,
403 dflt,
404 ..
405 } => (
406 name,
407 if dflt.is_some() {
408 "dflt"
409 } else if *opt {
410 "opt"
411 } else {
412 "req"
413 },
414 Some(ty),
415 ),
416 };
417 let Some(bm) = bmembers.iter().find(|x| x.name == oname) else {
418 continue;
419 }; let b_kind = if bm.kind == MKind::Der && bm.hidden {
421 "hidden"
422 } else {
423 kind_name(bm.kind)
424 };
425 let allowed: &[&str] = match b_kind {
427 "req" => &["req", "dflt", "der"],
428 "opt" => &["req", "opt", "dflt", "der"],
429 "dflt" => &["req", "dflt", "der"],
430 "der" => &["der"],
431 _ => &["hidden"],
432 };
433 if !allowed.contains(&o_kind) {
434 rep(
435 "E4032",
436 format!(
437 "illegal member-kind transition for {oname}: {b_kind} -> {o_kind} ({})",
438 decl_name.unwrap_or(name)
439 ),
440 );
441 continue;
442 }
443 let ot = try_resolve(&env, o_type);
444 if let (Some(ot), Some(bt)) = (ot, &bm.ty) {
445 if !subsumes(&env, &ot, bt) {
446 rep(
447 "E4030",
448 format!(
449 "override widens inherited member {oname} ({})",
450 decl_name.unwrap_or(name)
451 ),
452 );
453 }
454 }
455 }
456 };
457
458 struct Walk<'a> {
459 rep: &'a dyn Fn(&str, String),
460 check_endpoint: &'a dyn Fn(Option<&Value>, &str),
461 const_violation: &'a dyn Fn(&Rc<Expr>) -> Option<String>,
462 walk_expr: &'a dyn Fn(&Rc<Expr>),
463 check_record_ctx: &'a dyn Fn(&[MemberAst], i32, Option<&str>),
464 check_extension: &'a dyn Fn(&str, &[TypeAst], &TypeAst, Option<&str>),
465 }
466 impl<'a> Walk<'a> {
467 fn walk_type(&self, t: Option<&TypeAst>, depth: i32, decl_name: Option<&str>) {
468 let Some(t) = t else { return };
469 match t {
470 TypeAst::Range { lo, hi, .. } => {
471 let kinds = [js_typeof(lo), js_typeof(hi)];
472 if kinds[0] != kinds[1] && !kinds.contains(&"string") {
473 (self.rep)(
474 "E4010",
475 format!("mixed range endpoints: {}..{}", js_str(lo), js_str(hi)),
476 );
477 }
478 (self.check_endpoint)(Some(lo), "range endpoint");
479 (self.check_endpoint)(Some(hi), "range endpoint");
480 }
481 TypeAst::Record { members, .. } => {
482 (self.check_record_ctx)(members, depth, decl_name);
483 for m in members {
484 self.walk_member(m, depth, decl_name);
485 }
486 }
487 TypeAst::Map { key, val, .. } => {
488 self.walk_type(Some(key), depth, decl_name);
489 self.walk_type(Some(val), depth, decl_name);
490 }
491 TypeAst::Array { elem, lo, hi, .. } => {
492 (self.check_endpoint)(lo.as_ref(), "array size");
493 (self.check_endpoint)(hi.as_ref(), "array size");
494 self.walk_type(Some(elem), depth, decl_name);
495 }
496 TypeAst::Union { arms, .. } | TypeAst::Isect { arms, .. } => arms
497 .iter()
498 .for_each(|a| self.walk_type(Some(a), depth, decl_name)),
499 TypeAst::Func { params, ret, .. } => {
500 params
501 .iter()
502 .for_each(|p| self.walk_type(Some(p), depth, decl_name));
503 self.walk_type(Some(ret), depth, decl_name);
504 }
505 TypeAst::Named {
506 name,
507 args,
508 preds,
509 ext,
510 ..
511 } => {
512 args.iter()
513 .for_each(|a| self.walk_type(Some(a), depth, decl_name));
514 if let Some(x) = ext {
515 (self.check_extension)(name, args, x, decl_name);
516 self.walk_type(Some(x), depth + 1, decl_name);
517 }
518 for p in preds.iter().flatten() {
519 if let Some(bad) = (self.const_violation)(p) {
520 (self.rep)(
521 "E4021",
522 format!("non-constant predicate argument: {bad} (§4.13)"),
523 );
524 }
525 (self.walk_expr)(p);
526 }
527 }
528 TypeAst::Prim { .. } | TypeAst::Lit { .. } | TypeAst::Pattern { .. } => {}
529 }
530 }
531 fn walk_member(&self, m: &MemberAst, depth: i32, decl_name: Option<&str>) {
532 match m {
533 MemberAst::Value { ty, dflt, .. } => {
534 self.walk_type(Some(ty), depth + 1, decl_name);
535 if let Some(d) = dflt {
536 (self.walk_expr)(d);
537 }
538 }
539 MemberAst::Derived { ty, expr, .. } => {
540 self.walk_type(ty.as_ref(), depth + 1, decl_name);
541 (self.walk_expr)(expr);
542 }
543 MemberAst::Context { ty, .. } => self.walk_type(Some(ty), depth + 1, decl_name),
544 MemberAst::Assert { cond, .. } => (self.walk_expr)(cond),
545 MemberAst::When { cond, body, .. } => {
546 (self.walk_expr)(cond);
547 body.iter()
548 .for_each(|x| self.walk_member(x, depth, decl_name));
549 }
550 }
551 }
552 }
553 let walk = Walk {
554 rep: &rep,
555 check_endpoint: &check_endpoint,
556 const_violation: &const_violation,
557 walk_expr: &walk_expr,
558 check_record_ctx: &check_record_ctx,
559 check_extension: &check_extension,
560 };
561
562 let resolve_reported: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
564 let pattern_unknown = regex::Regex::new(r"pattern interpolation of .*: unknown type").unwrap();
565 let map_resolve_err = |msg: &str, where_: &str| {
566 let key = format!("{msg}|{where_}");
567 if !resolve_reported.borrow_mut().insert(key) {
568 return; }
570 if msg.contains("unknown dimension") || msg.contains("circular dimension") {
571 rep("E3003", format!("{msg} (in {where_})"));
572 } else if msg.contains("unknown unit") {
573 rep("E4073", format!("{msg} (in {where_})"));
574 } else if pattern_unknown.is_match(msg) || msg.contains("unknown type") {
575 rep("E3003", format!("{msg} (in {where_})"));
576 } else if msg.contains("generic arity") {
577 rep("E4022", format!("{msg} (in {where_})"));
578 } else if msg.contains("outside parameter") {
579 rep("E4023", format!("{msg} (in {where_})"));
580 } else if msg.contains("non-constant value argument") {
581 rep("E4021", format!("{msg} (in {where_})"));
582 } else if msg.contains("pattern interpolation") {
583 rep("E4117", format!("{msg} (in {where_})"));
584 } else if msg.contains("malformed pattern") {
585 rep("E4119", format!("{msg} (in {where_})"));
586 } else {
587 rep("E4001", format!("{msg} (in {where_})")); }
589 };
590 let resolve_or_report = |t: Option<&TypeAst>, where_: &str| -> Option<RT> {
591 let t = t?;
592 match env.resolve(t, None) {
593 Ok(rt) => Some(rt),
594 Err(msg) => {
595 map_resolve_err(&msg, where_);
596 None
597 }
598 }
599 };
600
601 fn check_resolved(
602 env: &Rc<Env>,
603 rep: &dyn Fn(&str, String),
604 rt: &RT,
605 name: &str,
606 seen: &mut HashSet<usize>,
607 ) {
608 let id = Rc::as_ptr(rt) as usize;
609 if seen.contains(&id) {
610 return;
611 }
612 seen.insert(id);
613 match &rt.k {
614 RTk::Range { lo, hi, .. } => {
615 let ks = [js_typeof(lo), js_typeof(hi)];
616 if !ks.contains(&"string") && ks[0] != ks[1] {
617 rep(
618 "E4010",
619 format!("mixed range endpoints after constant substitution in {name}"),
620 );
621 }
622 if structurally_empty(env, rt) {
623 rep("E4011", format!("empty range in {name}"));
624 }
625 }
626 RTk::Arr { elem, .. } => {
627 if structurally_empty(env, rt) {
628 rep("E4011", format!("empty array size in {name}"));
629 }
630 check_resolved(env, rep, elem, name, seen);
631 }
632 RTk::IsectN(arms) => {
633 if structurally_empty(env, rt) {
634 rep(
635 "E4012",
636 format!("structurally empty intersection in {name}"),
637 );
638 }
639 arms.iter()
640 .for_each(|a| check_resolved(env, rep, a, name, seen));
641 }
642 RTk::Map { key, val } => {
643 if !str_shaped(key) {
644 rep("E4015", format!("map key type not string-shaped in {name}"));
645 }
646 check_resolved(env, rep, val, name, seen);
647 }
648 RTk::Union(arms) => {
649 let recs: Vec<&RT> = arms.iter().filter(|a| is_rec(a)).collect();
650 if recs.len() >= 2 {
651 let lit_of = |r: &RT, n: &str| -> Option<Value> {
652 rec_members(r)
653 .iter()
654 .find(|x| x.name == n)
655 .and_then(|x| x.ty.as_ref())
656 .and_then(|t| {
657 if let RTk::Lit(v) = &t.k {
658 Some(v.clone())
659 } else {
660 None
661 }
662 })
663 };
664 let disc: Vec<String> = rec_members(recs[0])
665 .iter()
666 .filter(|m| {
667 matches!(m.ty.as_ref().map(|t| &t.k), Some(RTk::Lit(_)))
668 && recs.iter().all(|r| lit_of(r, &m.name).is_some())
669 })
670 .map(|m| m.name.clone())
671 .collect();
672 let tuples: HashSet<String> = recs
673 .iter()
674 .map(|r| {
675 format!(
676 "[{}]",
677 disc.iter()
678 .map(|d| json_str(&js_str(&lit_of(r, d).unwrap())))
679 .collect::<Vec<_>>()
680 .join(",")
681 )
682 })
683 .collect();
684 if disc.is_empty() || tuples.len() != recs.len() {
685 rep(
686 "E4013",
687 format!("record union arms not discriminable in {name}"),
688 );
689 }
690 }
691 let non_rec_obj = arms
692 .iter()
693 .filter(|a| matches!(a.k, RTk::Map { .. } | RTk::Quantity(_)))
694 .count();
695 if non_rec_obj > 1 {
696 rep(
697 "E4014",
698 format!("more than one non-record object arm in {name}"),
699 );
700 }
701 arms.iter()
702 .for_each(|a| check_resolved(env, rep, a, name, seen));
703 }
704 RTk::Rec(_) => {
705 for m in rec_members(rt) {
706 if let Some(t) = &m.ty {
707 check_resolved(env, rep, t, name, seen);
708 }
709 }
710 }
711 RTk::Pred { base, .. } => check_resolved(env, rep, base, name, seen),
712 RTk::Ref(target) => check_resolved(env, rep, target, name, seen),
713 _ => {}
714 }
715 }
716
717 let type_order = env.type_order.borrow().clone();
718 let type_entry = |n: &str| env.type_asts.borrow().get(n).cloned();
719 for name in &type_order {
720 let Some(decl) = type_entry(name) else {
721 continue;
722 };
723 if !decl.params.is_empty() {
724 continue; }
726 match env.resolve(&named(name), None) {
727 Ok(rt) => check_resolved(&env, &rep, &rt, name, &mut HashSet::new()),
728 Err(msg) => map_resolve_err(&msg, name),
729 }
730 }
731
732 for d in decls {
734 cur_decl.set(d.loc);
735 *tparams.borrow_mut() = match &d.body {
736 DeclBody::Type { params, .. } => params.iter().map(|p| p.name.clone()).collect(),
737 _ => HashSet::new(),
738 };
739 match &d.body {
740 DeclBody::Type { name, ty, .. } => walk.walk_type(Some(ty), 1, Some(name)),
741 DeclBody::Const { ty, expr, .. } => {
742 walk.walk_type(ty.as_ref(), 0, None);
743 walk_expr(expr);
744 }
745 DeclBody::Func {
746 params, ret, body, ..
747 } => {
748 params
749 .iter()
750 .for_each(|p| walk.walk_type(p.ty.as_ref(), 0, None));
751 walk.walk_type(ret.as_ref(), 0, None);
752 walk_expr(body);
753 }
754 DeclBody::Output { ty, expr, .. } => {
755 walk.walk_type(Some(ty), 0, None);
756 walk_expr(expr);
757 }
758 DeclBody::Input { ty, fallback, .. } => {
759 walk.walk_type(Some(ty), 0, None);
760 if let Some(f) = fallback {
761 walk_expr(f);
762 }
763 }
764 _ => {}
765 }
766 }
767
768 let mut cx0 = make_ctx(env.clone(), report.clone());
770 cx0.pos = pos.clone();
771 if let Some(h) = hooks {
772 cx0.record = h.record.clone();
773 cx0.resolve_hook = h.resolve_hook.clone();
774 }
775 let cx0 = cx0;
776 let is_bool_ty = |t: &Ty| match &t.rt {
777 None => true,
778 Some(r) => {
779 matches!(&r.k, RTk::Prim(n) if n == "bool") || matches!(&r.k, RTk::Lit(Value::Bool(_)))
780 }
781 };
782 let rec_ctx = |cx: &Ctx, rt: &RT, ast: Option<&TypeAst>| -> Ctx {
783 let mut c = cx.child();
784 for m in rec_members(rt) {
785 c.vars.insert(
786 m.name.clone(),
787 Ty {
788 rt: member_ty(&m),
789 abs: m.kind == MKind::Opt,
790 },
791 );
792 }
793 c.vars.insert("$this".into(), tyv(Some(rt.clone())));
794 c.vars
795 .insert("$path".into(), tyv(Some(ty(RTk::Prim("string".into())))));
796 if let Some(TypeAst::Record { members, .. }) = ast {
797 for m in members {
798 if let MemberAst::Context { variable, ty, .. } = m {
799 c.vars
800 .insert(variable.clone(), tyv(try_resolve(&env, Some(ty))));
801 }
802 }
803 }
804 c
805 };
806 fn check_member_ast(
807 env: &Rc<Env>,
808 rep: &dyn Fn(&str, String),
809 is_bool_ty: &dyn Fn(&Ty) -> bool,
810 cx: &Ctx,
811 m: &MemberAst,
812 ) {
813 match m {
814 MemberAst::Value {
815 ty, dflt: Some(d), ..
816 } => {
817 check_expr(cx, d, try_resolve(env, Some(ty)).as_ref());
818 }
819 MemberAst::Derived { ty, expr, .. } => {
820 check_expr(cx, expr, try_resolve(env, ty.as_ref()).as_ref());
821 }
822 MemberAst::Assert { cond, tail, .. } => {
823 if !is_bool_ty(&require_val(
824 cx,
825 cond,
826 infer(cx, cond),
827 "as an assert condition",
828 )) {
829 rep("E4001", "assert condition is not bool".into());
830 }
831 match tail {
832 Some(Tail::Inline { template, .. }) => {
833 for p in template {
834 if let TPart::Expr(x) = p {
835 infer(cx, x);
836 }
837 }
838 }
839 Some(Tail::Ref { args, .. }) => {
840 for a in args {
841 require_val(cx, a, infer(cx, a), "as a diagnostic argument");
842 }
843 }
844 None => {}
845 }
846 }
847 MemberAst::When { cond, body, .. } => {
848 if !is_bool_ty(&require_val(
849 cx,
850 cond,
851 infer(cx, cond),
852 "as a when condition",
853 )) {
854 rep("E4001", "when condition is not bool".into());
855 }
856 let c2 = apply_guards(cx, guards_of(cond, true));
857 for b in body {
858 check_member_ast(env, rep, is_bool_ty, &c2, b);
859 }
860 }
861 _ => {}
862 }
863 }
864
865 let seen_recs: RefCell<HashSet<usize>> = RefCell::new(HashSet::new());
866 fn check_record_exprs(
867 env: &Rc<Env>,
868 rep: &dyn Fn(&str, String),
869 is_bool_ty: &dyn Fn(&Ty) -> bool,
870 rec_ctx: &dyn Fn(&Ctx, &RT, Option<&TypeAst>) -> Ctx,
871 seen: &RefCell<HashSet<usize>>,
872 rt: &RT,
873 cx: &Ctx,
874 ast: Option<&TypeAst>,
875 ) {
876 let RTk::Rec(rec) = &rt.k else { return };
877 let id = Rc::as_ptr(rt) as usize;
878 if seen.borrow().contains(&id) {
879 return;
880 }
881 seen.borrow_mut().insert(id);
882 let cx_r = rec_ctx(cx, rt, ast);
883 let cx_for = |menv: &Option<Rc<Env>>| -> Ctx {
886 match menv {
887 Some(m) if !Rc::ptr_eq(m, &cx_r.env) => cx_r.with_env(m.clone()),
888 _ => cx_r.clone(),
889 }
890 };
891 let check_embedding = |member_rt: &RT, member_name: &str, key_rt: Option<&RT>| {
895 let RTk::Rec(mrec) = &member_rt.k else { return };
896 let site = format!(
897 "{}.{member_name}",
898 rt.name.borrow().clone().unwrap_or_else(|| "record".into())
899 );
900 let who = member_rt
901 .name
902 .borrow()
903 .clone()
904 .unwrap_or_else(|| "the member type".into());
905 for (var, cty) in mrec.ctx_decls.borrow().iter() {
906 if var == "$parent" {
907 let RTk::Ref(bound) = &cty.k else { continue };
908 if !subsumes(env, rt, bound) {
909 rep(
910 "E4090",
911 format!("embedding site {site} fails {who}'s $parent bound (§7.3)"),
912 );
913 }
914 } else if var == "$key" {
915 match key_rt {
916 None => rep("E4090", format!("embedding site {site} gives $key no meaning: {who} is a direct member, not a collection element (§7.3)")),
917 Some(k) => {
918 if !subsumes(env, k, cty) {
919 rep("E4090", format!("embedding site {site} fails {who}'s $key bound (§7.3)"));
920 }
921 }
922 }
923 }
924 }
925 };
926 let int_rt: RT = ty(RTk::Prim("int".into()));
927 let members = rec.members.borrow().clone();
928 for m in &members {
929 if m.kind == MKind::Der {
930 if let Some(x) = &m.expr {
931 check_expr(&cx_for(&m.menv), x, m.ty.as_ref());
932 }
933 }
934 if m.kind == MKind::Dflt {
935 if let Some(d) = &m.dflt {
936 check_expr(&cx_for(&m.menv), d, m.ty.as_ref());
937 }
938 }
939 let Some(mt) = &m.ty else { continue };
940 let nested: Option<(RT, Option<RT>)> = match &mt.k {
941 RTk::Rec(_) => Some((mt.clone(), None)),
942 RTk::Arr { elem, .. } if is_rec(elem) => Some((elem.clone(), Some(int_rt.clone()))),
943 RTk::Map { key, val } if is_rec(val) => Some((val.clone(), Some(key.clone()))),
944 _ => None,
945 };
946 if let Some((n, key_rt)) = nested {
947 check_embedding(&n, &m.name, key_rt.as_ref());
948 check_record_exprs(
949 env,
950 rep,
951 is_bool_ty,
952 rec_ctx,
953 seen,
954 &n,
955 &cx_for(&m.menv),
956 None,
957 );
958 }
959 }
960 let asserts = rec.asserts.borrow().clone();
961 for a in &asserts {
962 let m = if a.when {
963 MemberAst::When {
964 cond: a.cond.clone(),
965 body: a.body.clone(),
966 loc: None,
967 }
968 } else {
969 MemberAst::Assert {
970 name: a.name.clone(),
971 cond: a.cond.clone(),
972 tail: a.tail.clone(),
973 loc: None,
974 }
975 };
976 check_member_ast(env, rep, is_bool_ty, &cx_for(&a.menv), &m);
977 }
978 }
979
980 for name in &type_order {
981 let Some(decl) = type_entry(name) else {
982 continue;
983 };
984 if !decl.params.is_empty() {
985 continue;
986 }
987 let Ok(rt) = env.resolve(&named(name), None) else {
988 continue;
989 };
990 check_record_exprs(
991 &env,
992 &rep,
993 &is_bool_ty,
994 &rec_ctx,
995 &seen_recs,
996 &rt,
997 &cx0,
998 Some(&decl.ast),
999 );
1000 }
1001 fn check_root_type(rep: &dyn Fn(&str, String), root_name: &str, root_rt: &RT) {
1008 let arms: Vec<RT> = match &root_rt.k {
1009 RTk::Union(arms) => arms.clone(),
1010 _ => vec![root_rt.clone()],
1011 };
1012 for t in &arms {
1013 let RTk::Rec(r) = &t.k else { continue };
1014 let who = t.name.borrow().clone().unwrap_or_else(|| "its type".into());
1015 for (var, _) in r.ctx_decls.borrow().iter() {
1016 if var == "$parent" {
1017 rep("E4090", format!("root {root_name} gives $parent no meaning: {who} is the evaluation root's own type (§7.3)"));
1018 } else if var == "$key" {
1019 rep("E4090", format!("root {root_name} gives $key no meaning: {who} is the evaluation root's own type, not a collection element (§7.3)"));
1020 }
1021 }
1022 }
1023 }
1024 fn walk_root_bounds(
1025 env: &Rc<Env>,
1026 rep: &dyn Fn(&str, String),
1027 root_name: &str,
1028 root_rt: &RT,
1029 t: &RT,
1030 seen: &mut HashSet<usize>,
1031 ) {
1032 let id = Rc::as_ptr(t) as usize;
1033 if !seen.insert(id) {
1034 return;
1035 }
1036 match &t.k {
1037 RTk::Rec(r) => {
1038 for (var, cty) in r.ctx_decls.borrow().iter() {
1039 if var != "$root" {
1040 continue;
1041 }
1042 let RTk::Ref(bound) = &cty.k else { continue };
1043 if !subsumes(env, root_rt, bound) {
1044 rep(
1045 "E4090",
1046 format!(
1047 "root {root_name} fails {}'s $root bound (§7.3)",
1048 t.name
1049 .borrow()
1050 .clone()
1051 .unwrap_or_else(|| "a member type".into())
1052 ),
1053 );
1054 }
1055 }
1056 for m in rec_members(t) {
1057 if let Some(mt) = &m.ty {
1058 walk_root_bounds(env, rep, root_name, root_rt, mt, seen);
1059 }
1060 }
1061 }
1062 RTk::Arr { elem, .. } => walk_root_bounds(env, rep, root_name, root_rt, elem, seen),
1063 RTk::Map { val, .. } => walk_root_bounds(env, rep, root_name, root_rt, val, seen),
1064 RTk::Union(arms) | RTk::IsectN(arms) => arms
1065 .iter()
1066 .for_each(|a| walk_root_bounds(env, rep, root_name, root_rt, a, seen)),
1067 RTk::Pred { base, .. } => walk_root_bounds(env, rep, root_name, root_rt, base, seen),
1068 _ => {}
1069 }
1070 }
1071 for d in decls {
1072 cur_decl.set(d.loc);
1073 let (name, ty_ast) = match &d.body {
1074 DeclBody::Output { name, ty, .. } | DeclBody::Input { name, ty, .. } => (name, ty),
1075 _ => continue,
1076 };
1077 if let Some(rt) = try_resolve(&env, Some(ty_ast)) {
1078 check_root_type(&rep, name, &rt);
1079 walk_root_bounds(&env, &rep, name, &rt, &rt, &mut HashSet::new());
1080 }
1081 }
1082 for d in decls {
1083 cur_decl.set(d.loc);
1084 match &d.body {
1085 DeclBody::Const { name, ty, expr } => {
1086 let exp = ty
1087 .as_ref()
1088 .and_then(|t| resolve_or_report(Some(t), &format!("const {name}")));
1089 check_expr(&cx0, expr, exp.as_ref());
1090 }
1091 DeclBody::Func {
1092 name,
1093 params,
1094 ret,
1095 body,
1096 } => {
1097 let mut cx_f = cx0.child();
1098 for p in params {
1099 cx_f.vars.insert(
1100 p.name.clone(),
1101 tyv(resolve_or_report(p.ty.as_ref(), &format!("func {name}"))),
1102 );
1103 }
1104 let exp = ret
1105 .as_ref()
1106 .and_then(|t| resolve_or_report(Some(t), &format!("func {name}")));
1107 check_expr(&cx_f, body, exp.as_ref());
1108 }
1109 DeclBody::Output { name, ty, expr } => {
1110 let exp = resolve_or_report(Some(ty), &format!("output {name}"));
1111 check_expr(&cx0, expr, exp.as_ref());
1112 }
1113 DeclBody::Input {
1114 name,
1115 ty,
1116 fallback: Some(f),
1117 } => {
1118 let exp = resolve_or_report(Some(ty), &format!("input {name}"));
1119 check_expr(&cx0, f, exp.as_ref());
1120 }
1121 DeclBody::Input {
1122 name,
1123 ty,
1124 fallback: None,
1125 } => {
1126 resolve_or_report(Some(ty), &format!("input {name}"));
1127 }
1128 DeclBody::Diagnostic {
1129 params, template, ..
1130 } => {
1131 let mut cx_d = cx0.child();
1132 for p in params {
1133 cx_d.vars
1134 .insert(p.name.clone(), tyv(try_resolve(&env, p.ty.as_ref())));
1135 }
1136 for p in template {
1137 if let TPart::Expr(x) = p {
1138 infer(&cx_d, x);
1139 }
1140 }
1141 }
1142 DeclBody::Unit {
1143 name,
1144 factor: Some(f),
1145 ..
1146 } => {
1147 if let Some(bad) = const_violation(f) {
1148 rep(
1149 "E4021",
1150 format!("non-constant unit factor for {name}: {bad} (§3.16)"),
1151 );
1152 }
1153 }
1154 _ => {}
1155 }
1156 }
1157 *env.const_diag_sink.borrow_mut() = None;
1158 let result = out.borrow().clone();
1159 result
1160}
1161
1162#[allow(dead_code)]
1163fn _unused(_: &HashMap<String, String>) {}