oxilean_kernel/env/quot.rs
1//! In-kernel construction of the four `#QUOT` primitives.
2//!
3//! This module owns the canonical, universe-polymorphic types of `Quot`,
4//! `Quot.mk`, `Quot.lift` and `Quot.ind`, mirroring Lean's
5//! `src/kernel/quot.cpp`. The kernel *never* trusts a caller-supplied type for
6//! these primitives: [`Environment::add_quot`] builds them here and installs
7//! all four atomically, exactly once, and only after `Eq` is present with its
8//! expected inductive shape (needed because `Quot.lift`'s soundness hypothesis
9//! mentions `Eq`).
10//!
11//! The canonical types (with binder info per Lean):
12//!
13//! ```text
14//! Quot : {α : Sort u} → (α → α → Prop) → Sort u
15//! Quot.mk : {α : Sort u} → (r : α → α → Prop) → α → Quot r
16//! Quot.lift : {α : Sort u} → {r : α → α → Prop} → {β : Sort v} →
17//! (f : α → β) → (∀ a b, r a b → f a = f b) → Quot r → β
18//! Quot.ind : {α : Sort u} → {r : α → α → Prop} → {β : Quot r → Prop} →
19//! (∀ a, β (Quot.mk r a)) → ∀ q : Quot r, β q
20//! ```
21//!
22//! `Quot.sound` is **not** a `#QUOT` primitive: in Lean it is a plain `axiom`
23//! declared in `Init.Prelude` and it arrives in exports as a regular axiom
24//! record. Its canonical type is nevertheless owned by the kernel (see
25//! [`canonical_quot_sound_type`]) so that a replayer can verify the exported
26//! axiom instead of trusting it:
27//!
28//! ```text
29//! Quot.sound : ∀ {α : Sort u} {r : α → α → Prop} {a b : α},
30//! r a b → Quot.mk r a = Quot.mk r b
31//! ```
32
33use crate::declaration::{ConstantInfo, ConstantVal, QuotKind, QuotVal};
34use crate::env::EnvError;
35use crate::Node;
36use crate::{BinderInfo, Environment, Expr, Level, Name};
37use std::rc::Rc;
38
39/// Hierarchical name `Quot`.
40fn name_quot() -> Name {
41 Name::str("Quot")
42}
43/// Hierarchical name `Quot.mk`.
44fn name_quot_mk() -> Name {
45 Name::str("Quot").append_str("mk")
46}
47/// Hierarchical name `Quot.lift`.
48fn name_quot_lift() -> Name {
49 Name::str("Quot").append_str("lift")
50}
51/// Hierarchical name `Quot.ind`.
52fn name_quot_ind() -> Name {
53 Name::str("Quot").append_str("ind")
54}
55/// Hierarchical name `Eq`.
56fn name_eq() -> Name {
57 Name::str("Eq")
58}
59
60/// `Prop` = `Sort 0`.
61fn prop() -> Expr {
62 Expr::Sort(Level::zero())
63}
64/// `Sort u` for a level parameter named `u`.
65fn sort_param(u: &str) -> Expr {
66 Expr::Sort(Level::param(Name::str(u)))
67}
68/// A non-dependent arrow `dom → cod` (a `Pi` whose body ignores the binder).
69///
70/// The caller must supply `cod` already shifted for the extra binder if it
71/// mentions outer variables (here it never does, so no shift is needed).
72fn arrow(dom: Expr, cod: Expr) -> Expr {
73 Expr::Pi(
74 BinderInfo::Default,
75 Name::str("_"),
76 Node::new(dom),
77 Node::new(cod),
78 )
79}
80
81/// The relation type `α → α → Prop`, where `α` is `Expr::BVar(alpha_idx)` in the
82/// *current* context (i.e. the context in which this whole expression sits).
83///
84/// The two nested binders shift `α` outward by one each, so the caller passes
85/// the index of `α` as seen from the position where the relation type appears.
86fn rel_type(alpha_idx: u32) -> Expr {
87 // Innermost `Prop` ignores both binders.
88 let body = prop();
89 // `b : α` — under one extra binder, α is `alpha_idx + 1`.
90 let inner = Expr::Pi(
91 BinderInfo::Default,
92 Name::str("b"),
93 Node::new(Expr::BVar(alpha_idx + 1)),
94 Node::new(body),
95 );
96 // `a : α` — α is `alpha_idx` here.
97 Expr::Pi(
98 BinderInfo::Default,
99 Name::str("a"),
100 Node::new(Expr::BVar(alpha_idx)),
101 Node::new(inner),
102 )
103}
104
105/// The canonical type of `Quot`:
106/// `{α : Sort u} → (α → α → Prop) → Sort u`.
107fn quot_type() -> Expr {
108 // Body of the `r` binder: `Sort u`. Level params are name-based, so no
109 // de Bruijn shifting is needed for the sort.
110 let body = sort_param("u");
111 // `(r : α → α → Prop)` — α is BVar(0) at this depth.
112 let r_pi = Expr::Pi(
113 BinderInfo::Default,
114 Name::str("r"),
115 Node::new(rel_type(0)),
116 Node::new(body),
117 );
118 // `{α : Sort u}`.
119 Expr::Pi(
120 BinderInfo::Implicit,
121 Name::str("α"),
122 Node::new(sort_param("u")),
123 Node::new(r_pi),
124 )
125}
126
127/// `@Quot α r` where `α = BVar(alpha_idx)` and `r = BVar(r_idx)`.
128fn quot_app(alpha_idx: u32, r_idx: u32) -> Expr {
129 Expr::App(
130 Node::new(Expr::App(
131 Node::new(Expr::Const(name_quot(), vec![Level::param(Name::str("u"))])),
132 Node::new(Expr::BVar(alpha_idx)),
133 )),
134 Node::new(Expr::BVar(r_idx)),
135 )
136}
137
138/// The canonical type of `Quot.mk`:
139/// `{α : Sort u} → (r : α → α → Prop) → α → Quot r`.
140fn quot_mk_type() -> Expr {
141 // Context after binding α (idx grows as 0 on the outside):
142 // α (BVar increases inward), r, (element).
143 // Innermost result `@Quot α r`. In the context of the element binder:
144 // element = BVar(0), r = BVar(1), α = BVar(2).
145 let result = quot_app(2, 1);
146 // `(a : α)` — the quotiented element. α is BVar(1) here.
147 let elem_pi = Expr::Pi(
148 BinderInfo::Default,
149 Name::str("a"),
150 Node::new(Expr::BVar(1)),
151 Node::new(result),
152 );
153 // `(r : α → α → Prop)` — α is BVar(0) here.
154 let r_pi = Expr::Pi(
155 BinderInfo::Default,
156 Name::str("r"),
157 Node::new(rel_type(0)),
158 Node::new(elem_pi),
159 );
160 // `{α : Sort u}`.
161 Expr::Pi(
162 BinderInfo::Implicit,
163 Name::str("α"),
164 Node::new(sort_param("u")),
165 Node::new(r_pi),
166 )
167}
168
169/// `@Eq.{lvl} ty lhs rhs`.
170fn eq_app(lvl: Level, ty: Expr, lhs: Expr, rhs: Expr) -> Expr {
171 Expr::App(
172 Node::new(Expr::App(
173 Node::new(Expr::App(
174 Node::new(Expr::Const(name_eq(), vec![lvl])),
175 Node::new(ty),
176 )),
177 Node::new(lhs),
178 )),
179 Node::new(rhs),
180 )
181}
182
183/// The canonical type of `Quot.lift`:
184/// `{α : Sort u} → {r : α → α → Prop} → {β : Sort v} → (f : α → β) →
185/// (∀ a b, r a b → f a = f b) → Quot r → β`.
186fn quot_lift_type() -> Expr {
187 let lvl_v = Level::param(Name::str("v"));
188 // Binders, outermost → innermost: α, r, β, f, h, q.
189 //
190 // Build from the innermost result outward.
191 //
192 // Result type `β`. In the context of `q` binder:
193 // q = 0, h = 1, f = 2, β = 3, r = 4, α = 5.
194 let result = Expr::BVar(3);
195 // `Quot r` — the `q` binder domain. In the context *before* `q` (i.e. inside
196 // the `h` binder body position): h = 0, f = 1, β = 2, r = 3, α = 4.
197 let q_dom = quot_app(4, 3);
198 let q_pi = Expr::Pi(
199 BinderInfo::Default,
200 Name::str("q"),
201 Node::new(q_dom),
202 Node::new(result),
203 );
204 // The soundness hypothesis `h : ∀ (a b : α), r a b → f a = f b`.
205 //
206 // In the context before `h`: f = 0, β = 1, r = 2, α = 3.
207 // We add binders a, b, then the arrow `r a b → f a = f b`.
208 //
209 // In the context after binding a, b: b = 0, a = 1, f = 2, β = 3, r = 4,
210 // α = 5.
211 //
212 // Domain `r a b`:
213 let r_a_b = Expr::App(
214 Node::new(Expr::App(
215 Node::new(Expr::BVar(4)), // r
216 Node::new(Expr::BVar(1)), // a
217 )),
218 Node::new(Expr::BVar(0)), // b
219 );
220 // Codomain `f a = f b`. It sits *under* the non-dependent arrow binder
221 // (`_ : r a b`), so every variable shifts by 1: _ = 0, b = 1, a = 2, f = 3,
222 // β = 4, r = 5, α = 6.
223 let eq_fa_fb = {
224 let f_a = Expr::App(Node::new(Expr::BVar(3)), Node::new(Expr::BVar(2)));
225 let f_b = Expr::App(Node::new(Expr::BVar(3)), Node::new(Expr::BVar(1)));
226 eq_app(lvl_v.clone(), Expr::BVar(4), f_a, f_b)
227 };
228 let impl_arrow = arrow(r_a_b, eq_fa_fb);
229 // `∀ (b : α), r a b → f a = f b`. b domain is α. In the context after
230 // binding a: α = BVar(4) (α=3 before `a`, +1 for `a`).
231 let b_pi = Expr::Pi(
232 BinderInfo::Default,
233 Name::str("b"),
234 Node::new(Expr::BVar(4)),
235 Node::new(impl_arrow),
236 );
237 // `∀ (a : α), ...`. a domain is α. Before `a`, α = BVar(3).
238 let a_pi = Expr::Pi(
239 BinderInfo::Default,
240 Name::str("a"),
241 Node::new(Expr::BVar(3)),
242 Node::new(b_pi),
243 );
244 let h_pi = Expr::Pi(
245 BinderInfo::Default,
246 Name::str("h"),
247 Node::new(a_pi),
248 Node::new(q_pi),
249 );
250 // `(f : α → β)`. In the context before `f`: β = 0, r = 1, α = 2.
251 // Domain `α → β`: α = BVar(2); β under the `α` binder of the arrow = BVar(1)
252 // shifted by the arrow binder → β = BVar(1). Rebuild explicitly.
253 let f_dom = Expr::Pi(
254 BinderInfo::Default,
255 Name::str("_"),
256 Node::new(Expr::BVar(2)), // α
257 Node::new(Expr::BVar(1)), // β (shifted by the arrow binder)
258 );
259 let f_pi = Expr::Pi(
260 BinderInfo::Default,
261 Name::str("f"),
262 Node::new(f_dom),
263 Node::new(h_pi),
264 );
265 // `{β : Sort v}`. Before `β`: r = 0, α = 1.
266 let beta_pi = Expr::Pi(
267 BinderInfo::Implicit,
268 Name::str("β"),
269 Node::new(Expr::Sort(lvl_v)),
270 Node::new(f_pi),
271 );
272 // `{r : α → α → Prop}`. Before `r`: α = 0.
273 let r_pi = Expr::Pi(
274 BinderInfo::Implicit,
275 Name::str("r"),
276 Node::new(rel_type(0)),
277 Node::new(beta_pi),
278 );
279 // `{α : Sort u}`.
280 Expr::Pi(
281 BinderInfo::Implicit,
282 Name::str("α"),
283 Node::new(sort_param("u")),
284 Node::new(r_pi),
285 )
286}
287
288/// The canonical type of `Quot.ind`:
289/// `{α : Sort u} → {r : α → α → Prop} → {β : Quot r → Prop} →
290/// (∀ a, β (Quot.mk r a)) → ∀ q : Quot r, β q`.
291fn quot_ind_type() -> Expr {
292 // Binders, outermost → innermost: α, r, β, m (minor premise), q.
293 //
294 // Result `β q`. In the context of `q`: q = 0, m = 1, β = 2, r = 3, α = 4.
295 let result = Expr::App(Node::new(Expr::BVar(2)), Node::new(Expr::BVar(0)));
296 // `q : Quot r`. Before `q`: m = 0, β = 1, r = 2, α = 3.
297 let q_dom = quot_app(3, 2);
298 let q_pi = Expr::Pi(
299 BinderInfo::Default,
300 Name::str("q"),
301 Node::new(q_dom),
302 Node::new(result),
303 );
304 // Minor premise `m : ∀ (a : α), β (Quot.mk r a)`.
305 // Before `m`: β = 0, r = 1, α = 2.
306 // Under the `a` binder: β = 1, r = 2, α = 3, a = 0.
307 //
308 // `Quot.mk α r a` = `@Quot.mk α r a`.
309 let mk_app = Expr::App(
310 Node::new(Expr::App(
311 Node::new(Expr::App(
312 Node::new(Expr::Const(
313 name_quot_mk(),
314 vec![Level::param(Name::str("u"))],
315 )),
316 Node::new(Expr::BVar(3)), // α
317 )),
318 Node::new(Expr::BVar(2)), // r
319 )),
320 Node::new(Expr::BVar(0)), // a
321 );
322 // `β (Quot.mk α r a)` — β = BVar(1).
323 let beta_mk = Expr::App(Node::new(Expr::BVar(1)), Node::new(mk_app));
324 // `∀ (a : α), β (Quot.mk r a)`. a domain α = BVar(2) before `a`.
325 let a_pi = Expr::Pi(
326 BinderInfo::Default,
327 Name::str("a"),
328 Node::new(Expr::BVar(2)),
329 Node::new(beta_mk),
330 );
331 let m_pi = Expr::Pi(
332 BinderInfo::Default,
333 Name::str("m"),
334 Node::new(a_pi),
335 Node::new(q_pi),
336 );
337 // `{β : Quot r → Prop}`. Before `β`: r = 0, α = 1.
338 // The domain `Quot r → Prop` = `Pi(_ : Quot r, Prop)`. The `Quot r` is the
339 // *domain* of the arrow, evaluated in the same context (NOT under the `_`
340 // binder), so α = BVar(1), r = BVar(0). Only the codomain `Prop` sits under
341 // the `_` binder (and it ignores it).
342 let beta_dom = Expr::Pi(
343 BinderInfo::Default,
344 Name::str("_"),
345 Node::new(quot_app(1, 0)),
346 Node::new(prop()),
347 );
348 let beta_pi = Expr::Pi(
349 BinderInfo::Implicit,
350 Name::str("β"),
351 Node::new(beta_dom),
352 Node::new(m_pi),
353 );
354 // `{r : α → α → Prop}`. Before `r`: α = 0.
355 let r_pi = Expr::Pi(
356 BinderInfo::Implicit,
357 Name::str("r"),
358 Node::new(rel_type(0)),
359 Node::new(beta_pi),
360 );
361 // `{α : Sort u}`.
362 Expr::Pi(
363 BinderInfo::Implicit,
364 Name::str("α"),
365 Node::new(sort_param("u")),
366 Node::new(r_pi),
367 )
368}
369
370/// The canonical universe-polymorphic type for a given quotient kind.
371///
372/// - [`QuotKind::Type`], [`QuotKind::Mk`] use only the level parameter `u`.
373/// - [`QuotKind::Lift`] additionally uses `v`.
374/// - [`QuotKind::Ind`] uses only `u`.
375///
376/// The returned type is closed (no loose bound variables) and is the exact
377/// type the kernel installs / requires for the primitive.
378pub fn canonical_quot_type(kind: QuotKind) -> Expr {
379 match kind {
380 QuotKind::Type => quot_type(),
381 QuotKind::Mk => quot_mk_type(),
382 QuotKind::Lift => quot_lift_type(),
383 QuotKind::Ind => quot_ind_type(),
384 }
385}
386
387/// The universe level parameters for a given quotient kind (in order).
388pub fn canonical_quot_level_params(kind: QuotKind) -> Vec<Name> {
389 match kind {
390 QuotKind::Type | QuotKind::Mk | QuotKind::Ind => vec![Name::str("u")],
391 QuotKind::Lift => vec![Name::str("u"), Name::str("v")],
392 }
393}
394
395/// The hierarchical name `Quot.sound` of the quotient soundness axiom.
396pub fn quot_sound_name() -> Name {
397 Name::str("Quot").append_str("sound")
398}
399
400/// The canonical type of the quotient soundness **axiom** `Quot.sound`
401/// (mirroring `Init.Prelude`):
402///
403/// ```text
404/// ∀ {α : Sort u} {r : α → α → Prop} {a b : α},
405/// r a b → Quot.mk r a = Quot.mk r b
406/// ```
407///
408/// `Quot.sound` is *not* installed by [`Environment::add_quot`] — in Lean it is
409/// an ordinary axiom, added after the `#QUOT` primitives. This constructor
410/// exists so a replayer can validate the exported axiom's type (up to
411/// definitional equality, after aligning the single level parameter) instead of
412/// accepting an arbitrary assertion under the trusted name `Quot.sound`.
413///
414/// The returned type is closed and universe-polymorphic in
415/// [`canonical_quot_sound_level_params`] (`[u]`).
416pub fn canonical_quot_sound_type() -> Expr {
417 let lvl_u = Level::param(Name::str("u"));
418 // Binders, outermost → innermost: α, r, a, b, then the anonymous
419 // hypothesis `_ : r a b`, with body `Quot.mk r a = Quot.mk r b`.
420 //
421 // Body context: _ = 0, b = 1, a = 2, r = 3, α = 4.
422 // `@Quot α r : Sort u` — the `Eq` instance's type argument.
423 let quot_alpha_r = quot_app(4, 3);
424 // `@Quot.mk α r x` for x ∈ {a, b}.
425 let mk = |x: u32| {
426 Expr::App(
427 Node::new(Expr::App(
428 Node::new(Expr::App(
429 Node::new(Expr::Const(name_quot_mk(), vec![lvl_u.clone()])),
430 Node::new(Expr::BVar(4)), // α
431 )),
432 Node::new(Expr::BVar(3)), // r
433 )),
434 Node::new(Expr::BVar(x)),
435 )
436 };
437 let (mk_a, mk_b) = (mk(2), mk(1));
438 let body = eq_app(lvl_u, quot_alpha_r, mk_a, mk_b);
439 // Hypothesis domain `r a b`. Context before the hypothesis binder:
440 // b = 0, a = 1, r = 2, α = 3.
441 let r_a_b = Expr::App(
442 Node::new(Expr::App(
443 Node::new(Expr::BVar(2)), // r
444 Node::new(Expr::BVar(1)), // a
445 )),
446 Node::new(Expr::BVar(0)), // b
447 );
448 let hyp_arrow = Expr::Pi(
449 BinderInfo::Default,
450 Name::str("_"),
451 Node::new(r_a_b),
452 Node::new(body),
453 );
454 // `{b : α}` — before `b`: a = 0, r = 1, α = 2.
455 let b_pi = Expr::Pi(
456 BinderInfo::Implicit,
457 Name::str("b"),
458 Node::new(Expr::BVar(2)),
459 Node::new(hyp_arrow),
460 );
461 // `{a : α}` — before `a`: r = 0, α = 1.
462 let a_pi = Expr::Pi(
463 BinderInfo::Implicit,
464 Name::str("a"),
465 Node::new(Expr::BVar(1)),
466 Node::new(b_pi),
467 );
468 // `{r : α → α → Prop}` — before `r`: α = 0.
469 let r_pi = Expr::Pi(
470 BinderInfo::Implicit,
471 Name::str("r"),
472 Node::new(rel_type(0)),
473 Node::new(a_pi),
474 );
475 // `{α : Sort u}`.
476 Expr::Pi(
477 BinderInfo::Implicit,
478 Name::str("α"),
479 Node::new(sort_param("u")),
480 Node::new(r_pi),
481 )
482}
483
484/// The universe level parameters of the canonical `Quot.sound` axiom (`[u]`).
485pub fn canonical_quot_sound_level_params() -> Vec<Name> {
486 vec![Name::str("u")]
487}
488
489/// Build the `QuotVal` for a canonical quotient primitive.
490fn canonical_quot_val(name: Name, kind: QuotKind) -> QuotVal {
491 QuotVal {
492 common: ConstantVal {
493 name,
494 level_params: canonical_quot_level_params(kind),
495 ty: canonical_quot_type(kind),
496 },
497 kind,
498 }
499}
500
501/// Verify that `Eq` is present with its expected inductive shape:
502/// a single-constructor inductive living in `Prop`, whose sole constructor is
503/// registered and points back to `Eq`.
504///
505/// This mirrors Lean's `check_eq_type` precondition of `add_quot`: `Quot.lift`'s
506/// soundness hypothesis is stated with `Eq`, so `Eq` must already exist.
507///
508/// The check is deliberately agnostic about the exact spelling of the
509/// constructor name (`Eq.refl` may be encoded either as a flat single-atom
510/// `Name` or as a hierarchical `Eq.refl`), and instead validates the structural
511/// relationship (`ctor.induct == Eq`).
512fn check_eq_present(env: &Environment) -> Result<(), EnvError> {
513 let eq_name = name_eq();
514 let iv = env.get_inductive_val(&eq_name).ok_or_else(|| {
515 EnvError::InvalidQuotient("`Eq` must be declared before quotients".into())
516 })?;
517 // Lean's `Eq` is a `Prop`-valued inductive with exactly one constructor.
518 if iv.ctors.len() != 1 {
519 return Err(EnvError::InvalidQuotient(
520 "`Eq` must be an inductive with exactly one constructor".into(),
521 ));
522 }
523 if !iv.is_prop {
524 return Err(EnvError::InvalidQuotient(
525 "`Eq` must be a `Prop`-valued inductive".into(),
526 ));
527 }
528 let ctor_name = &iv.ctors[0];
529 let cv = env
530 .get_constructor_val(ctor_name)
531 .ok_or_else(|| EnvError::InvalidQuotient("`Eq`'s constructor must be registered".into()))?;
532 if cv.induct != eq_name {
533 return Err(EnvError::InvalidQuotient(
534 "`Eq`'s constructor does not point back to `Eq`".into(),
535 ));
536 }
537 Ok(())
538}
539
540impl Environment {
541 /// Install the four `#QUOT` primitives (`Quot`, `Quot.mk`, `Quot.lift`,
542 /// `Quot.ind`) with their canonical, kernel-constructed types.
543 ///
544 /// This is the trusted `#QUOT` entry point, mirroring Lean's
545 /// `add_quot` in `src/kernel/quot.cpp`:
546 ///
547 /// - **Precondition:** `Eq` must already exist as an inductive of the
548 /// expected shape (one constructor `Eq.refl`), because `Quot.lift`'s
549 /// soundness hypothesis mentions `Eq`. A typed error is returned
550 /// otherwise.
551 /// - All four types are constructed **in-kernel** (universe-polymorphic in
552 /// `u`, plus `v` for `Quot.lift`); the caller never supplies them.
553 /// - The four constants are added atomically. If any name already exists,
554 /// nothing is installed.
555 /// - A once-only [`Environment::quot_initialized`] flag is set; a second
556 /// `add_quot` is a typed error, and any other `ConstantInfo::Quotient`
557 /// addition is rejected by [`Environment::add_constant`].
558 pub fn add_quot(&mut self) -> Result<(), EnvError> {
559 if self.quot_initialized() {
560 return Err(EnvError::InvalidQuotient(
561 "quotients have already been initialized".into(),
562 ));
563 }
564 check_eq_present(self)?;
565 let decls = [
566 (name_quot(), QuotKind::Type),
567 (name_quot_mk(), QuotKind::Mk),
568 (name_quot_lift(), QuotKind::Lift),
569 (name_quot_ind(), QuotKind::Ind),
570 ];
571 // Atomicity: reject up-front if any target name is taken.
572 for (name, _) in &decls {
573 if self.contains(name) {
574 return Err(EnvError::DuplicateDeclaration(name.clone()));
575 }
576 }
577 for (name, kind) in decls {
578 let qv = canonical_quot_val(name, kind);
579 self.insert_quotient(ConstantInfo::Quotient(qv));
580 }
581 self.set_quot_initialized();
582 Ok(())
583 }
584}