feat: type-check generated recursors in the kernel This PR adds a defensive check to the kernel. **TL;DR.** When the kernel generates a recursor for an inductive type, it installs the recursor and its computation rules with `add_core`, which does not re-check them. This PR adds a verification pass that (1) type-checks each generated recursor's type and (2) checks that each computation rule is type-preserving: reducing the recursor applied to a constructor yields a term whose type is the recursor's declared result type. This catches a recursor whose minor-premise type and reduction rule disagree, for example a minor premise that expects an induction hypothesis while the rule omits it. Checking only that a rule's right-hand side has some type is not enough, because an under-applied minor premise is still a well-typed (function) term. The check is defense-in-depth: it does not change what the kernel accepts for well-formed inductives, and only rejects declarations that were already malformed. When the kernel adds an inductive type, it generates the recursor's type and its computation rules and installs them with `add_core`, which does not re-check them. The surrounding machinery is trusted to produce a consistent recursor. This PR re-verifies that output. The check has two parts. First, the recursor's type is type-checked. Second, for each constructor, the recursor applied to that constructor is reduced by one step and the type of the reduct is compared against the type of the un-reduced application; the two must be definitionally equal. The second part is what separates a type-preserving rule from a merely well-typed right-hand side: if a minor premise's type expects an induction hypothesis that the rule does not supply, the reduct is an under-applied minor premise. That reduct still has a type, but a function type rather than the recursor's result type, so the check fails.
diff --git a/src/kernel/inductive.cpp b/src/kernel/inductive.cpp index a148923..438f92d 100644 --- a/src/kernel/inductive.cpp +++ b/src/kernel/inductive.cpp
@@ -818,6 +818,53 @@ } } + /** \brief Defensively type-check the generated recursors. + + `add_core` installs a recursor and its computation rules without re-checking them. We verify + here that (1) each recursor's type is well typed, and (2) each computation rule is + type-preserving: reducing the recursor applied to a constructor yields a term whose type is + the recursor's declared result type. This catches a recursor whose minor-premise type and + reduction rule disagree; checking only that a rule's right-hand side has *some* type is + insufficient, because an under-applied minor premise is still a well-typed (function) term. */ + void check_recursors() { + buffer<expr> Cs; collect_Cs(Cs); + buffer<expr> minors; collect_minor_premises(minors); + for (unsigned d_idx = 0; d_idx < m_ind_types.size(); d_idx++) { + name rec_name = mk_rec_name(m_ind_types[d_idx].get_name()); + constant_info rec_ci = m_env.get(rec_name); + /* (1) The recursor type must be well typed. */ + tc().check(rec_ci.get_type(), get_rec_lparams()); + expr rec_pre = mk_app(mk_app(mk_app(mk_constant(rec_name, get_rec_levels()), m_params), Cs), minors); + /* (2) Each computation rule must preserve types. */ + for (constructor const & cnstr : m_ind_types[d_idx].get_cnstrs()) { + buffer<expr> b_u; + expr t = constructor_type(cnstr); + unsigned i = 0; + while (is_pi(t)) { + if (i < m_nparams) { + t = instantiate(binding_body(t), m_params[i]); + } else { + expr l = mk_local_decl_for(t); + b_u.push_back(l); + t = instantiate(binding_body(t), l); + } + i++; + } + buffer<expr> it_indices; + get_I_indices(t, it_indices); + expr intro_app = mk_app(mk_app(mk_constant(constructor_name(cnstr), m_levels), m_params), b_u); + expr lhs = mk_app(mk_app(rec_pre, it_indices), intro_app); + type_checker tcheck = tc(); + expr expected = tcheck.infer(lhs); + expr reduct = tcheck.whnf(lhs); + expr actual = tcheck.infer(reduct); + if (!tcheck.is_def_eq(actual, expected)) + throw kernel_exception(m_env, sstream() << "generated recursor computation rule for '" + << constructor_name(cnstr) << "' is not type-preserving"); + } + } + } + environment operator()() { m_env.check_duplicated_univ_params(m_lparams); check_inductive_types(); @@ -828,6 +875,7 @@ init_K_target(); mk_rec_infos(); declare_recursors(); + check_recursors(); return m_env; } };