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

7.0 KiB
Raw Blame History

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_absabs); 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 COMPLETEextern 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.