Skip to main content

logicaffeine_kernel/
inductive_compile.rs

1//! Nested-inductive compiler (K3) — the UNTRUSTED front-end for inductives that recur
2//! nested inside a container, `RTree := rnode : TList RTree → RTree`.
3//!
4//! Such a type is not directly a strict-positivity-checkable inductive: `RTree` appears as
5//! an ARGUMENT of `TList`, not to the right of an arrow. The Lean/Coq resolution is
6//! SPECIALIZATION: replace the container `TList RTree` by a fresh sibling `RTree$TList`
7//! defined MUTUALLY with `RTree`, mirroring `TList`'s constructors with the element type
8//! fixed to `RTree`:
9//!
10//! ```text
11//! RTree       := rnode : RTree$TList → RTree
12//! RTree$TList := RTree$TList_TNil | RTree$TList_TCons : RTree → RTree$TList → RTree$TList
13//! ```
14//!
15//! This block is an ordinary mutual inductive (K3b), so its recursor recurses through the
16//! specialized list — the whole point. To relate the specialized form back to the generic
17//! `TList RTree`, the compiler emits CONVERSION ISOS `RTree$TList ↔ TList RTree`.
18//!
19//! Crucially this module is UNTRUSTED: it only produces terms. The caller registers the
20//! mutual block through the trusted mutual machinery and TYPE-CHECKS every iso through the
21//! trusted kernel — a mis-compiled sibling or iso is rejected, never trusted. Zero new
22//! trusted code, exactly as Lean compiles nested inductives to mutual ones.
23
24use crate::context::{Context, MutualInductive};
25use crate::error::{KernelError, KernelResult};
26use crate::term::Term;
27
28/// A nested inductive declaration: a name, its sort, and constructors whose argument types
29/// may mention the inductive nested inside a registered container (`TList RTree`).
30pub struct NestedDecl {
31    pub name: String,
32    pub sort: Term,
33    pub constructors: Vec<(String, Term)>,
34}
35
36/// One container specialized for this inductive, with the names of its conversion isos.
37#[derive(Debug, Clone)]
38pub struct IsoNames {
39    /// The generic container that was specialized (e.g. `TList`).
40    pub container: String,
41    /// The fresh mutual sibling (e.g. `RTree$TList`).
42    pub sibling: String,
43    /// `to_generic : sibling → container name` — specialized to generic.
44    pub to_generic: String,
45    /// `from_generic : container name → sibling` — generic to specialized.
46    pub from_generic: String,
47}
48
49/// The compiled artifacts of a nested inductive: the mutual block to register, and the
50/// iso DEFINITIONS `(name, type, body)` to kernel-check and add. Nothing here is trusted
51/// until the caller checks it.
52pub struct Compiled {
53    pub block: Vec<MutualInductive>,
54    pub isos: Vec<(String, Term, Term)>,
55    pub iso_names: Vec<IsoNames>,
56    pub siblings: Vec<String>,
57}
58
59/// What a nested-inductive registration produced: the specialized sibling type names and
60/// the conversion isos relating each to its generic container.
61#[derive(Debug, Clone)]
62pub struct NestedInfo {
63    pub siblings: Vec<String>,
64    pub isos: Vec<IsoNames>,
65}
66
67// --- small builders ---
68fn g(n: &str) -> Term {
69    Term::Global(n.to_string())
70}
71fn v(n: &str) -> Term {
72    Term::Var(n.to_string())
73}
74fn app(f: Term, x: Term) -> Term {
75    Term::App(Box::new(f), Box::new(x))
76}
77fn apps(f: Term, xs: Vec<Term>) -> Term {
78    xs.into_iter().fold(f, app)
79}
80fn pi(p: &str, t: Term, b: Term) -> Term {
81    Term::Pi { param: p.to_string(), param_type: Box::new(t), body_type: Box::new(b) }
82}
83fn arrow(a: Term, b: Term) -> Term {
84    pi("_", a, b)
85}
86fn lam(p: &str, t: Term, b: Term) -> Term {
87    Term::Lambda { param: p.to_string(), param_type: Box::new(t), body: Box::new(b) }
88}
89
90/// The role of one value argument of a container constructor, relative to the container's
91/// element parameter `A`.
92enum ArgKind {
93    /// The bare element `A` — carries a value of the (nested) element type.
94    Elem,
95    /// A RECURSIVE container occurrence `Container A` — the spine.
96    Recursive,
97    /// Anything else (with `A` substituted by the element) — carried through unchanged.
98    Other(Term),
99}
100
101/// Peel a term's leading `Π`s into `(name, type)` pairs plus the residual.
102fn peel_pis(t: &Term) -> (Vec<(String, Term)>, Term) {
103    let mut params = Vec::new();
104    let mut cur = t.clone();
105    while let Term::Pi { param, param_type, body_type } = cur {
106        params.push((param, *param_type));
107        cur = *body_type;
108    }
109    (params, cur)
110}
111
112fn is_global(t: &Term, name: &str) -> bool {
113    matches!(t, Term::Global(n) if n == name)
114}
115
116/// Analyse a container constructor `Π(A). vt₁ → … → vtₖ → Container A`: return the element
117/// parameter name `A` and the KIND of each value argument.
118fn analyze_container_ctor(ctor_ty: &Term, container: &str) -> KernelResult<(String, Vec<ArgKind>)> {
119    let (params, _residual) = peel_pis(ctor_ty);
120    if params.is_empty() {
121        return Err(KernelError::CertificationError(format!(
122            "nested-compile: container '{container}' constructor has no element parameter"
123        )));
124    }
125    let a = params[0].0.clone();
126    let mut kinds = Vec::new();
127    for (_, ty) in &params[1..] {
128        let kind = match ty {
129            Term::Var(n) if *n == a => ArgKind::Elem,
130            Term::App(f, x) if is_global(f, container) && matches!(x.as_ref(), Term::Var(n) if *n == a) => {
131                ArgKind::Recursive
132            }
133            _ => ArgKind::Other(ty.clone()),
134        };
135        kinds.push(kind);
136    }
137    Ok((a, kinds))
138}
139
140/// True if `name` occurs anywhere in `ty`.
141fn occurs_anywhere(ty: &Term, name: &str) -> bool {
142    match ty {
143        Term::Global(n) => n == name,
144        Term::App(f, x) => occurs_anywhere(f, name) || occurs_anywhere(x, name),
145        Term::Pi { param_type, body_type, .. } => {
146            occurs_anywhere(param_type, name) || occurs_anywhere(body_type, name)
147        }
148        Term::Lambda { param_type, body, .. } => {
149            occurs_anywhere(param_type, name) || occurs_anywhere(body, name)
150        }
151        _ => false,
152    }
153}
154
155/// True if `ty` is a constructor argument that must be SPECIALIZED: its head is a registered
156/// container inductive (≠ the inductive being defined) and `name` occurs somewhere inside it
157/// (`TList name`, `TList (TList name)`). A bare `name`, a `Nat`, a positive `Nat → name`, or a
158/// container not mentioning `name` (`TList Nat`) is NOT nested — it passes through unchanged.
159fn is_nested_container_type(ctx: &Context, decl: &NestedDecl, ty: &Term) -> bool {
160    let mut head = ty;
161    while let Term::App(f, _) = head {
162        head = f;
163    }
164    matches!(head, Term::Global(c) if c != &decl.name && ctx.is_inductive(c))
165        && occurs_anywhere(ty, &decl.name)
166}
167
168/// The result of specializing one nested type: its specialized REPRESENTATIVE (either the
169/// inductive itself for the base leaf, or a fresh sibling), the GENERIC type it stands for,
170/// and — for a non-base node — the names of its conversion isos (applied on element fields of
171/// any type that nests it).
172struct Spec {
173    repr: String,
174    generic: Term,
175    to: Option<String>,
176    from: Option<String>,
177}
178
179/// One specialized sibling to emit: a mutual inductive mirroring `container` with its element
180/// fixed to `elem_repr`, plus the data its conversion isos need (the element's generic form and
181/// the element's own isos, which the sibling's isos delegate to).
182struct SibNode {
183    container: String,
184    repr: String,
185    elem_repr: String,
186    elem_generic: Term,
187    inner_to: Option<String>,
188    inner_from: Option<String>,
189    ctors: Vec<(String, Vec<ArgKind>)>,
190    to_name: String,
191    from_name: String,
192}
193
194/// Recursively specialize a nested type, accumulating the siblings to emit (in POST-ORDER, so
195/// an inner sibling is registered before the outer sibling whose isos reference it). The base
196/// case is the inductive itself; `Container inner` specializes `inner` first, then mirrors
197/// `Container` with element `= inner`'s representative.
198fn specialize(
199    ctx: &Context,
200    decl: &NestedDecl,
201    ty: &Term,
202    nodes: &mut Vec<SibNode>,
203    seen: &mut std::collections::HashSet<String>,
204) -> KernelResult<Spec> {
205    if is_global(ty, &decl.name) {
206        return Ok(Spec { repr: decl.name.clone(), generic: g(&decl.name), to: None, from: None });
207    }
208    if let Term::App(f, inner_ty) = ty {
209        if let Term::Global(c) = f.as_ref() {
210            if c != &decl.name && ctx.is_inductive(c) {
211                let inner = specialize(ctx, decl, inner_ty, nodes, seen)?;
212                let repr = format!("{}${}", inner.repr, c);
213                let generic = app(g(c), inner.generic.clone());
214                let to_name = format!("{repr}_to_{c}");
215                let from_name = format!("{repr}_from_{c}");
216                if seen.insert(repr.clone()) {
217                    // Only PURE unary containers (every field the element or a recursive
218                    // occurrence — `List`/`TList`) specialize soundly; anything else is refused
219                    // fail-closed rather than emitting an inconsistent block.
220                    let mut ctors = Vec::new();
221                    for (orig_ctor, orig_ty) in ctx.get_constructors(c) {
222                        let (_a, kinds) = analyze_container_ctor(orig_ty, c)?;
223                        if kinds.iter().any(|k| matches!(k, ArgKind::Other(_))) {
224                            return Err(KernelError::CertificationError(format!(
225                                "nested-compile: container '{c}' constructor '{orig_ctor}' has an \
226                                 argument that is neither the element nor a recursive occurrence \
227                                 — specializing it is not supported (only pure containers like \
228                                 `List`/`TList`)"
229                            )));
230                        }
231                        ctors.push((orig_ctor.to_string(), kinds));
232                    }
233                    nodes.push(SibNode {
234                        container: c.clone(),
235                        repr: repr.clone(),
236                        elem_repr: inner.repr.clone(),
237                        elem_generic: inner.generic.clone(),
238                        inner_to: inner.to.clone(),
239                        inner_from: inner.from.clone(),
240                        ctors,
241                        to_name: to_name.clone(),
242                        from_name: from_name.clone(),
243                    });
244                }
245                return Ok(Spec { repr, generic, to: Some(to_name), from: Some(from_name) });
246            }
247        }
248    }
249    Err(KernelError::CertificationError(format!(
250        "nested-compile: '{}' occurs in argument type {ty} in a position that is not a nesting \
251         inside a registered unary container (only `Container …` nestings are specialized)",
252        decl.name
253    )))
254}
255
256/// Compile a nested inductive into a mutual block plus conversion isos. Untrusted: every
257/// artifact is checked by the caller. Handles arbitrary nesting DEPTH (`TList (TList RTree)`)
258/// by recursive specialization — each container level becomes its own mutual sibling.
259pub fn compile_nested(ctx: &Context, decl: &NestedDecl) -> KernelResult<Compiled> {
260    // Universe safety: the specialized siblings are emitted at `Type 0` (see below), so the
261    // nested inductive must itself be `Type 0`. Refusing anything else keeps the emitted
262    // block universe-consistent rather than silently registering a higher-universe field in
263    // a `Type 0` inductive.
264    if decl.sort != Term::Sort(crate::term::Universe::Type(0)) {
265        return Err(KernelError::CertificationError(format!(
266            "nested-compile: '{}' must be a `Type 0` inductive (specialized siblings are \
267             `Type 0`); higher-universe nesting is not supported",
268            decl.name
269        )));
270    }
271
272    // 1. The inductive's own constructors, with each NESTED argument (`TList name`,
273    //    `TList (TList name)`, …) replaced by its outermost specialized sibling — recording
274    //    every sibling that must be emitted (in post-order, inner-first).
275    let mut nodes: Vec<SibNode> = Vec::new();
276    let mut seen = std::collections::HashSet::new();
277    let mut own_ctors = Vec::new();
278    for (cname, cty) in &decl.constructors {
279        let (params, residual) = peel_pis(cty);
280        let mut rebuilt = residual;
281        for (pname, pty) in params.into_iter().rev() {
282            let pty2 = if is_nested_container_type(ctx, decl, &pty) {
283                g(&specialize(ctx, decl, &pty, &mut nodes, &mut seen)?.repr)
284            } else {
285                pty
286            };
287            rebuilt = pi(&pname, pty2, rebuilt);
288        }
289        own_ctors.push((cname.clone(), rebuilt));
290    }
291
292    if nodes.is_empty() {
293        return Err(KernelError::CertificationError(format!(
294            "nested-compile: '{}' has no nested container occurrence — register it directly",
295            decl.name
296        )));
297    }
298
299    // 2. The mutual block: the inductive itself, then one specialized sibling per node.
300    let mut block = vec![MutualInductive {
301        name: decl.name.clone(),
302        sort: decl.sort.clone(),
303        num_params: 0,
304        constructors: own_ctors,
305    }];
306    let mut siblings = Vec::new();
307    for node in &nodes {
308        siblings.push(node.repr.clone());
309        let mut sib_ctors = Vec::new();
310        for (orig, kinds) in &node.ctors {
311            let mut sty = g(&node.repr);
312            for kind in kinds.iter().rev() {
313                let arg_ty = match kind {
314                    ArgKind::Elem => g(&node.elem_repr),
315                    ArgKind::Recursive => g(&node.repr),
316                    ArgKind::Other(t) => t.clone(),
317                };
318                sty = arrow(arg_ty, sty);
319            }
320            sib_ctors.push((format!("{}_{}", node.repr, orig), sty));
321        }
322        block.push(MutualInductive {
323            name: node.repr.clone(),
324            sort: Term::Sort(crate::term::Universe::Type(0)),
325            num_params: 0,
326            constructors: sib_ctors,
327        });
328    }
329
330    // 3. The conversion isos, one pair per node — emitted inner-first so the outer iso's
331    //    element-field delegation to the inner iso resolves when it is kernel-checked.
332    let mut isos = Vec::new();
333    let mut iso_names = Vec::new();
334    for node in &nodes {
335        let to_ty = arrow(g(&node.repr), app(g(&node.container), node.elem_generic.clone()));
336        let from_ty = arrow(app(g(&node.container), node.elem_generic.clone()), g(&node.repr));
337        isos.push((node.to_name.clone(), to_ty, build_to_generic(node)));
338        isos.push((node.from_name.clone(), from_ty, build_from_generic(node)));
339        iso_names.push(IsoNames {
340            container: node.container.clone(),
341            sibling: node.repr.clone(),
342            to_generic: node.to_name.clone(),
343            from_generic: node.from_name.clone(),
344        });
345    }
346
347    Ok(Compiled { block, isos, iso_names, siblings })
348}
349
350/// `to_generic : sibling → Container elem_generic` — rebuild each sibling constructor as the
351/// corresponding container constructor at the element's GENERIC type, recursing on spine args
352/// and delegating element fields to the INNER iso (identity at the base leaf).
353fn build_to_generic(node: &SibNode) -> Term {
354    let mut cases = Vec::new();
355    for (orig_ctor, kinds) in &node.ctors {
356        // λa₀ … a_{k-1}. Container_ctor elem_generic (convert a₀) … (convert a_{k-1})
357        let mut body = apps(g(orig_ctor), vec![node.elem_generic.clone()]);
358        for (i, kind) in kinds.iter().enumerate() {
359            body = app(body, convert_to(kind, v(&format!("a{i}")), "rec", &node.inner_to));
360        }
361        for (i, kind) in kinds.iter().enumerate().rev() {
362            let ty = match kind {
363                ArgKind::Elem => g(&node.elem_repr),
364                ArgKind::Recursive => g(&node.repr),
365                ArgKind::Other(t) => t.clone(),
366            };
367            body = lam(&format!("a{i}"), ty, body);
368        }
369        cases.push(body);
370    }
371    Term::Fix {
372        name: "rec".to_string(),
373        body: Box::new(lam(
374            "x",
375            g(&node.repr),
376            Term::Match {
377                discriminant: Box::new(v("x")),
378                motive: Box::new(lam("_", g(&node.repr), app(g(&node.container), node.elem_generic.clone()))),
379                cases,
380            },
381        )),
382    }
383}
384
385/// `from_generic : Container elem_generic → sibling` — rebuild each container constructor
386/// (matched at the element's generic type) as the corresponding sibling constructor, recursing
387/// on spine and delegating element fields to the inner iso's `from` (identity at the base leaf).
388fn build_from_generic(node: &SibNode) -> Term {
389    let mut cases = Vec::new();
390    for (orig_ctor, kinds) in &node.ctors {
391        let mut body = g(&format!("{}_{}", node.repr, orig_ctor));
392        for (i, kind) in kinds.iter().enumerate() {
393            body = app(body, convert_from(kind, v(&format!("a{i}")), "rec", &node.inner_from));
394        }
395        for (i, kind) in kinds.iter().enumerate().rev() {
396            // The binder types are the CONTAINER constructor's value args at the generic element.
397            let ty = match kind {
398                ArgKind::Elem => node.elem_generic.clone(),
399                ArgKind::Recursive => app(g(&node.container), node.elem_generic.clone()),
400                ArgKind::Other(t) => t.clone(),
401            };
402            body = lam(&format!("a{i}"), ty, body);
403        }
404        cases.push(body);
405    }
406    Term::Fix {
407        name: "rec".to_string(),
408        body: Box::new(lam(
409            "x",
410            app(g(&node.container), node.elem_generic.clone()),
411            Term::Match {
412                discriminant: Box::new(v("x")),
413                motive: Box::new(lam("_", app(g(&node.container), node.elem_generic.clone()), g(&node.repr))),
414                cases,
415            },
416        )),
417    }
418}
419
420/// Convert one argument in `to_generic`: recurse (`rec a`) on a spine occurrence, delegate an
421/// element field to the inner `to` iso (identity at the base leaf), pass others unchanged.
422fn convert_to(kind: &ArgKind, a: Term, rec: &str, inner_to: &Option<String>) -> Term {
423    match kind {
424        ArgKind::Recursive => app(v(rec), a),
425        ArgKind::Elem => match inner_to {
426            Some(iso) => app(g(iso), a),
427            None => a,
428        },
429        ArgKind::Other(_) => a,
430    }
431}
432
433/// Convert one argument in `from_generic`: recurse on a spine occurrence, delegate an element
434/// field to the inner `from` iso (identity at the base leaf), pass others unchanged.
435fn convert_from(kind: &ArgKind, a: Term, rec: &str, inner_from: &Option<String>) -> Term {
436    match kind {
437        ArgKind::Recursive => app(v(rec), a),
438        ArgKind::Elem => match inner_from {
439            Some(iso) => app(g(iso), a),
440            None => a,
441        },
442        ArgKind::Other(_) => a,
443    }
444}