Test "rec-missing-ih"
Expected: ✋ reject · Size: 289.6 KB · Lines: 5.5 k · lean4export: 3.1.0 · Lean: 4.29.1 · 📄 Declaration
Proof of False from a generated recursor whose reduction rule drops the
induction hypothesis.
Mechanism
-
Definitional equality is not transitive here. The test builds three functions
Bool → BoolfromAcc.recfor which a kernel reportsrcA ≡ rcBandrcB ≡ rcC— both by proof irrelevance on theAccargument, the latter after some iota steps — butrcA ≢ rcC, because there the twoAccproofs have different types (Acc (· < ·) 1vs.Acc (· < ·) 0). -
In the affected kernels, the defeq cache closes that relation transitively, but only for hash-equal terms. Established equalities are kept in a union-find structure, and the comparison returns early, without consulting it, when the hashes differ:
~~~cpp if (is_eqp(a, b)) return true; if (m_use_hash && hash(a) != hash(b)) return false; // skips the union-find ... node_ref r1 = find(to_node(a)); node_ref r2 = find(to_node(b)); if (r1 == r2) return true; ~~~
So whether
rcA ≡ rcCholds depends on the hash of the surrounding term. The three constants and the two paddings are picked so that the hashes collide for the free variables that the affected implementations create while building the minor premises of the recursor (_ind_fresh.3,_ind_fresh.9), but not for the pass that builds the recursor rules (_ind_fresh.14). -
A K-like reduction is made to depend on that comparison.
Native64TwoHashGateis a K-like inductive predicate (one parameter, fourBoolindices, one field-less constructor pinning the indices), so reducingGate.rec … hrequires the indices ofh's type to be definitionally equal to the ones ofGate.intro's result type.Native64TwoHashOwner.stephas a recursive argument whose type is such aGate.recapplication, which therefore reduces toOwnerin one pass but not in the other.
The resulting Native64TwoHashOwner.rec has a step minor premise expecting
four arguments (including the induction hypothesis) but a rule that applies it
to only three, so the ih binder swallows the next argument. That makes the
Prop-valued badProp reduce to Bool, and a Prop with two
distinguishable inhabitants gives False.
Affected kernels not only accept these declarations, they also re-derive the
same broken recursor when replaying the export data. This was the case for
the official kernel at v4.28.0, v4.29.1, v4.33.0 and nightly-2026-08-01.
Kernels that construct the recursor independently reject the export, mostly
with an error about Native64TwoHashOwner.step having an invalid occurrence of
the datatype being declared — which is also what the affected kernels reported
as soon as one of the hashes no longer collided.
Fixed by leanprover/lean4#14806, which replaces the union-find defeq cache with an order-independent one, so that a hash collision can no longer make a comparison succeed that fails on its own.
| Checker | Result | ⏱️ | 🧠 | |||
|---|---|---|---|---|---|---|
| mathgraph | ✋ | 4 ms | 97.9 MB | |||
| nanoda | ✋ | 12 ms | 3.9 MB | |||
| nanobruijn | ✋ | 17 ms | 18.9 MB | |||
| ind-models | 💥 | 121 ms | 106.2 MB | |||
| official | ✋ | 53 ms | 66.5 MB | |||
| lean4lean | ✋ | 58 ms | 100.1 MB | |||
| evmlean | ✋ | 2.1 m | 429.9 MB | |||
| mini | 🚫 | 49 ms | 74.1 MB | |||
| sokonanoda | ✋ | 4 ms | 102.2 MB | |||
| nanoclo | ✋ | 8 ms | 65.8 MB | |||
| zignodamus | ✋ | 5 ms | 9.2 MB | |||
| vow-lean-kernel | ✋ | 899 ms | 10.3 MB | |||
| official-v4.28.0 | 👍 | 65 ms | 72.6 MB | |||
| still-nanoda | ✋ | 12 ms | 3.8 MB | |||
| kiota | ✋ | 12 ms | 18.9 MB | |||
| rpylean | 👍 | 12 ms | 28.0 MB | |||
| nyaya | 👍 | 72 ms | 19.7 MB | |||
| parse-only | 👍 | 43 ms | 65.5 MB |