1use std::collections::{HashMap, HashSet};
26
27use crate::term::{int_lit, lit_bigint, Literal, Term, Universe};
28use crate::Context;
29
30#[derive(Debug, Clone, PartialEq)]
32pub enum ReCheckError {
33 Unsupported(String),
38 Ill(String),
42}
43
44impl ReCheckError {
45 fn ill(msg: impl Into<String>) -> Self {
46 ReCheckError::Ill(msg.into())
47 }
48 fn unsupported(msg: impl Into<String>) -> Self {
49 ReCheckError::Unsupported(msg.into())
50 }
51}
52
53type RResult<T> = Result<T, ReCheckError>;
54
55#[derive(Debug, Clone, PartialEq)]
60enum Db {
61 Sort(Universe),
62 Var(usize),
64 Global(String),
67 Const { name: String, levels: Vec<Universe> },
69 Pi(Box<Db>, Box<Db>),
71 Lam(Box<Db>, Box<Db>),
73 App(Box<Db>, Box<Db>),
74 Match { disc: Box<Db>, motive: Box<Db>, cases: Vec<Db> },
77 Fix(Box<Db>),
79 MutualFix { defs: Vec<Db>, index: usize },
84 Let(Box<Db>, Box<Db>, Box<Db>),
86 Lit(Literal),
87}
88
89fn to_db(term: &Term, scope: &mut Vec<String>) -> RResult<Db> {
98 match term {
99 Term::Sort(u) => Ok(Db::Sort(u.clone())),
100 Term::Var(name) => {
101 for (depth_from_inner, bound) in scope.iter().rev().enumerate() {
102 if bound == name {
103 return Ok(Db::Var(depth_from_inner));
104 }
105 }
106 Err(ReCheckError::ill(format!("unbound local variable '{}'", name)))
107 }
108 Term::Global(name) => Ok(Db::Global(name.clone())),
109 Term::Const { name, levels } => {
110 Ok(Db::Const { name: name.clone(), levels: levels.clone() })
111 }
112 Term::Pi { param, param_type, body_type } => {
113 let dom = to_db(param_type, scope)?;
114 scope.push(param.clone());
115 let body = to_db(body_type, scope);
116 scope.pop();
117 Ok(Db::Pi(Box::new(dom), Box::new(body?)))
118 }
119 Term::Lambda { param, param_type, body } => {
120 let dom = to_db(param_type, scope)?;
121 scope.push(param.clone());
122 let inner = to_db(body, scope);
123 scope.pop();
124 Ok(Db::Lam(Box::new(dom), Box::new(inner?)))
125 }
126 Term::App(f, a) => Ok(Db::App(Box::new(to_db(f, scope)?), Box::new(to_db(a, scope)?))),
127 Term::Match { discriminant, motive, cases } => {
128 let disc = to_db(discriminant, scope)?;
129 let mot = to_db(motive, scope)?;
130 let cs = cases.iter().map(|c| to_db(c, scope)).collect::<RResult<Vec<_>>>()?;
131 Ok(Db::Match { disc: Box::new(disc), motive: Box::new(mot), cases: cs })
132 }
133 Term::Fix { name, body } => {
134 scope.push(name.clone());
135 let b = to_db(body, scope);
136 scope.pop();
137 Ok(Db::Fix(Box::new(b?)))
138 }
139 Term::MutualFix { defs, index } => {
142 let names: Vec<String> = defs.iter().map(|(n, _)| n.clone()).collect();
143 for n in &names {
144 scope.push(n.clone());
145 }
146 let mut dbs = Vec::with_capacity(defs.len());
147 let mut err = None;
148 for (_, body) in defs {
149 match to_db(body, scope) {
150 Ok(d) => dbs.push(d),
151 Err(e) => {
152 err = Some(e);
153 break;
154 }
155 }
156 }
157 for _ in &names {
158 scope.pop();
159 }
160 match err {
161 Some(e) => Err(e),
162 None => Ok(Db::MutualFix { defs: dbs, index: *index }),
163 }
164 }
165 Term::Let { name, ty, value, body } => {
166 let d_ty = to_db(ty, scope)?;
167 let d_val = to_db(value, scope)?;
168 scope.push(name.clone());
169 let d_body = to_db(body, scope);
170 scope.pop();
171 Ok(Db::Let(Box::new(d_ty), Box::new(d_val), Box::new(d_body?)))
172 }
173 Term::Lit(l) => Ok(Db::Lit(l.clone())),
174 Term::Hole => Err(ReCheckError::unsupported("Hole (unelaborated implicit)")),
175 }
176}
177
178fn from_db(t: &Db, depth: usize) -> Term {
182 match t {
183 Db::Sort(u) => Term::Sort(u.clone()),
184 Db::Var(k) => {
185 let binder = depth.saturating_sub(1).saturating_sub(*k);
186 Term::Var(format!("v{}", binder))
187 }
188 Db::Global(n) => Term::Global(n.clone()),
189 Db::Const { name, levels } => Term::Const { name: name.clone(), levels: levels.clone() },
190 Db::Pi(a, b) => Term::Pi {
191 param: format!("v{}", depth),
192 param_type: Box::new(from_db(a, depth)),
193 body_type: Box::new(from_db(b, depth + 1)),
194 },
195 Db::Lam(a, b) => Term::Lambda {
196 param: format!("v{}", depth),
197 param_type: Box::new(from_db(a, depth)),
198 body: Box::new(from_db(b, depth + 1)),
199 },
200 Db::App(f, a) => Term::App(Box::new(from_db(f, depth)), Box::new(from_db(a, depth))),
201 Db::Match { disc, motive, cases } => Term::Match {
202 discriminant: Box::new(from_db(disc, depth)),
203 motive: Box::new(from_db(motive, depth)),
204 cases: cases.iter().map(|c| from_db(c, depth)).collect(),
205 },
206 Db::Fix(body) => Term::Fix {
207 name: format!("rec{}", depth),
208 body: Box::new(from_db(body, depth + 1)),
209 },
210 Db::MutualFix { defs, index } => {
211 let n = defs.len();
212 Term::MutualFix {
213 defs: defs
214 .iter()
215 .enumerate()
216 .map(|(j, b)| (format!("rec{depth}_{j}"), from_db(b, depth + n)))
217 .collect(),
218 index: *index,
219 }
220 }
221 Db::Let(ty, value, body) => Term::Let {
222 name: format!("v{}", depth),
223 ty: Box::new(from_db(ty, depth)),
224 value: Box::new(from_db(value, depth)),
225 body: Box::new(from_db(body, depth + 1)),
226 },
227 Db::Lit(l) => Term::Lit(l.clone()),
228 }
229}
230
231fn shift(t: &Db, d: isize, cutoff: usize) -> Db {
238 match t {
239 Db::Var(k) => {
240 if *k >= cutoff {
241 Db::Var((*k as isize + d) as usize)
242 } else {
243 Db::Var(*k)
244 }
245 }
246 Db::Pi(a, b) => Db::Pi(Box::new(shift(a, d, cutoff)), Box::new(shift(b, d, cutoff + 1))),
247 Db::Lam(a, b) => Db::Lam(Box::new(shift(a, d, cutoff)), Box::new(shift(b, d, cutoff + 1))),
248 Db::App(f, a) => Db::App(Box::new(shift(f, d, cutoff)), Box::new(shift(a, d, cutoff))),
249 Db::Match { disc, motive, cases } => Db::Match {
250 disc: Box::new(shift(disc, d, cutoff)),
251 motive: Box::new(shift(motive, d, cutoff)),
252 cases: cases.iter().map(|c| shift(c, d, cutoff)).collect(),
253 },
254 Db::Fix(body) => Db::Fix(Box::new(shift(body, d, cutoff + 1))),
255 Db::MutualFix { defs, index } => Db::MutualFix {
256 defs: defs.iter().map(|b| shift(b, d, cutoff + defs.len())).collect(),
258 index: *index,
259 },
260 Db::Let(ty, value, body) => Db::Let(
261 Box::new(shift(ty, d, cutoff)),
262 Box::new(shift(value, d, cutoff)),
263 Box::new(shift(body, d, cutoff + 1)),
264 ),
265 Db::Sort(_) | Db::Global(_) | Db::Const { .. } | Db::Lit(_) => t.clone(),
266 }
267}
268
269fn subst(t: &Db, j: usize, s: &Db) -> Db {
272 match t {
273 Db::Var(k) => {
274 if *k == j {
275 s.clone()
276 } else {
277 Db::Var(*k)
278 }
279 }
280 Db::Pi(a, b) => Db::Pi(
281 Box::new(subst(a, j, s)),
282 Box::new(subst(b, j + 1, &shift(s, 1, 0))),
283 ),
284 Db::Lam(a, b) => Db::Lam(
285 Box::new(subst(a, j, s)),
286 Box::new(subst(b, j + 1, &shift(s, 1, 0))),
287 ),
288 Db::App(f, a) => Db::App(Box::new(subst(f, j, s)), Box::new(subst(a, j, s))),
289 Db::Match { disc, motive, cases } => Db::Match {
290 disc: Box::new(subst(disc, j, s)),
291 motive: Box::new(subst(motive, j, s)),
292 cases: cases.iter().map(|c| subst(c, j, s)).collect(),
293 },
294 Db::Fix(body) => Db::Fix(Box::new(subst(body, j + 1, &shift(s, 1, 0)))),
295 Db::MutualFix { defs, index } => {
296 let n = defs.len();
299 Db::MutualFix {
300 defs: defs.iter().map(|b| subst(b, j + n, &shift(s, n as isize, 0))).collect(),
301 index: *index,
302 }
303 }
304 Db::Let(ty, value, body) => Db::Let(
305 Box::new(subst(ty, j, s)),
306 Box::new(subst(value, j, s)),
307 Box::new(subst(body, j + 1, &shift(s, 1, 0))),
308 ),
309 Db::Sort(_) | Db::Global(_) | Db::Const { .. } | Db::Lit(_) => t.clone(),
310 }
311}
312
313fn beta_open(body: &Db, arg: &Db) -> Db {
315 shift(&subst(body, 0, &shift(arg, 1, 0)), -1, 0)
316}
317
318fn open_mutual(defs: &[Db], index: usize) -> Db {
323 let n = defs.len();
324 let mut result = defs[index].clone();
325 for k in 0..n {
326 let proj = Db::MutualFix { defs: defs.to_vec(), index: n - 1 - k };
327 result = beta_open(&result, &proj);
328 }
329 result
330}
331
332fn spine(t: &Db) -> (Db, Vec<Db>) {
334 let mut args = Vec::new();
335 let mut cur = t.clone();
336 while let Db::App(f, a) = cur {
337 args.push(*a);
338 cur = *f;
339 }
340 args.reverse();
341 (cur, args)
342}
343
344fn ctor_headed(genv: &Context, t: &Db) -> bool {
347 let (head, _) = spine(&whnf(genv, t));
348 matches!(head, Db::Global(n) if genv.is_constructor(&n))
349}
350
351fn try_builtin(genv: &Context, t: &Db) -> Option<Db> {
356 let (head, args) = spine(t);
357 let op = match &head {
358 Db::Global(n) if args.len() == 2 => n.as_str(),
359 _ => return None,
360 };
361 if !matches!(op, "add" | "sub" | "mul" | "div" | "mod" | "le" | "lt" | "ge" | "gt") {
362 return None;
363 }
364 let (xl, yl) = match (whnf(genv, &args[0]), whnf(genv, &args[1])) {
365 (Db::Lit(xl), Db::Lit(yl)) => (xl, yl),
366 _ => return None,
367 };
368 let bool_op = |b: bool| Some(Db::Global(if b { "true" } else { "false" }.to_string()));
369
370 if let (Literal::Int(x), Literal::Int(y)) = (&xl, &yl) {
372 let fast = match op {
373 "add" => x.checked_add(*y),
374 "sub" => x.checked_sub(*y),
375 "mul" => x.checked_mul(*y),
376 "div" => x.checked_div(*y),
377 "mod" => x.checked_rem(*y),
378 _ => None,
379 };
380 if let Some(r) = fast {
381 return Some(Db::Lit(Literal::Int(r)));
382 }
383 match op {
384 "le" => return bool_op(*x <= *y),
385 "lt" => return bool_op(*x < *y),
386 "ge" => return bool_op(*x >= *y),
387 "gt" => return bool_op(*x > *y),
388 _ => {}
389 }
390 }
391
392 let (xb, yb) = (lit_bigint(&xl)?, lit_bigint(&yl)?);
395 let big = match op {
396 "add" => Some(xb.add(&yb)),
397 "sub" => Some(xb.sub(&yb)),
398 "mul" => Some(xb.mul(&yb)),
399 "div" => xb.div_rem(&yb).map(|(q, _)| q),
400 "mod" => xb.div_rem(&yb).map(|(_, r)| r),
401 _ => None,
402 };
403 if let Some(r) = big {
404 return Some(Db::Lit(int_lit(r)));
405 }
406 match op {
407 "le" => bool_op(xb <= yb),
408 "lt" => bool_op(xb < yb),
409 "ge" => bool_op(xb >= yb),
410 "gt" => bool_op(xb > yb),
411 _ => None,
412 }
413}
414
415fn pi_count(t: &Db) -> usize {
418 match t {
419 Db::Pi(_, b) => 1 + pi_count(b),
420 _ => 0,
421 }
422}
423
424fn inductive_arity(genv: &Context, ind: &str) -> usize {
427 match genv.get_global(ind) {
428 Some(ty) => to_db(&ty.clone(), &mut Vec::new()).map(|d| pi_count(&d)).unwrap_or(0),
429 None => 0,
430 }
431}
432
433fn try_quot_lift_db(genv: &Context, t: &Db) -> Option<Db> {
440 let (head, args) = spine(t);
441 if !matches!(&head, Db::Global(n) if n == "Quot_lift") || args.len() != 6 {
442 return None;
443 }
444 let q = whnf(genv, &args[5]);
445 let (qh, qa) = spine(&q);
446 if !matches!(&qh, Db::Global(n) if n == "Quot_mk") || qa.len() != 3 {
447 return None;
448 }
449 Some(Db::App(Box::new(args[3].clone()), Box::new(qa[2].clone())))
450}
451
452fn whnf(genv: &Context, t: &Db) -> Db {
453 let mut cur = t.clone();
454 let mut fuel: usize = 1_000_000;
457 loop {
458 if fuel == 0 {
459 return cur;
460 }
461 fuel -= 1;
462 if matches!(cur, Db::App(..)) {
465 if let Some(reduced) = try_quot_lift_db(genv, &cur) {
466 cur = reduced;
467 continue;
468 }
469 }
470 if let Db::App(f, a) = &cur {
475 if matches!(f.as_ref(), Db::Global(n) if n == "reduceBool") {
476 let av = whnf(genv, a);
477 if matches!(&av, Db::Global(n) if n == "true" || n == "false") {
478 cur = av;
479 continue;
480 }
481 }
482 }
483 match cur {
484 Db::Global(name) => {
488 if let Some(body) = genv.get_definition_body(&name) {
489 if let Ok(db) = to_db(&body.clone(), &mut Vec::new()) {
490 cur = db;
491 continue;
492 }
493 }
494 return Db::Global(name);
495 }
496 Db::App(f, a) => {
497 let fw = whnf(genv, &f);
498 match fw {
499 Db::Lam(_, body) => {
500 cur = beta_open(&body, &a);
501 }
502 Db::Fix(fbody) => {
506 let (_, args) = spine(&Db::App(Box::new(Db::Fix(fbody.clone())), a.clone()));
507 if args.iter().any(|x| ctor_headed(genv, x)) {
508 let unfolded = beta_open(&fbody, &Db::Fix(fbody.clone()));
509 cur = Db::App(Box::new(unfolded), a);
510 } else {
511 return Db::App(Box::new(Db::Fix(fbody)), a);
512 }
513 }
514 Db::MutualFix { defs, index } => {
517 let applied =
518 Db::App(Box::new(Db::MutualFix { defs: defs.clone(), index }), a.clone());
519 let (_, args) = spine(&applied);
520 if args.iter().any(|x| ctor_headed(genv, x)) {
521 cur = Db::App(Box::new(open_mutual(&defs, index)), a);
522 } else {
523 return Db::App(Box::new(Db::MutualFix { defs, index }), a);
524 }
525 }
526 other => {
527 let full = Db::App(Box::new(other), a);
532 if let Some(r) = try_builtin(genv, &full) {
533 cur = r;
534 } else {
535 return full;
536 }
537 }
538 }
539 }
540 Db::Match { disc, motive, cases } => {
541 let mut d = whnf(genv, &disc);
542 if matches!(&d, Db::Lit(Literal::Nat(_))) {
546 d = db_nat_peano_step(&d);
547 }
548 let (head, args) = spine(&d);
549 if let Db::Global(cname) = &head {
550 if let Some(ind) = genv.constructor_inductive(cname) {
551 let ctor_names: Vec<String> =
552 genv.get_constructors(ind).iter().map(|(n, _)| n.to_string()).collect();
553 if let Some(idx) = ctor_names.iter().position(|n| n == cname) {
554 if idx < cases.len() {
555 let arity = inductive_arity(genv, ind);
556 let val_args =
557 if args.len() >= arity { &args[arity..] } else { &args[..] };
558 let mut res = cases[idx].clone();
559 for a in val_args {
560 res = Db::App(Box::new(res), Box::new(a.clone()));
561 }
562 cur = res;
563 continue;
564 }
565 }
566 }
567 }
568 return Db::Match { disc: Box::new(d), motive, cases };
569 }
570 Db::Let(_ty, value, body) => {
572 cur = beta_open(&body, &value);
573 }
574 other => return other,
575 }
576 }
577}
578
579fn db_nat_peano_step(t: &Db) -> Db {
586 match t {
587 Db::Lit(Literal::Nat(n)) if *n <= logicaffeine_base::BigInt::from_i64(0) => {
590 Db::Global("Zero".to_string())
591 }
592 Db::Lit(Literal::Nat(n)) => Db::App(
593 Box::new(Db::Global("Succ".to_string())),
594 Box::new(Db::Lit(Literal::Nat(n.sub(&logicaffeine_base::BigInt::from_i64(1))))),
595 ),
596 other => other.clone(),
597 }
598}
599
600fn db_nat_peano_headed(t: &Db) -> bool {
603 match t {
604 Db::Global(n) => n == "Zero",
605 Db::App(f, _) => matches!(f.as_ref(), Db::Global(n) if n == "Succ"),
606 _ => false,
607 }
608}
609
610fn def_eq(genv: &Context, lctx: &[Db], a: &Db, b: &Db) -> bool {
613 let a = whnf(genv, a);
614 let b = whnf(genv, b);
615
616 match (&a, &b) {
620 (Db::Lit(Literal::Nat(x)), Db::Lit(Literal::Nat(y))) => return x == y,
621 (Db::Lit(Literal::Nat(_)), _) if db_nat_peano_headed(&b) => {
622 return def_eq(genv, lctx, &db_nat_peano_step(&a), &b);
623 }
624 (_, Db::Lit(Literal::Nat(_))) if db_nat_peano_headed(&a) => {
625 return def_eq(genv, lctx, &a, &db_nat_peano_step(&b));
626 }
627 _ => {}
628 }
629
630 if let Db::Lam(dom, body) = &a {
633 if !matches!(b, Db::Lam(..)) {
634 let bx = whnf(genv, &Db::App(Box::new(shift(&b, 1, 0)), Box::new(Db::Var(0))));
635 let mut ext = lctx.to_vec();
636 ext.push((**dom).clone());
637 return def_eq(genv, &ext, body, &bx);
638 }
639 }
640 if let Db::Lam(dom, body) = &b {
641 if !matches!(a, Db::Lam(..)) {
642 let ax = whnf(genv, &Db::App(Box::new(shift(&a, 1, 0)), Box::new(Db::Var(0))));
643 let mut ext = lctx.to_vec();
644 ext.push((**dom).clone());
645 return def_eq(genv, &ext, &ax, body);
646 }
647 }
648
649 if let Some(eq) = try_struct_eta_db(genv, lctx, &a, &b) {
653 return eq;
654 }
655 if let Some(eq) = try_struct_eta_db(genv, lctx, &b, &a) {
656 return eq;
657 }
658
659 let congruent = match (&a, &b) {
660 (Db::Sort(x), Db::Sort(y)) => x.equiv(y),
661 (Db::Var(i), Db::Var(j)) => i == j,
662 (Db::Global(m), Db::Global(n)) => m == n,
663 (Db::Lit(x), Db::Lit(y)) => x == y,
664 (Db::App(f1, a1), Db::App(f2, a2)) => {
665 def_eq(genv, lctx, f1, f2) && def_eq(genv, lctx, a1, a2)
666 }
667 (Db::Pi(d1, b1), Db::Pi(d2, b2)) => {
668 def_eq(genv, lctx, d1, d2) && {
669 let mut ext = lctx.to_vec();
670 ext.push((**d1).clone());
671 def_eq(genv, &ext, b1, b2)
672 }
673 }
674 (Db::Lam(d1, b1), Db::Lam(d2, b2)) => {
675 def_eq(genv, lctx, d1, d2) && {
676 let mut ext = lctx.to_vec();
677 ext.push((**d1).clone());
678 def_eq(genv, &ext, b1, b2)
679 }
680 }
681 (
682 Db::Match { disc: d1, motive: m1, cases: c1 },
683 Db::Match { disc: d2, motive: m2, cases: c2 },
684 ) => {
685 def_eq(genv, lctx, d1, d2)
686 && def_eq(genv, lctx, m1, m2)
687 && c1.len() == c2.len()
688 && c1.iter().zip(c2.iter()).all(|(x, y)| def_eq(genv, lctx, x, y))
689 }
690 (Db::Fix(b1), Db::Fix(b2)) => {
691 let mut ext = lctx.to_vec();
692 ext.push(Db::Sort(Universe::Prop)); def_eq(genv, &ext, b1, b2)
694 }
695 (Db::MutualFix { defs: d1, index: i1 }, Db::MutualFix { defs: d2, index: i2 }) => {
696 i1 == i2
697 && d1.len() == d2.len()
698 && {
699 let mut ext = lctx.to_vec();
700 for _ in 0..d1.len() {
701 ext.push(Db::Sort(Universe::Prop)); }
703 d1.iter().zip(d2.iter()).all(|(a, b)| def_eq(genv, &ext, a, b))
704 }
705 }
706 (Db::Const { name: n1, levels: l1 }, Db::Const { name: n2, levels: l2 }) => {
707 n1 == n2 && l1.len() == l2.len() && l1.iter().zip(l2.iter()).all(|(a, b)| a.equiv(b))
708 }
709 _ => false,
710 };
711 if congruent {
712 return true;
713 }
714
715 proof_irrel(genv, lctx, &a, &b)
717}
718
719fn try_struct_eta_db(genv: &Context, lctx: &[Db], mk_term: &Db, other: &Db) -> Option<bool> {
723 let (head, args) = spine(mk_term);
724 let Db::Global(hname) = &head else { return None };
725 let (_sname, info) = genv.struct_of_constructor(hname)?;
726 let nfields = info.projections.len();
727 if args.len() != info.num_params + nfields {
728 return None;
729 }
730 let (ohead, _) = spine(other);
731 if matches!(&ohead, Db::Global(n) if n == hname) {
732 return None;
733 }
734 let params = &args[..info.num_params];
735 let field_args = &args[info.num_params..];
736 Some(info.projections.iter().enumerate().all(|(i, proj)| {
737 let mut app = Db::Global(proj.clone());
739 for p in params {
740 app = Db::App(Box::new(app), Box::new(p.clone()));
741 }
742 app = Db::App(Box::new(app), Box::new(other.clone()));
743 def_eq(genv, lctx, &field_args[i], &app)
744 }))
745}
746
747fn proof_irrel(genv: &Context, lctx: &[Db], a: &Db, b: &Db) -> bool {
750 let ta = match infer(genv, lctx, a) {
751 Ok(t) => t,
752 Err(_) => return false,
753 };
754 match infer(genv, lctx, &ta) {
755 Ok(s) if matches!(
756 whnf(genv, &s),
757 Db::Sort(Universe::Prop) | Db::Sort(Universe::SProp)
758 ) => {}
759 _ => return false,
760 }
761 match infer(genv, lctx, b) {
762 Ok(tb) => def_eq(genv, lctx, &ta, &tb),
763 Err(_) => false,
764 }
765}
766
767fn is_sub(genv: &Context, lctx: &[Db], sub: &Db, sup: &Db) -> bool {
770 let s = whnf(genv, sub);
771 let t = whnf(genv, sup);
772 match (&s, &t) {
773 (Db::Sort(x), Db::Sort(y)) => x.is_subtype_of(y),
774 (Db::Pi(d1, b1), Db::Pi(d2, b2)) => {
775 def_eq(genv, lctx, d1, d2) && {
776 let mut ext = lctx.to_vec();
777 ext.push((**d1).clone());
778 is_sub(genv, &ext, b1, b2)
779 }
780 }
781 _ => def_eq(genv, lctx, &s, &t),
782 }
783}
784
785fn var_type(lctx: &[Db], k: usize) -> RResult<Db> {
792 if k >= lctx.len() {
793 return Err(ReCheckError::ill(format!("de Bruijn index {} out of range", k)));
794 }
795 let stored = &lctx[lctx.len() - 1 - k];
796 Ok(shift(stored, (k + 1) as isize, 0))
797}
798
799fn infer(genv: &Context, lctx: &[Db], t: &Db) -> RResult<Db> {
801 match t {
802 Db::Sort(u) => Ok(Db::Sort(u.succ())),
803 Db::Var(k) => var_type(lctx, *k),
804 Db::Global(name) => {
805 let ty = genv
806 .get_global(name)
807 .ok_or_else(|| ReCheckError::ill(format!("unknown global '{}'", name)))?;
808 to_db(&ty.clone(), &mut Vec::new())
809 }
810 Db::Const { name, levels } => {
811 let (params, ty, _body) = genv
812 .get_universe_poly(name)
813 .ok_or_else(|| ReCheckError::ill(format!("unknown universe-poly global '{}'", name)))?;
814 if params.len() != levels.len() {
815 return Err(ReCheckError::ill(format!(
816 "universe-poly '{}' expects {} levels, got {}",
817 name,
818 params.len(),
819 levels.len()
820 )));
821 }
822 let subst: std::collections::HashMap<String, Universe> =
823 params.iter().cloned().zip(levels.iter().cloned()).collect();
824 let instantiated = crate::term::instantiate_universes(&ty.clone(), &subst);
825 to_db(&instantiated, &mut Vec::new())
826 }
827 Db::Lit(Literal::Nat(n)) if *n < logicaffeine_base::BigInt::from_i64(0) => {
828 Err(ReCheckError::ill("a `Nat` literal must be non-negative".to_string()))
829 }
830 Db::Lit(l) => Ok(Db::Global(lit_type_name(l).to_string())),
831 Db::Pi(dom, body) => {
832 let dom_sort = infer_sort(genv, lctx, dom)?;
833 let mut ext = lctx.to_vec();
834 ext.push((**dom).clone());
835 let body_sort = infer_sort(genv, &ext, body)?;
836 let pi_sort = dom_sort.imax(&body_sort);
839 Ok(Db::Sort(pi_sort))
840 }
841 Db::Lam(dom, body) => {
842 let _ = infer_sort(genv, lctx, dom)?;
843 let mut ext = lctx.to_vec();
844 ext.push((**dom).clone());
845 let body_ty = infer(genv, &ext, body)?;
846 Ok(Db::Pi(dom.clone(), Box::new(body_ty)))
847 }
848 Db::App(f, a) => {
849 let f_ty = whnf(genv, &infer(genv, lctx, f)?);
850 match f_ty {
851 Db::Pi(dom, body) => {
852 check(genv, lctx, a, &dom)?;
853 Ok(beta_open(&body, a))
854 }
855 other => Err(ReCheckError::ill(format!(
856 "application of a non-function (type {})",
857 from_db(&other, lctx.len())
858 ))),
859 }
860 }
861 Db::Match { disc, motive, cases } => infer_match(genv, lctx, disc, motive, cases),
862 Db::Fix(body) => infer_fix(genv, lctx, body),
863 Db::MutualFix { defs, index } => infer_mutual_fix(genv, lctx, defs, *index),
864 Db::Let(ty, value, body) => {
868 let _ = infer_sort(genv, lctx, ty)?;
869 check(genv, lctx, value, ty)?;
870 infer(genv, lctx, &beta_open(body, value))
871 }
872 }
873}
874
875fn effective_motive(genv: &Context, lctx: &[Db], motive: &Db, disc_ty: &Db) -> RResult<Db> {
879 let motive_ty = whnf(genv, &infer(genv, lctx, motive)?);
880 match &motive_ty {
881 Db::Pi(dom, cod) => {
882 if !def_eq(genv, lctx, dom, disc_ty) {
883 return Err(ReCheckError::ill(format!(
884 "motive domain {} does not match discriminant type {}",
885 from_db(dom, lctx.len()),
886 from_db(disc_ty, lctx.len())
887 )));
888 }
889 let mut ext = lctx.to_vec();
890 ext.push(disc_ty.clone());
891 infer_sort(genv, &ext, cod)?;
892 Ok(motive.clone())
893 }
894 Db::Sort(_) => Ok(Db::Lam(Box::new(disc_ty.clone()), Box::new(shift(motive, 1, 0)))),
895 other => Err(ReCheckError::ill(format!(
896 "motive is neither a function nor a type (type {})",
897 from_db(other, lctx.len())
898 ))),
899 }
900}
901
902fn infer_fix(genv: &Context, lctx: &[Db], body: &Db) -> RResult<Db> {
906 let fix_level = lctx.len();
907
908 let mut inner = lctx.to_vec();
913 inner.push(Db::Sort(Universe::Prop));
914 let fix_ty_inner = fix_type(genv, &inner, body)?;
915 let fix_ty = shift(&fix_ty_inner, -1, 0);
918
919 check_terminates(genv, body, fix_level)?;
922
923 let mut ext = lctx.to_vec();
925 ext.push(fix_ty.clone());
926 let _ = infer(genv, &ext, body)?;
927
928 Ok(fix_ty)
929}
930
931fn infer_mutual_fix(genv: &Context, lctx: &[Db], defs: &[Db], index: usize) -> RResult<Db> {
936 let n = defs.len();
937 if n == 0 || index >= n {
938 return Err(ReCheckError::ill("malformed mutual fixpoint".to_string()));
939 }
940 let base = lctx.len();
941
942 let mut inner = lctx.to_vec();
947 for _ in 0..n {
948 inner.push(Db::Sort(Universe::Prop));
949 }
950 let mut types: Vec<Db> = Vec::with_capacity(n);
951 for body in defs {
952 let ty_inner = fix_type(genv, &inner, body)?;
953 types.push(shift(&ty_inner, -(n as isize), 0));
954 }
955
956 check_terminates_mutual(genv, defs, base)?;
958
959 let mut ext = lctx.to_vec();
963 for (j, ty) in types.iter().enumerate() {
964 ext.push(shift(ty, j as isize, 0));
965 }
966 for body in defs {
967 let _ = infer(genv, &ext, body)?;
968 }
969
970 Ok(types[index].clone())
971}
972
973fn fix_type(genv: &Context, lctx: &[Db], body: &Db) -> RResult<Db> {
977 match body {
978 Db::Lam(dom, inner) => {
979 let _ = infer_sort(genv, lctx, dom)?;
980 let mut ext = lctx.to_vec();
981 ext.push((**dom).clone());
982 let inner_ty = fix_type(genv, &ext, inner)?;
983 Ok(Db::Pi(dom.clone(), Box::new(inner_ty)))
984 }
985 Db::Match { disc, motive, .. } => {
986 let disc_ty = whnf(genv, &infer(genv, lctx, disc)?);
987 match_return_type(genv, lctx, motive, disc, &disc_ty)
988 }
989 other => infer(genv, lctx, other),
990 }
991}
992
993fn level_of(depth: usize, k: usize) -> Option<usize> {
1010 depth.checked_sub(1)?.checked_sub(k)
1011}
1012
1013fn count_pi_named(t: &Term) -> usize {
1015 match t {
1016 Term::Pi { body_type, .. } => 1 + count_pi_named(body_type),
1017 _ => 0,
1018 }
1019}
1020
1021fn locate_struct<'a>(
1026 genv: &Context,
1027 body: &'a Db,
1028 base_depth: usize,
1029) -> RResult<(usize, String, &'a Db, usize)> {
1030 let mut chain: Vec<(usize, Option<String>, &Db)> = Vec::new();
1031 let mut cur = body;
1032 let mut depth = base_depth;
1033 while let Db::Lam(dom, inner) = cur {
1034 let dom_h = whnf(genv, dom);
1035 chain.push((depth, extract_inductive(genv, &dom_h).map(|(n, _)| n), inner));
1036 cur = inner;
1037 depth += 1;
1038 }
1039 let scrutinee = match cur {
1040 Db::Match { disc, .. } => match &**disc {
1041 Db::Var(k) => {
1042 level_of(depth, *k).and_then(|lvl| chain.iter().position(|(l, ..)| *l == lvl))
1043 }
1044 _ => None,
1045 },
1046 _ => None,
1047 };
1048 let idx = scrutinee
1049 .filter(|&i| chain[i].1.is_some())
1050 .or_else(|| chain.iter().position(|(_, ind, _)| ind.is_some()))
1051 .ok_or_else(|| {
1052 ReCheckError::ill(
1053 "fixpoint has no inductive parameter for structural recursion".to_string(),
1054 )
1055 })?;
1056 Ok((chain[idx].0, chain[idx].1.clone().unwrap(), chain[idx].2, idx))
1057}
1058
1059fn check_terminates(genv: &Context, body: &Db, fix_level: usize) -> RResult<()> {
1063 let (struct_level, ind, guard_body, struct_pos) =
1064 locate_struct(genv, body, fix_level + 1)?;
1065 let mut fix_positions = HashMap::new();
1066 fix_positions.insert(fix_level, struct_pos);
1067 guard(genv, guard_body, &fix_positions, struct_level, &ind, &HashSet::new(), struct_level + 1)
1068}
1069
1070fn check_terminates_mutual(genv: &Context, defs: &[Db], base: usize) -> RResult<()> {
1076 let n = defs.len();
1077 let base_depth = base + n;
1078 let mut fix_positions = HashMap::new();
1079 let mut located: Vec<(usize, String, &Db)> = Vec::with_capacity(n);
1080 for (j, body) in defs.iter().enumerate() {
1081 let (struct_level, ind, guard_body, struct_pos) = locate_struct(genv, body, base_depth)?;
1082 fix_positions.insert(base + j, struct_pos);
1083 located.push((struct_level, ind, guard_body));
1084 }
1085 for (struct_level, ind, guard_body) in &located {
1086 guard(
1087 genv,
1088 guard_body,
1089 &fix_positions,
1090 *struct_level,
1091 ind,
1092 &HashSet::new(),
1093 struct_level + 1,
1094 )?;
1095 }
1096 Ok(())
1097}
1098
1099fn guard(
1104 genv: &Context,
1105 term: &Db,
1106 fix_positions: &HashMap<usize, usize>,
1107 struct_level: usize,
1108 struct_type: &str,
1109 smaller: &HashSet<usize>,
1110 depth: usize,
1111) -> RResult<()> {
1112 match term {
1113 Db::App(..) => {
1114 let (head, args) = spine(term);
1115 if let Db::Var(k) = &head {
1116 if let Some(&pos) = level_of(depth, *k).and_then(|lvl| fix_positions.get(&lvl)) {
1117 match args.get(pos) {
1123 Some(arg) => {
1124 let mut h: &Db = arg;
1125 while let Db::App(f, _) = h {
1126 h = f;
1127 }
1128 match h {
1129 Db::Var(j)
1130 if level_of(depth, *j)
1131 .is_some_and(|l| smaller.contains(&l)) => {}
1132 Db::Var(_) => {
1133 return Err(ReCheckError::ill(
1134 "recursive call on an argument not headed by a \
1135 structurally-smaller variable"
1136 .to_string(),
1137 ))
1138 }
1139 _ => {
1140 return Err(ReCheckError::ill(
1141 "recursive call whose structural argument is not a \
1142 variable or an application of one — cannot certify it \
1143 decreases"
1144 .to_string(),
1145 ))
1146 }
1147 }
1148 }
1149 None => {
1150 return Err(ReCheckError::ill(
1151 "recursive call is missing its structural argument".to_string(),
1152 ))
1153 }
1154 }
1155 for a in &args {
1156 guard(genv, a, fix_positions, struct_level, struct_type, smaller, depth)?;
1157 }
1158 return Ok(());
1159 }
1160 }
1161 guard(genv, &head, fix_positions, struct_level, struct_type, smaller, depth)?;
1162 for a in &args {
1163 guard(genv, a, fix_positions, struct_level, struct_type, smaller, depth)?;
1164 }
1165 Ok(())
1166 }
1167 Db::Match { disc, motive, cases } => {
1168 guard(genv, motive, fix_positions, struct_level, struct_type, smaller, depth)?;
1171 if let Db::Var(k) = &**disc {
1174 if level_of(depth, *k) == Some(struct_level) {
1175 return guard_match_cases(
1176 genv, cases, struct_type, fix_positions, struct_level, smaller, depth,
1177 );
1178 }
1179 }
1180 guard(genv, disc, fix_positions, struct_level, struct_type, smaller, depth)?;
1181 for c in cases {
1182 guard(genv, c, fix_positions, struct_level, struct_type, smaller, depth)?;
1183 }
1184 Ok(())
1185 }
1186 Db::Lam(dom, inner) | Db::Pi(dom, inner) => {
1189 guard(genv, dom, fix_positions, struct_level, struct_type, smaller, depth)?;
1190 guard(genv, inner, fix_positions, struct_level, struct_type, smaller, depth + 1)
1191 }
1192 Db::Fix(inner) => {
1193 guard(genv, inner, fix_positions, struct_level, struct_type, smaller, depth + 1)
1194 }
1195 Db::MutualFix { defs, .. } => {
1199 for b in defs {
1200 guard(genv, b, fix_positions, struct_level, struct_type, smaller, depth + defs.len())?;
1201 }
1202 Ok(())
1203 }
1204 Db::Let(ty, value, body) => {
1205 guard(genv, ty, fix_positions, struct_level, struct_type, smaller, depth)?;
1208 guard(genv, value, fix_positions, struct_level, struct_type, smaller, depth)?;
1209 guard(genv, body, fix_positions, struct_level, struct_type, smaller, depth + 1)
1210 }
1211 Db::Var(k) => {
1212 if level_of(depth, *k).is_some_and(|lvl| fix_positions.contains_key(&lvl)) {
1215 Err(ReCheckError::ill(
1216 "recursive name occurs as a first-class value, not applied to a \
1217 structurally-smaller argument"
1218 .to_string(),
1219 ))
1220 } else {
1221 Ok(())
1222 }
1223 }
1224 Db::Sort(_) | Db::Global(_) | Db::Const { .. } | Db::Lit(_) => Ok(()),
1225 }
1226}
1227
1228fn guard_match_cases(
1231 genv: &Context,
1232 cases: &[Db],
1233 struct_type: &str,
1234 fix_positions: &HashMap<usize, usize>,
1235 struct_level: usize,
1236 smaller: &HashSet<usize>,
1237 depth: usize,
1238) -> RResult<()> {
1239 let arities: Vec<usize> =
1240 genv.get_constructors(struct_type).iter().map(|(_, t)| count_pi_named(t)).collect();
1241 for (i, case) in cases.iter().enumerate() {
1242 let arity = arities.get(i).copied().unwrap_or(0);
1243 let mut smaller2 = smaller.clone();
1244 let mut cur = case;
1245 let mut d = depth;
1246 for _ in 0..arity {
1250 if let Db::Lam(_, inner) = cur {
1251 smaller2.insert(d);
1252 cur = inner;
1253 d += 1;
1254 } else {
1255 break;
1256 }
1257 }
1258 guard(genv, cur, fix_positions, struct_level, struct_type, &smaller2, d)?;
1259 }
1260 Ok(())
1261}
1262
1263fn infer_match(
1268 genv: &Context,
1269 lctx: &[Db],
1270 disc: &Db,
1271 motive: &Db,
1272 cases: &[Db],
1273) -> RResult<Db> {
1274 let disc_ty = whnf(genv, &infer(genv, lctx, disc)?);
1276 let (ind_name, type_args) = extract_inductive(genv, &disc_ty).ok_or_else(|| {
1277 ReCheckError::unsupported(format!(
1278 "match discriminant of unrecognized type {}",
1279 from_db(&disc_ty, lctx.len())
1280 ))
1281 })?;
1282
1283 let p = genv.inductive_num_params(&ind_name).min(type_args.len());
1287 let indexed = type_args.len() > p;
1288 let eff_motive = if indexed {
1289 let _ = infer(genv, lctx, motive)?; None
1291 } else {
1292 Some(effective_motive(genv, lctx, motive, &disc_ty)?)
1293 };
1294
1295 let ctor_names: Vec<String> =
1297 genv.get_constructors(&ind_name).iter().map(|(n, _)| n.to_string()).collect();
1298 if cases.len() != ctor_names.len() {
1299 return Err(ReCheckError::ill(format!(
1300 "match on {} has {} cases but {} constructors",
1301 ind_name,
1302 cases.len(),
1303 ctor_names.len()
1304 )));
1305 }
1306
1307 for (case, cname) in cases.iter().zip(ctor_names.iter()) {
1309 let case_ty = match &eff_motive {
1310 Some(eff) => case_type(genv, eff, cname, &type_args)?,
1311 None => case_type_indexed(genv, motive, cname, &type_args[..p])?,
1312 };
1313 check_case(genv, lctx, case, &case_ty)?;
1314 }
1315
1316 let ret = match_return_type(genv, lctx, motive, disc, &disc_ty)?;
1318
1319 let disc_sort = whnf(genv, &infer(genv, lctx, &disc_ty)?);
1322 if matches!(disc_sort, Db::Sort(Universe::Prop)) {
1323 let ret_sort = whnf(genv, &infer(genv, lctx, &ret)?);
1324 let large = !matches!(ret_sort, Db::Sort(Universe::Prop));
1325 if large && !is_subsingleton(genv, &ind_name)? {
1326 return Err(ReCheckError::ill(format!(
1327 "large elimination of non-subsingleton proposition '{}' into a larger sort",
1328 ind_name
1329 )));
1330 }
1331 }
1332
1333 Ok(ret)
1334}
1335
1336fn extract_inductive(genv: &Context, ty: &Db) -> Option<(String, Vec<Db>)> {
1339 let (head, args) = spine(ty);
1340 match head {
1341 Db::Global(name) if genv.is_inductive(&name) => Some((name, args)),
1342 _ => None,
1343 }
1344}
1345
1346fn case_type(genv: &Context, motive: &Db, ctor_name: &str, type_args: &[Db]) -> RResult<Db> {
1352 let ctor_named = genv
1353 .get_global(ctor_name)
1354 .ok_or_else(|| ReCheckError::ill(format!("unknown constructor '{}'", ctor_name)))?
1355 .clone();
1356 let ctor_db = to_db(&ctor_named, &mut Vec::new())?;
1357
1358 let mut body = ctor_db;
1360 for ta in type_args {
1361 match body {
1362 Db::Pi(_dom, rest) => body = beta_open(&rest, ta),
1363 _ => {
1364 return Err(ReCheckError::ill(format!(
1365 "constructor '{}' has fewer parameters than the discriminant's type arguments",
1366 ctor_name
1367 )))
1368 }
1369 }
1370 }
1371
1372 let mut value_doms = Vec::new();
1374 let mut cur = body;
1375 while let Db::Pi(dom, rest) = cur {
1376 value_doms.push(*dom);
1377 cur = *rest;
1378 }
1379 let num_val = value_doms.len();
1380 let lift = num_val as isize;
1381
1382 let mut applied = Db::Global(ctor_name.to_string());
1384 for ta in type_args {
1385 applied = Db::App(Box::new(applied), Box::new(shift(ta, lift, 0)));
1386 }
1387 for p in 0..num_val {
1388 applied = Db::App(Box::new(applied), Box::new(Db::Var(num_val - 1 - p)));
1389 }
1390
1391 let motive_bot = shift(motive, lift, 0);
1392 let cod = whnf(genv, &Db::App(Box::new(motive_bot), Box::new(applied)));
1393
1394 let mut case_ty = cod;
1396 for dom in value_doms.into_iter().rev() {
1397 case_ty = Db::Pi(Box::new(dom), Box::new(case_ty));
1398 }
1399 Ok(case_ty)
1400}
1401
1402fn match_return_type(genv: &Context, lctx: &[Db], motive: &Db, disc: &Db, disc_ty: &Db) -> RResult<Db> {
1407 let (ind_name, type_args) = extract_inductive(genv, disc_ty).ok_or_else(|| {
1408 ReCheckError::unsupported(format!(
1409 "match discriminant of unrecognized type {}",
1410 from_db(disc_ty, lctx.len())
1411 ))
1412 })?;
1413 let p = genv.inductive_num_params(&ind_name).min(type_args.len());
1414 if type_args.len() > p {
1415 let mut ret = motive.clone();
1416 for idx in &type_args[p..] {
1417 ret = Db::App(Box::new(ret), Box::new(idx.clone()));
1418 }
1419 ret = Db::App(Box::new(ret), Box::new(disc.clone()));
1420 Ok(whnf(genv, &ret))
1421 } else {
1422 let eff = effective_motive(genv, lctx, motive, disc_ty)?;
1423 Ok(whnf(genv, &Db::App(Box::new(eff), Box::new(disc.clone()))))
1424 }
1425}
1426
1427fn case_type_indexed(genv: &Context, motive: &Db, ctor_name: &str, params: &[Db]) -> RResult<Db> {
1433 let ctor_named = genv
1434 .get_global(ctor_name)
1435 .ok_or_else(|| ReCheckError::ill(format!("unknown constructor '{}'", ctor_name)))?
1436 .clone();
1437 let ctor_db = to_db(&ctor_named, &mut Vec::new())?;
1438
1439 let mut body = ctor_db;
1441 for pa in params {
1442 match body {
1443 Db::Pi(_dom, rest) => body = beta_open(&rest, pa),
1444 _ => {
1445 return Err(ReCheckError::ill(format!(
1446 "constructor '{}' has fewer parameters than the inductive",
1447 ctor_name
1448 )))
1449 }
1450 }
1451 }
1452
1453 let mut value_doms = Vec::new();
1455 let mut cur = body;
1456 while let Db::Pi(dom, rest) = cur {
1457 value_doms.push(*dom);
1458 cur = *rest;
1459 }
1460 let num_val = value_doms.len();
1461 let lift = num_val as isize;
1462
1463 let (_head, res_args) = spine(&cur);
1466 let result_indices: Vec<Db> = res_args.into_iter().skip(params.len()).collect();
1467
1468 let mut applied = Db::Global(ctor_name.to_string());
1470 for pa in params {
1471 applied = Db::App(Box::new(applied), Box::new(shift(pa, lift, 0)));
1472 }
1473 for i in 0..num_val {
1474 applied = Db::App(Box::new(applied), Box::new(Db::Var(num_val - 1 - i)));
1475 }
1476
1477 let mut cod = shift(motive, lift, 0);
1479 for ri in &result_indices {
1480 cod = Db::App(Box::new(cod), Box::new(ri.clone()));
1481 }
1482 cod = Db::App(Box::new(cod), Box::new(applied));
1483 let cod = whnf(genv, &cod);
1484
1485 let mut case_ty = cod;
1487 for dom in value_doms.into_iter().rev() {
1488 case_ty = Db::Pi(Box::new(dom), Box::new(case_ty));
1489 }
1490 Ok(case_ty)
1491}
1492
1493fn is_subsingleton(genv: &Context, ind: &str) -> RResult<bool> {
1497 let ctors: Vec<(String, Term)> = genv
1498 .get_constructors(ind)
1499 .iter()
1500 .map(|(n, t)| (n.to_string(), (*t).clone()))
1501 .collect();
1502 match ctors.len() {
1503 0 => Ok(true),
1504 1 => {
1505 let arity = inductive_arity(genv, ind);
1506 let ctor_db = to_db(&ctors[0].1, &mut Vec::new())?;
1507 let mut lctx: Vec<Db> = Vec::new();
1508 let mut cur = ctor_db;
1509 let mut i = 0;
1510 while let Db::Pi(dom, rest) = cur {
1511 if i >= arity && infer_sort(genv, &lctx, &dom)? != Universe::Prop {
1512 return Ok(false);
1513 }
1514 lctx.push(*dom);
1515 cur = *rest;
1516 i += 1;
1517 }
1518 Ok(true)
1519 }
1520 _ => Ok(false),
1521 }
1522}
1523
1524fn infer_sort(genv: &Context, lctx: &[Db], t: &Db) -> RResult<Universe> {
1526 let ty = whnf(genv, &infer(genv, lctx, t)?);
1527 match ty {
1528 Db::Sort(u) => Ok(u),
1529 other => Err(ReCheckError::ill(format!(
1530 "expected a type, got something of type {}",
1531 from_db(&other, lctx.len())
1532 ))),
1533 }
1534}
1535
1536fn check_case(genv: &Context, lctx: &[Db], case: &Db, case_ty: &Db) -> RResult<()> {
1545 match (case, case_ty) {
1546 (Db::Lam(_, body), Db::Pi(dom, cod)) => {
1547 let mut ext = lctx.to_vec();
1548 ext.push((**dom).clone());
1549 check_case(genv, &ext, body, cod)
1550 }
1551 _ => check(genv, lctx, case, case_ty),
1552 }
1553}
1554
1555fn check(genv: &Context, lctx: &[Db], t: &Db, expected: &Db) -> RResult<()> {
1556 let inferred = infer(genv, lctx, t)?;
1557 if is_sub(genv, lctx, &inferred, expected) {
1558 Ok(())
1559 } else {
1560 Err(ReCheckError::ill(format!(
1561 "type mismatch: have {}, expected {}",
1562 from_db(&whnf(genv, &inferred), lctx.len()),
1563 from_db(&whnf(genv, expected), lctx.len())
1564 )))
1565 }
1566}
1567
1568fn lit_type_name(l: &Literal) -> &'static str {
1569 match l {
1570 Literal::Int(_) | Literal::BigInt(_) => "Int",
1571 Literal::Nat(_) => "Nat",
1572 Literal::Float(_) => "Float",
1573 Literal::Text(_) => "Text",
1574 Literal::Duration(_) => "Duration",
1575 Literal::Date(_) => "Date",
1576 Literal::Moment(_) => "Moment",
1577 }
1578}
1579
1580pub fn recheck(ctx: &Context, term: &Term) -> RResult<Term> {
1592 let db = to_db(term, &mut Vec::new())?;
1593 let ty = infer(ctx, &[], &db)?;
1594 Ok(from_db(&ty, 0))
1595}
1596
1597#[derive(Debug, Clone, PartialEq)]
1599pub enum DoubleCheck {
1600 Agreed,
1603 MainOnlyReCheckerIncomplete(String),
1608 Disagree(String),
1611}
1612
1613pub fn double_check(ctx: &Context, term: &Term) -> DoubleCheck {
1620 let main = crate::infer_type(ctx, term);
1621 let recheck_result = recheck(ctx, term);
1622 match (main, recheck_result) {
1623 (Ok(main_ty), Ok(re_ty)) => {
1624 match (to_db(&main_ty, &mut Vec::new()), to_db(&re_ty, &mut Vec::new())) {
1625 (Ok(m), Ok(r)) if def_eq(ctx, &[], &m, &r) => DoubleCheck::Agreed,
1626 (Ok(_), Ok(_)) => DoubleCheck::Disagree(format!(
1627 "kernels inferred different types: main={}, recheck={}",
1628 main_ty, re_ty
1629 )),
1630 _ => DoubleCheck::MainOnlyReCheckerIncomplete(
1631 "inferred type outside re-checker fragment".to_string(),
1632 ),
1633 }
1634 }
1635 (Ok(_), Err(ReCheckError::Unsupported(why))) => {
1636 DoubleCheck::MainOnlyReCheckerIncomplete(why)
1637 }
1638 (Ok(_), Err(ReCheckError::Ill(why))) => DoubleCheck::Disagree(format!(
1639 "main kernel accepted but re-checker rejected: {}",
1640 why
1641 )),
1642 (Err(e), Ok(_)) => DoubleCheck::Disagree(format!(
1643 "re-checker accepted but main kernel rejected: {:?}",
1644 e
1645 )),
1646 (Err(_), Err(_)) => DoubleCheck::Agreed,
1647 }
1648}