Parser: a 'kw_extern' branch in the var-decl-with-type-annotation path (beside #foreign) parses 'name : type extern [LIB] ["csym"];' into VarDecl.is_extern/extern_lib/extern_name; the trailing diagnostic now lists 'extern'. Lowering: registerTopLevelGlobal uses extern_name orelse foreign_name orelse name for the C symbol and sets is_extern = is_foreign or is_extern; globalInitValue returns null (no initializer) for extern globals too. examples/1225 green: '__stdinp : *void extern;' lowers to '@__stdinp = external global ptr'; @__stdinp reads non-null. Suite green (636 corpus / 443 unit). Phase 1 done: extern functions (bare + rename) and data globals (bare + rename) all work, behavior-equivalent to the matching #foreign form. export (Phase 2), aggregates (Phase 3), docs + A->B gate (Phase 4) remain. green commit.
103 lines
7.0 KiB
Markdown
103 lines
7.0 KiB
Markdown
# sx `extern`/`export` + `#foreign` retirement — Checkpoint (FFI-linkage stream)
|
||
|
||
Companion to `current/PLAN-EXTERN-EXPORT.md` — one merged plan: **Part A** adds
|
||
`extern`/`export`, **Part B** migrates `#foreign` and purges `foreign`. Update after
|
||
every commit, one step at a time per the cadence rule.
|
||
|
||
## Last completed step
|
||
**Phase 1.2d** (green) — **PHASE 1 COMPLETE.** `extern` data globals now parse + lower:
|
||
added a `kw_extern` branch in the var-decl-with-type-annotation path (`parser.zig:~451`,
|
||
beside `#foreign`) parsing `[LIB] ["csym"]` into `is_extern`/`extern_lib`/`extern_name`
|
||
(+ updated the trailing diagnostic to list `extern`); `registerTopLevelGlobal` now uses
|
||
`extern_name orelse foreign_name orelse name` for the C symbol and sets `is_extern =
|
||
is_foreign or is_extern`; `globalInitValue` returns null (no init) for extern too.
|
||
Example **1225 green** — `__stdinp : *void extern;` lowers to `@__stdinp = external
|
||
global ptr` and `@__stdinp` reads non-null. Full suite green (636 corpus / 443 unit, 0
|
||
fail). Kickoff exit criteria met: suite green; extern libc fn + global bindings run;
|
||
`#foreign` unregressed (all its examples pass → snapshots unchanged).
|
||
|
||
## Current state
|
||
Syntax: bare `extern`/`export`, postfix after `callconv(.c)`, `extern ⇒ callconv(.c)`.
|
||
**Decision 4 revised** (user 2026-06-14): `extern` carries an optional `LIB`+`"csym"`
|
||
axis (`extern_lib`/`extern_name`) like `#foreign`; the `#library` decl + build-flag
|
||
linking stays separate. **`extern` IS FULLY WORKING** (PHASE 1 DONE): functions —
|
||
bare (`f :: (…) -> R extern;`) AND renamed (`extern [LIB] "csym"`); data globals —
|
||
bare (`g : T extern;`) AND renamed. All behavior-equivalent to the matching `#foreign`
|
||
form (external linkage, C ABI, no sx ctx). `extern_lib` is parsed + stored but is a
|
||
*reference* only — actual linking stays the `#library`/build-flag axis (same as
|
||
`#foreign`'s lib ref). `export` NOT started (Phase 2). 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 (bare
|
||
fn), 1224 (fn rename), 1225 (bare global).
|
||
|
||
## Next step
|
||
**SESSION STOP** — kickoff scope was Phases 0–1 only; Phase 1 is complete. Do NOT
|
||
start Phase 2 here. Next session picks up **Phase 2 — `export`** (define + expose):
|
||
fills the four export-gap conditions in `decl.zig` (all on the *define* path, not
|
||
`declareExtern`): (i) force `.external` linkage when `extern_export == .export_`
|
||
(`:2382`/`:2514`); (ii) promote to C ABI (`:2110`/the `lowerFunction` cc at `:2522`);
|
||
(iii) symbol-name override via `export "csym"` (already parsed into `extern_name` —
|
||
just consume on the define path); (iv) ctx already suppressed for `.export_`? NO —
|
||
`funcWantsImplicitCtx` currently suppresses only `.extern_`; broaden to `!= .none`
|
||
(or add `.export_`). Start with an xfail multi-file test: an `export fn` called from a
|
||
companion `.c` caller. Then Phase 3 (aggregates), Phase 4 (interplay/diagnostics/docs
|
||
+ the A→B gate: unit test that `#foreign` and `extern` lower to identical IR).
|
||
|
||
**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`)
|
||
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;
|
||
verify/align it at the A→B gate (a bare-extern lib-less fn should be policed like its
|
||
`#foreign` twin). Adding it now would be untested — needs a cross-module example.
|
||
|
||
## Open decisions
|
||
Part A ratified (bare / postfix / `⇒ callconv(.c)` / lib-separate). Part B (confirm
|
||
before Phase 9): runtime-class rename target — `Runtime*Class*` (recommended);
|
||
historical carve-out — keep `issues/*.md` provenance, gate the live tree only.
|
||
|
||
## Log
|
||
- (init) Plan written; FFI-linkage stream opened.
|
||
- (merge) Folded FOREIGN-MIGRATION in as Part B; deleted the split plan + checkpoint.
|
||
- (0.0) Added `kw_extern`/`kw_export` tokens + keyword-map entries + LSP keyword
|
||
classification + `lex linkage keywords` test. Suite green; no identifier collisions
|
||
in the corpus. `lock` commit.
|
||
- (0.1) Added `ast.ExternExportModifier` + `FnDecl.extern_export` +
|
||
`VarDecl.is_extern`/`extern_name` + `parseOptionalExternExport()` (unconsumed) + 2
|
||
parser unit tests. Suite green (443/633). `lock` commit.
|
||
- (1.0a) Wired fn-path extern parsing (`parseFnDecl` + both lookahead predicates) +
|
||
added `FnDecl.extern_lib`/`extern_name` + `VarDecl.extern_lib` per user feedback
|
||
(decision 4 revised: extern carries an optional lib axis). Unconsumed by lowering.
|
||
Suite green (443/633). `lock` commit.
|
||
- (1.0b) Added `examples/1223-ffi-extern-fn.sx` + hand-authored success snapshots.
|
||
RED (634 ran, 1 failed — sema `body produces no value`). `xfail` commit; 1.1 greens it.
|
||
- (1.1) Wired extern fn lowering (6 edits in `decl.zig`, all declare-only routing
|
||
mirroring `foreign_expr`): `funcWantsImplicitCtx` + `declareFunction` cc +
|
||
`lazyLowerFunction`/`lowerFunction`/`lowerFunctionBodyInto` guards. 1223 green;
|
||
`declare i32 @abs(i32)` (C ABI, no ctx). Suite green (634/443). `green` commit.
|
||
- (1.2a) Added `examples/1224-ffi-extern-fn-rename.sx` (`c_abs :: … extern "abs";`) +
|
||
hand-authored success snapshot (`c_abs(-42) = 42`). RED (635 ran, 1 failed — parse
|
||
error: `"abs"` after `extern` not yet accepted). `xfail`; 1.2b greens it. (Also
|
||
recovered a formatter-clobbered `parser.zig` — see Known issues.)
|
||
- (1.2b) `parseFnDecl` parses the optional `[LIB] ["csym"]` tail into
|
||
`extern_lib`/`extern_name`; `declareFunction` unifies the rename (foreign c_name OR
|
||
extern_name → declare under C name, map sx→C) and extends the dedupe guard to
|
||
extern. 1224 green (`c_abs`→`abs`); 1223 unregressed. Suite green (635/443).
|
||
`green` commit. extern_lib parsed+stored (lib linking stays the `#library` axis).
|
||
- (1.2c) Added `examples/1225-ffi-extern-global.sx` (`__stdinp : *void extern;`,
|
||
mirrors `#foreign` global 1205) + success snapshot. RED (636 ran, 1 failed — parse
|
||
error: var-decl `extern` not accepted). `xfail`; 1.2d greens it.
|
||
- (1.2d) Parser `kw_extern` branch in the var-decl path (`[LIB] ["csym"]` →
|
||
`is_extern`/`extern_lib`/`extern_name`) + `registerTopLevelGlobal`/`globalInitValue`
|
||
consume `is_extern`. 1225 green (`@__stdinp = external global ptr`). Suite green
|
||
(636/443). `green` commit. **PHASE 1 COMPLETE** — `extern` fns + globals fully work.
|
||
|
||
## Known issues
|
||
- **Workflow hazard (1.2):** an editor format-on-save (or `zig fmt`) clobbered the
|
||
working-tree `src/parser.zig` between commits — it reformatted one-liners AND
|
||
silently dropped my `hasFnBodyAfterArrow` extern edit, reverting 1223 to a parse
|
||
error. Recovered with `git checkout src/parser.zig` (HEAD had the correct,
|
||
committed version). **After any Edit-tool change to a file the IDE may have open,
|
||
rebuild + run the affected example before trusting the edit.**
|