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.
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:
extern LIB "csym"rename for fns — extendparseOptionalExternExport()(orparseFnDecl) to parse the optionalLIBident +"csym"string after the keyword intoFnDecl.extern_lib/extern_name; consume them indeclareFunction(mirror the#foreignc_name block atdecl.zig:~2119: declare under the C name, map sx→C inforeign_name_map/dedupe). New example renaming a libc symbol (e.g.c_abs :: (n: i32) -> i32 extern "abs";).- extern-global
g : T extern [LIB] ["csym"];— parse path atparser.zig:425(the var-decl with type annotation): accept postfixextern→ setVarDecl.is_extern/extern_lib/extern_name; lower like the#foreignglobal (decl.zig:~1115-1137,.is_extern = vd.is_foreign). New example mirroringexamples/1205-ffi-foreign-globalwithextern.
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_exporttokens + keyword-map entries + LSP keyword classification +lex linkage keywordstest. Suite green; no identifier collisions in the corpus.lockcommit. - (0.1) Added
ast.ExternExportModifier+FnDecl.extern_export+VarDecl.is_extern/extern_name+parseOptionalExternExport()(unconsumed) + 2 parser unit tests. Suite green (443/633).lockcommit. - (1.0a) Wired fn-path extern parsing (
parseFnDecl+ both lookahead predicates) + addedFnDecl.extern_lib/extern_name+VarDecl.extern_libper user feedback (decision 4 revised: extern carries an optional lib axis). Unconsumed by lowering. Suite green (443/633).lockcommit. - (1.0b) Added
examples/1223-ffi-extern-fn.sx+ hand-authored success snapshots. RED (634 ran, 1 failed — semabody produces no value).xfailcommit; 1.1 greens it. - (1.1) Wired extern fn lowering (6 edits in
decl.zig, all declare-only routing mirroringforeign_expr):funcWantsImplicitCtx+declareFunctioncc +lazyLowerFunction/lowerFunction/lowerFunctionBodyIntoguards. 1223 green;declare i32 @abs(i32)(C ABI, no ctx). Suite green (634/443).greencommit. - (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"afterexternnot yet accepted).xfail; 1.2b greens it. (Also recovered a formatter-clobberedparser.zig— see Known issues.) - (1.2b)
parseFnDeclparses the optional[LIB] ["csym"]tail intoextern_lib/extern_name;declareFunctionunifies 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).greencommit. extern_lib parsed+stored (lib linking stays the#libraryaxis).
Known issues
- Workflow hazard (1.2): an editor format-on-save (or
zig fmt) clobbered the working-treesrc/parser.zigbetween commits — it reformatted one-liners AND silently dropped myhasFnBodyAfterArrowextern edit, reverting 1223 to a parse error. Recovered withgit 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.