Files
sx/current/CHECKPOINT-EXTERN-EXPORT.md
agra 5777ff62ad feat(ffi-linkage): consume extern LIB "csym" rename for fns (Phase 1.2b)
parseFnDecl parses the optional [LIB] ["csym"] tail after the
extern/export keyword into FnDecl.extern_lib/extern_name (mirrors
'#foreign LIB "csym"'). declareFunction unifies the symbol-name
override: rename_c_name = foreign_expr.c_name (for #foreign) OR
fd.extern_name (for extern) -> declare under the C name and map sx->C
in foreign_name_map; the dedupe guard now covers extern too.

examples/1224 green: 'c_abs :: (n) -> i32 extern "abs";' resolves
c_abs to libc abs -> c_abs(-42) = 42. 1223 (bare extern) unregressed.
Suite green (635 corpus / 443 unit).

extern_lib is parsed + stored but not a linking driver — like
'#foreign libc', it references a lib; the #library decl + build flags
remain the separate linking axis (decision 4). green commit.
2026-06-14 13:30:59 +03:00

6.0 KiB

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.1 (green) — wired extern fn lowering in decl.zig; example 1223 now green, full suite green (634 corpus / 443 unit, 0 fail). A bare extern fn lowers exactly like a lib-less #foreign import: declare i32 @abs(i32) #0 — external linkage, C ABI, NO __sx_ctx param; calls emit call i32 @abs(i32 -7) and resolve against the default-linked libc. Six edits, all routing extern declare-only (mirroring the foreign_expr guards): (1) funcWantsImplicitCtx suppresses ctx for .extern_; (2) declareFunction adds is_extern_decl and (3) includes it in the C-ABI promotion; (4) lazyLowerFunction routes .extern_→declare-only; (5) lowerFunction declare-only guard; (6) lowerFunctionBodyInto never promotes/lowers an extern stub. Hand-authored 1.0b snapshot matched byte-exact — no regen needed. No .ir snapshot added (the trivial declare i32 @abs(i32) doesn't warrant a 1000-line full-prelude dump; behavioral .stdout already catches ctx/linkage regressions).

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 FUNCTIONS WORK (import; bare form, no rename) — parse + lower complete, behavior-equivalent to a lib-less #foreign fn. Still TODO in Phase 1: the extern LIB "csym" lib/rename axis (fields exist, unconsumed) and the extern-global form. export not started (Phase 2). Part B foreign footprint to purge: 643 lines / ~57 identifiers in src/ + 28 doc lines. End-state invariant: zero foreign (Phase 9.4 gate). Done: 0.0 tokens, 0.1 AST/parser plumbing, 1.0a fn-path parsing + lib/name fields, 1.0b xfail example, 1.1 fn lowering (green).

Next step

Phase 1.2 (green) — two parts, each its own xfail→green or behavior-lock:

  1. extern LIB "csym" rename for fns — extend parseOptionalExternExport() (or parseFnDecl) to parse the optional LIB ident + "csym" string after the keyword into FnDecl.extern_lib/extern_name; consume them in declareFunction (mirror the #foreign c_name block at decl.zig:~2119: declare under the C name, map sx→C in foreign_name_map/dedupe). New example renaming a libc symbol (e.g. c_abs :: (n: i32) -> i32 extern "abs";).
  2. extern-global g : T extern [LIB] ["csym"]; — parse path at parser.zig:425 (the var-decl with type annotation): accept postfix extern → set VarDecl.is_extern/extern_lib/extern_name; lower like the #foreign global (decl.zig:~1115-1137, .is_extern = vd.is_foreign). New example mirroring examples/1205-ffi-foreign-global with extern.

Stop at end of Phase 1 (do NOT start Phase 2 export or Part B migration). Then the A→B gate: a unit test that #foreign and extern lower to identical IR.

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).

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.