|
| 1 | +(* SPDX-License-Identifier: MPL-2.0 *) |
| 2 | +(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *) |
| 3 | + |
| 4 | +(* |
| 5 | + F5_RenderFaithful.v |
| 6 | + ═══════════════════ |
| 7 | + F-5 (docs/PROOF-NEEDS.adoc): `render_ty` faithfulness / non-collision. |
| 8 | +
|
| 9 | + lib/face.ml's `render_ty` rewrites a small fixed vocabulary of canonical type |
| 10 | + names per face — Unit, Bool, and Option[_] — leaving everything else |
| 11 | + untouched. F-5 asks: is that renaming injective (it never collapses two |
| 12 | + distinct canonical types onto one displayed string), even where face lexemes |
| 13 | + overlap — Js renders Unit as "null" and Option[T] as "T | null", so both |
| 14 | + mention "null"? |
| 15 | +
|
| 16 | + This models the canonical-type vocabulary render_ty branches on (`cty`), the |
| 17 | + displayed-name lexemes (`name` — distinct constructors model distinct on-screen |
| 18 | + tokens), and the rendered form (`rty`), then mirrors render_ty as |
| 19 | + `render : face → cty → rty`. Two fidelity points carried over from the OCaml: |
| 20 | +
|
| 21 | + * Unit/Bool are special-cased only at the WHOLE-type level (`if s = "Unit"`), |
| 22 | + never on a nested occurrence. |
| 23 | + * Option's inner is rendered *canonically* — the OCaml `global_replace` |
| 24 | + captures the canonical substring `\1` — so Js `Option[Option[Int]]` ⇒ |
| 25 | + "Option[Int] | null" (inner Option intact), exactly the greedy-regex result. |
| 26 | +
|
| 27 | + Theorems: `canon_inj`; `render_inj` (every face's renaming is injective — the |
| 28 | + non-collapse guarantee); and the pointed `js_no_collapse` / `cafe_no_collapse` |
| 29 | + / `option_never_atom` (Unit and Option[_] never read as the same type despite |
| 30 | + the shared "null"/"?" lexeme). Axiom-free, no Admitted. |
| 31 | +
|
| 32 | + Scope (honest): models the displayed-name vocabulary, not raw OCaml strings; |
| 33 | + assumes well-formed type strings (no adversarial text around `Option[...]`). |
| 34 | +
|
| 35 | + `.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore. |
| 36 | +*) |
| 37 | + |
| 38 | +Require Import PeanoNat. |
| 39 | + |
| 40 | +(* The six faces (lib/face.ml `face`). *) |
| 41 | +Inductive face := Canonical | Python | Js | Pseudocode | Lucid | Cafe. |
| 42 | + |
| 43 | +(* Displayed atom lexemes. Distinct constructors ⇒ distinct on-screen names — |
| 44 | + faithful, because the real renamings (None/null/nothing/bool/boolean/Boolean) |
| 45 | + are genuinely distinct tokens and no canonical base name equals any of them. *) |
| 46 | +Inductive name := |
| 47 | +| NUnit | NBool |
| 48 | +| NNone | NNothing | NNull |
| 49 | +| Nbool | NBoolean | NbooleanJs |
| 50 | +| NBase (n : nat). |
| 51 | + |
| 52 | +(* Rendered type forms. *) |
| 53 | +Inductive rty := |
| 54 | +| RNm (x : name) |
| 55 | +| ROptBr (r : rty) (* Option[ r ] *) |
| 56 | +| ROrNull (r : rty) (* r | null *) |
| 57 | +| RMaybe (r : rty) (* Maybe r *) |
| 58 | +| RQ (r : rty). (* r ? *) |
| 59 | + |
| 60 | +(* Canonical types: the vocabulary render_ty distinguishes. *) |
| 61 | +Inductive cty := |
| 62 | +| CUnit | CBool | CBase (n : nat) | COption (a : cty). |
| 63 | + |
| 64 | +(* Canonical rendering — Option's inner stays canonical (matches the regex \1). *) |
| 65 | +Fixpoint canon (t : cty) : rty := |
| 66 | + match t with |
| 67 | + | CUnit => RNm NUnit |
| 68 | + | CBool => RNm NBool |
| 69 | + | CBase n => RNm (NBase n) |
| 70 | + | COption a => ROptBr (canon a) |
| 71 | + end. |
| 72 | + |
| 73 | +(* Per-face rendering, mirroring lib/face.ml render_ty: top-level special-case |
| 74 | + for Unit/Bool; per-face Option wrapper with a canonical inner. *) |
| 75 | +Definition render (f : face) (t : cty) : rty := |
| 76 | + match t with |
| 77 | + | CUnit => |
| 78 | + match f with |
| 79 | + | Canonical | Lucid => RNm NUnit |
| 80 | + | Python => RNm NNone |
| 81 | + | Js | Cafe => RNm NNull |
| 82 | + | Pseudocode => RNm NNothing |
| 83 | + end |
| 84 | + | CBool => |
| 85 | + match f with |
| 86 | + | Canonical => RNm NBool |
| 87 | + | Python => RNm Nbool |
| 88 | + | Js => RNm NbooleanJs |
| 89 | + | Pseudocode | Lucid | Cafe => RNm NBoolean |
| 90 | + end |
| 91 | + | CBase n => RNm (NBase n) |
| 92 | + | COption a => |
| 93 | + match f with |
| 94 | + | Canonical | Python | Pseudocode => ROptBr (canon a) |
| 95 | + | Js => ROrNull (canon a) |
| 96 | + | Lucid => RMaybe (canon a) |
| 97 | + | Cafe => RQ (canon a) |
| 98 | + end |
| 99 | + end. |
| 100 | + |
| 101 | +(* ── canonical rendering is injective ──────────────────────────────────── *) |
| 102 | +Lemma canon_inj : forall t1 t2, canon t1 = canon t2 -> t1 = t2. |
| 103 | +Proof. |
| 104 | + induction t1; destruct t2; simpl; intro H; |
| 105 | + try discriminate; try reflexivity. |
| 106 | + - inversion H; reflexivity. (* CBase = CBase *) |
| 107 | + - inversion H; f_equal; apply IHt1; assumption. (* COption = COption *) |
| 108 | +Qed. |
| 109 | + |
| 110 | +(* ── every face's renaming is injective: the non-collapse guarantee ─────── *) |
| 111 | +Theorem render_inj : forall f t1 t2, render f t1 = render f t2 -> t1 = t2. |
| 112 | +Proof. |
| 113 | + intros f t1 t2 H; destruct f, t1, t2; cbn in H; |
| 114 | + solve [ discriminate |
| 115 | + | reflexivity |
| 116 | + | inversion H; subst; reflexivity |
| 117 | + | inversion H; f_equal; apply canon_inj; assumption ]. |
| 118 | +Qed. |
| 119 | + |
| 120 | +(* ── the pointed overlaps don't collapse ─────────────────────────────────── |
| 121 | + Js renders Unit as "null" and Option[T] as "T | null"; Cafe as "null"/"T?". |
| 122 | + The shared lexeme never makes Unit and an Option read as the same type. *) |
| 123 | +Corollary js_no_collapse : forall a, render Js CUnit <> render Js (COption a). |
| 124 | +Proof. intros a H; discriminate. Qed. |
| 125 | + |
| 126 | +Corollary cafe_no_collapse : forall a, render Cafe CUnit <> render Cafe (COption a). |
| 127 | +Proof. intros a H; discriminate. Qed. |
| 128 | + |
| 129 | +(* No Option ever collides with a base/Unit/Bool atom (different rendered shape). *) |
| 130 | +Corollary option_never_atom : forall f a x, render f (COption a) <> RNm x. |
| 131 | +Proof. intros f a x H; destruct f; cbn in H; discriminate. Qed. |
| 132 | + |
| 133 | +Print Assumptions render_inj. |
| 134 | +Print Assumptions js_no_collapse. |
| 135 | +Print Assumptions option_never_atom. |
0 commit comments