From aafcbf6d7828ec0d3cea6383843e29178d0f509c Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 14 Jun 2026 16:08:01 +0300 Subject: [PATCH] =?UTF-8?q?docs(ffi-linkage):=20checkpoint=20=E2=80=94=20P?= =?UTF-8?q?hase=204=20complete,=20Part=20A=20done,=20A=E2=86=92B=20gate=20?= =?UTF-8?q?locked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- current/CHECKPOINT-EXTERN-EXPORT.md | 77 ++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/current/CHECKPOINT-EXTERN-EXPORT.md b/current/CHECKPOINT-EXTERN-EXPORT.md index bddf048..364ed18 100644 --- a/current/CHECKPOINT-EXTERN-EXPORT.md +++ b/current/CHECKPOINT-EXTERN-EXPORT.md @@ -5,7 +5,22 @@ Companion to `current/PLAN-EXTERN-EXPORT.md` — one merged plan: **Part A** add every commit, one step at a time per the cadence rule. ## Last completed step -**Phase 3.1** (green) — **PHASE 3 COMPLETE.** Postfix `extern`/`export` on `#objc_class`/ +**Phase 4** (green) — **PHASE 4 COMPLETE → PART A DONE; GATE A→B LOCKED.** Four pieces: +(1) **GATE A→B unit test** (`lower.test.zig`, `lowerSrcToIr` helper + "GATE A→B" test) — +asserts `#foreign` and `extern`/`export` lower to byte-identical printed IR for a sample +fn, data global, and Obj-C runtime class. This is the hard gate: Part B may not start +migrating `#foreign` until it's green. Verified live (negative-probe: mutating one side +fails the assertion). (2) **Diagnostic — `#foreign` + postfix conflict** (1174): prefix +`#foreign` combined with postfix `extern`/`export` on an aggregate is now a clean parse +error (was a confusing internal "compiler bug" during class synthesis). (3) **Diagnostic +— `extern`+`export` mutual exclusion** (1175): both keywords on one fn decl is a clean +error (was bare "expected ';'"). (4) **Docs**: `specs.md` + `readme.md` document the three +`extern`/`export` axes (fns, globals, aggregates) alongside `#foreign` (which stays +documented until the Part B cutover). Suite green (643 corpus / 444 unit, 0 fail). +NOTE: `extern`+`callconv` redundancy needs no diagnostic — `callconv(.c) extern` is a +harmless dup (both `.c`), and any non-`.c` callconv already errors on its own. + +### Prior: Phase 3.1 (green) — **PHASE 3 COMPLETE.** Postfix `extern`/`export` on `#objc_class`/ `#jni_class` aggregates fully works. `parseForeignClassDecl` now parses an optional `extern`/`export` modifier in the slot **between** the `("X")` directive args and the `{` body (`parser.zig:~1409`): `extern`→`is_foreign_eff = true` (reference an existing runtime @@ -47,22 +62,30 @@ data globals — bare + renamed. export: functions — bare (`f :: (…) -> R ex AND renamed (`export "csym"`); external linkage, C ABI, no ctx, force-lowered as a root. All behavior-equivalent to the matching `#foreign` form. `extern_lib` is parsed + stored but is a *reference* only — actual linking stays the `#library`/build-flag axis. -Aggregates NOT started (Phase 3). Part B `foreign` footprint to purge: 643 lines / ~57 -identifiers in `src/` + 28 doc lines. End-state invariant: **zero `foreign`** (Phase 9.4 -gate). Examples: 1223 (extern bare fn), 1224 (extern fn rename), 1225 (extern bare -global), 1226 (export bare fn, AOT), 1227 (export fn rename, AOT). +**Aggregates DONE (Phase 3)**: postfix `extern`/`export` on `#objc_class`/`#jni_class` +(reference vs define+register). **Interplay/diagnostics/docs DONE (Phase 4)** + the +**A→B GATE IS LOCKED** (`#foreign` ≡ `extern`/`export` IR for fn/global/class). **PART A +COMPLETE.** Part B `foreign` footprint to purge: 643 lines / ~57 identifiers in `src/` + +~28 doc lines. End-state invariant: **zero `foreign`** (Phase 9.4 gate). Examples: 1223 +(extern bare fn), 1224 (extern fn rename), 1225 (extern bare global), 1226 (export bare fn, +AOT), 1227 (export fn rename, AOT), 1348 (objc extern class), 1349 (objc export class), 1426 +(jni extern class), 1174/1175 (interplay diagnostics). ## Next step -**Phase 4 — interplay, diagnostics, docs (+ the A→B gate).** (a) Diagnostics for combining -the surfaces: reject `extern`+`export` together; decide/handle prefix `#foreign` **and** -postfix `extern`/`export` on the same aggregate (today the postfix silently overrides -`is_foreign` — Phase 4 should reject the redundant/contradictory combo, mirroring the fn -path). (b) `extern`+`callconv` stacking/redundancy on fns. (c) docs — `specs.md`/`readme.md` -document the three `extern`/`export` axes (fns, globals, aggregates); `#foreign` stays -documented until the Part B cutover. (d) **GATE A→B (hard):** unit test asserting `#foreign` -and `extern`/`export` lower to identical IR for a sample fn / global / **class** — lock -before any Part B migration. Also pick up the two **Deferred** items below at this gate. (interplay/diagnostics/docs + the A→B gate: unit test that -`#foreign` and `extern`/`export` lower to identical IR) before Part B migration. +**PART B — Phase 5 (`#foreign` becomes an alias for `extern`).** Part A is complete and +the A→B gate is locked, so migration can begin. Phase 5.0 (`lock`): route the four +`#foreign` parser paths (`parser.zig:316,425,1305,1970`) to build the **same** extern-named +AST as `extern`/`export` — suite green, all snapshots unchanged. Phase 5.1 (`lock`): unit +test that `#foreign` and `extern` produce identical IR (the gate already covers this — extend +or reuse `lowerSrcToIr`). Then Phases 6–7 migrate stdlib + examples (empty snapshot diff per +batch), Phase 8 cutover (hard-reject `#foreign`), Phase 9 total `foreign` purge. + +**⚠ CONFIRM BEFORE PART B (Open decisions 5 & 6):** runtime-class rename target +(`Runtime*Class*` recommended vs `Extern*Class*`) and the historical carve-out (keep +`issues/*.md` provenance, gate live tree only — recommended). These decide Phase 9 renames; +the plan says confirm before Phase 9, but worth raising with the user before sinking Part B +effort. **Also pick up the two Deferred items below at the start of Part B** (the +visibility-gate equivalence in particular needs a cross-module example). **FUTURE MILESTONE — C→sx-by-name in JIT (`sx run`).** Investigated this session (user-requested spike, RESOLVED feasible-but-blocked). Adding the C `#source` objects @@ -78,9 +101,9 @@ examples crashed in the spike. A real impl needs a C++ shim in `llvm_shim.c` milestone, NOT Phase 2/3 scope. The AOT `.aot`-marker corpus mode is the pragmatic test path and works today. Spike fully reverted (target.zig/main.zig at HEAD). -**Deferred (do in Phase 4):** (a) docs — `specs.md`/`readme.md` document -`extern`/`export` (the plan defers docs to Phase 4; `#foreign` stays documented until -the Part B cutover); (b) visibility-gate equivalence — bare `extern` (no `extern_lib`) +**Deferred (carry into Part B):** (a) ~~docs~~ — DONE in Phase 4 (`specs.md`/`readme.md` +document `extern`/`export`; `#foreign` stays until the Part B cutover); (b) visibility-gate +equivalence — bare `extern` (no `extern_lib`) is currently unconditionally visible via the `c_import_bare` gate (`decl.zig:~2241`, `fd.body.data != .foreign_expr → return true`), whereas a lib-less `#foreign` is policed by `visibleOverEdges`. Single-file examples don't exercise this; @@ -162,6 +185,24 @@ historical carve-out — keep `issues/*.md` provenance, gate the live tree only. the cadence rule). Suite green (641/443). `lock` commit. (Note: `-Dupdate-goldens` newline-normalizes empty stderr → reverted unrelated 1226/1227 churn, kept new stderr 0-byte per repo convention; runner normalizes both.) +- (4.gate) **GATE A→B** — added `lowerSrcToIr` helper + "GATE A→B" test to `lower.test.zig`: + `#foreign` ≡ `extern`/`export` byte-identical printed IR for fn / global / Obj-C class. + Verified live via negative-probe (mutate one side → assertion fails). Behavior-lock; the + equivalence was prototyped first with `sx ir` (LLVM IR byte-identical for all three). + Suite green (641/444). `test` commit. +- (4.diag1) Added `examples/1174-diagnostics-foreign-postfix-conflict.sx` — prefix `#foreign` + + postfix `export` on an aggregate previously surfaced a confusing internal + "emitObjcDefinedClassAllocImp … compiler bug". `xfail` (golden = clean message) → `green`: + `parseForeignClassDecl` rejects the combo at the postfix keyword (`failFmt`). Suite green. +- (4.docs) `specs.md` (new "`extern`/`export` linkage keywords" subsection after the + `#foreign` FFI docs) + `readme.md` (C Interop section) document the three axes. `docs` commit. +- (4.diag2) Added `examples/1175-diagnostics-extern-export-conflict.sx` — `extern export` on + one fn decl previously gave bare "expected ';'". `xfail` (golden = clean message) → `green`: + `parseFnDecl` rejects a second linkage keyword after `parseOptionalExternExport`. Suite + green (643/444). **PHASE 4 COMPLETE → PART A DONE.** (Workflow note: `-Dupdate-goldens` + keeps newline-normalizing the 5 empty-stderr files [1226/1227/1348/1349/1426] — revert + that churn after each regen; a flaky `0712-sha256-streaming` timeout appears only under + concurrent `zig build` load, not a real failure.) ## Known issues - **Workflow hazard (1.2):** an editor format-on-save (or `zig fmt`) clobbered the