Files
sx/current/CHECKPOINT-EXTERN-EXPORT.md
agra 6932426c41 feat(ffi-linkage): lower extern data globals (Phase 1.2d) — Phase 1 complete
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.
2026-06-14 13:39:05 +03:00

103 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 01 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.**