docs(ffi-linkage): checkpoint — Phase 3 complete (aggregate extern/export)

This commit is contained in:
agra
2026-06-14 15:14:09 +03:00
parent 91d70bd864
commit d4f683f525

View File

@@ -5,12 +5,18 @@ 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.0** (xfail) — `examples/1348-ffi-objc-extern-class.sx` exercises the new
postfix-`extern` aggregate spelling (`NSObject :: #objc_class("NSObject") extern { … }`,
the new way to write `#foreign #objc_class("NSObject") { … }`). RED: parser rejects
`extern` after the `#objc_class(...)` directive (`expected '{'`). Green-state snapshots
hand-authored (exit 0, stdout `extern-class dispatch ok`). Suite: 639 corpus / 443 unit,
**1 fail (1348, the intended xfail)**. Step 3.1 wires parse+lowering to green it.
**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
class, == legacy `#foreign`); `export``is_foreign_eff = false` (define + register a new sx
class, == bare `#objc_class` with no `#foreign`). The modifier maps straight onto the same
`is_foreign` decision the prefix `#foreign` already fed the node, so **no `objc_class.zig`/
lowering change was needed** — the new surface reuses the existing reference-vs-define path.
Examples: **1348** (objc `extern` import, dispatches `NSObject.alloc().init()` → green via
JIT), **1349** (objc `export` defined class, `SxBar.alloc()`/`bump`/`get``counter: 2`),
**1426** (jni `extern` import, parse-only `parse-only ok`). Suite green (641 corpus / 443
unit, 0 fail).
### Prior: Phase 2.2 (green) — **PHASE 2 COMPLETE.** `export` (define + expose) fully works:
external linkage + C ABI + no sx ctx + force-lowered root + optional `"csym"` rename.
@@ -47,13 +53,15 @@ gate). Examples: 1223 (extern bare fn), 1224 (extern fn rename), 1225 (extern ba
global), 1226 (export bare fn, AOT), 1227 (export fn rename, AOT).
## Next step
**Phase 3.1** (green) — wire the postfix `extern`/`export` aggregate path. `parseForeignClassDecl`
already consumes the directive + `("X")` + body; add an optional postfix-modifier slot
**after** the `)` and before the `{`: `extern`→reference (== `is_foreign`), `export`
define+register (== no `#foreign`). Map the postfix modifier onto the same downstream
`is_foreign` decision that `tryParseForeignClassPrefix` feeds, so `objc_class.zig` lowering
is unchanged. Green 1348; add a jni postfix test + an `export` (defined-class) test for the
per-runtime coverage. Then Phase 4 (interplay/diagnostics/docs + the A→B gate: unit test that
**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.
**FUTURE MILESTONE — C→sx-by-name in JIT (`sx run`).** Investigated this session
@@ -143,6 +151,17 @@ historical carve-out — keep `issues/*.md` provenance, gate the live tree only.
- (3.0) Added `examples/1348-ffi-objc-extern-class.sx` (postfix `extern` on `#objc_class`,
new spelling of `#foreign #objc_class`). RED (parser: `expected '{'` after the
directive). Hand-authored green snapshots. `xfail` commit; 3.1 greens it.
- (3.1a) Wired the postfix `extern`/`export` aggregate slot in `parseForeignClassDecl`
(optional modifier between `("X")` and `{`; `var is_foreign_eff` overrides the passed
`is_foreign`, threaded into the `foreign_class_decl` node). No lowering change — reuses
the existing `is_foreign` reference-vs-define path. 1348 green. Suite green (639/443).
`green` commit. **PHASE 3 COMPLETE.**
- (3.1b) Behavior-lock: added `examples/1426-ffi-jni-extern-class.sx` (jni `extern`,
parse-only) + `examples/1349-ffi-objc-export-class.sx` (objc `export` defined class,
`counter: 2`). Both pass against the 3.1a parser change (locked in their own commit per
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.)
## Known issues
- **Workflow hazard (1.2):** an editor format-on-save (or `zig fmt`) clobbered the