Skip to main content

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}