1use crate::context::{Context, MutualInductive};
40use crate::error::KernelResult;
41use crate::term::{Term, Universe};
42use std::collections::BTreeSet;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub(crate) enum Kind {
47 Obs,
48 Refl,
49 Transp,
50 Cast,
51}
52
53#[derive(Debug)]
55pub(crate) struct Certificate {
56 pub(crate) constants: Vec<(String, Kind)>,
57 pub(crate) dependencies: BTreeSet<String>,
58}
59
60impl Certificate {
61 pub(crate) fn names(&self) -> impl Iterator<Item = &str> {
62 self.constants.iter().map(|(n, _)| n.as_str())
63 }
64}
65
66pub(crate) fn kind(ctx: &Context, name: &str) -> Option<Kind> {
68 ctx.observational
69 .as_ref()?
70 .constants
71 .iter()
72 .find(|(n, _)| n == name)
73 .map(|(_, k)| *k)
74}
75
76fn g(name: &str) -> Term {
77 Term::Global(name.to_string())
78}
79
80fn v(name: &str) -> Term {
81 Term::Var(name.to_string())
82}
83
84fn app(f: Term, args: impl IntoIterator<Item = Term>) -> Term {
85 args.into_iter()
86 .fold(f, |f, a| Term::App(Box::new(f), Box::new(a)))
87}
88
89fn pi(param: &str, ty: Term, body: Term) -> Term {
90 Term::Pi {
91 param: param.to_string(),
92 param_type: Box::new(ty),
93 body_type: Box::new(body),
94 }
95}
96
97fn sort(u: Universe) -> Term {
98 Term::Sort(u)
99}
100
101fn obs_at(u: Universe) -> Term {
102 Term::Const {
103 name: "Obs".to_string(),
104 levels: vec![u],
105 }
106}
107
108pub fn register(ctx: &mut Context) -> KernelResult<()> {
110 let certificate = declare(ctx)?;
111 ctx.certify_observational(certificate);
112 Ok(())
113}
114
115fn declare(ctx: &mut Context) -> KernelResult<Certificate> {
118 let sprop = || sort(Universe::SProp);
119 let u = || Universe::Var("u".to_string());
120 ctx.add_mutual_inductives(&[MutualInductive {
121 name: "STrue".to_string(),
122 sort: sprop(),
123 num_params: 0,
124 constructors: vec![("sI".to_string(), g("STrue"))],
125 }])?;
126 ctx.add_mutual_inductives(&[MutualInductive {
127 name: "SFalse".to_string(),
128 sort: sprop(),
129 num_params: 0,
130 constructors: vec![],
131 }])?;
132 ctx.add_mutual_inductives(&[MutualInductive {
133 name: "SAnd".to_string(),
134 sort: pi("A", sprop(), pi("B", sprop(), sprop())),
135 num_params: 2,
136 constructors: vec![(
137 "spair".to_string(),
138 pi(
139 "A",
140 sprop(),
141 pi(
142 "B",
143 sprop(),
144 pi(
145 "a",
146 v("A"),
147 pi("b", v("B"), app(g("SAnd"), [v("A"), v("B")])),
148 ),
149 ),
150 ),
151 )],
152 }])?;
153 ctx.add_mutual_inductives(&[MutualInductive {
154 name: "SSigma".to_string(),
155 sort: pi("A", sprop(), pi("B", pi("_", v("A"), sprop()), sprop())),
156 num_params: 2,
157 constructors: vec![(
158 "sexists".to_string(),
159 pi(
160 "A",
161 sprop(),
162 pi(
163 "B",
164 pi("_", v("A"), sprop()),
165 pi(
166 "a",
167 v("A"),
168 pi(
169 "b",
170 app(v("B"), [v("a")]),
171 app(g("SSigma"), [v("A"), v("B")]),
172 ),
173 ),
174 ),
175 ),
176 )],
177 }])?;
178 let sbox = || Term::Const {
179 name: "SBox".to_string(),
180 levels: vec![u()],
181 };
182 ctx.add_universe_poly_inductives(
183 &["u".to_string()],
184 &[MutualInductive {
185 name: "SBox".to_string(),
186 sort: pi("A", sort(u()), sprop()),
187 num_params: 1,
188 constructors: vec![(
189 "sbox".to_string(),
190 pi("A", sort(u()), pi("a", v("A"), app(sbox(), [v("A")]))),
191 )],
192 }],
193 )?;
194 register_projections(ctx)?;
195 register_closure(ctx)?;
196 let levels = vec!["u".to_string()];
197 ctx.add_universe_poly_declaration(
198 "Obs",
199 levels.clone(),
200 pi("A", sort(u()), pi("a", v("A"), pi("b", v("A"), sprop()))),
201 )?;
202 let obs = |ty: Term, a: Term, b: Term| app(obs_at(u()), [ty, a, b]);
203 ctx.add_universe_poly_declaration(
204 "Obs_refl",
205 levels.clone(),
206 pi("A", sort(u()), pi("a", v("A"), obs(v("A"), v("a"), v("a")))),
207 )?;
208 let refl = Term::Const {
209 name: "Obs_refl".to_string(),
210 levels: vec![u()],
211 };
212 ctx.add_universe_poly_declaration(
213 "Obs_transp",
214 levels.clone(),
215 pi(
216 "A",
217 sort(u()),
218 pi(
219 "a",
220 v("A"),
221 pi(
222 "P",
223 pi("b", v("A"), pi("_", obs(v("A"), v("a"), v("b")), sprop())),
224 pi(
225 "h",
226 app(v("P"), [v("a"), app(refl, [v("A"), v("a")])]),
227 pi(
228 "b",
229 v("A"),
230 pi(
231 "e",
232 obs(v("A"), v("a"), v("b")),
233 app(v("P"), [v("b"), v("e")]),
234 ),
235 ),
236 ),
237 ),
238 ),
239 ),
240 )?;
241 let types_equal = app(obs_at(u().succ()), [sort(u()), v("A"), v("B")]);
242 ctx.add_universe_poly_declaration(
243 "Obs_cast",
244 levels,
245 pi(
246 "A",
247 sort(u()),
248 pi(
249 "B",
250 sort(u()),
251 pi("e", types_equal, pi("_", v("A"), v("B"))),
252 ),
253 ),
254 )?;
255 let constants = vec![
256 ("Obs".to_string(), Kind::Obs),
257 ("Obs_refl".to_string(), Kind::Refl),
258 ("Obs_transp".to_string(), Kind::Transp),
259 ("Obs_cast".to_string(), Kind::Cast),
260 ];
261 let mut dependencies: BTreeSet<String> = constants.iter().map(|(n, _)| n.clone()).collect();
262 for name in [
263 "STrue", "sI", "SFalse", "SAnd", "spair", "SSigma", "sexists", "SBox", "sbox",
264 ]
265 .into_iter()
266 .chain(PROJECTIONS)
267 .chain(CLOSURE)
268 {
269 dependencies.insert(name.to_string());
270 }
271 Ok(Certificate {
272 constants,
273 dependencies,
274 })
275}
276
277const PROJECTIONS: [&str; 4] = ["SAnd_left", "SAnd_right", "SSigma_fst", "SSigma_snd"];
280
281fn register_projections(ctx: &mut Context) -> KernelResult<()> {
282 let sprop = || sort(Universe::SProp);
283 let and = || app(g("SAnd"), [v("A"), v("B")]);
284 let family = || pi("_", v("A"), sprop());
285 let sigma = || app(g("SSigma"), [v("A"), v("B")]);
286 let out_of = |discriminant: &str, owner: Term, motive: Term, case: Term| Term::Match {
287 discriminant: Box::new(v(discriminant)),
288 motive: Box::new(lam(discriminant, owner, motive)),
289 cases: vec![case],
290 };
291 for (name, field, ty) in [("SAnd_left", "a", v("A")), ("SAnd_right", "b", v("B"))] {
292 let body = out_of(
293 "p",
294 and(),
295 ty.clone(),
296 lam("a", v("A"), lam("b", v("B"), v(field))),
297 );
298 ctx.declare(
299 name,
300 pi("A", sprop(), pi("B", sprop(), pi("p", and(), ty))),
301 lam("A", sprop(), lam("B", sprop(), lam("p", and(), body))),
302 )?;
303 }
304 let fields = |chosen: &str| lam("a", v("A"), lam("b", app(v("B"), [v("a")]), v(chosen)));
305 let fst = out_of("s", sigma(), v("A"), fields("a"));
306 ctx.declare(
307 "SSigma_fst",
308 pi("A", sprop(), pi("B", family(), pi("s", sigma(), v("A")))),
309 lam("A", sprop(), lam("B", family(), lam("s", sigma(), fst))),
310 )?;
311 let second = || app(v("B"), [app(g("SSigma_fst"), [v("A"), v("B"), v("s")])]);
312 let snd = out_of("s", sigma(), second(), fields("b"));
313 ctx.declare(
314 "SSigma_snd",
315 pi("A", sprop(), pi("B", family(), pi("s", sigma(), second()))),
316 lam("A", sprop(), lam("B", family(), lam("s", sigma(), snd))),
317 )
318}
319
320const CLOSURE: [&str; 5] = [
322 "EqvGen",
323 "EqvGen_rel",
324 "EqvGen_refl",
325 "EqvGen_symm",
326 "EqvGen_trans",
327];
328
329fn register_closure(ctx: &mut Context) -> KernelResult<()> {
331 let u = || Universe::Var("u".to_string());
332 let relation = || pi("_", v("A"), pi("_", v("A"), sort(Universe::Prop)));
333 let closure = |x: &str, y: &str| {
334 app(
335 Term::Const {
336 name: "EqvGen".to_string(),
337 levels: vec![u()],
338 },
339 [v("A"), v("r"), v(x), v(y)],
340 )
341 };
342 let over = |body: Term| pi("A", sort(u()), pi("r", relation(), body));
343 let point = |name: &str, body: Term| pi(name, v("A"), body);
344 ctx.add_universe_poly_inductives(
345 &["u".to_string()],
346 &[MutualInductive {
347 name: "EqvGen".to_string(),
348 sort: over(point("x", point("y", sort(Universe::Prop)))),
349 num_params: 2,
350 constructors: vec![
351 (
352 "EqvGen_rel".to_string(),
353 over(point(
354 "x",
355 point(
356 "y",
357 pi("_", app(v("r"), [v("x"), v("y")]), closure("x", "y")),
358 ),
359 )),
360 ),
361 (
362 "EqvGen_refl".to_string(),
363 over(point("x", closure("x", "x"))),
364 ),
365 (
366 "EqvGen_symm".to_string(),
367 over(point(
368 "x",
369 point("y", pi("_", closure("x", "y"), closure("y", "x"))),
370 )),
371 ),
372 (
373 "EqvGen_trans".to_string(),
374 over(point(
375 "x",
376 point(
377 "y",
378 point(
379 "z",
380 pi(
381 "_",
382 closure("x", "y"),
383 pi("_", closure("y", "z"), closure("x", "z")),
384 ),
385 ),
386 ),
387 )),
388 ),
389 ],
390 }],
391 )
392}
393
394pub(crate) fn reduce(ctx: &Context, head: &Term, args: &[&Term]) -> Option<Term> {
398 let Term::Const { name, levels } = head else {
399 return None;
400 };
401 if levels.len() != 1 {
402 return None;
403 }
404 match (kind(ctx, name)?, args) {
405 (Kind::Obs, [ty, a, b]) => equality(ctx, ty, a, b),
406 (Kind::Cast, [from, to, proof, value]) => carry(ctx, from, to, proof, value),
407 _ => None,
408 }
409}
410
411fn equality(ctx: &Context, ty: &Term, a: &Term, b: &Term) -> Option<Term> {
422 match ty {
423 Term::Pi {
424 param,
425 param_type,
426 body_type,
427 } => pointwise(ctx, param, param_type, body_type, a, b),
428 Term::Sort(Universe::SProp) => Some(app(g("SAnd"), [arrow(a, b), arrow(b, a)])),
429 Term::Sort(level) if level.equiv(&Universe::Prop) => {
430 let squash = |p: Term| {
431 app(
432 Term::Const {
433 name: "SBox".to_string(),
434 levels: vec![Universe::Prop],
435 },
436 [p],
437 )
438 };
439 Some(app(g("SAnd"), [squash(arrow(a, b)), squash(arrow(b, a))]))
440 }
441 Term::Sort(_) => type_equality(ctx, a, b).map(TypeEquality::into_term),
442 _ => values(ctx, ty, a, b),
443 }
444}
445
446fn pointwise(
448 ctx: &Context,
449 param: &str,
450 domain: &Term,
451 codomain: &Term,
452 a: &Term,
453 b: &Term,
454) -> Option<Term> {
455 let x = fresh(param, &[a, b, domain, &scoped(param, codomain)]);
456 let codomain = if x == param {
457 codomain.clone()
458 } else {
459 substitute(codomain, param, &v(&x))
460 };
461 let level = level_of(&ctx.extend(&x, domain.clone()), &codomain)?;
462 let at = |t: &Term| Term::App(Box::new(t.clone()), Box::new(v(&x)));
463 Some(pi(&x, domain.clone(), obs(level, codomain, at(a), at(b))))
464}
465
466enum TypeEquality {
468 Holds,
470 Fails,
472 Products {
475 domain: Universe,
476 domains: Term,
477 codomains: Term,
478 },
479 Family { parts: Vec<Term>, dependent: bool },
481 Quotient { parts: Vec<Term> },
483}
484
485impl TypeEquality {
486 fn into_term(self) -> Term {
487 match self {
488 TypeEquality::Holds => g("STrue"),
489 TypeEquality::Fails => g("SFalse"),
490 TypeEquality::Products {
491 domains, codomains, ..
492 } => app(g("SSigma"), [domains, codomains]),
493 TypeEquality::Family { parts, .. } | TypeEquality::Quotient { parts } => {
494 conjunction(parts)
495 }
496 }
497 }
498}
499
500fn type_equality(ctx: &Context, a: &Term, b: &Term) -> Option<TypeEquality> {
506 let (left, right) = (former(ctx, a)?, former(ctx, b)?);
507 for side in [a, b] {
508 if !crate::type_checker::infer_sort(ctx, side)
509 .ok()?
510 .is_never_prop()
511 {
512 return None;
513 }
514 }
515 match (left, right) {
516 (Former::Sort(s), Former::Sort(t)) => {
517 if s.equiv(t) {
518 Some(TypeEquality::Holds)
519 } else if distinct_levels(s, t) {
520 Some(TypeEquality::Fails)
521 } else {
522 None
523 }
524 }
525 (Former::Pi(p, d, c), Former::Pi(q, e, k)) => products(ctx, (p, d, c), (q, e, k)),
526 (Former::Family(i, li, xs), Former::Family(j, lj, ys)) => {
527 if i != j {
528 return Some(TypeEquality::Fails);
529 }
530 families(ctx, a, (i, li, &xs), (lj, &ys))
531 }
532 (Former::Quotient(h, xs), Former::Quotient(k, ys)) => {
533 if !same_quotient(h, k)? {
534 return Some(TypeEquality::Fails);
535 }
536 quotients(ctx, h, &xs, &ys)
537 }
538 _ => Some(TypeEquality::Fails),
539 }
540}
541
542enum Former<'a> {
544 Sort(&'a Universe),
545 Pi(&'a str, &'a Term, &'a Term),
546 Family(&'a str, &'a [Universe], Vec<&'a Term>),
547 Quotient(&'a Term, Vec<&'a Term>),
549}
550
551fn former<'a>(ctx: &Context, t: &'a Term) -> Option<Former<'a>> {
552 match t {
553 Term::Sort(level) => Some(Former::Sort(level)),
554 Term::Pi {
555 param,
556 param_type,
557 body_type,
558 } => Some(Former::Pi(param, param_type, body_type)),
559 _ => {
560 let (head, args) = spine(t);
561 match head {
562 Term::Global(name) if ctx.is_inductive(name) => {
563 Some(Former::Family(name, &[], args))
564 }
565 Term::Const { name, levels } if ctx.is_inductive(name) => {
566 Some(Former::Family(name, levels, args))
567 }
568 _ if quotient_part(ctx, head) == Some(QuotientPart::Former) => {
569 Some(Former::Quotient(head, args))
570 }
571 _ => None,
572 }
573 }
574 }
575}
576
577#[derive(PartialEq)]
578enum QuotientPart {
579 Former,
580 Class,
581}
582
583fn quotient_part(ctx: &Context, head: &Term) -> Option<QuotientPart> {
585 use crate::quotient::{Kind, PreludeKind};
586 match head {
587 Term::Global(name) => match crate::quotient::prelude_kind(ctx, name)? {
588 PreludeKind::Type => Some(QuotientPart::Former),
589 PreludeKind::Constructor => Some(QuotientPart::Class),
590 _ => None,
591 },
592 Term::Const { name, levels } => match crate::quotient::kind(ctx, name, levels)? {
593 Kind::Type => Some(QuotientPart::Former),
594 Kind::Constructor => Some(QuotientPart::Class),
595 _ => None,
596 },
597 _ => None,
598 }
599}
600
601fn classes(ctx: &Context, ty: &Term, carrier: &[&Term], a: &Term, b: &Term) -> Option<Term> {
604 if carrier.len() != 2
605 || !crate::type_checker::infer_sort(ctx, ty)
606 .ok()?
607 .is_never_prop()
608 {
609 return None;
610 }
611 let ((c, xs), (d, ys)) = (spine(a), spine(b));
612 if quotient_part(ctx, c) != Some(QuotientPart::Class)
613 || quotient_part(ctx, d) != Some(QuotientPart::Class)
614 {
615 return None;
616 }
617 let ([_, _, x], [_, _, y]) = (xs.as_slice(), ys.as_slice()) else {
618 return None;
619 };
620 let level = level_of(ctx, carrier[0])?;
621 let closure = app(
622 Term::Const {
623 name: "EqvGen".to_string(),
624 levels: vec![level],
625 },
626 [
627 carrier[0].clone(),
628 carrier[1].clone(),
629 (*x).clone(),
630 (*y).clone(),
631 ],
632 );
633 Some(app(
634 Term::Const {
635 name: "SBox".to_string(),
636 levels: vec![Universe::Prop],
637 },
638 [closure],
639 ))
640}
641
642fn quotients(ctx: &Context, head: &Term, xs: &[&Term], ys: &[&Term]) -> Option<TypeEquality> {
645 if xs.len() != 2 || ys.len() != 2 {
646 return None;
647 }
648 let level = crate::type_checker::infer_sort(ctx, xs[0]).ok()?;
649 let arity = match head {
650 Term::Global(name) => {
651 let header = ctx.get_global(name)?;
652 let [u] = crate::term::free_level_vars(header).try_into().ok()?;
653 crate::term::instantiate_universes(header, &[(u, level)].into_iter().collect())
654 }
655 _ => crate::type_checker::infer_type(ctx, head).ok()?,
656 };
657 let (parts, _) = componentwise(ctx, arity, xs, ys, &|_| true)?;
661 Some(TypeEquality::Quotient { parts })
662}
663
664fn same_quotient(h: &Term, k: &Term) -> Option<bool> {
666 match (h, k) {
667 (Term::Global(m), Term::Global(n)) => Some(m == n),
668 (Term::Const { name: m, levels: l }, Term::Const { name: n, levels: k }) => {
669 if m != n {
670 return Some(false);
671 }
672 (l.len() == k.len() && l.iter().zip(k).all(|(a, b)| a.equiv(b))).then_some(true)
673 }
674 _ => Some(false),
675 }
676}
677
678fn distinct_levels(s: &Universe, t: &Universe) -> bool {
681 s.succ().is_subtype_of(t)
682 || t.succ().is_subtype_of(s)
683 || (closed(s) && closed(t) && !s.equiv(t))
684}
685
686fn closed(level: &Universe) -> bool {
687 match level {
688 Universe::SProp | Universe::Prop | Universe::Type(_) => true,
689 Universe::Var(_) => false,
690 Universe::Succ(l) => closed(l),
691 Universe::Max(a, b) | Universe::IMax(a, b) => closed(a) && closed(b),
692 }
693}
694
695fn products(
699 ctx: &Context,
700 (p, d, c): (&str, &Term, &Term),
701 (q, e, k): (&str, &Term, &Term),
702) -> Option<TypeEquality> {
703 let domain = level_of(ctx, d)?;
704 if !domain.equiv(&level_of(ctx, e)?) {
705 return None;
706 }
707 let (c_scoped, k_scoped) = (scoped(p, c), scoped(q, k));
708 let proof = fresh("e", &[d, e, &c_scoped, &k_scoped]);
709 let x = fresh(q, &[d, e, &c_scoped, &k_scoped, &v(&proof)]);
710 let domains = obs(
711 domain.succ(),
712 Term::Sort(domain.clone()),
713 e.clone(),
714 d.clone(),
715 );
716 let carried = app(
717 cast_at(domain.clone()),
718 [e.clone(), d.clone(), v(&proof), v(&x)],
719 );
720 let left = substitute(c, p, &carried);
721 let right = if x == q {
722 k.clone()
723 } else {
724 substitute(k, q, &v(&x))
725 };
726 let inside = ctx.extend(&proof, domains.clone()).extend(&x, e.clone());
727 let codomain = level_of(&inside, &left)?;
728 if !codomain.equiv(&level_of(&inside, &right)?) {
729 return None;
730 }
731 let codomains_equal = pi(
732 &x,
733 e.clone(),
734 obs(codomain.succ(), Term::Sort(codomain), left, right),
735 );
736 let codomains = lam(&proof, domains.clone(), codomains_equal);
737 Some(TypeEquality::Products {
738 domain,
739 domains,
740 codomains,
741 })
742}
743
744fn families(
750 ctx: &Context,
751 a: &Term,
752 (family, levels, xs): (&str, &[Universe], &[&Term]),
753 (other_levels, ys): (&[Universe], &[&Term]),
754) -> Option<TypeEquality> {
755 if levels.len() != other_levels.len()
756 || levels.iter().zip(other_levels).any(|(l, m)| !l.equiv(m))
757 {
758 return None;
759 }
760 let universe = crate::type_checker::infer_sort(ctx, a).ok()?;
761 let arity = crate::type_checker::infer_type(ctx, &family_head(family, levels)).ok()?;
762 let small = |t: &Term| small(ctx, t, &universe);
763 let (parts, dependent) = componentwise(ctx, arity, xs, ys, &small)?;
764 Some(TypeEquality::Family { parts, dependent })
765}
766
767fn family_head(family: &str, levels: &[Universe]) -> Term {
768 if levels.is_empty() {
769 g(family)
770 } else {
771 Term::Const {
772 name: family.to_string(),
773 levels: levels.to_vec(),
774 }
775 }
776}
777
778fn small(ctx: &Context, t: &Term, universe: &Universe) -> bool {
781 if crate::type_checker::infer_sort(ctx, t).is_ok_and(|sort| sort.is_subtype_of(universe)) {
782 return true;
783 }
784 match crate::reduction::normalize(ctx, t) {
785 Term::Sort(level) => level.is_subtype_of(universe),
786 Term::Pi {
787 param,
788 param_type,
789 body_type,
790 } => {
791 crate::type_checker::infer_sort(ctx, ¶m_type)
792 .is_ok_and(|sort| sort.is_subtype_of(universe))
793 && small(&ctx.extend(¶m, *param_type), &body_type, universe)
794 }
795 _ => false,
796 }
797}
798
799fn values(ctx: &Context, ty: &Term, a: &Term, b: &Term) -> Option<Term> {
802 if let Some(Former::Quotient(_, carrier)) = former(ctx, ty) {
803 return classes(ctx, ty, &carrier, a, b);
804 }
805 let (family, arguments) = spine(ty);
806 let (Term::Global(family) | Term::Const { name: family, .. }) = family else {
807 return None;
808 };
809 if !ctx.is_inductive(family)
810 || !crate::type_checker::infer_sort(ctx, ty)
811 .ok()?
812 .is_never_prop()
813 {
814 return None;
815 }
816 let (a, b) = (
817 crate::reduction::constructor_form(ctx, a)?,
818 crate::reduction::constructor_form(ctx, b)?,
819 );
820 let ((left, xs), (right, ys)) = (spine(&a), spine(&b));
821 let (Term::Global(c) | Term::Const { name: c, .. }) = left else {
822 return None;
823 };
824 let (Term::Global(d) | Term::Const { name: d, .. }) = right else {
825 return None;
826 };
827 if ctx.constructor_inductive(c) != Some(family) || ctx.constructor_inductive(d) != Some(family)
828 {
829 return None;
830 }
831 if c != d {
832 return Some(g("SFalse"));
833 }
834 let parameters = ctx.inductive_num_params(family);
835 if arguments.len() < parameters || xs.len() < parameters || xs.len() != ys.len() {
836 return None;
837 }
838 let telescope = peel(
839 crate::type_checker::infer_type(ctx, left).ok()?,
840 &arguments[..parameters],
841 )?;
842 let (parts, _) = componentwise(
843 ctx,
844 telescope,
845 &xs[parameters..],
846 &ys[parameters..],
847 &|_| true,
848 )?;
849 Some(conjunction(parts))
850}
851
852fn componentwise(
859 ctx: &Context,
860 telescope: Term,
861 xs: &[&Term],
862 ys: &[&Term],
863 admit: &dyn Fn(&Term) -> bool,
864) -> Option<(Vec<Term>, bool)> {
865 if xs.len() != ys.len() {
866 return None;
867 }
868 let (mut generic, mut left, mut right) = (telescope.clone(), telescope.clone(), telescope);
869 let mut earlier: Vec<String> = Vec::new();
870 let mut parts = Vec::with_capacity(xs.len());
871 let mut any_dependent = false;
872 for (x, y) in xs.iter().zip(ys) {
873 let Term::Pi {
874 param: name,
875 param_type: shape,
876 body_type: rest,
877 } = generic
878 else {
879 return None;
880 };
881 let Term::Pi {
882 param: lp,
883 param_type: lt,
884 body_type: lb,
885 } = left
886 else {
887 return None;
888 };
889 let Term::Pi {
890 param: rp,
891 param_type: rt,
892 body_type: rb,
893 } = right
894 else {
895 return None;
896 };
897 if !admit(<) || !admit(&rt) {
898 return None;
899 }
900 let dependent = earlier
901 .iter()
902 .any(|n| crate::type_checker::occurs_free(&shape, n));
903 any_dependent |= dependent;
904 parts.push(if dependent {
905 heterogeneous(ctx, <, &rt, x, y)?
906 } else {
907 obs(level_of(ctx, <)?, *lt, (*x).clone(), (*y).clone())
908 });
909 earlier.push(name);
910 generic = *rest;
911 left = substitute(&lb, &lp, x);
912 right = substitute(&rb, &rp, y);
913 }
914 if matches!(left, Term::Pi { .. }) || matches!(right, Term::Pi { .. }) {
915 return None;
916 }
917 Some((parts, any_dependent))
918}
919
920fn heterogeneous(ctx: &Context, from: &Term, to: &Term, x: &Term, y: &Term) -> Option<Term> {
922 let level = level_of(ctx, from)?;
923 if !level.equiv(&level_of(ctx, to)?) {
924 return None;
925 }
926 let types_equal = obs(
927 level.succ(),
928 Term::Sort(level.clone()),
929 from.clone(),
930 to.clone(),
931 );
932 let e = fresh("e", &[from, to, x, y]);
933 let carried = app(
934 cast_at(level.clone()),
935 [from.clone(), to.clone(), v(&e), x.clone()],
936 );
937 Some(app(
938 g("SSigma"),
939 [
940 types_equal.clone(),
941 lam(&e, types_equal, obs(level, to.clone(), carried, y.clone())),
942 ],
943 ))
944}
945
946fn carry(ctx: &Context, from: &Term, to: &Term, proof: &Term, value: &Term) -> Option<Term> {
959 match type_equality(ctx, from, to)? {
960 TypeEquality::Holds => Some(value.clone()),
961 TypeEquality::Products {
962 domain,
963 domains,
964 codomains,
965 } => {
966 let (
967 Term::Pi {
968 param: p,
969 param_type: d,
970 body_type: c,
971 },
972 Term::Pi {
973 param: q,
974 param_type: e,
975 body_type: k,
976 },
977 ) = (from, to)
978 else {
979 return None;
980 };
981 let x = fresh(
982 q,
983 &[
984 d,
985 e,
986 &scoped(p, c),
987 &scoped(q, k),
988 proof,
989 value,
990 &domains,
991 &codomains,
992 ],
993 );
994 let first = app(
995 g("SSigma_fst"),
996 [domains.clone(), codomains.clone(), proof.clone()],
997 );
998 let back = app(
999 cast_at(domain),
1000 [(**e).clone(), (**d).clone(), first, v(&x)],
1001 );
1002 let left = substitute(c, p, &back);
1003 let right = if x == *q {
1004 (**k).clone()
1005 } else {
1006 substitute(k, q, &v(&x))
1007 };
1008 let inside = ctx.extend(&x, (**e).clone());
1009 let codomain = level_of(&inside, &left)?;
1010 if !codomain.equiv(&level_of(&inside, &right)?) {
1011 return None;
1012 }
1013 let second = app(g("SSigma_snd"), [domains, codomains, proof.clone(), v(&x)]);
1014 let image = Term::App(Box::new(value.clone()), Box::new(back));
1015 Some(lam(
1016 &x,
1017 (**e).clone(),
1018 app(cast_at(codomain), [left, right, second, image]),
1019 ))
1020 }
1021 TypeEquality::Family {
1022 parts,
1023 dependent: false,
1024 } => carry_value(ctx, from, to, &parts, proof, value),
1025 TypeEquality::Quotient { parts } => carry_class(ctx, from, to, &parts, proof, value),
1026 TypeEquality::Fails | TypeEquality::Family { .. } => None,
1027 }
1028}
1029
1030fn carry_class(
1033 ctx: &Context,
1034 from: &Term,
1035 to: &Term,
1036 parts: &[Term],
1037 proof: &Term,
1038 value: &Term,
1039) -> Option<Term> {
1040 let (Former::Quotient(_, xs), Former::Quotient(_, ys)) = (former(ctx, from)?, former(ctx, to)?)
1041 else {
1042 return None;
1043 };
1044 let (constructor, arguments) = spine(value);
1045 let [_, _, x] = arguments.as_slice() else {
1046 return None;
1047 };
1048 if quotient_part(ctx, constructor) != Some(QuotientPart::Class) {
1049 return None;
1050 }
1051 let carriers = components(parts, proof).into_iter().next()?;
1052 let representative = app(
1053 cast_at(level_of(ctx, xs[0])?),
1054 [xs[0].clone(), ys[0].clone(), carriers, (*x).clone()],
1055 );
1056 Some(app(
1057 constructor.clone(),
1058 [ys[0].clone(), ys[1].clone(), representative],
1059 ))
1060}
1061
1062fn carry_value(
1066 ctx: &Context,
1067 from: &Term,
1068 to: &Term,
1069 parts: &[Term],
1070 proof: &Term,
1071 value: &Term,
1072) -> Option<Term> {
1073 let (Former::Family(family, levels, ps), Former::Family(_, _, qs)) =
1074 (former(ctx, from)?, former(ctx, to)?)
1075 else {
1076 return None;
1077 };
1078 if ctx.inductive_num_indices(family) != 0 || ps.len() != parts.len() || qs.len() != ps.len() {
1079 return None;
1080 }
1081 let value = crate::reduction::constructor_form(ctx, value)?;
1082 let (constructor, arguments) = spine(&value);
1083 let (Term::Global(c) | Term::Const { name: c, .. }) = constructor else {
1084 return None;
1085 };
1086 if ctx.constructor_inductive(c) != Some(family) || arguments.len() < ps.len() {
1087 return None;
1088 }
1089 let fields = &arguments[ps.len()..];
1090 let telescope = crate::type_checker::infer_type(ctx, constructor).ok()?;
1091 if !fields_independent(&telescope, ps.len()) {
1092 return None;
1093 }
1094 let arity = crate::type_checker::infer_type(ctx, &family_head(family, levels)).ok()?;
1095 let parameter_types = telescope_types(arity, &ps)?;
1096 let components = components(parts, proof);
1097 let owned = |xs: &[&Term]| xs.iter().map(|x| (*x).clone()).collect::<Vec<Term>>();
1098 let (ps, qs) = (owned(&ps), owned(&qs));
1099 let mut carried = Vec::with_capacity(fields.len());
1100 for k in 0..fields.len() {
1101 let source = field_type(&telescope, &ps, fields, k)?;
1102 let target = field_type(&telescope, &qs, fields, k)?;
1103 let level = level_of(ctx, &source)?;
1104 if !level.equiv(&level_of(ctx, &target)?) {
1105 return None;
1106 }
1107 let mut evidence = app(
1108 Term::Const {
1109 name: "Obs_refl".to_string(),
1110 levels: vec![level.succ()],
1111 },
1112 [Term::Sort(level.clone()), source.clone()],
1113 );
1114 for (i, parameter_type) in parameter_types.iter().enumerate() {
1115 let mut avoid: Vec<&Term> =
1116 ps.iter().chain(&qs).chain(fields.iter().copied()).collect();
1117 avoid.push(&telescope);
1118 let b = fresh("b", &avoid);
1119 let h = fresh("h", &[&avoid[..], &[&v(&b)]].concat());
1120 let mixed: Vec<Term> = qs[..i]
1121 .iter()
1122 .cloned()
1123 .chain([v(&b)])
1124 .chain(ps[i + 1..].iter().cloned())
1125 .collect();
1126 let moved = field_type(&telescope, &mixed, fields, k)?;
1127 let parameter = level_of(ctx, parameter_type)?;
1128 let motive = lam(
1129 &b,
1130 parameter_type.clone(),
1131 lam(
1132 &h,
1133 obs(
1134 parameter.clone(),
1135 parameter_type.clone(),
1136 ps[i].clone(),
1137 v(&b),
1138 ),
1139 obs(
1140 level.succ(),
1141 Term::Sort(level.clone()),
1142 source.clone(),
1143 moved,
1144 ),
1145 ),
1146 );
1147 evidence = app(
1148 Term::Const {
1149 name: "Obs_transp".to_string(),
1150 levels: vec![parameter],
1151 },
1152 [
1153 parameter_type.clone(),
1154 ps[i].clone(),
1155 motive,
1156 evidence,
1157 qs[i].clone(),
1158 components[i].clone(),
1159 ],
1160 );
1161 }
1162 carried.push(app(
1163 cast_at(level),
1164 [source, target, evidence, fields[k].clone()],
1165 ));
1166 }
1167 Some(app(constructor.clone(), qs.into_iter().chain(carried)))
1168}
1169
1170fn fields_independent(telescope: &Term, parameters: usize) -> bool {
1172 let mut cur = telescope;
1173 for _ in 0..parameters {
1174 let Term::Pi { body_type, .. } = cur else {
1175 return false;
1176 };
1177 cur = body_type;
1178 }
1179 let mut earlier: Vec<&str> = Vec::new();
1180 while let Term::Pi {
1181 param,
1182 param_type,
1183 body_type,
1184 } = cur
1185 {
1186 if earlier
1187 .iter()
1188 .any(|n| crate::type_checker::occurs_free(param_type, n))
1189 {
1190 return false;
1191 }
1192 earlier.push(param);
1193 cur = body_type;
1194 }
1195 true
1196}
1197
1198fn telescope_types(telescope: Term, values: &[&Term]) -> Option<Vec<Term>> {
1200 let mut cur = telescope;
1201 let mut types = Vec::with_capacity(values.len());
1202 for value in values {
1203 let Term::Pi {
1204 param,
1205 param_type,
1206 body_type,
1207 } = cur
1208 else {
1209 return None;
1210 };
1211 types.push(*param_type);
1212 cur = substitute(&body_type, ¶m, value);
1213 }
1214 Some(types)
1215}
1216
1217fn peel(telescope: Term, values: &[&Term]) -> Option<Term> {
1219 let mut cur = telescope;
1220 for value in values {
1221 let Term::Pi {
1222 param, body_type, ..
1223 } = cur
1224 else {
1225 return None;
1226 };
1227 cur = substitute(&body_type, ¶m, value);
1228 }
1229 Some(cur)
1230}
1231
1232fn field_type(telescope: &Term, parameters: &[Term], fields: &[&Term], k: usize) -> Option<Term> {
1234 let parameters: Vec<&Term> = parameters.iter().collect();
1235 let cur = peel(peel(telescope.clone(), ¶meters)?, &fields[..k])?;
1236 let Term::Pi { param_type, .. } = cur else {
1237 return None;
1238 };
1239 Some(*param_type)
1240}
1241
1242fn components(parts: &[Term], proof: &Term) -> Vec<Term> {
1244 let mut out = Vec::with_capacity(parts.len());
1245 let mut rest = proof.clone();
1246 for (j, part) in parts.iter().enumerate() {
1247 if j + 1 == parts.len() {
1248 out.push(rest);
1249 break;
1250 }
1251 let tail = conjunction(parts[j + 1..].to_vec());
1252 out.push(app(
1253 g("SAnd_left"),
1254 [part.clone(), tail.clone(), rest.clone()],
1255 ));
1256 rest = app(g("SAnd_right"), [part.clone(), tail, rest]);
1257 }
1258 out
1259}
1260
1261fn conjunction(mut parts: Vec<Term>) -> Term {
1263 let Some(last) = parts.pop() else {
1264 return g("STrue");
1265 };
1266 parts
1267 .into_iter()
1268 .rev()
1269 .fold(last, |rest, part| app(g("SAnd"), [part, rest]))
1270}
1271
1272fn level_of(ctx: &Context, ty: &Term) -> Option<Universe> {
1274 crate::type_checker::infer_sort(ctx, ty)
1275 .ok()
1276 .map(|sort| sort.least_level())
1277}
1278
1279fn obs(level: Universe, ty: Term, a: Term, b: Term) -> Term {
1280 app(obs_at(level), [ty, a, b])
1281}
1282
1283fn cast_at(level: Universe) -> Term {
1284 Term::Const {
1285 name: "Obs_cast".to_string(),
1286 levels: vec![level],
1287 }
1288}
1289
1290fn arrow(a: &Term, b: &Term) -> Term {
1291 pi(&fresh("_", &[b]), a.clone(), b.clone())
1292}
1293
1294fn lam(param: &str, ty: Term, body: Term) -> Term {
1295 Term::Lambda {
1296 param: param.to_string(),
1297 param_type: Box::new(ty),
1298 body: Box::new(body),
1299 }
1300}
1301
1302fn substitute(body: &Term, var: &str, replacement: &Term) -> Term {
1303 crate::type_checker::substitute(body, var, replacement)
1304}
1305
1306fn spine(t: &Term) -> (&Term, Vec<&Term>) {
1308 let mut args = Vec::new();
1309 let mut cur = t;
1310 while let Term::App(f, a) = cur {
1311 args.push(a.as_ref());
1312 cur = f.as_ref();
1313 }
1314 args.reverse();
1315 (cur, args)
1316}
1317
1318fn scoped(param: &str, body: &Term) -> Term {
1321 lam(param, Term::Sort(Universe::Prop), body.clone())
1322}
1323
1324fn fresh(base: &str, terms: &[&Term]) -> String {
1326 let taken = |n: &str| terms.iter().any(|t| crate::type_checker::occurs_free(t, n));
1327 if !taken(base) {
1328 return base.to_string();
1329 }
1330 (1..)
1331 .map(|i| format!("{base}{i}"))
1332 .find(|n| !taken(n))
1333 .expect("a fresh name")
1334}
1335
1336#[cfg(test)]
1337#[path = "../../logicaffeine_tests/unit/logicaffeine_kernel/observational/tests.rs"]
1338pub(crate) mod tests;