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