Skip to content

Commit a63252f

Browse files
Idris2 F1.4: has_var weakening (hvShift) + structural shift index (toward #108) (#114)
## Summary Continues the Idris2 F1.4 substitution-lemma port (#108). Builds on the foundation merged in #113 (append algebra + shape invariant) by landing the **`has_var` weakening lemma `hvShift`** — the lynchpin of the shift layer (4b). Two Idris-specific techniques here unblock the rest of the port (the Coq proof gets both "for free" via `Prop` erasure + `inversion`): 1. **Structural shift index `shiftIdx`** (not `if n < c`): `shiftIdx Z n = S n`, `shiftIdx (S c) Z = Z`, `shiftIdx (S c)(S n) = S (shiftIdx c n)`. Every index step is now **definitional** — no fighting Idris's lazy-`if` / `Ord`-unfolding inside `rewrite` (which was mangling `LT` into metavariables). The bridge to the kernel `shift` (which uses `if`) is `shiftIdxCorrect`, proven **once** (the only place the `if`-reasoning is needed). 2. **`HasVar` inversion lemma `varInv`** → `VarInvR` with **propositional** equalities + **erased** witnesses. Idris can't pattern-match `HVHere` when the usage is a stuck term (`uappend dg di`): its index `USnoc (uzero g) One` won't unify. The lemma returns the constructor disjunction as equations the caller consumes without matching. `hvShift` then cases `n` (Z/S) to recover a relevant index and recurses on `i`, reconstructing the `HVHere` context via `uappendInj`/`uzeroTappend`. ## Verification (Idris2 0.7.0) ``` idris2 --build solo-core.ipkg # 8/8, exit 0 ``` - Only hole remains `?todo_preservation`; no `believe_me`/`assert_total`/`postulate`. ## Remaining (this branch, toward closing #108) `htShift` (15-case term weakening; uses `hvShift` for `Var`, `shiftIdx` under binders) → `htShift0`/`weakeningAppend` → substitution core (`hvSubst`/`htSubst`) → `substLemma0`/`subst2Lemma` → `preservation`. The index friction (the worst part) is now solved by `shiftIdx`; the rest is mechanical-but-long, each inversion at a known term constructor matching directly (the usage is a free field on `THApp`/`THLam`/… so no inversion lemma needed there). ## Test plan - [x] `idris2 --build solo-core.ipkg` exits 0; `hvShift`/`shiftIdxCorrect`/`varInv` verified - [ ] `htShift` and onward - [ ] `?todo_preservation` discharged (closes #108) Refs #108 https://claude.ai/code/session_01BwV2DWsjkBiNP3oscimMLV --- _Generated by [Claude Code](https://claude.ai/code/session_01BwV2DWsjkBiNP3oscimMLV)_ --------- Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent c5e0cbf commit a63252f

1 file changed

Lines changed: 90 additions & 0 deletions

File tree

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,93 @@ shapeType g (Let q t1 t2) (THLet d1 d2 _ h1 h2 prf) =
219219
trans (uaddLen (uscale q d1) d2 _ prf) (trans (uscaleLen q d1) (shapeType g t1 h1))
220220
shapeType g (MkEcho m a b t1) (THEcho h) = shapeType g t1 h
221221
shapeType g (Weaken t1) (THWeaken h) = shapeType g t1 h
222+
223+
||| `n < 0` is `False` (the shift base case needs this; `<` does not reduce
224+
||| on an abstract `n` without casing it).
225+
nLtZero : (n : Nat) -> (n < 0) = False
226+
nLtZero Z = Refl
227+
nLtZero (S _) = Refl
228+
229+
-- HasVar inversion. Idris cannot pattern-match `HVHere` when the usage is a
230+
-- stuck term (e.g. `uappend dg di`): `HVHere`'s usage index `USnoc (uzero g)
231+
-- One` will not unify with it. The standard fix is an inversion lemma taking
232+
-- the usage as a FLEXIBLE parameter and returning the constructor disjunction
233+
-- with PROPOSITIONAL equalities — which the caller consumes without matching.
234+
||| Inversion result for `HasVar (TSnoc g0 a0) dd nn aa`. Equations are
235+
||| PROPOSITIONAL (not in the indices), so the caller matches `VIH`/`VIT`
236+
||| without unifying its abstract `dd` against a constructor's usage shape;
237+
||| the `HVThere` witnesses `d0`/`n0` are ERASED (the caller recovers a
238+
||| relevant index from its own `nn`-pattern + the equations).
239+
public export
240+
data VarInvR : Tctx -> Ty -> Uvec -> Nat -> Ty -> Type where
241+
VIH : (dd = USnoc (uzero g0) One) -> (nn = Z) -> (aa = a0) -> VarInvR g0 a0 dd nn aa
242+
VIT : {0 d0 : Uvec} -> {0 n0 : Nat}
243+
-> (dd = USnoc d0 Zero) -> (nn = S n0) -> HasVar g0 d0 n0 aa
244+
-> VarInvR g0 a0 dd nn aa
245+
246+
public export
247+
varInv : HasVar (TSnoc g0 a0) dd nn aa -> VarInvR g0 a0 dd nn aa
248+
varInv HVHere = VIH Refl Refl Refl
249+
varInv (HVThere hv) = VIT Refl Refl hv
250+
251+
||| The de Bruijn shift index, defined STRUCTURALLY (not via `if n < c`):
252+
||| inserting a binder at cutoff `c` keeps indices below `c` and bumps the
253+
||| rest. `shiftIdx c n` reduces definitionally on its arguments — so the
254+
||| shift-lemma index arithmetic needs no lazy-`if`/`Ord` rewriting. The
255+
||| bridge to the kernel's `shift` (which uses `if`) is `shiftIdxCorrect`.
256+
public export
257+
shiftIdx : Nat -> Nat -> Nat
258+
shiftIdx Z n = S n
259+
shiftIdx (S c) Z = Z
260+
shiftIdx (S c) (S n) = S (shiftIdx c n)
261+
262+
||| Push `S` through the shift-index conditional (used only in the bridge).
263+
sPushIf : (b : Bool) -> (x, y : Nat) -> S (ifThenElse b x y) = ifThenElse b (S x) (S y)
264+
sPushIf True x y = Refl
265+
sPushIf False x y = Refl
266+
267+
||| `shiftIdx` agrees with the kernel `shift`'s `if n < c then n else S n`.
268+
||| Proven once (the only place the lazy-`if` index reasoning is needed);
269+
||| consumed by `htShift`'s `Var` case to connect `hvShift` to
270+
||| `shift (tlen i) (Var n)`.
271+
public export
272+
shiftIdxCorrect : (c, n : Nat) -> shiftIdx c n = ifThenElse (n < c) n (S n)
273+
shiftIdxCorrect Z n = rewrite nLtZero n in Refl
274+
shiftIdxCorrect (S c) Z = Refl
275+
shiftIdxCorrect (S c) (S n) = rewrite shiftIdxCorrect c n in sPushIf (n < c) n (S n)
276+
277+
||| has_var weakening: insert a fresh Zero-usage binder `c` at cutoff `tlen i`,
278+
||| shifting the looked-up index by `shiftIdx (tlen i)`. `i`,`g`,`c`,`dg`,`di`,
279+
||| `n` are explicit/relevant (the `HVHere` reconstruction needs the contexts;
280+
||| Idris erases the indices). With `shiftIdx`, every index step is definitional.
281+
public export
282+
hvShift : (i, g : Tctx) -> (c : Ty) -> (dg, di : Uvec) -> (n : Nat)
283+
-> ulen di = tlen i
284+
-> HasVar (tappend g i) (uappend dg di) n a
285+
-> HasVar (tappend (TSnoc g c) i) (uappend (USnoc dg Zero) di)
286+
(shiftIdx (tlen i) n) a
287+
hvShift TEmpty g c dg UEmpty n hlen hv = HVThere hv
288+
hvShift TEmpty g c dg (USnoc _ _) n hlen hv = void (sNotZ' hlen)
289+
hvShift (TSnoc i' a') g c dg UEmpty n hlen hv = void (zNotS' hlen)
290+
hvShift (TSnoc i' a') g c dg (USnoc di' qd) Z hlen hv =
291+
-- shiftIdx (S (tlen i')) Z = Z : the head variable stays at 0 (HVHere).
292+
case varInv hv of
293+
VIH heq _ ha =>
294+
let (hdgdi, hqd) = usnocInj heq
295+
(hdg, hdi) = uappendInj di' (uzero i') dg (uzero g)
296+
(trans (predEq' hlen) (sym (uzeroLen i')))
297+
(trans hdgdi (uzeroTappend g i'))
298+
in rewrite hqd in rewrite hdg in rewrite hdi in
299+
rewrite sym (uzeroTappend (TSnoc g c) i') in
300+
rewrite ha in HVHere
301+
VIT _ hn _ => void (zNotS' hn)
302+
hvShift (TSnoc i' a') g c dg (USnoc di' qd) (S n0) hlen hv =
303+
-- shiftIdx (S (tlen i')) (S n0) = S (shiftIdx (tlen i') n0) : recurse (HVThere).
304+
case varInv hv of
305+
VIH _ hn _ => void (sNotZ' hn)
306+
VIT heq hn hv' =>
307+
let (hdgdi, hqd) = usnocInj heq
308+
hv'' : HasVar (tappend g i') (uappend dg di') n0 a
309+
hv'' = rewrite hdgdi in rewrite predEq' hn in hv'
310+
ih = hvShift i' g c dg di' n0 (predEq' hlen) hv''
311+
in rewrite hqd in HVThere ih

0 commit comments

Comments
 (0)