IxVM: handle wrapped recursor telescopes and theorem-headed iota majors - #545
Merged
Conversation
The canonical recursor reconstruction collected index domains off the
inductive's declared type with a structural-only peel. When that type is
a definitional wrapper — an `abbrev` such as mathlib's
`MorphismProperty C := ...` — the index binder only appears after whnf,
so the peel met an `App` node, fell off the match, and aborted
mid-execution. Every recursor for such an inductive was wrongly
rejected: 33 of the 34 reported mathlib failures, all with
"no match case for value 3" (tag 3 = App).
collect_index_doms now mirrors the reference's loop
(inductive.rs build_motive_type_flat, `for _ in 0..n_indices { whnf;
All => push; _ => break }`): structural first — whnf of a Forall is
that Forall, so the orders agree and the common case pays nothing —
whnf on the fallback, and a tolerant stop. The empty whnf context
mirrors the reference, which performs this walk with no ctx pushes; a
context-poorer whnf can only under-reduce, never invent a reduction.
The tolerant stop is not a weakened gate: get_result_sort_level already
peels n_params + n_indices Foralls off the same declared type
intolerantly (and whnf'd) before any of this runs, so reaching the
index walk with fewer binders is impossible for a block that validated.
Only this one site changed. The five sibling telescope walks were left
alone — no evidence implicates them, and each whnf costs.
Fixture IxVMInd.HiddenIdx pins the shape (an inductive whose declared
type is an abbrev over a predicate), verified to abort before the fix.
All kernel FFT pins re-harvested: 59 of 68 shifted, every one by
<= 0.0002% — the fixed cost of the added call site.
Regression guard, all flat vs this base: bitblast goCache eq_def 41.1s,
goCache_Inv_of_Inv 112s, UInt8.ofBitVec_not 2.3s, Std.Time ofDays
5.1s. Both test suites green. Rat.instEncodable still fails — a
separate root cause (theorem-headed iota major), fixed separately.
A recursor can only fire once its major reaches constructor form, but a major can legitimately be a THEOREM: `abstractNestedProofs` lifts inline proofs into auto-theorems, so `And.rec motive minor MAJOR` gets a `..._proof_1` there. The general reducer keeps `Thm` heads stuck, so the iota never fired and `Rat.instEncodable` was rejected with "inferred type is not def-eq to the expected type". Unfolding theorems globally (as the reference's delta loop does) fixes the verdict but costs `Std.Time...ofDays._proof_1` 14x, 5.1s -> 69.8s: every proof term the check touches then gets driven through its body. Theorem unfolding is only needed to drive an iota major, so it lives at that one site, `whnf_iota_major`, called from `try_iota`. Under-reducing elsewhere is the safe direction — it can only leave a term stuck, never manufacture a reduction. That alone still regressed ofDays, because a K major is typically a proof and whnf'ing it evaluates whatever decision procedure produced it (ofDays' major is an `Eq.rec` over a `decide +kernel` proof, so reducing it runs the whole Rat/Nat procedure over 14-digit literals). The reference synthesizes the K constructor from the RAW major first (whnf.rs:1349-1356), so `try_k_synth_iota` now runs above `try_iota`. The gate infers the type of the raw major, so its verdict does not depend on the major having been whnf'd; this is ordering only. Hoisting the gate broke its precondition: it reads the major with `list_lookup`, whose `Cons` binding is irrefutable, and its only previous caller was `try_iota`'s stuck branch, which had already established `major_idx < spine_len`. A partially applied recursor then walked off the end of the spine and aborted on `Nil`, which surfaced as "no match case for value 1" on two `Std.Tactic.BVDecide` constants. The gate now re-establishes the same length guard itself; a recursor short of its major cannot iota at all, so declining is also the right verdict. Both halves get a two-sided fixture: `thmMajorUse` is rejected without `whnf_iota_major` and accepted with it, `partialKRec` aborts without the length guard and passes with it. Guard set flat: ofDays 5.1s, goCache._mutual.eq_def 41s, goCache_Inv_of_Inv._mutual 112s, UInt8.ofBitVec_not 2.3s. All 34 previously-failing mathlib constants pass, `Rat.instEncodable` in 119s. Pins re-harvested for the added call site; the largest shift is +0.03% and several recursor-heavy entries drop, which is the avoided major whnf.
arthurpaulino
enabled auto-merge (squash)
August 10, 2026 21:12
samuelburnham
approved these changes
Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix two WHNF gaps that caused IxVM to reject valid Lean constants involving recursors:
Together these changes fix all 34 previously reported mathlib failures, including
Rat.instEncodableand the family of failures involving wrapped inductive types.Details
Canonical recursor reconstruction previously peeled an inductive's declared type structurally. For declarations whose index telescope is hidden behind an
abbrev, this encountered an application instead of aForalland aborted.collect_index_domsnow preserves the cheap structural fast path, falls back to WHNF when necessary, and stops tolerantly in the same way as the reference implementation. The earlier validation of the complete parameter and index telescope remains strict, so this does not weaken the kernel check.Separately, a recursor major may legitimately be headed by an auto-generated theorem after Lean's
abstractNestedProofspass. General IxVM WHNF intentionally leaves theorem heads stuck, preventing iota reduction in this case. The newwhnf_iota_majorunfolds theorem heads only while reducing an iota major. This gives the required reduction without globally expanding every proof term.For K-like recursors, synthesis now runs against the raw major before attempting major WHNF, as in the reference kernel. This avoids evaluating potentially large decision procedures merely to reduce proof-valued majors. Because this moves the K gate ahead of
try_iota, it now checks that the spine actually contains the major before callinglist_lookup; partially applied recursors simply decline the iota step.Tests and cost
HiddenIdx/PredOvercoverage for index binders hidden by a definitional wrapper.thmMajorUsecoverage for theorem-headed iota majors.partialKReccoverage for partially applied K recursors.Rat.instEncodablecompletes in 119 seconds.Std.Time...ofDays._proof_15.1s, bitblastgoCacheequality 41s,goCache_Inv_of_Inv112s, andUInt8.ofBitVec_not2.3s.