Skip to content

Commit 887bc05

Browse files
proof(Semantics_L1): restore region_shrink_preserves_typing_l1_gen bullet structure via m-polymorphic helper (L2-β #3) (#189)
## Summary Restores the pre-L2 proof body of `region_shrink_preserves_typing_l1_gen` as a modality-polymorphic helper `region_shrink_preserves_typing_l1_gen_m`, mirroring the pattern established by #187/#188 (`shift_typing_gen_l1_m`, `subst_typing_gen_l1_m`). The Linear wrapper `_gen` becomes a one-line derivation. Net effect: the outer `Admitted` on `_gen` is gone (now Qed via wrapper); one structural residual remains as an internal `admit` inside `_gen_m` — the genuine T_Region_Active_L1 [rr = r] shadowed sub-case (list-vs-multiset bridge), unchanged in substance from the pre-L2 baseline. ## What changed - New `count_occ_le_l1_m` (m-polymorphic count monotonicity); the existing Linear `count_occ_le_l1` becomes a 2-line wrapper. - New `region_shrink_preserves_typing_l1_gen_m` with the 29-bullet proof body adapted from commit 56f592f. Three new Affine-only constructors (T_Lam_L1_Affine, T_Case_L1_Affine, T_If_L1_Affine) close identically to their Linear siblings since region operations are mode-blind. - `region_shrink_preserves_typing_l1_gen` now derives from `_gen_m` by `apply (… Linear); auto.` — Qed. - Design-note comment above the lemma corrected: the earlier suggestion that the value-restricted wrapper could bypass `_gen_m` by inducting on `is_value` fails on the VLam case (lambda body's internal `ERegion r' e'` shadowing); wrapper still goes through `_gen_m`. ## Net debt `formal/Semantics_L1.v`: 21 Qed + 3 Admitted → **23 Qed + 3 Admitted**. The 3 Admitted: 1. `region_shrink_preserves_typing_l1_gen_m` — NEW location of the structural residual; internal `admit` in the T_Region_Active_L1 shadowed sub-case 2. `region_liveness_at_split_l1_gen` — owner-gated per CLAUDE.md doctrine; closes at L2 dispatch 3. `preservation_l1` — capstone ## Out of scope The structural residual itself — closure options listed in PROOF-NEEDS.md §2 (L2-β follow-up #4). ## Test plan - [x] `coqc` accepts the file (full Coq tree builds with no errors) - [x] `Counterexample.v` re-builds cleanly (no regression of the 5 pinned Qed lemmas) - [x] No new `Axiom` declarations - [x] No legacy `Semantics.v` / `Typing.v` edits - [x] No closure attempts on `region_liveness_at_split_l1_gen` (owner-gated) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c861efb commit 887bc05

2 files changed

Lines changed: 202 additions & 27 deletions

File tree

PROOF-NEEDS.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ For the architectural background see
5656
| L1 judgment indexed by modality `m : Modality` | `formal/TypingL1.v` | landed via PRs #176 + #177 |
5757
| L2 modality core (`Modality.v`, `linear_to_affine`) | `formal/Modality.v` | 1 Qed, zero axioms |
5858
| L3 calculus (echo / residue fiber + degrade + no-section proof) | `formal/Echo.v` | 12 Qed, 0 admits |
59-
| Linear-mode forward progress lemmas | `formal/Semantics_L1.v` | 25 Qed; 3 residual admits (L2-β follow-up) |
59+
| Linear-mode forward progress lemmas | `formal/Semantics_L1.v` | 23 Qed; 3 residual admits (L2-β follow-up) |
6060
| Counterexample regression witness | `formal/Counterexample.v` | 5 Qed (`bad_input_untypable_l1` proved under both modes) |
6161
| Operational checker (Rust, ephapax-linear sublanguage) | `ephapax-linear/src/linear.rs` | working — discharges resource-exact obligation |
6262

@@ -68,7 +68,7 @@ For the architectural background see
6868
| Linear ⇒ Affine weakening | `formal/TypingL1.v` `linear_to_affine` | Qed, zero axioms |
6969
| Operational checker (Rust, ephapax-affine sublanguage) | `ephapax-linear/src/affine.rs` | working — permits weakening / graceful abandonment |
7070
| Affine-mode echo discipline (LEcho Affine = lowered triple) | `formal/Echo.v` (calculus) | calculus done; rule wiring pending |
71-
| Affine forward progress lemmas | `formal/Semantics_L1.v` | bullet-structure rewrites + subst_typing_gen_l1_m m-polymorphic generalisation landed 2026-05-27; remaining 3 admits are L2-β deeper-than-bullet debt (see §2) |
71+
| Affine forward progress lemmas | `formal/Semantics_L1.v` | bullet-structure rewrites + subst_typing_gen_l1_m + region_shrink_preserves_typing_l1_gen_m m-polymorphic generalisations landed 2026-05-27; remaining 3 admits are L2-β deeper-than-bullet debt (see §2) |
7272

7373
### Counterexample regression
7474

@@ -90,7 +90,8 @@ For the architectural background see
9090
| ✅ Close 3 pure bullet-structure regressions (typing_preserves_bindings_l1, unrestricted_flag_unchanged_l1, shift_typing_gen_l1) | `formal/Semantics_L1.v` | done 2026-05-27 |
9191
| ✅ Generalise typing_preserves_length_l1 to modality-polymorphic | `formal/Semantics_L1.v` | done 2026-05-27 |
9292
| ✅ Generalise subst_typing_gen_l1 to modality-polymorphic + Linear wrapper (also generalised typing_preserves_bindings_l1, output_shape_at_l1, loc_retype_at_R_l1) | `formal/Semantics_L1.v` | done 2026-05-27 (L2-β follow-up #2) |
93-
| Close region_shrink_preserves_typing_l1_gen T_Region_Active_L1 case (list-vs-multiset bridge) | `formal/Semantics_L1.v` | structural; deeper than bullet-restoration |
93+
| ✅ Restore region_shrink_preserves_typing_l1_gen bullet structure via m-polymorphic helper (residual list-vs-multiset structural admit isolated to T_Region_Active_L1 shadowed case inside _gen_m) | `formal/Semantics_L1.v` | done 2026-05-27 (L2-β follow-up #3) |
94+
| Close T_Region_Active_L1 [rr = r] shadowed sub-case of region_shrink_preserves_typing_l1_gen_m (list-vs-multiset bridge — option (a) L1 perm lemma, (b) multiset reformulation, or (c) T_Region_*_L1 redesign per the case's own note) | `formal/Semantics_L1.v` | structural; non-trivial — investigation owed; lambda-body's shadowing internal ERegion is the obstacle |
9495
| State and prove `preservation_l1` for both modes | `formal/Semantics_L1.v` | depends on region_shrink + region_liveness narrow admit |
9596

9697
### Near-term (L3 wiring — design + mechanisation)
@@ -222,7 +223,7 @@ to the owner**:
222223
| `formal/Typing.v` (legacy) | n/a | 0 | 🛑 archaeology — `Counterexample.v` depends on falsity |
223224
| `formal/Counterexample.v` | **5** | 0 | ✅ pinned regression witness |
224225
| `formal/TypingL1.v` | **2** | 0 | ✅ active — L1 judgment, modality-indexed |
225-
| `formal/Semantics_L1.v` | **25** | **3** | ✅ active — bullet-structure regressions + subst_typing_gen_l1_m closed 2026-05-27; 3 residual admits are deeper L2-β debt (region_shrink T_Region_Active_L1 list-vs-multiset case, region_liveness_at_split narrow admit per ERegion counterexample, preservation_l1 cap) |
226+
| `formal/Semantics_L1.v` | **23** | **3** | ✅ active — bullet-structure regressions + subst_typing_gen_l1_m + region_shrink_preserves_typing_l1_gen_m closed 2026-05-27; 3 residual admits are deeper L2-β debt (region_shrink T_Region_Active_L1 list-vs-multiset case now isolated in _gen_m internal admit, region_liveness_at_split narrow admit per ERegion counterexample, preservation_l1 cap) |
226227
| `formal/Modality.v` | **1** | 0 | ✅ active — L2 core, zero axioms |
227228
| `formal/Echo.v` | **12** | 0 | ✅ active — L3 calculus, not yet wired into L1 |
228229
| `formal/TypingL2.v` | (wrapper) | (wrapper) | ✅ thin re-indexing through `TypingL1.has_type_l1` |

formal/Semantics_L1.v

Lines changed: 197 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@
1414
*** - unrestricted_flag_unchanged_l1 ***
1515
*** - shift_typing_gen_l1 (via shift_typing_gen_l1_m + wrapper) ***
1616
*** - subst_typing_gen_l1 (via subst_typing_gen_l1_m + wrapper) ***
17+
*** - count_occ_le_l1 (via count_occ_le_l1_m + wrapper) ***
18+
*** - region_shrink_preserves_typing_l1_gen (via ***
19+
*** region_shrink_preserves_typing_l1_gen_m + wrapper) ***
1720
*** - typing_preserves_length_l1, output_shape_at_l1, ***
1821
*** loc_retype_at_R_l1 all generalised to m-polymorphic ***
1922
*** ***
2023
*** Residual admits (L2-β follow-up): ***
21-
*** 1. region_shrink_preserves_typing_l1_gen — list-vs-multiset ***
22-
*** structural mismatch in T_Region_Active_L1 shadowed case ***
24+
*** 1. region_shrink_preserves_typing_l1_gen_m — list-vs- ***
25+
*** multiset structural mismatch in T_Region_Active_L1 ***
26+
*** shadowed case (1 internal admit). Bullet structure ***
27+
*** restored 2026-05-27; only the structural sub-case ***
28+
*** remains. ***
2329
*** 2. region_liveness_at_split_l1_gen — 1 narrow admit in the ***
2430
*** T_Region_Active_L1 [r = rv] sub-case (genuinely false ***
2531
*** per documented counterexample ERegion rv (EI32 5)) ***
@@ -266,12 +272,12 @@ Qed.
266272

267273
(** Count monotonicity: every L1 typing rule has [cnt r R'
268274
<= cnt r R] for every region [r]. *)
269-
Lemma count_occ_le_l1 :
270-
forall R G e T R' G',
271-
R; G |=L1 e : T -| R'; G' ->
275+
Lemma count_occ_le_l1_m :
276+
forall m R G e T R' G',
277+
R ; G |=L1[m] e : T -| R' ; G' ->
272278
forall r, cnt r R' <= cnt r R.
273279
Proof.
274-
intros R G e T R' G' Ht.
280+
intros m R G e T R' G' Ht.
275281
induction Ht; intros r0; unfold cnt in *; simpl in *;
276282
try lia;
277283
try (specialize (IHHt r0); lia);
@@ -304,6 +310,16 @@ Proof.
304310
lia.
305311
Qed.
306312

313+
(** Linear specialisation — preserves the legacy call sites. *)
314+
Lemma count_occ_le_l1 :
315+
forall R G e T R' G',
316+
R; G |=L1 e : T -| R'; G' ->
317+
forall r, cnt r R' <= cnt r R.
318+
Proof.
319+
intros R G e T R' G' Ht r.
320+
exact (count_occ_le_l1_m Linear R G e T R' G' Ht r).
321+
Qed.
322+
307323
(** Corollary: if [r] appears in the output, it appeared at least
308324
that many times in the input. *)
309325
Lemma count_occ_in_input_l1 :
@@ -344,11 +360,17 @@ Qed.
344360
list-structure agreement*, which set-equivalence (and even
345361
multiset-equivalence in some sub-sub-cases) does not provide.
346362
347-
Resolution: this sub-case remains [admit] in [_gen]; the
348-
wrapper [region_shrink_preserves_typing_l1] is proved directly
349-
by induction on [is_value] (values have no [T_Region_*_L1]
350-
inversion), bypassing [_gen] for actual usage in
351-
[preservation_l1]. *)
363+
Resolution: this sub-case remains an internal [admit] in
364+
[region_shrink_preserves_typing_l1_gen_m] (the m-polymorphic
365+
helper). The Linear wrapper [_gen] and the value-restricted
366+
wrapper [region_shrink_preserves_typing_l1] (used by
367+
[preservation_l1]'s S_Region_Exit case) both still depend on
368+
it: an earlier note here suggested the value-wrapper could
369+
bypass [_gen_m] by induction on [is_value], but that fails on
370+
[VLam] — the lambda body is a non-value whose internal
371+
[ERegion r' e'] subterm may shadow the outer [r], hitting the
372+
same structural residual. Closure options are listed in the
373+
case's own comment within [_gen_m]. *)
352374

353375
(** Auxiliary general L1 region-shrinkage lemma — no [is_value] or
354376
[~ In r (free_regions T)] premises, mirroring the legacy
@@ -362,25 +384,177 @@ Qed.
362384
Once the L1 permutation infrastructure lands, this helper closes to
363385
Qed and the targeted lemma follows in one line. *)
364386

387+
(** ** Modality-polymorphic generalisation.
388+
389+
Mirrors the L2-β pattern used by [subst_typing_gen_l1_m] and
390+
[shift_typing_gen_l1_m]: the structural shrinkage lemma is
391+
mode-blind (region operations don't depend on the modality),
392+
so we prove it once at arbitrary [m] and derive the Linear
393+
specialisation as a wrapper.
394+
395+
One genuine residual [admit] remains — the [T_Region_Active_L1]
396+
[rr = r] shadowed sub-case, documented as the list-vs-multiset
397+
bridge in PROOF-NEEDS.md §2 and in the design-note comment
398+
above this lemma block. The L1 region-permutation infrastructure
399+
(analog of [region_env_perm_typing] and [region_add_typing])
400+
is needed to close it; bridging options are listed in the
401+
case's own comment. *)
402+
Lemma region_shrink_preserves_typing_l1_gen_m :
403+
forall m R G e T R' G',
404+
R ; G |=L1[m] e : T -| R' ; G' ->
405+
forall r,
406+
expr_free_of_region r e ->
407+
remove_first r R ; G |=L1[m] e : T -| remove_first r R' ; G'.
408+
Proof.
409+
intros m R G e T R' G' Ht.
410+
induction Ht; intros rr Hfree; simpl in Hfree.
411+
- (* T_Unit_L1 *) apply T_Unit_L1.
412+
- (* T_Bool_L1 *) apply T_Bool_L1.
413+
- (* T_I32_L1 *) apply T_I32_L1.
414+
- (* T_Var_Lin_L1 *) eapply T_Var_Lin_L1; eauto.
415+
- (* T_Var_Unr_L1 *) eapply T_Var_Unr_L1; eauto.
416+
- (* T_Loc_L1 *)
417+
apply T_Loc_L1.
418+
apply remove_first_preserves_other; [exact Hfree | exact H].
419+
- (* T_StringNew_L1 *)
420+
apply T_StringNew_L1.
421+
apply remove_first_preserves_other; [exact Hfree | exact H].
422+
- (* T_StringConcat_L1 *)
423+
destruct Hfree as [Hf1 Hf2].
424+
eapply T_StringConcat_L1; [eapply IHHt1; auto | eapply IHHt2; auto].
425+
- (* T_StringLen_L1 *)
426+
eapply T_StringLen_L1. eapply IHHt; auto.
427+
- (* T_Let_L1 *)
428+
destruct Hfree as [Hf1 Hf2].
429+
eapply T_Let_L1; [eapply IHHt1; auto | eapply IHHt2; auto].
430+
- (* T_LetLin_L1 *)
431+
destruct Hfree as [Hf1 Hf2].
432+
eapply T_LetLin_L1; [exact H | eapply IHHt1; auto | eapply IHHt2; auto].
433+
- (* T_Lam_L1_Linear *)
434+
apply T_Lam_L1_Linear. eapply IHHt; auto.
435+
- (* T_Lam_L1_Affine *)
436+
eapply T_Lam_L1_Affine. eapply IHHt; auto.
437+
- (* T_App_L1 *)
438+
destruct Hfree as [Hf1 Hf2].
439+
eapply T_App_L1; [eapply IHHt1; auto | eapply IHHt2; auto].
440+
- (* T_Pair_L1 *)
441+
destruct Hfree as [Hf1 Hf2].
442+
eapply T_Pair_L1; [eapply IHHt1; auto | eapply IHHt2; auto].
443+
- (* T_Fst_L1 *)
444+
eapply T_Fst_L1; [eapply IHHt; auto | exact H].
445+
- (* T_Snd_L1 *)
446+
eapply T_Snd_L1; [eapply IHHt; auto | exact H].
447+
- (* T_Inl_L1 *)
448+
eapply T_Inl_L1. eapply IHHt; auto.
449+
- (* T_Inr_L1 *)
450+
eapply T_Inr_L1. eapply IHHt; auto.
451+
- (* T_Case_L1_Linear *)
452+
destruct Hfree as [Hf1 [Hf2 Hf3]].
453+
eapply T_Case_L1_Linear;
454+
[eapply IHHt1; auto | eapply IHHt2; auto | eapply IHHt3; auto].
455+
- (* T_Case_L1_Affine *)
456+
destruct Hfree as [Hf1 [Hf2 Hf3]].
457+
eapply T_Case_L1_Affine;
458+
[eapply IHHt1; auto | eapply IHHt2; auto | eapply IHHt3; auto].
459+
- (* T_If_L1_Linear *)
460+
destruct Hfree as [Hf1 [Hf2 Hf3]].
461+
eapply T_If_L1_Linear;
462+
[eapply IHHt1; auto | eapply IHHt2; auto | eapply IHHt3; auto].
463+
- (* T_If_L1_Affine *)
464+
destruct Hfree as [Hf1 [Hf2 Hf3]].
465+
eapply T_If_L1_Affine;
466+
[eapply IHHt1; auto | eapply IHHt2; auto | eapply IHHt3; auto].
467+
- (* T_Region_L1: ERegion r e (fresh r).
468+
Shadowed sub-case (rr = r) closes via count-monotonicity
469+
vacuity; descend sub-case (rr <> r) via the
470+
remove_first/remove_first_L1 commutation rewrite. *)
471+
destruct (String.eqb rr r) eqn:Heq.
472+
+ (* rr = r: shadowed. Hfree is True (irrelevant). *)
473+
apply String.eqb_eq in Heq. subst r.
474+
rewrite (remove_first_not_in_id _ _ H).
475+
destruct (in_dec string_dec rr (remove_first_L1 rr R_body)) as [Hin | Hnotin].
476+
* (* Multiple rr's in R_body — VACUOUS by count monotonicity.
477+
Body is typed at (rr :: R) with ~In rr R, so the body input
478+
has count_occ rr = 1. By count_occ_le_l1_m, count_occ rr R_body
479+
<= 1. But Hin says count_occ rr R_body >= 2. Contradiction. *)
480+
exfalso.
481+
pose proof (count_occ_le_l1_m _ _ _ _ _ _ _ Ht rr) as Hle.
482+
unfold cnt in Hle. simpl in Hle.
483+
destruct (string_dec rr rr) as [_|Hbad]; [|apply Hbad; reflexivity].
484+
apply (count_occ_In string_dec) in Hin.
485+
pose proof (remove_first_L1_count_eq_self rr R_body) as Hself.
486+
unfold cnt in Hself. rewrite Hself in Hin.
487+
apply (count_occ_not_In string_dec) in H.
488+
lia.
489+
* (* At most one rr in R_body. Then
490+
remove_first rr (remove_first_L1 rr R_body) =
491+
remove_first_L1 rr R_body. *)
492+
rewrite (remove_first_not_in_id _ _ Hnotin).
493+
eapply T_Region_L1; eauto.
494+
+ (* rr <> r: descend. *)
495+
apply String.eqb_neq in Heq.
496+
assert (Hgoal_eq : remove_first rr (remove_first_L1 r R_body) =
497+
remove_first_L1 r (remove_first rr R_body)).
498+
{ rewrite (remove_first_eq_l1 r R_body).
499+
rewrite (remove_first_eq_l1 r (remove_first rr R_body)).
500+
apply remove_first_comm. }
501+
rewrite Hgoal_eq.
502+
eapply T_Region_L1.
503+
* intro Hin. apply H. eapply remove_first_subset; exact Hin.
504+
* exact H0.
505+
* apply remove_first_preserves_other; [intro Hbad; apply Heq; exact Hbad | exact H1].
506+
* specialize (IHHt rr Hfree).
507+
simpl in IHHt.
508+
destruct (String.eqb rr r) eqn:Heq2.
509+
-- exfalso. apply String.eqb_eq in Heq2. apply Heq. exact Heq2.
510+
-- exact IHHt.
511+
- (* T_Region_Active_L1: ERegion r e (r currently active). *)
512+
destruct (String.eqb rr r) eqn:Heq.
513+
+ (* rr = r: shadowed; Hfree True. RESIDUAL — structural
514+
list-vs-multiset mismatch documented in PROOF-NEEDS.md §2
515+
and in the design-note comment above this lemma block.
516+
Bridging requires either:
517+
(a) a stronger L1 perm lemma that produces specifically-shaped
518+
outputs (requires reformulating output as a function of
519+
input + derivation shape), or
520+
(b) a reformulation of [remove_first_L1] to be multiset-based
521+
(lose ordering), or
522+
(c) a redesign of the L1 [T_Region_*_L1] rules to not depend on
523+
first-occurrence semantics. *)
524+
admit.
525+
+ (* rr <> r: descend. *)
526+
apply String.eqb_neq in Heq.
527+
assert (Hgoal_eq : remove_first rr (remove_first_L1 r R_body) =
528+
remove_first_L1 r (remove_first rr R_body)).
529+
{ rewrite (remove_first_eq_l1 r R_body).
530+
rewrite (remove_first_eq_l1 r (remove_first rr R_body)).
531+
apply remove_first_comm. }
532+
rewrite Hgoal_eq.
533+
eapply T_Region_Active_L1.
534+
* apply remove_first_preserves_other; [intro Hbad; apply Heq; exact Hbad | exact H].
535+
* exact H0.
536+
* apply remove_first_preserves_other; [intro Hbad; apply Heq; exact Hbad | exact H1].
537+
* eapply IHHt. exact Hfree.
538+
- (* T_Borrow_L1 *)
539+
eapply T_Borrow_L1. exact H.
540+
- (* T_Borrow_Val_L1 *)
541+
eapply T_Borrow_Val_L1; [exact H | eapply IHHt; auto].
542+
- (* T_Drop_L1 *)
543+
eapply T_Drop_L1; [exact H | eapply IHHt; auto].
544+
- (* T_Copy_L1 *)
545+
eapply T_Copy_L1; [exact H | eapply IHHt; auto].
546+
Admitted.
547+
365548
Lemma region_shrink_preserves_typing_l1_gen :
366549
forall R G e T R' G',
367550
has_type_l1_linear R G e T R' G' ->
368551
forall r,
369552
expr_free_of_region r e ->
370553
has_type_l1_linear (remove_first r R) G e T (remove_first r R') G'.
371554
Proof.
372-
(** L2 REGRESSION: this proof's body was written for the pre-L2
373-
[has_type_l1] (6 args, single T_Lam_L1 / T_Case_L1 / T_If_L1
374-
constructors). The L2 refactor expanded to 27 constructors
375-
(including Affine-only T_Lam_L1_Affine, T_Case_L1_Affine,
376-
T_If_L1_Affine), which shifts the bullet ordering generated
377-
by [induction Ht]. The original case-by-case proof body is
378-
preserved in git history at commit 56f592f
379-
([proof/l1-region-threading-design] tip). Restoring it is a
380-
mechanical bullet-structure rewrite plus 3 [discriminate]
381-
dispatches for the Affine-only constructors. Tracked as
382-
L2-β follow-up. *)
383-
Admitted.
555+
intros R G e T R' G' Ht r Hfree.
556+
apply (region_shrink_preserves_typing_l1_gen_m Linear); auto.
557+
Qed.
384558

385559
Lemma region_shrink_preserves_typing_l1 :
386560
forall R G v T R' G' r,

0 commit comments

Comments
 (0)