docs(ffi-linkage): checkpoint — Phase 4 complete, Part A done, A→B gate locked
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user