Skip to main content

oxilean_kernel/inductive/
derive.rs

1//! Kernel-side inductive family checking and recursor derivation.
2//!
3//! This module implements the Lean 4 kernel's `inductive.cpp` semantics for
4//! declaring (mutual) inductive families:
5//!
6//! 1. **Signature checking** — every type in the family is a telescope of
7//!    `num_params` shared parameters followed by indices, ending in a sort;
8//!    all types of a mutual family must land in the *same* sort.
9//! 2. **Constructor checking** — each constructor takes the family
10//!    parameters first (definitionally equal binders), then fields; each
11//!    field's universe must fit the inductive's universe (no constraint when
12//!    the family lives in `Prop`); the codomain must be the parent type
13//!    applied to exactly the parameters followed by index terms.
14//! 3. **Strict positivity** — constructor argument types are reduced to WHNF
15//!    (so negativity hidden behind definitions is found) and occurrences of
16//!    any family member may only appear as the codomain of a field telescope
17//!    applied to exactly the parameters. Occurrences nested under another
18//!    type constructor (`mk : List T → T`) are *nested inductives*, which
19//!    this kernel deliberately rejects with
20//!    [`KernelError::UnsupportedNestedInductive`] instead of compiling them
21//!    to mutual families.
22//! 4. **Recursor derivation** — the recursors are derived *by the kernel*
23//!    (never trusted from the outside): one recursor per family member,
24//!    each carrying all `k` motives and all minor premises. Minor premises
25//!    include induction hypotheses for recursive fields (Pi-wrapped for
26//!    reflexive fields). The elimination universe is decided by the kernel:
27//!    a family whose resultant sort is never `0` eliminates into any
28//!    universe (a fresh level parameter is prepended); a possibly-`Prop`
29//!    family eliminates only into `Prop` unless it is a subsingleton
30//!    eliminator (at most one constructor whose every field is a proposition
31//!    or appears among the resulting indices — e.g. `Eq`, `Acc`, `False`).
32//!    The K flag is set exactly per Lean: single non-mutual `Prop` type with
33//!    one constructor whose arguments are all parameters.
34//!
35//! Recursor rule right-hand sides use Lean's convention: each RHS is a
36//! **closed lambda** over `params ++ motives ++ minors ++ fields`; iota
37//! reduction applies it to the recursor's premises and the constructor's
38//! fields and lets beta do the rest (see `reduce::iota`).
39
40use crate::declaration::{
41    ConstantInfo, ConstantVal, ConstructorVal, InductiveVal, RecursorRule, RecursorVal,
42};
43use crate::expr_util::{get_app_fn, get_app_fn_args, mk_app};
44use crate::infer::LocalDecl;
45use crate::instantiate::instantiate_type_lparams;
46use crate::level::{is_equivalent, is_geq, normalize as normalize_level};
47use crate::subst::instantiate;
48use crate::Node;
49use crate::{
50    BinderInfo, Environment, Expr, FVarId, KernelError, Level, LevelView, Name, TypeChecker,
51};
52use std::rc::Rc;
53
54/// A single inductive type inside a (possibly mutual) family declaration.
55#[derive(Clone, Debug)]
56pub struct InductiveSpec {
57    /// Name of the inductive type.
58    pub name: Name,
59    /// Full type: `Π params.. indices.., Sort l`.
60    pub ty: Expr,
61    /// Constructors as `(name, full type)` pairs. The constructor type must
62    /// re-bind the family parameters first.
63    pub ctors: Vec<(Name, Expr)>,
64    /// Optional explicit recursor name (defaults to `<name>.rec`).
65    pub rec_name: Option<Name>,
66}
67
68impl InductiveSpec {
69    /// Create a spec with the default recursor name `<name>.rec`.
70    pub fn new(name: Name, ty: Expr, ctors: Vec<(Name, Expr)>) -> Self {
71        Self {
72            name,
73            ty,
74            ctors,
75            rec_name: None,
76        }
77    }
78    /// Override the generated recursor's name.
79    pub fn with_rec_name(mut self, rec_name: Name) -> Self {
80        self.rec_name = Some(rec_name);
81        self
82    }
83    fn effective_rec_name(&self) -> Name {
84        self.rec_name
85            .clone()
86            .unwrap_or_else(|| Name::mk_str(self.name.clone(), "rec"))
87    }
88}
89
90/// The kernel-derived declarations for an inductive family.
91#[derive(Clone, Debug)]
92pub struct DerivedFamily {
93    /// One `ConstantInfo::Inductive` per family member (declaration order).
94    pub inductives: Vec<ConstantInfo>,
95    /// All `ConstantInfo::Constructor`s (family order, then ctor order).
96    pub constructors: Vec<ConstantInfo>,
97    /// One `ConstantInfo::Recursor` per family member.
98    pub recursors: Vec<ConstantInfo>,
99}
100
101impl DerivedFamily {
102    /// All constant infos in dependency order (inductives, ctors, recursors).
103    pub fn into_constant_infos(self) -> Vec<ConstantInfo> {
104        let mut out = self.inductives;
105        out.extend(self.constructors);
106        out.extend(self.recursors);
107        out
108    }
109}
110
111// ---------------------------------------------------------------------------
112// Derivation context: fresh free variables + optional type checker.
113// ---------------------------------------------------------------------------
114
115/// Base offset for derivation-local free variables so they can never collide
116/// with the `TypeChecker`'s internal fresh variables (which count up from 0).
117const DERIVE_FVAR_BASE: u64 = 1 << 40;
118
119#[derive(Clone, Debug)]
120struct Binder {
121    fvar: FVarId,
122    bi: BinderInfo,
123    name: Name,
124    ty: Expr,
125}
126
127fn fv(b: &Binder) -> Expr {
128    Expr::FVar(b.fvar)
129}
130
131/// Checking/derivation context. In checked mode it owns a `TypeChecker`
132/// (WHNF, def-eq, sort inference over the working environment); in
133/// unchecked mode (legacy `InductiveType::to_constant_infos`) peeling is
134/// purely syntactic and no typing judgements are made.
135struct DeriveCtx<'e> {
136    tc: Option<TypeChecker<'e>>,
137    next: u64,
138}
139
140impl<'e> DeriveCtx<'e> {
141    fn checked(env: &'e Environment) -> Self {
142        Self {
143            tc: Some(TypeChecker::new(env)),
144            next: 0,
145        }
146    }
147    fn unchecked() -> Self {
148        Self { tc: None, next: 0 }
149    }
150    fn is_checked(&self) -> bool {
151        self.tc.is_some()
152    }
153    fn fresh(&mut self, bi: BinderInfo, name: Name, ty: Expr) -> Binder {
154        let id = FVarId(DERIVE_FVAR_BASE + self.next);
155        self.next += 1;
156        if let Some(tc) = self.tc.as_mut() {
157            tc.push_local(LocalDecl {
158                fvar: id,
159                name: name.clone(),
160                ty: ty.clone(),
161                val: None,
162            });
163        }
164        Binder {
165            fvar: id,
166            bi,
167            name,
168            ty,
169        }
170    }
171    fn whnf(&mut self, e: &Expr) -> Expr {
172        match self.tc.as_mut() {
173            Some(tc) => tc.whnf(e),
174            None => e.clone(),
175        }
176    }
177    fn is_def_eq(&mut self, a: &Expr, b: &Expr) -> bool {
178        match self.tc.as_mut() {
179            Some(tc) => tc.is_def_eq(a, b),
180            None => a == b,
181        }
182    }
183    #[allow(clippy::result_large_err)]
184    fn ensure_sort(&mut self, e: &Expr) -> Result<Option<Level>, KernelError> {
185        match self.tc.as_mut() {
186            Some(tc) => tc.ensure_sort(e).map(Some),
187            None => Ok(None),
188        }
189    }
190}
191
192// ---------------------------------------------------------------------------
193// FVar abstraction (telescope closing).
194// ---------------------------------------------------------------------------
195
196/// Replace `fvar` with the bound variable for the binder about to be
197/// wrapped. All expressions built by this module are fvar-closed (they never
198/// contain loose BVars), so — unlike `subst::abstract_expr` — existing bound
199/// variables are never shifted.
200fn abstract_fvar(expr: &Expr, fvar: FVarId, depth: u32) -> Expr {
201    match expr {
202        Expr::FVar(id) if *id == fvar => Expr::BVar(depth),
203        Expr::BVar(_) | Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => {
204            expr.clone()
205        }
206        Expr::App(f, a) => Expr::App(
207            Node::new(abstract_fvar(f, fvar, depth)),
208            Node::new(abstract_fvar(a, fvar, depth)),
209        ),
210        Expr::Lam(bi, n, ty, body) => Expr::Lam(
211            *bi,
212            n.clone(),
213            Node::new(abstract_fvar(ty, fvar, depth)),
214            Node::new(abstract_fvar(body, fvar, depth + 1)),
215        ),
216        Expr::Pi(bi, n, ty, body) => Expr::Pi(
217            *bi,
218            n.clone(),
219            Node::new(abstract_fvar(ty, fvar, depth)),
220            Node::new(abstract_fvar(body, fvar, depth + 1)),
221        ),
222        Expr::Let(n, ty, val, body) => Expr::Let(
223            n.clone(),
224            Node::new(abstract_fvar(ty, fvar, depth)),
225            Node::new(abstract_fvar(val, fvar, depth)),
226            Node::new(abstract_fvar(body, fvar, depth + 1)),
227        ),
228        Expr::Proj(n, i, e) => Expr::Proj(n.clone(), *i, Node::new(abstract_fvar(e, fvar, depth))),
229    }
230}
231
232/// Close `body` over `binders` (outermost first) with Pi (`lam == false`)
233/// or Lambda (`lam == true`) binders. `bi_override` forces a binder info on
234/// every binder of the group (recursors make params/motives/indices
235/// implicit regardless of how they were declared).
236fn close(binders: &[Binder], body: &Expr, lam: bool, bi_override: Option<BinderInfo>) -> Expr {
237    let mut r = body.clone();
238    for b in binders.iter().rev() {
239        let inner = abstract_fvar(&r, b.fvar, 0);
240        let bi = bi_override.unwrap_or(b.bi);
241        r = if lam {
242            Expr::Lam(
243                bi,
244                b.name.clone(),
245                Node::new(b.ty.clone()),
246                Node::new(inner),
247            )
248        } else {
249            Expr::Pi(
250                bi,
251                b.name.clone(),
252                Node::new(b.ty.clone()),
253                Node::new(inner),
254            )
255        };
256    }
257    r
258}
259
260// ---------------------------------------------------------------------------
261// Small syntactic helpers.
262// ---------------------------------------------------------------------------
263
264pub(crate) fn has_const_occ(e: &Expr, names: &[Name]) -> bool {
265    match e {
266        Expr::Const(n, _) => names.contains(n),
267        Expr::BVar(_) | Expr::FVar(_) | Expr::Sort(_) | Expr::Lit(_) => false,
268        Expr::App(f, a) => has_const_occ(f, names) || has_const_occ(a, names),
269        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
270            has_const_occ(ty, names) || has_const_occ(body, names)
271        }
272        Expr::Let(_, ty, val, body) => {
273            has_const_occ(ty, names) || has_const_occ(val, names) || has_const_occ(body, names)
274        }
275        Expr::Proj(_, _, e) => has_const_occ(e, names),
276    }
277}
278
279fn collect_level_param_names(l: &Level, out: &mut Vec<Name>) {
280    match l.view() {
281        LevelView::Zero => {}
282        LevelView::Succ(a) => collect_level_param_names(a, out),
283        LevelView::Max(a, b) | LevelView::IMax(a, b) => {
284            collect_level_param_names(a, out);
285            collect_level_param_names(b, out);
286        }
287        LevelView::Param(n) => {
288            if !out.contains(n) {
289                out.push(n.clone());
290            }
291        }
292        LevelView::MVar(_) => {}
293    }
294}
295
296fn collect_expr_level_params(e: &Expr, out: &mut Vec<Name>) {
297    match e {
298        Expr::Sort(l) => collect_level_param_names(l, out),
299        Expr::Const(_, ls) => {
300            for l in ls {
301                collect_level_param_names(l, out);
302            }
303        }
304        Expr::BVar(_) | Expr::FVar(_) | Expr::Lit(_) => {}
305        Expr::App(f, a) => {
306            collect_expr_level_params(f, out);
307            collect_expr_level_params(a, out);
308        }
309        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
310            collect_expr_level_params(ty, out);
311            collect_expr_level_params(body, out);
312        }
313        Expr::Let(_, ty, val, body) => {
314            collect_expr_level_params(ty, out);
315            collect_expr_level_params(val, out);
316            collect_expr_level_params(body, out);
317        }
318        Expr::Proj(_, _, e) => collect_expr_level_params(e, out),
319    }
320}
321
322#[allow(clippy::result_large_err)]
323fn check_level_params_declared(
324    e: &Expr,
325    lparams: &[Name],
326    what: &str,
327    name: &Name,
328) -> Result<(), KernelError> {
329    let mut used = Vec::new();
330    collect_expr_level_params(e, &mut used);
331    for u in used {
332        if !lparams.contains(&u) {
333            return Err(KernelError::InvalidInductive(format!(
334                "{} '{}' uses undeclared universe parameter '{}'",
335                what, name, u
336            )));
337        }
338    }
339    Ok(())
340}
341
342// ---------------------------------------------------------------------------
343// Telescope peeling.
344// ---------------------------------------------------------------------------
345
346/// Peel every Pi binder from `ty` into fresh fvars (WHNF-assisted in checked
347/// mode), returning the binders and the fvar-instantiated remainder.
348fn peel_all(ctx: &mut DeriveCtx<'_>, ty: &Expr) -> (Vec<Binder>, Expr) {
349    let mut out = Vec::new();
350    let mut cur = ty.clone();
351    loop {
352        let curw = if matches!(cur, Expr::Pi(_, _, _, _)) {
353            cur
354        } else {
355            let w = ctx.whnf(&cur);
356            if !matches!(w, Expr::Pi(_, _, _, _)) {
357                return (out, w);
358            }
359            w
360        };
361        if let Expr::Pi(bi, name, dom, body) = curw {
362            let b = ctx.fresh(bi, name, (*dom).clone());
363            cur = instantiate(&body, &fv(&b));
364            out.push(b);
365        } else {
366            return (out, curw);
367        }
368    }
369}
370
371/// Peel `params.len()` binders from `ty`, instantiating each with the
372/// corresponding shared parameter fvar. In checked mode the binder domain
373/// must be definitionally equal to the parameter's type.
374#[allow(clippy::result_large_err)]
375fn peel_against_params(
376    ctx: &mut DeriveCtx<'_>,
377    ty: &Expr,
378    params: &[Binder],
379    what: &str,
380    name: &Name,
381) -> Result<Expr, KernelError> {
382    let mut cur = ty.clone();
383    for (i, p) in params.iter().enumerate() {
384        let curw = if matches!(cur, Expr::Pi(_, _, _, _)) {
385            cur
386        } else {
387            ctx.whnf(&cur)
388        };
389        match curw {
390            Expr::Pi(_, _, dom, body) => {
391                if ctx.is_checked() && !ctx.is_def_eq(&dom, &p.ty) {
392                    return Err(KernelError::InvalidInductive(format!(
393                        "{} '{}': parameter #{} does not match the family parameter type",
394                        what,
395                        name,
396                        i + 1
397                    )));
398                }
399                cur = instantiate(&body, &fv(p));
400            }
401            _ => {
402                return Err(KernelError::InvalidInductive(format!(
403                    "{} '{}' does not bind the {} family parameter(s)",
404                    what,
405                    name,
406                    params.len()
407                )));
408            }
409        }
410    }
411    Ok(cur)
412}
413
414// ---------------------------------------------------------------------------
415// Family signature (pre-analysis of the inductive types themselves).
416// ---------------------------------------------------------------------------
417
418struct FamilyPre {
419    lparams: Vec<Name>,
420    ind_levels: Vec<Level>,
421    np: usize,
422    params: Vec<Binder>,
423    names: Vec<Name>,
424    /// Per-type index telescopes (fvar binders reused for motives and the
425    /// outer recursor telescope; both fully close over them).
426    indices: Vec<Vec<Binder>>,
427    /// Resultant sort level (shared across the family).
428    ind_level: Level,
429}
430
431#[allow(clippy::result_large_err)]
432fn build_family_pre(
433    ctx: &mut DeriveCtx<'_>,
434    lparams: &[Name],
435    num_params: u32,
436    types: &[(Name, Expr)],
437) -> Result<FamilyPre, KernelError> {
438    if types.is_empty() {
439        return Err(KernelError::InvalidInductive(
440            "inductive family must contain at least one type".to_string(),
441        ));
442    }
443    for (i, lp) in lparams.iter().enumerate() {
444        if lparams[..i].contains(lp) {
445            return Err(KernelError::InvalidInductive(format!(
446                "duplicate universe parameter '{}'",
447                lp
448            )));
449        }
450    }
451    for (i, (n, _)) in types.iter().enumerate() {
452        if types[..i].iter().any(|(m, _)| m == n) {
453            return Err(KernelError::InvalidInductive(format!(
454                "duplicate inductive type name '{}'",
455                n
456            )));
457        }
458    }
459    let np = num_params as usize;
460    let ind_levels: Vec<Level> = lparams.iter().cloned().map(Level::param).collect();
461
462    for (n, ty) in types {
463        check_level_params_declared(ty, lparams, "inductive type", n)?;
464        if ctx.is_checked() {
465            ctx.ensure_sort(ty)?;
466        }
467    }
468
469    // Shared parameter telescope from the first type.
470    let mut params = Vec::new();
471    let mut cur = types[0].1.clone();
472    for i in 0..np {
473        let curw = if matches!(cur, Expr::Pi(_, _, _, _)) {
474            cur
475        } else {
476            ctx.whnf(&cur)
477        };
478        match curw {
479            Expr::Pi(bi, name, dom, body) => {
480                let b = ctx.fresh(bi, name, (*dom).clone());
481                cur = instantiate(&body, &fv(&b));
482                params.push(b);
483            }
484            _ => {
485                return Err(KernelError::InvalidInductive(format!(
486                    "inductive type '{}' has fewer than {} parameter binders (got {})",
487                    types[0].0, np, i
488                )));
489            }
490        }
491    }
492
493    let mut names = Vec::new();
494    let mut indices = Vec::new();
495    let mut ind_level: Option<Level> = None;
496    for (j, (n, ty)) in types.iter().enumerate() {
497        let rest = if j == 0 {
498            cur.clone()
499        } else {
500            peel_against_params(ctx, ty, &params, "inductive type", n)?
501        };
502        let (idx_binders, cod) = peel_all(ctx, &rest);
503        let codw = ctx.whnf(&cod);
504        let level = match codw {
505            Expr::Sort(l) => l,
506            other => {
507                return Err(KernelError::InvalidInductive(format!(
508                    "inductive type '{}' must end in a sort, found '{}'",
509                    n, other
510                )));
511            }
512        };
513        match &ind_level {
514            None => ind_level = Some(level),
515            Some(l0) => {
516                if !is_equivalent(l0, &level) {
517                    return Err(KernelError::InvalidInductive(format!(
518                        "mutually inductive type '{}' lives in a different universe than '{}'",
519                        n, types[0].0
520                    )));
521                }
522            }
523        }
524        names.push(n.clone());
525        indices.push(idx_binders);
526    }
527    // Normalize so `is_zero`/`is_not_zero` see through `imax`/`max` shapes.
528    let ind_level = normalize_level(&ind_level.unwrap_or_else(Level::zero));
529    Ok(FamilyPre {
530        lparams: lparams.to_vec(),
531        ind_levels,
532        np,
533        params,
534        names,
535        indices,
536        ind_level,
537    })
538}
539
540/// `T_j params index-args` with exactly the declared levels and parameters?
541/// Returns the family position and the index argument terms.
542fn as_valid_ind_app(pre: &FamilyPre, e: &Expr, strict: bool) -> Option<(usize, Vec<Expr>)> {
543    let (head, args) = get_app_fn_args(e);
544    let (n, levels) = match head {
545        Expr::Const(n, levels) => (n, levels),
546        _ => return None,
547    };
548    let j = pre.names.iter().position(|x| x == n)?;
549    if strict {
550        if levels.len() != pre.ind_levels.len()
551            || !levels
552                .iter()
553                .zip(&pre.ind_levels)
554                .all(|(a, b)| is_equivalent(a, b))
555        {
556            return None;
557        }
558        if args.len() != pre.np + pre.indices[j].len() {
559            return None;
560        }
561        for (arg, p) in args.iter().zip(&pre.params) {
562            if **arg != Expr::FVar(p.fvar) {
563                return None;
564            }
565        }
566    }
567    let idx = args.iter().skip(pre.np).map(|e| (*e).clone()).collect();
568    Some((j, idx))
569}
570
571// ---------------------------------------------------------------------------
572// Constructor analysis (checking + field classification).
573// ---------------------------------------------------------------------------
574
575struct RecFieldSig {
576    /// The field's own Pi telescope (non-empty for reflexive fields).
577    ds: Vec<Binder>,
578    /// Which family member the field recurses into.
579    type_idx: usize,
580    /// Index terms of the recursive occurrence (excludes the parameters).
581    index_args: Vec<Expr>,
582}
583
584struct FieldSig {
585    binder: Binder,
586    /// Sort level of the field's type (checked mode only).
587    sort_level: Option<Level>,
588    rec: Option<RecFieldSig>,
589}
590
591struct CtorSig {
592    name: Name,
593    ty: Expr,
594    fields: Vec<FieldSig>,
595    /// Index arguments of the constructor's codomain (excludes parameters).
596    ret_index_args: Vec<Expr>,
597}
598
599/// Lean's `check_positivity`: WHNF, then any occurrence of a family member
600/// must be a strictly positive one — never inside a Pi domain, and at the
601/// spine only as a valid `T params indices` application. Occurrences nested
602/// under another constant's arguments are classified as nested inductives.
603#[allow(clippy::result_large_err)]
604fn check_positivity(
605    ctx: &mut DeriveCtx<'_>,
606    pre: &FamilyPre,
607    ty: &Expr,
608    ctor_name: &Name,
609    decl_name: &Name,
610) -> Result<(), KernelError> {
611    let t = ctx.whnf(ty);
612    if !has_const_occ(&t, &pre.names) {
613        return Ok(());
614    }
615    match t {
616        Expr::Pi(bi, name, dom, body) => {
617            if has_const_occ(&dom, &pre.names) {
618                return Err(KernelError::InvalidInductive(format!(
619                    "non-strictly-positive occurrence of '{}' in constructor '{}'",
620                    decl_name, ctor_name
621                )));
622            }
623            let b = ctx.fresh(bi, name, (*dom).clone());
624            check_positivity(ctx, pre, &instantiate(&body, &fv(&b)), ctor_name, decl_name)
625        }
626        _ => {
627            if as_valid_ind_app(pre, &t, true).is_some() {
628                return Ok(());
629            }
630            match get_app_fn(&t) {
631                Expr::Const(c, _) if !pre.names.contains(c) => {
632                    Err(KernelError::UnsupportedNestedInductive(decl_name.clone()))
633                }
634                _ => Err(KernelError::InvalidInductive(format!(
635                    "invalid occurrence of '{}' in constructor '{}' (recursive occurrences \
636                     must be applied to exactly the family parameters)",
637                    decl_name, ctor_name
638                ))),
639            }
640        }
641    }
642}
643
644/// Classify a field as recursive: peel the field's own Pi telescope and
645/// test whether the codomain is a valid application of a family member.
646fn classify_rec_field(
647    ctx: &mut DeriveCtx<'_>,
648    pre: &FamilyPre,
649    field_ty: &Expr,
650) -> Option<RecFieldSig> {
651    if !has_const_occ(field_ty, &pre.names) {
652        return None;
653    }
654    let (ds, cod) = peel_all(ctx, field_ty);
655    let codw = ctx.whnf(&cod);
656    let strict = ctx.is_checked();
657    let (type_idx, index_args) = as_valid_ind_app(pre, &codw, strict)?;
658    Some(RecFieldSig {
659        ds,
660        type_idx,
661        index_args,
662    })
663}
664
665#[allow(clippy::result_large_err)]
666fn check_ctor(
667    ctx: &mut DeriveCtx<'_>,
668    pre: &FamilyPre,
669    type_idx: usize,
670    cname: &Name,
671    cty: &Expr,
672) -> Result<CtorSig, KernelError> {
673    check_level_params_declared(cty, &pre.lparams, "constructor", cname)?;
674    if ctx.is_checked() {
675        // Fully typecheck the constructor type (it must be a type).
676        ctx.ensure_sort(cty)?;
677    }
678    let decl_name = &pre.names[type_idx];
679    let rest = peel_against_params(ctx, cty, &pre.params, "constructor", cname)?;
680    let (field_binders, cod) = peel_all(ctx, &rest);
681
682    let mut fields = Vec::with_capacity(field_binders.len());
683    for b in field_binders {
684        // Normalized so a Prop-valued Pi field (`imax(u, 0)`) is recognised
685        // as `0` by the universe rule and the elimination decision.
686        let sort_level = ctx.ensure_sort(&b.ty)?.map(|l| normalize_level(&l));
687        if let Some(sl) = &sort_level {
688            // Universe rule: unless the family lives in Prop, every field
689            // must fit inside the inductive's universe.
690            if !pre.ind_level.is_zero() && !is_geq(&pre.ind_level, sl) {
691                return Err(KernelError::InvalidInductive(format!(
692                    "universe level of field '{}' in constructor '{}' is too big \
693                     for inductive type '{}'",
694                    b.name, cname, decl_name
695                )));
696            }
697        }
698        if ctx.is_checked() {
699            check_positivity(ctx, pre, &b.ty, cname, decl_name)?;
700        }
701        let rec = classify_rec_field(ctx, pre, &b.ty);
702        fields.push(FieldSig {
703            binder: b,
704            sort_level,
705            rec,
706        });
707    }
708
709    let codw = ctx.whnf(&cod);
710    let ret_index_args = if ctx.is_checked() {
711        match as_valid_ind_app(pre, &codw, true) {
712            Some((j, idx)) if j == type_idx => idx,
713            _ => {
714                return Err(KernelError::InvalidInductive(format!(
715                    "constructor '{}' must return '{}' applied to exactly the family \
716                     parameters followed by index terms",
717                    cname, decl_name
718                )));
719            }
720        }
721    } else {
722        match as_valid_ind_app(pre, &codw, false) {
723            Some((j, idx)) if j == type_idx => idx,
724            _ => {
725                return Err(KernelError::InvalidInductive(format!(
726                    "constructor '{}' does not return inductive type '{}'",
727                    cname, decl_name
728                )));
729            }
730        }
731    };
732
733    Ok(CtorSig {
734        name: cname.clone(),
735        ty: cty.clone(),
736        fields,
737        ret_index_args,
738    })
739}
740
741// ---------------------------------------------------------------------------
742// Elimination universe / K flag.
743// ---------------------------------------------------------------------------
744
745/// Lean's `elim_only_at_universe_zero`, inverted: does the family support
746/// large elimination? The decision is made by the kernel from the checked
747/// signature — the caller-supplied `is_prop` flag is never consulted.
748fn is_large_eliminating(pre: &FamilyPre, ctors: &[Vec<CtorSig>]) -> bool {
749    if pre.ind_level.is_not_zero() {
750        // The resultant sort is never 0: not a proposition.
751        return true;
752    }
753    if pre.names.len() > 1 {
754        return false;
755    }
756    let cs = &ctors[0];
757    match cs.len() {
758        0 => true, // e.g. False / Empty: eliminates anywhere.
759        1 => {
760            // Subsingleton eliminator: every field either lives in Prop or
761            // appears among the resulting type's indices.
762            let c = &cs[0];
763            c.fields.iter().all(|f| {
764                let field_is_prop = matches!(&f.sort_level, Some(l) if l.is_zero());
765                field_is_prop || c.ret_index_args.contains(&Expr::FVar(f.binder.fvar))
766            })
767        }
768        _ => false,
769    }
770}
771
772/// Lean's `init_K_target`: K-like reduction is enabled only for a single
773/// (non-mutual) Prop inductive with exactly one constructor whose arguments
774/// are all parameters.
775fn compute_k_target(pre: &FamilyPre, ctors: &[Vec<CtorSig>]) -> bool {
776    pre.names.len() == 1
777        && pre.ind_level.is_zero()
778        && ctors[0].len() == 1
779        && ctors[0][0].fields.is_empty()
780}
781
782/// Fresh elimination-universe parameter name: `u`, `u_1`, `u_2`, ...
783fn mk_elim_level_name(lparams: &[Name]) -> Name {
784    let mut cand = Name::str("u");
785    let mut i = 1u32;
786    while lparams.contains(&cand) {
787        cand = Name::str(format!("u_{}", i));
788        i += 1;
789    }
790    cand
791}
792
793// ---------------------------------------------------------------------------
794// Recursor derivation.
795// ---------------------------------------------------------------------------
796
797fn last_component_name(n: &Name, fallback: &str) -> Name {
798    Name::str(n.last_str().unwrap_or(fallback).to_string())
799}
800
801#[allow(clippy::result_large_err)]
802fn derive_family_core(
803    ctx: &mut DeriveCtx<'_>,
804    pre: &FamilyPre,
805    specs: &[InductiveSpec],
806    ctors: Vec<Vec<CtorSig>>,
807) -> Result<DerivedFamily, KernelError> {
808    let k = pre.names.len();
809    let total_ctors: usize = ctors.iter().map(|c| c.len()).sum();
810    let rec_names: Vec<Name> = specs.iter().map(|s| s.effective_rec_name()).collect();
811
812    let large_elim = is_large_eliminating(pre, &ctors);
813    let k_target = compute_k_target(pre, &ctors);
814    let (rec_lparams, elim_level) = if large_elim {
815        let fresh = mk_elim_level_name(&pre.lparams);
816        let mut lp = Vec::with_capacity(pre.lparams.len() + 1);
817        lp.push(fresh.clone());
818        lp.extend(pre.lparams.iter().cloned());
819        (lp, Level::param(fresh))
820    } else {
821        (pre.lparams.clone(), Level::zero())
822    };
823    let rec_levels: Vec<Level> = rec_lparams.iter().cloned().map(Level::param).collect();
824    let param_exprs: Vec<Expr> = pre.params.iter().map(fv).collect();
825
826    // --- Motives (one per family member) -----------------------------------
827    let mut motives: Vec<Binder> = Vec::with_capacity(k);
828    for j in 0..k {
829        let mut t_args = param_exprs.clone();
830        t_args.extend(pre.indices[j].iter().map(fv));
831        let t_ty = mk_app(
832            Expr::Const(pre.names[j].clone(), pre.ind_levels.clone()),
833            &t_args,
834        );
835        let x = ctx.fresh(BinderInfo::Default, Name::str("t"), t_ty);
836        let mut dom = pre.indices[j].clone();
837        dom.push(x);
838        let motive_ty = close(
839            &dom,
840            &Expr::Sort(elim_level.clone()),
841            false,
842            Some(BinderInfo::Default),
843        );
844        let mname = if k == 1 {
845            Name::str("motive")
846        } else {
847            Name::str(format!("motive_{}", j + 1))
848        };
849        motives.push(ctx.fresh(BinderInfo::Implicit, mname, motive_ty));
850    }
851    let motive_exprs: Vec<Expr> = motives.iter().map(fv).collect();
852
853    // --- Minor premises (all constructors, family order) -------------------
854    // For each ctor we also remember the per-field IH values used in the
855    // reduction rule (grouped after the fields, mirroring the minor's type).
856    struct MinorPlan {
857        type_idx: usize,
858        ctor_idx: usize,
859        minor: Binder,
860    }
861    let mut minors: Vec<MinorPlan> = Vec::with_capacity(total_ctors);
862    for (j, cs) in ctors.iter().enumerate() {
863        for (ci, c) in cs.iter().enumerate() {
864            let mut ih_binders: Vec<Binder> = Vec::new();
865            for f in &c.fields {
866                if let Some(rf) = &f.rec {
867                    let ds_exprs: Vec<Expr> = rf.ds.iter().map(fv).collect();
868                    let applied_field = mk_app(fv(&f.binder), &ds_exprs);
869                    let mut m_args = rf.index_args.clone();
870                    m_args.push(applied_field);
871                    let ih_body = mk_app(fv(&motives[rf.type_idx]), &m_args);
872                    let ih_ty = close(&rf.ds, &ih_body, false, None);
873                    let ih_name =
874                        Name::str(format!("{}_ih", f.binder.name.last_str().unwrap_or("a")));
875                    ih_binders.push(ctx.fresh(BinderInfo::Default, ih_name, ih_ty));
876                }
877            }
878            let mut ctor_args = param_exprs.clone();
879            ctor_args.extend(c.fields.iter().map(|f| fv(&f.binder)));
880            let ctor_app = mk_app(
881                Expr::Const(c.name.clone(), pre.ind_levels.clone()),
882                &ctor_args,
883            );
884            let mut concl_args = c.ret_index_args.clone();
885            concl_args.push(ctor_app);
886            let concl = mk_app(fv(&motives[j]), &concl_args);
887            let mut tele: Vec<Binder> = c.fields.iter().map(|f| f.binder.clone()).collect();
888            tele.extend(ih_binders);
889            let minor_ty = close(&tele, &concl, false, None);
890            let minor = ctx.fresh(
891                BinderInfo::Default,
892                last_component_name(&c.name, "minor"),
893                minor_ty,
894            );
895            minors.push(MinorPlan {
896                type_idx: j,
897                ctor_idx: ci,
898                minor,
899            });
900        }
901    }
902    let minor_binders: Vec<Binder> = minors.iter().map(|m| m.minor.clone()).collect();
903    let minor_exprs: Vec<Expr> = minor_binders.iter().map(fv).collect();
904
905    // --- Recursor per family member -----------------------------------------
906    let mut recursors = Vec::with_capacity(k);
907    for j in 0..k {
908        // Type: Π {params} {motives} (minors) {indices} (t : T_j params idx), motive_j idx t
909        let mut t_args = param_exprs.clone();
910        t_args.extend(pre.indices[j].iter().map(fv));
911        let major_ty = mk_app(
912            Expr::Const(pre.names[j].clone(), pre.ind_levels.clone()),
913            &t_args,
914        );
915        let major = ctx.fresh(BinderInfo::Default, Name::str("t"), major_ty);
916        let mut concl_args: Vec<Expr> = pre.indices[j].iter().map(fv).collect();
917        concl_args.push(fv(&major));
918        let concl = mk_app(fv(&motives[j]), &concl_args);
919
920        let mut rec_ty = close(std::slice::from_ref(&major), &concl, false, None);
921        rec_ty = close(&pre.indices[j], &rec_ty, false, Some(BinderInfo::Implicit));
922        rec_ty = close(&minor_binders, &rec_ty, false, Some(BinderInfo::Default));
923        rec_ty = close(&motives, &rec_ty, false, Some(BinderInfo::Implicit));
924        rec_ty = close(&pre.params, &rec_ty, false, Some(BinderInfo::Implicit));
925
926        // Rules: one per own constructor; RHS is a closed lambda over
927        // params ++ motives ++ minors ++ fields (Lean's convention).
928        let mut rules = Vec::with_capacity(ctors[j].len());
929        for plan in minors.iter().filter(|m| m.type_idx == j) {
930            let c = &ctors[j][plan.ctor_idx];
931            let mut body_args: Vec<Expr> = c.fields.iter().map(|f| fv(&f.binder)).collect();
932            for f in &c.fields {
933                if let Some(rf) = &f.rec {
934                    let ds_exprs: Vec<Expr> = rf.ds.iter().map(fv).collect();
935                    let mut rec_args = param_exprs.clone();
936                    rec_args.extend(motive_exprs.iter().cloned());
937                    rec_args.extend(minor_exprs.iter().cloned());
938                    rec_args.extend(rf.index_args.iter().cloned());
939                    rec_args.push(mk_app(fv(&f.binder), &ds_exprs));
940                    let rec_call = mk_app(
941                        Expr::Const(rec_names[rf.type_idx].clone(), rec_levels.clone()),
942                        &rec_args,
943                    );
944                    body_args.push(close(&rf.ds, &rec_call, true, Some(BinderInfo::Default)));
945                }
946            }
947            let body = mk_app(fv(&plan.minor), &body_args);
948            let field_binders: Vec<Binder> = c.fields.iter().map(|f| f.binder.clone()).collect();
949            let mut rhs = close(&field_binders, &body, true, Some(BinderInfo::Default));
950            rhs = close(&minor_binders, &rhs, true, Some(BinderInfo::Default));
951            rhs = close(&motives, &rhs, true, Some(BinderInfo::Default));
952            rhs = close(&pre.params, &rhs, true, Some(BinderInfo::Default));
953            rules.push(RecursorRule {
954                ctor: c.name.clone(),
955                nfields: c.fields.len() as u32,
956                rhs,
957            });
958        }
959
960        recursors.push(ConstantInfo::Recursor(RecursorVal {
961            common: ConstantVal {
962                name: rec_names[j].clone(),
963                level_params: rec_lparams.clone(),
964                ty: rec_ty,
965            },
966            all: pre.names.clone(),
967            num_params: pre.np as u32,
968            num_indices: pre.indices[j].len() as u32,
969            num_motives: k as u32,
970            num_minors: total_ctors as u32,
971            rules,
972            k: k_target,
973            is_unsafe: false,
974        }));
975    }
976
977    // --- Inductive + constructor infos --------------------------------------
978    let is_rec_family = ctors
979        .iter()
980        .flatten()
981        .any(|c| c.fields.iter().any(|f| f.rec.is_some()));
982    let is_reflexive = ctors.iter().flatten().any(|c| {
983        c.fields
984            .iter()
985            .any(|f| matches!(&f.rec, Some(rf) if !rf.ds.is_empty()))
986    });
987    let is_prop = pre.ind_level.is_zero();
988
989    let mut inductives = Vec::with_capacity(k);
990    let mut constructors = Vec::with_capacity(total_ctors);
991    for (j, spec) in specs.iter().enumerate() {
992        inductives.push(ConstantInfo::Inductive(InductiveVal {
993            common: ConstantVal {
994                name: pre.names[j].clone(),
995                level_params: pre.lparams.clone(),
996                ty: spec.ty.clone(),
997            },
998            num_params: pre.np as u32,
999            num_indices: pre.indices[j].len() as u32,
1000            all: pre.names.clone(),
1001            ctors: ctors[j].iter().map(|c| c.name.clone()).collect(),
1002            num_nested: 0,
1003            is_rec: is_rec_family,
1004            is_unsafe: false,
1005            is_reflexive,
1006            is_prop,
1007        }));
1008        for (ci, c) in ctors[j].iter().enumerate() {
1009            constructors.push(ConstantInfo::Constructor(ConstructorVal {
1010                common: ConstantVal {
1011                    name: c.name.clone(),
1012                    level_params: pre.lparams.clone(),
1013                    ty: c.ty.clone(),
1014                },
1015                induct: pre.names[j].clone(),
1016                cidx: ci as u32,
1017                num_params: pre.np as u32,
1018                num_fields: c.fields.len() as u32,
1019                is_unsafe: false,
1020            }));
1021        }
1022    }
1023
1024    Ok(DerivedFamily {
1025        inductives,
1026        constructors,
1027        recursors,
1028    })
1029}
1030
1031// ---------------------------------------------------------------------------
1032// Public entry points.
1033// ---------------------------------------------------------------------------
1034
1035/// Every type a family is declared with — the inductive types and the
1036/// constructor types — must be a closed term: no free variable, no loose
1037/// bound variable (see `check::check_closed_term`). And none may be deeper
1038/// than this thread's term-depth limit, where it has one (see
1039/// `check::check_term_depth`).
1040#[allow(clippy::result_large_err)]
1041pub(crate) fn check_specs_closed(specs: &[InductiveSpec]) -> Result<(), KernelError> {
1042    for spec in specs {
1043        crate::check::check_term_depth(&spec.name, &spec.ty)?;
1044        crate::check::check_closed_term(&spec.name, &spec.ty)?;
1045        for (ctor_name, ctor_ty) in &spec.ctors {
1046            crate::check::check_term_depth(ctor_name, ctor_ty)?;
1047            crate::check::check_closed_term(ctor_name, ctor_ty)?;
1048        }
1049    }
1050    Ok(())
1051}
1052
1053#[allow(clippy::result_large_err)]
1054fn check_distinct_ctor_names(specs: &[InductiveSpec]) -> Result<(), KernelError> {
1055    let mut seen: Vec<&Name> = Vec::new();
1056    for spec in specs {
1057        for (cname, _) in &spec.ctors {
1058            if seen.contains(&cname) {
1059                return Err(KernelError::InvalidInductive(format!(
1060                    "duplicate constructor name '{}'",
1061                    cname
1062                )));
1063            }
1064            seen.push(cname);
1065        }
1066    }
1067    Ok(())
1068}
1069
1070/// Check an inductive family against `env` and derive its constructors and
1071/// recursors, without modifying `env`. This is the kernel's independent
1072/// re-derivation path: signature checking, constructor telescope/universe
1073/// checking, WHNF-hardened strict positivity, and Lean-exact recursor
1074/// generation (elimination universe and K flag computed by the kernel).
1075///
1076/// The family types themselves may already exist in `env` (they must then
1077/// be identical `Inductive` declarations); otherwise they are staged into a
1078/// private working copy so that constructor types can reference them.
1079#[allow(clippy::result_large_err)]
1080pub fn check_and_derive_family(
1081    env: &Environment,
1082    lparams: &[Name],
1083    num_params: u32,
1084    specs: &[InductiveSpec],
1085) -> Result<DerivedFamily, KernelError> {
1086    check_distinct_ctor_names(specs)?;
1087    check_specs_closed(specs)?;
1088    let family_names: Vec<Name> = specs.iter().map(|s| s.name.clone()).collect();
1089
1090    // Stage the type constants so constructor types can mention them.
1091    let mut work = env.clone();
1092    for spec in specs {
1093        match work.find(&spec.name) {
1094            Some(ConstantInfo::Inductive(iv)) => {
1095                if iv.common.ty != spec.ty || iv.common.level_params != lparams {
1096                    return Err(KernelError::InvalidInductive(format!(
1097                        "inductive type '{}' is already declared with a different signature",
1098                        spec.name
1099                    )));
1100                }
1101            }
1102            Some(_) => {
1103                return Err(KernelError::InvalidInductive(format!(
1104                    "'{}' is already declared and is not an inductive type",
1105                    spec.name
1106                )));
1107            }
1108            None => {
1109                let stub = ConstantInfo::Inductive(InductiveVal {
1110                    common: ConstantVal {
1111                        name: spec.name.clone(),
1112                        level_params: lparams.to_vec(),
1113                        ty: spec.ty.clone(),
1114                    },
1115                    num_params,
1116                    num_indices: 0,
1117                    all: family_names.clone(),
1118                    ctors: spec.ctors.iter().map(|(n, _)| n.clone()).collect(),
1119                    num_nested: 0,
1120                    is_rec: false,
1121                    is_unsafe: false,
1122                    is_reflexive: false,
1123                    is_prop: false,
1124                });
1125                work.add_constant(stub)
1126                    .map_err(|e| KernelError::Other(e.to_string()))?;
1127            }
1128        }
1129    }
1130
1131    let types: Vec<(Name, Expr)> = specs
1132        .iter()
1133        .map(|s| (s.name.clone(), s.ty.clone()))
1134        .collect();
1135    let mut ctx = DeriveCtx::checked(&work);
1136    let pre = build_family_pre(&mut ctx, lparams, num_params, &types)?;
1137    let mut ctor_sigs = Vec::with_capacity(specs.len());
1138    for (j, spec) in specs.iter().enumerate() {
1139        let mut cs = Vec::with_capacity(spec.ctors.len());
1140        for (cname, cty) in &spec.ctors {
1141            cs.push(check_ctor(&mut ctx, &pre, j, cname, cty)?);
1142        }
1143        ctor_sigs.push(cs);
1144    }
1145    derive_family_core(&mut ctx, &pre, specs, ctor_sigs)
1146}
1147
1148/// Check an inductive family and add its types, constructors, and
1149/// kernel-derived recursors to `env`. This is the entry point an export
1150/// replayer should use for `inductive` blocks (mutual families included).
1151#[allow(clippy::result_large_err)]
1152pub fn add_inductive_family(
1153    env: &mut Environment,
1154    lparams: Vec<Name>,
1155    num_params: u32,
1156    specs: Vec<InductiveSpec>,
1157) -> Result<(), KernelError> {
1158    let fam = check_and_derive_family(env, &lparams, num_params, &specs)?;
1159    for ci in fam.into_constant_infos() {
1160        env.add_constant(ci)
1161            .map_err(|e| KernelError::Other(e.to_string()))?;
1162    }
1163    Ok(())
1164}
1165
1166/// Purely syntactic derivation without an environment (legacy
1167/// `InductiveType::to_constant_infos` path). No positivity, universe, or
1168/// def-eq checks are performed, and — lacking sort inference — a
1169/// possibly-Prop single-constructor type is granted large elimination only
1170/// when every field appears among the resulting indices (this
1171/// under-approximates Lean's rule towards *small* elimination, which is the
1172/// sound direction). Use [`check_and_derive_family`] whenever an
1173/// environment is available.
1174#[allow(clippy::result_large_err)]
1175pub fn derive_family_unchecked(
1176    lparams: &[Name],
1177    num_params: u32,
1178    specs: &[InductiveSpec],
1179) -> Result<DerivedFamily, KernelError> {
1180    check_distinct_ctor_names(specs)?;
1181    let types: Vec<(Name, Expr)> = specs
1182        .iter()
1183        .map(|s| (s.name.clone(), s.ty.clone()))
1184        .collect();
1185    let mut ctx = DeriveCtx::unchecked();
1186    let pre = build_family_pre(&mut ctx, lparams, num_params, &types)?;
1187    let mut ctor_sigs = Vec::with_capacity(specs.len());
1188    for (j, spec) in specs.iter().enumerate() {
1189        let mut cs = Vec::with_capacity(spec.ctors.len());
1190        for (cname, cty) in &spec.ctors {
1191            cs.push(check_ctor(&mut ctx, &pre, j, cname, cty)?);
1192        }
1193        ctor_sigs.push(cs);
1194    }
1195    derive_family_core(&mut ctx, &pre, specs, ctor_sigs)
1196}
1197
1198// ---------------------------------------------------------------------------
1199// Verification of externally-supplied declarations (export replay path).
1200// ---------------------------------------------------------------------------
1201
1202/// Validate an externally-supplied `InductiveVal` (declaration-level checks;
1203/// constructor and recursor consistency is enforced when those arrive).
1204/// Crucially, the `is_prop` flag must agree with the *declared type* — a
1205/// caller cannot lie its way into large elimination (the recursor check
1206/// recomputes the elimination universe from the type anyway).
1207#[allow(clippy::result_large_err)]
1208pub(crate) fn validate_inductive_val(
1209    env: &Environment,
1210    iv: &InductiveVal,
1211) -> Result<(), KernelError> {
1212    for (i, lp) in iv.common.level_params.iter().enumerate() {
1213        if iv.common.level_params[..i].contains(lp) {
1214            return Err(KernelError::InvalidInductive(format!(
1215                "duplicate universe parameter '{}' in inductive '{}'",
1216                lp, iv.common.name
1217            )));
1218        }
1219    }
1220    check_level_params_declared(
1221        &iv.common.ty,
1222        &iv.common.level_params,
1223        "inductive type",
1224        &iv.common.name,
1225    )?;
1226    let mut ctx = DeriveCtx::checked(env);
1227    ctx.ensure_sort(&iv.common.ty)?;
1228    let (binders, rest) = peel_all(&mut ctx, &iv.common.ty);
1229    let restw = ctx.whnf(&rest);
1230    let level = match restw {
1231        Expr::Sort(l) => l,
1232        other => {
1233            return Err(KernelError::InvalidInductive(format!(
1234                "inductive type '{}' must end in a sort, found '{}'",
1235                iv.common.name, other
1236            )));
1237        }
1238    };
1239    if binders.len() != (iv.num_params + iv.num_indices) as usize {
1240        return Err(KernelError::InvalidInductive(format!(
1241            "inductive '{}' declares {} params + {} indices but its type has {} binders",
1242            iv.common.name,
1243            iv.num_params,
1244            iv.num_indices,
1245            binders.len()
1246        )));
1247    }
1248    if iv.is_prop != level.is_zero() {
1249        return Err(KernelError::InvalidInductive(format!(
1250            "inductive '{}': is_prop flag ({}) is inconsistent with the declared sort",
1251            iv.common.name, iv.is_prop
1252        )));
1253    }
1254    if !iv.all.contains(&iv.common.name) {
1255        return Err(KernelError::InvalidInductive(format!(
1256            "inductive '{}' is missing from its own mutual group",
1257            iv.common.name
1258        )));
1259    }
1260    for (i, c) in iv.ctors.iter().enumerate() {
1261        if iv.ctors[..i].contains(c) {
1262            return Err(KernelError::InvalidInductive(format!(
1263                "duplicate constructor '{}' in inductive '{}'",
1264                c, iv.common.name
1265            )));
1266        }
1267    }
1268    Ok(())
1269}
1270
1271/// Build the family pre-analysis for the mutual group of `iv` from the
1272/// environment (all sibling `InductiveVal`s must already be present).
1273#[allow(clippy::result_large_err)]
1274fn family_pre_from_env<'e>(
1275    env: &'e Environment,
1276    all: &[Name],
1277    err_kind: fn(String) -> KernelError,
1278) -> Result<(DeriveCtx<'e>, FamilyPre), KernelError> {
1279    let mut lparams: Option<Vec<Name>> = None;
1280    let mut np: Option<u32> = None;
1281    let mut types = Vec::with_capacity(all.len());
1282    for tname in all {
1283        let siv = env.get_inductive_val(tname).ok_or_else(|| {
1284            err_kind(format!(
1285                "mutual inductive sibling '{}' is not declared in the environment",
1286                tname
1287            ))
1288        })?;
1289        if siv.all != all {
1290            return Err(err_kind(format!(
1291                "inductive '{}' declares a different mutual group",
1292                tname
1293            )));
1294        }
1295        match (&lparams, &np) {
1296            (None, None) => {
1297                lparams = Some(siv.common.level_params.clone());
1298                np = Some(siv.num_params);
1299            }
1300            (Some(lp), Some(n)) if *lp != siv.common.level_params || *n != siv.num_params => {
1301                return Err(err_kind(format!(
1302                    "mutual inductive '{}' disagrees on level params or param count",
1303                    tname
1304                )));
1305            }
1306            _ => {}
1307        }
1308        types.push((tname.clone(), siv.common.ty.clone()));
1309    }
1310    let lparams = lparams.unwrap_or_default();
1311    let np = np.unwrap_or(0);
1312    let mut ctx = DeriveCtx::checked(env);
1313    let pre = build_family_pre(&mut ctx, &lparams, np, &types)?;
1314    // Cross-check the stored index counts against the recomputed telescopes.
1315    for (j, tname) in all.iter().enumerate() {
1316        if let Some(siv) = env.get_inductive_val(tname) {
1317            if siv.num_indices as usize != pre.indices[j].len() {
1318                return Err(err_kind(format!(
1319                    "inductive '{}' declares {} indices but its type has {}",
1320                    tname,
1321                    siv.num_indices,
1322                    pre.indices[j].len()
1323                )));
1324            }
1325        }
1326    }
1327    Ok((ctx, pre))
1328}
1329
1330/// Verify an externally-supplied `ConstructorVal` against the environment's
1331/// inductive: parameter telescope (def-eq), field count, universe fit,
1332/// strict positivity, and a valid codomain. Metadata (cidx, num_params,
1333/// level params) must agree with the parent inductive.
1334#[allow(clippy::result_large_err)]
1335pub(crate) fn verify_constructor_val(
1336    env: &Environment,
1337    cv: &ConstructorVal,
1338) -> Result<(), KernelError> {
1339    let iv = env.get_inductive_val(&cv.induct).ok_or_else(|| {
1340        KernelError::InvalidInductive(format!(
1341            "constructor '{}' references unknown inductive '{}'",
1342            cv.common.name, cv.induct
1343        ))
1344    })?;
1345    if cv.common.level_params != iv.common.level_params {
1346        return Err(KernelError::InvalidInductive(format!(
1347            "constructor '{}' must use the level parameters of '{}'",
1348            cv.common.name, cv.induct
1349        )));
1350    }
1351    if cv.num_params != iv.num_params {
1352        return Err(KernelError::InvalidInductive(format!(
1353            "constructor '{}': num_params ({}) disagrees with inductive '{}' ({})",
1354            cv.common.name, cv.num_params, cv.induct, iv.num_params
1355        )));
1356    }
1357    match iv.ctors.iter().position(|c| c == &cv.common.name) {
1358        Some(pos) if pos as u32 == cv.cidx => {}
1359        Some(pos) => {
1360            return Err(KernelError::InvalidInductive(format!(
1361                "constructor '{}' has cidx {} but is at position {} in '{}'",
1362                cv.common.name, cv.cidx, pos, cv.induct
1363            )));
1364        }
1365        None => {
1366            return Err(KernelError::InvalidInductive(format!(
1367                "constructor '{}' is not listed by inductive '{}'",
1368                cv.common.name, cv.induct
1369            )));
1370        }
1371    }
1372    let all = iv.all.clone();
1373    let induct = cv.induct.clone();
1374    let (mut ctx, pre) = family_pre_from_env(env, &all, KernelError::InvalidInductive)?;
1375    let type_idx = pre.names.iter().position(|n| *n == induct).ok_or_else(|| {
1376        KernelError::InvalidInductive(format!(
1377            "inductive '{}' missing from its own mutual group",
1378            induct
1379        ))
1380    })?;
1381    let sig = check_ctor(&mut ctx, &pre, type_idx, &cv.common.name, &cv.common.ty)?;
1382    if sig.fields.len() != cv.num_fields as usize {
1383        return Err(KernelError::InvalidInductive(format!(
1384            "constructor '{}': num_fields ({}) disagrees with its type ({} fields)",
1385            cv.common.name,
1386            cv.num_fields,
1387            sig.fields.len()
1388        )));
1389    }
1390    Ok(())
1391}
1392
1393/// Verify an externally-supplied `RecursorVal` by *re-deriving* the
1394/// recursors for its inductive family from the environment and requiring
1395/// the supplied declaration to match the derivation: identical structural
1396/// metadata (param/index/motive/minor counts, K flag) and definitionally
1397/// equal type and rule right-hand sides (up to level-parameter renaming).
1398/// This is the "re-derive, don't trust" hardening for export replay.
1399#[allow(clippy::result_large_err)]
1400pub(crate) fn verify_recursor_val(env: &Environment, rv: &RecursorVal) -> Result<(), KernelError> {
1401    if rv.all.is_empty() {
1402        return Err(KernelError::InvalidRecursor(format!(
1403            "recursor '{}' has an empty mutual group",
1404            rv.common.name
1405        )));
1406    }
1407    // Reconstruct the family specs from the environment.
1408    let mut lparams: Option<Vec<Name>> = None;
1409    let mut np: Option<u32> = None;
1410    let mut specs = Vec::with_capacity(rv.all.len());
1411    let single = rv.all.len() == 1;
1412    // Sibling recursor names inside rule RHSes must match structurally. For
1413    // a single-type family the only possible self-reference is the recursor
1414    // being verified, so use its own name; for mutual families follow the
1415    // supplied name's style (hierarchical `T.rec` vs flat `"T.rec"`).
1416    let flat_style = rv.common.name.depth() <= 1;
1417    for tname in &rv.all {
1418        let iv = env.get_inductive_val(tname).ok_or_else(|| {
1419            KernelError::InvalidRecursor(format!(
1420                "recursor '{}' references unknown inductive '{}'",
1421                rv.common.name, tname
1422            ))
1423        })?;
1424        if iv.all != rv.all {
1425            return Err(KernelError::InvalidRecursor(format!(
1426                "recursor '{}' and inductive '{}' disagree on the mutual group",
1427                rv.common.name, tname
1428            )));
1429        }
1430        match (&lparams, &np) {
1431            (None, None) => {
1432                lparams = Some(iv.common.level_params.clone());
1433                np = Some(iv.num_params);
1434            }
1435            (Some(lp), Some(n)) if *lp != iv.common.level_params || *n != iv.num_params => {
1436                return Err(KernelError::InvalidRecursor(format!(
1437                    "mutual inductive '{}' disagrees on level params or param count",
1438                    tname
1439                )));
1440            }
1441            _ => {}
1442        }
1443        let mut ctors = Vec::with_capacity(iv.ctors.len());
1444        for cname in &iv.ctors {
1445            let cval = env.get_constructor_val(cname).ok_or_else(|| {
1446                KernelError::InvalidRecursor(format!(
1447                    "recursor '{}': constructor '{}' of '{}' is not declared yet",
1448                    rv.common.name, cname, tname
1449                ))
1450            })?;
1451            ctors.push((cname.clone(), cval.common.ty.clone()));
1452        }
1453        let rec_name = if single {
1454            rv.common.name.clone()
1455        } else if flat_style {
1456            Name::str(format!("{}.rec", tname))
1457        } else {
1458            Name::mk_str(tname.clone(), "rec")
1459        };
1460        specs.push(InductiveSpec {
1461            name: tname.clone(),
1462            ty: iv.common.ty.clone(),
1463            ctors,
1464            rec_name: Some(rec_name),
1465        });
1466    }
1467    let lparams = lparams.unwrap_or_default();
1468    let np = np.unwrap_or(0);
1469    let fam = check_and_derive_family(env, &lparams, np, &specs)?;
1470
1471    let mut tc = TypeChecker::new(env);
1472    for d_ci in &fam.recursors {
1473        if let ConstantInfo::Recursor(d) = d_ci {
1474            if recursor_matches(&mut tc, d, rv) {
1475                return Ok(());
1476            }
1477        }
1478    }
1479    Err(KernelError::InvalidRecursor(format!(
1480        "recursor '{}' does not match the kernel-derived recursor for '{}'",
1481        rv.common.name, rv.all[0]
1482    )))
1483}
1484
1485pub(crate) fn recursor_matches(
1486    tc: &mut TypeChecker<'_>,
1487    d: &RecursorVal,
1488    rv: &RecursorVal,
1489) -> bool {
1490    if d.common.level_params.len() != rv.common.level_params.len() {
1491        return false;
1492    }
1493    if d.num_params != rv.num_params
1494        || d.num_indices != rv.num_indices
1495        || d.num_motives != rv.num_motives
1496        || d.num_minors != rv.num_minors
1497        || d.k != rv.k
1498        || d.rules.len() != rv.rules.len()
1499    {
1500        return false;
1501    }
1502    // Align the derived declaration onto the supplied level parameter names.
1503    let levels: Vec<Level> = rv
1504        .common
1505        .level_params
1506        .iter()
1507        .cloned()
1508        .map(Level::param)
1509        .collect();
1510    let d_ty = instantiate_type_lparams(&d.common.ty, &d.common.level_params, &levels);
1511    if !tc.is_def_eq(&d_ty, &rv.common.ty) {
1512        return false;
1513    }
1514    for drule in &d.rules {
1515        let srule = match rv.get_rule(&drule.ctor) {
1516            Some(r) => r,
1517            None => return false,
1518        };
1519        if srule.nfields != drule.nfields {
1520            return false;
1521        }
1522        let d_rhs = instantiate_type_lparams(&drule.rhs, &d.common.level_params, &levels);
1523        if !tc.is_def_eq(&d_rhs, &srule.rhs) {
1524            return false;
1525        }
1526    }
1527    true
1528}
1529
1530#[cfg(test)]
1531mod tests {
1532    use super::*;
1533
1534    fn nat_spec() -> InductiveSpec {
1535        let nat = Expr::Const(Name::str("Nat"), vec![]);
1536        InductiveSpec::new(
1537            Name::str("Nat"),
1538            Expr::Sort(Level::succ(Level::zero())),
1539            vec![
1540                (Name::str("Nat.zero"), nat.clone()),
1541                (
1542                    Name::str("Nat.succ"),
1543                    Expr::Pi(
1544                        BinderInfo::Default,
1545                        Name::str("n"),
1546                        Node::new(nat.clone()),
1547                        Node::new(nat),
1548                    ),
1549                ),
1550            ],
1551        )
1552        .with_rec_name(Name::str("Nat.rec"))
1553    }
1554
1555    #[test]
1556    fn nat_derivation_shape() {
1557        let env = Environment::new();
1558        let fam = check_and_derive_family(&env, &[], 0, &[nat_spec()]).expect("derive Nat");
1559        assert_eq!(fam.inductives.len(), 1);
1560        assert_eq!(fam.constructors.len(), 2);
1561        assert_eq!(fam.recursors.len(), 1);
1562        let rv = match &fam.recursors[0] {
1563            ConstantInfo::Recursor(rv) => rv,
1564            other => panic!("expected recursor, got {:?}", other),
1565        };
1566        assert_eq!(rv.num_motives, 1);
1567        assert_eq!(rv.num_minors, 2);
1568        assert_eq!(rv.num_params, 0);
1569        assert_eq!(rv.num_indices, 0);
1570        assert!(!rv.k);
1571        // Large elimination: fresh level param "u" prepended.
1572        assert_eq!(rv.common.level_params, vec![Name::str("u")]);
1573        // succ minor has an IH: (n : Nat) → motive n → motive (Nat.succ n).
1574        let succ_rule = rv.get_rule(&Name::str("Nat.succ")).expect("succ rule");
1575        assert_eq!(succ_rule.nfields, 1);
1576    }
1577
1578    #[test]
1579    fn unchecked_matches_checked_for_nat() {
1580        let env = Environment::new();
1581        let a = check_and_derive_family(&env, &[], 0, &[nat_spec()]).expect("checked");
1582        let b = derive_family_unchecked(&[], 0, &[nat_spec()]).expect("unchecked");
1583        assert_eq!(a.recursors, b.recursors);
1584        assert_eq!(a.constructors, b.constructors);
1585    }
1586
1587    #[test]
1588    fn nested_inductive_rejected() {
1589        // inductive T | mk : List T → T  (nested occurrence under List)
1590        let mut env = Environment::new();
1591        crate::builtin::init_builtin_env(&mut env).expect("builtin env");
1592        let t = Expr::Const(Name::str("T"), vec![]);
1593        let list_t = Expr::App(
1594            Node::new(Expr::Const(Name::str("List"), vec![Level::zero()])),
1595            Node::new(t.clone()),
1596        );
1597        let spec = InductiveSpec::new(
1598            Name::str("T"),
1599            Expr::Sort(Level::succ(Level::zero())),
1600            vec![(
1601                Name::str("T.mk"),
1602                Expr::Pi(
1603                    BinderInfo::Default,
1604                    Name::str("l"),
1605                    Node::new(list_t),
1606                    Node::new(t),
1607                ),
1608            )],
1609        );
1610        let err = check_and_derive_family(&env, &[], 0, &[spec]).expect_err("must reject");
1611        assert!(matches!(err, KernelError::UnsupportedNestedInductive(_)));
1612    }
1613}