Skip to content

Commit 3e09f6a

Browse files
Idris2 F1.4: resolve the htShift binder blocker (Option A) + land htShift (#108) (#115)
## Summary Continues the Idris2 F1.4 substitution-lemma port (#108). The earlier revision of this PR surfaced an **architectural blocker** on `htShift` (term weakening) and asked for a kernel-design decision. This revision **takes Option A and lands the full lemma.** ### Option A — explicit bound-type fields (the unblock) `htShift` shifts under binders, so its binder cases extend the context with the bound type and recurse on the body. `THLam` worked (its type is in the term), but `THLet`/`THCase`/`THLetPair` bound their eliminated types only as **erased implicit indices**, so the extended context `TSnoc i <bound>` was unconstructible under Idris erasure. Fix: promote those bound types to **explicit, runtime-relevant fields** — the ADR-003 pattern already used for the usage splits on `THApp`/`THTensor`. Consumers updated: `progress` (Soundness.idr ×3), `shapeType` (Substitution.idr ×3). `progress` re-verified total; the Coq twin is untouched (it never needed this — `Prop` indices erase without blocking proofs). ### `htShift` — all 14 cases, total, hole-free - `Var` via `hvShift` + `shiftVarLemma` (structural `shiftIdx`); - `Unit` usage forced to zero (`unitInv` + `uappendInj`); - `Lam` / `Let` / `Case` / `LetPair` binder recursion — the last three are exactly the cases the blocker walled off, now constructible; - `App` / `Tensor` / `Let` usage-splitting: `usplitLemma`/`uappendSplit` decompose each premise along the G/I boundary, `uaddSplitBoundary` splits the `uadd` premise, `uaddUshift` reassembles, `ushiftUscale` handles the `q`-scaled legs. The module's blocker note is updated to RESOLVED. ## Test plan - [x] `idris2 --build solo-core.ipkg` exits 0; `%default total` ⇒ `htShift` is total - [x] only remaining hole is the documented `?todo_preservation` - [x] Coq track unchanged; `progress` re-verified against the new fields - [ ] next: `htSubst` → `subst2Lemma` → `preservation` (closes the hole) Refs #108 https://claude.ai/code/session_01BwV2DWsjkBiNP3oscimMLV --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a63252f commit 3e09f6a

3 files changed

Lines changed: 197 additions & 19 deletions

File tree

proofs/verification/idris/solo-core/Soundness.idr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ progress (Tensor t1 t2) (THTensor d1 d2 d1d d2d prf) =
245245
Left v1 => case progress t2 d2' of
246246
Right (MkStepsTo _ s2) => Right (MkStepsTo _ (STensor2 v1 s2))
247247
Left v2 => Left (VTensor v1 v2)
248-
progress (LetPair t1 t2) (THLetPair d1 d2 d1d _ prf) =
248+
progress (LetPair t1 t2) (THLetPair d1 d2 _ _ d1d _ prf) =
249249
let (e1, _) = uaddEmpty d1 d2 prf
250250
d1' = coeUse e1 d1d
251251
in case progress t1 d1' of
@@ -261,7 +261,7 @@ progress (Inr a t) (THInr d) =
261261
case progress t d of
262262
Left v => Left (VInr v)
263263
Right (MkStepsTo _ s) => Right (MkStepsTo _ (SInr1 s))
264-
progress (Case t tL tR) (THCase d1 d2 d dL dR prf) =
264+
progress (Case t tL tR) (THCase d1 d2 _ _ d dL dR prf) =
265265
let (e1, _) = uaddEmpty d1 d2 prf
266266
d' = coeUse e1 d
267267
in case progress t d' of
@@ -273,7 +273,7 @@ progress (Case t tL tR) (THCase d1 d2 d dL dR prf) =
273273
Right r =>
274274
let (_ ** _ ** (eq, v')) = r in
275275
rewrite eq in Right (MkStepsTo _ (SCaseR v'))
276-
progress (Let q t1 t2) (THLet d1 d2 _ d1d _ prf) =
276+
progress (Let q t1 t2) (THLet d1 d2 _ _ d1d _ prf) =
277277
let (es, _) = uaddEmpty (uscale q d1) d2 prf
278278
d1' = coeUse (uscaleEmpty q d1 es) d1d
279279
in case progress t1 d1' of

proofs/verification/idris/solo-core/Substitution.idr

Lines changed: 191 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,37 @@
1414
--
1515
-- Layers (mirroring the Coq twin):
1616
-- 4a append-context algebra (tappend/uappend + injectivity/split) [VERIFIED]
17-
-- 4b shape + shift (shapeType [VERIFIED]; hvShift/htShift/
17+
-- 4b shape + shift (shapeType/hvShift/htShift [VERIFIED];
1818
-- htShift0/weakeningAppend — PENDING)
1919
-- 4c substitution core (reassoc algebra, hvSubst, htSubst,
2020
-- substLemma0, subst2Lemma — PENDING)
2121
-- 4d preservation (consumes 4c; congruences recurse on Step
2222
-- — PENDING; Soundness.preservation stays
2323
-- the honest hole until 4d lands)
2424
--
25-
-- STATUS (Idris track, #108): this module is HOLE-FREE for the layers it
26-
-- currently contains (4a + the shape invariant). It does NOT yet prove
27-
-- preservation. The remaining layers each thread the type context (and term)
28-
-- RELEVANTLY because Idris erases the indices of `Has`/`HasVar` (ADR-003) —
29-
-- the same wall the `progress` proof navigated with explicit-term recursion.
30-
-- That makes the Idris port heavier than the Coq original (whose `Prop`
31-
-- indices erase without blocking the proofs), but the technique is settled
32-
-- (see `shapeType`/`shapeVar` below): pass `g`/`t` explicitly, recurse on the
33-
-- non-binding premise so erased branch-binder types are never demanded.
25+
-- STATUS (Idris track, #108): HOLE-FREE for the layers it currently contains —
26+
-- 4a (append algebra), the shape invariant, `hvShift` (has_var weakening),
27+
-- the structural shift index `shiftIdx`/`shiftIdxCorrect`/`shiftVarLemma`, and
28+
-- `htShift` (term weakening, all 14 constructor cases, total) on top of its
29+
-- support lemmas (`unitInv`, `usplitLemma`, `uaddUshift`, `ushiftUscale`).
30+
-- It does NOT yet prove preservation (htSubst/subst2Lemma/preservation pending).
31+
--
32+
-- ARCHITECTURAL BLOCKER on `htShift` — RESOLVED 2026-06-15 via Option A.
33+
-- `htShift` shifts under binders, so its binder cases extend the type context
34+
-- with the binder's type and recurse on the BODY. For `Lam q a body` the bound
35+
-- type `a` is in the term and recoverable. But `Let`/`Case`/`LetPair` did NOT
36+
-- annotate their bound types in the term — those types lived only as ERASED
37+
-- indices of the typing derivation (`THLet`/`THCase`/`THLetPair`), so the
38+
-- extended context `TSnoc i <bound-type>` for the body recursion could not be
39+
-- constructed under Idris erasure. (Coq was unaffected — `Prop` indices erase
40+
-- without blocking proofs; `progress` was unaffected — it never recurses into
41+
-- those bodies.)
42+
--
43+
-- FIX (Option A): `THLet`/`THCase`/`THLetPair` now carry their bound types as
44+
-- explicit, runtime-relevant fields — the ADR-003 pattern already used for the
45+
-- usage splits (`THApp`/`THTensor`). `progress` was re-verified against the new
46+
-- fields; the build stays green. With the bound types available, all 14
47+
-- `htShift` cases typecheck and the lemma is total (below).
3448

3549
module Substitution
3650

@@ -211,11 +225,11 @@ shapeType g (With t1 t2) (THWith h1 h2) = shapeType g t1 h1
211225
shapeType g (Fst t1) (THFst h) = shapeType g t1 h
212226
shapeType g (Snd t1) (THSnd h) = shapeType g t1 h
213227
shapeType g (Tensor t1 t2) (THTensor d1 d2 h1 h2 prf) = trans (uaddLen d1 d2 _ prf) (shapeType g t1 h1)
214-
shapeType g (LetPair t1 t2)(THLetPair d1 d2 h1 hb prf)= trans (uaddLen d1 d2 _ prf) (shapeType g t1 h1)
228+
shapeType g (LetPair t1 t2)(THLetPair d1 d2 _ _ h1 hb prf)= trans (uaddLen d1 d2 _ prf) (shapeType g t1 h1)
215229
shapeType g (Inl b t1) (THInl h) = shapeType g t1 h
216230
shapeType g (Inr a t1) (THInr h) = shapeType g t1 h
217-
shapeType g (Case t tL tR) (THCase d1 d2 h hL hR prf) = trans (uaddLen d1 d2 _ prf) (shapeType g t h)
218-
shapeType g (Let q t1 t2) (THLet d1 d2 _ h1 h2 prf) =
231+
shapeType g (Case t tL tR) (THCase d1 d2 _ _ h hL hR prf) = trans (uaddLen d1 d2 _ prf) (shapeType g t h)
232+
shapeType g (Let q t1 t2) (THLet d1 d2 _ _ h1 h2 prf) =
219233
trans (uaddLen (uscale q d1) d2 _ prf) (trans (uscaleLen q d1) (shapeType g t1 h1))
220234
shapeType g (MkEcho m a b t1) (THEcho h) = shapeType g t1 h
221235
shapeType g (Weaken t1) (THWeaken h) = shapeType g t1 h
@@ -309,3 +323,167 @@ hvShift (TSnoc i' a') g c dg (USnoc di' qd) (S n0) hlen hv =
309323
hv'' = rewrite hdgdi in rewrite predEq' hn in hv'
310324
ih = hvShift i' g c dg di' n0 (predEq' hlen) hv''
311325
in rewrite hqd in HVThere ih
326+
327+
------------------------------------------------------------
328+
-- 4b (cont). Term weakening: htShift
329+
------------------------------------------------------------
330+
331+
trueNotFalse : True = False -> Void
332+
trueNotFalse Refl impossible
333+
334+
||| `shiftIdx` vs the kernel `<`, split by the boolean (avoids the surface-form
335+
||| mismatch `n < c` vs `compareNat n c == LT` that breaks `rewrite`).
336+
shiftIdxTrue : (c, n : Nat) -> (n < c) = True -> shiftIdx c n = n
337+
shiftIdxTrue Z n prf = void (trueNotFalse (trans (sym prf) (nLtZero n)))
338+
shiftIdxTrue (S c) Z prf = Refl
339+
shiftIdxTrue (S c) (S n) prf = cong S (shiftIdxTrue c n prf)
340+
341+
shiftIdxFalse : (c, n : Nat) -> (n < c) = False -> shiftIdx c n = S n
342+
shiftIdxFalse Z n prf = Refl
343+
shiftIdxFalse (S c) Z prf = void (trueNotFalse prf)
344+
shiftIdxFalse (S c) (S n) prf = cong S (shiftIdxFalse c n prf)
345+
346+
||| The kernel `shift` on a variable equals `Var` of the structural `shiftIdx`.
347+
public export
348+
shiftVarLemma : (c, n : Nat) -> shift c (Var n) = Var (shiftIdx c n)
349+
shiftVarLemma c n with (n < c) proof prf
350+
shiftVarLemma c n | True = rewrite shiftIdxTrue c n prf in Refl
351+
shiftVarLemma c n | False = rewrite shiftIdxFalse c n prf in Refl
352+
353+
||| UnitT inversion (its usage `uzero g` is not a free field; direct `case`
354+
||| would be stuck, same as `HVHere`). Also yields the type equation, since
355+
||| the caller's result type is not yet refined to `TUnit`.
356+
public export
357+
unitInv : Has g dd UnitT aa -> (dd = uzero g, aa = TUnit)
358+
unitInv THUnit = (Refl, Refl)
359+
360+
||| Split a derivation's usage along the `G`/`I` append boundary via the shape
361+
||| invariant. `g`,`i`,`t` relevant (shapeType needs the context + term).
362+
public export
363+
usplitLemma : (g, i : Tctx) -> (dd : Uvec) -> (t : Tm) -> Has (tappend g i) dd t a
364+
-> (dg : Uvec ** di : Uvec ** (dd = uappend dg di, ulen di = tlen i))
365+
usplitLemma g i dd t h =
366+
uappendSplit (tlen i) dd
367+
(rewrite trans (shapeType (tappend g i) t h) (tappendLen g i) in
368+
rewrite plusCommutative (tlen g) (tlen i) in lteAddRight (tlen i))
369+
370+
||| Boundary-insert: add two `(USnoc _ Zero)`-headed appends.
371+
public export
372+
uaddUshift : (ag, ai, bg, bi, cg, ci : Uvec)
373+
-> uadd ag bg = Just cg -> uadd ai bi = Just ci
374+
-> uadd (uappend (USnoc ag Zero) ai) (uappend (USnoc bg Zero) bi)
375+
= Just (uappend (USnoc cg Zero) ci)
376+
uaddUshift ag ai bg bi cg ci hg hi =
377+
uaddUappend ai bi ci (USnoc ag Zero) (USnoc bg Zero) (USnoc cg Zero)
378+
hi (rewrite hg in Refl)
379+
380+
||| Scaling commutes with the boundary-insert.
381+
public export
382+
ushiftUscale : (q : Q) -> (dg, di : Uvec)
383+
-> uscale q (uappend (USnoc dg Zero) di)
384+
= uappend (USnoc (uscale q dg) Zero) (uscale q di)
385+
ushiftUscale q dg di =
386+
rewrite uscaleUappend q (USnoc dg Zero) di in rewrite qMulZeroR q in Refl
387+
388+
------------------------------------------------------------
389+
-- 4b (cont). Term weakening: htShift (open-context shift)
390+
------------------------------------------------------------
391+
392+
||| Insert a fresh `Zero`-usage binder `c` at cutoff `tlen i` into a typing
393+
||| derivation, shifting the term by `shift (tlen i)`. The general (prefix `i`)
394+
||| form is what the under-binder substitution induction needs.
395+
|||
396+
||| The binder cases recurse on the body in a context extended by the bound
397+
||| type. `Lam` reads its type from the term; `Let`/`Case`/`LetPair` read theirs
398+
||| from the explicit type fields added to `THLet`/`THCase`/`THLetPair` (Option
399+
||| A — without them the extended context `TSnoc i <bound>` is unconstructible
400+
||| under Idris erasure). The usage-splitting rules decompose each premise's
401+
||| usage along the `G`/`I` boundary (`usplitLemma`/`uappendSplit` + the
402+
||| `(Refl, _)` let-pattern performs the substitution), split the `uadd` premise
403+
||| with `uaddSplitBoundary`, and reassemble with the boundary insert
404+
||| `uaddUshift` (composing `ushiftUscale` for the `q`-scaled legs of App/Let).
405+
public export
406+
htShift : (i, g : Tctx) -> (c : Ty) -> (dg, di : Uvec) -> (t : Tm)
407+
-> ulen di = tlen i
408+
-> Has (tappend g i) (uappend dg di) t a
409+
-> Has (tappend (TSnoc g c) i) (uappend (USnoc dg Zero) di)
410+
(shift (tlen i) t) a
411+
htShift i g c dg di (Var n) hlen (THVar hv) =
412+
rewrite shiftVarLemma (tlen i) n in
413+
THVar (hvShift i g c dg di n hlen hv)
414+
htShift i g c dg di UnitT hlen h =
415+
let (hd, ha) = unitInv h
416+
(hdg, hdi) = uappendInj di (uzero i) dg (uzero g)
417+
(trans hlen (sym (uzeroLen i)))
418+
(trans hd (uzeroTappend g i))
419+
in rewrite ha in rewrite hdg in rewrite hdi in
420+
rewrite sym (uzeroTappend (TSnoc g c) i) in THUnit
421+
htShift i g c dg di (Lam q a' body) hlen (THLam bodyD) =
422+
THLam (htShift (TSnoc i a') g c dg (USnoc di q) body (cong S hlen) bodyD)
423+
htShift i g c dg di (App t1 t2) hlen (THApp d1 d2 q h1 h2 prf) =
424+
let (d1g ** d1i ** (Refl, l1)) = usplitLemma g i d1 t1 h1
425+
(d2g ** d2i ** (Refl, l2)) = usplitLemma g i d2 t2 h2
426+
(hg, hi) = uaddSplitBoundary d1g d1i (uscale q d2g) (uscale q d2i) dg di
427+
(trans l1 (sym (trans (uscaleLen q d2i) l2)))
428+
(trans hlen (sym l1))
429+
(rewrite sym (uscaleUappend q d2g d2i) in prf)
430+
in THApp (uappend (USnoc d1g Zero) d1i) (uappend (USnoc d2g Zero) d2i) q
431+
(htShift i g c d1g d1i t1 l1 h1)
432+
(htShift i g c d2g d2i t2 l2 h2)
433+
(rewrite ushiftUscale q d2g d2i in
434+
uaddUshift d1g d1i (uscale q d2g) (uscale q d2i) dg di hg hi)
435+
htShift i g c dg di (With t1 t2) hlen (THWith h1 h2) =
436+
THWith (htShift i g c dg di t1 hlen h1) (htShift i g c dg di t2 hlen h2)
437+
htShift i g c dg di (Fst t) hlen (THFst h) = THFst (htShift i g c dg di t hlen h)
438+
htShift i g c dg di (Snd t) hlen (THSnd h) = THSnd (htShift i g c dg di t hlen h)
439+
htShift i g c dg di (Tensor t1 t2) hlen (THTensor d1 d2 h1 h2 prf) =
440+
let (d1g ** d1i ** (Refl, l1)) = usplitLemma g i d1 t1 h1
441+
(d2g ** d2i ** (Refl, l2)) = usplitLemma g i d2 t2 h2
442+
(hg, hi) = uaddSplitBoundary d1g d1i d2g d2i dg di
443+
(trans l1 (sym l2)) (trans hlen (sym l1)) prf
444+
in THTensor (uappend (USnoc d1g Zero) d1i) (uappend (USnoc d2g Zero) d2i)
445+
(htShift i g c d1g d1i t1 l1 h1)
446+
(htShift i g c d2g d2i t2 l2 h2)
447+
(uaddUshift d1g d1i d2g d2i dg di hg hi)
448+
htShift i g c dg di (LetPair t1 t2) hlen (THLetPair d1 d2 a' b' h1 hb prf) =
449+
let (d1g ** d1i ** (Refl, l1)) = usplitLemma g i d1 t1 h1
450+
(d2g ** d2i ** (Refl, l2)) = uappendSplit (tlen i) d2
451+
(rewrite trans (predEq' (predEq' (shapeType (TSnoc (TSnoc (tappend g i) a') b') t2 hb))) (tappendLen g i) in
452+
rewrite plusCommutative (tlen g) (tlen i) in lteAddRight (tlen i))
453+
(hg, hi) = uaddSplitBoundary d1g d1i d2g d2i dg di
454+
(trans l1 (sym l2)) (trans hlen (sym l1)) prf
455+
in THLetPair (uappend (USnoc d1g Zero) d1i) (uappend (USnoc d2g Zero) d2i) a' b'
456+
(htShift i g c d1g d1i t1 l1 h1)
457+
(htShift (TSnoc (TSnoc i a') b') g c d2g (USnoc (USnoc d2i One) One) t2
458+
(cong S (cong S l2)) hb)
459+
(uaddUshift d1g d1i d2g d2i dg di hg hi)
460+
htShift i g c dg di (Inl b0 t) hlen (THInl h) = THInl (htShift i g c dg di t hlen h)
461+
htShift i g c dg di (Inr a0 t) hlen (THInr h) = THInr (htShift i g c dg di t hlen h)
462+
htShift i g c dg di (Case t tL tR) hlen (THCase d1 d2 a' b' h hL hR prf) =
463+
let (d1g ** d1i ** (Refl, l1)) = usplitLemma g i d1 t h
464+
(d2g ** d2i ** (Refl, l2)) = uappendSplit (tlen i) d2
465+
(rewrite trans (predEq' (shapeType (TSnoc (tappend g i) a') tL hL)) (tappendLen g i) in
466+
rewrite plusCommutative (tlen g) (tlen i) in lteAddRight (tlen i))
467+
(hg, hi) = uaddSplitBoundary d1g d1i d2g d2i dg di
468+
(trans l1 (sym l2)) (trans hlen (sym l1)) prf
469+
in THCase (uappend (USnoc d1g Zero) d1i) (uappend (USnoc d2g Zero) d2i) a' b'
470+
(htShift i g c d1g d1i t l1 h)
471+
(htShift (TSnoc i a') g c d2g (USnoc d2i One) tL (cong S l2) hL)
472+
(htShift (TSnoc i b') g c d2g (USnoc d2i One) tR (cong S l2) hR)
473+
(uaddUshift d1g d1i d2g d2i dg di hg hi)
474+
htShift i g c dg di (Let q t1 t2) hlen (THLet d1 d2 _ a' h1 h2 prf) =
475+
let (d1g ** d1i ** (Refl, l1)) = usplitLemma g i d1 t1 h1
476+
(d2g ** d2i ** (Refl, l2)) = uappendSplit (tlen i) d2
477+
(rewrite trans (predEq' (shapeType (TSnoc (tappend g i) a') t2 h2)) (tappendLen g i) in
478+
rewrite plusCommutative (tlen g) (tlen i) in lteAddRight (tlen i))
479+
(hg, hi) = uaddSplitBoundary (uscale q d1g) (uscale q d1i) d2g d2i dg di
480+
(trans (uscaleLen q d1i) (trans l1 (sym l2)))
481+
(trans hlen (sym (trans (uscaleLen q d1i) l1)))
482+
(rewrite sym (uscaleUappend q d1g d1i) in prf)
483+
in THLet (uappend (USnoc d1g Zero) d1i) (uappend (USnoc d2g Zero) d2i) q a'
484+
(htShift i g c d1g d1i t1 l1 h1)
485+
(htShift (TSnoc i a') g c d2g (USnoc d2i q) t2 (cong S l2) h2)
486+
(rewrite ushiftUscale q d1g d1i in
487+
uaddUshift (uscale q d1g) (uscale q d1i) d2g d2i dg di hg hi)
488+
htShift i g c dg di (MkEcho m aE bE t) hlen (THEcho h) = THEcho (htShift i g c dg di t hlen h)
489+
htShift i g c dg di (Weaken t) hlen (THWeaken h) = THWeaken (htShift i g c dg di t hlen h)

proofs/verification/idris/solo-core/Typing.idr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ data Has : Tctx -> Uvec -> Tm -> Ty -> Type where
106106

107107
||| T-LetPair: eliminate a tensor. The body `t2` binds two variables —
108108
||| x:a at de Bruijn index 1, y:b at index 0 — each used linearly (One).
109-
THLetPair : (d1, d2 : Uvec)
109+
THLetPair : (d1, d2 : Uvec) -> (a, b : Ty)
110110
-> Has g d1 t1 (TTensor a b)
111111
-> Has (TSnoc (TSnoc g a) b) (USnoc (USnoc d2 One) One) t2 c
112112
-> uadd d1 d2 = Just d
@@ -123,7 +123,7 @@ data Has : Tctx -> Uvec -> Tm -> Ty -> Type where
123123
||| value with quantity One and is typed under `d2` extended with that
124124
||| binder. Branches agree on `d2` and on the result type. The whole
125125
||| `case` is typed under `d1 + d2`.
126-
THCase : (d1, d2 : Uvec)
126+
THCase : (d1, d2 : Uvec) -> (a, b : Ty)
127127
-> Has g d1 t (TSum a b)
128128
-> Has (TSnoc g a) (USnoc d2 One) tL c
129129
-> Has (TSnoc g b) (USnoc d2 One) tR c
@@ -132,7 +132,7 @@ data Has : Tctx -> Uvec -> Tm -> Ty -> Type where
132132

133133
||| T-Let: bind `t1` with declared quantity `q`. The RHS is typed
134134
||| under `d1`, scaled by `q`, then added to `d2` (the body's usage).
135-
THLet : (d1, d2 : Uvec) -> (q : Q)
135+
THLet : (d1, d2 : Uvec) -> (q : Q) -> (a : Ty)
136136
-> Has g d1 t1 a
137137
-> Has (TSnoc g a) (USnoc d2 q) t2 b
138138
-> uadd (uscale q d1) d2 = Just d

0 commit comments

Comments
 (0)