diff --git a/current/CHECKPOINT-EXTERN-EXPORT.md b/current/CHECKPOINT-EXTERN-EXPORT.md index c3c6615..8e5efd3 100644 --- a/current/CHECKPOINT-EXTERN-EXPORT.md +++ b/current/CHECKPOINT-EXTERN-EXPORT.md @@ -5,9 +5,30 @@ Companion to `current/PLAN-EXTERN-EXPORT.md` — one merged plan: **Part A** add every commit, one step at a time per the cadence rule. ## Last completed step -**Phase 5.0 prereq — extern C-variadic tail** (xfail `9a2c78d` → fix `0fdc821`) — -the SECOND (and last) deferred fn-path prerequisite. **BOTH fn-path prereqs now -done.** The C-variadic `...` handling was keyed on the `#foreign` (`foreign_expr`) +**Phase 5.0 prereqs 3 & 4 — plain-free classification + extern lib-ref validation** +(plain-free: xfail `2706521` → fix `3c94c14`; lib-ref: xfail `38c3240` → fix +`ad6aed3`). Two MORE extern/#foreign divergences found while de-risking the fn-path +flip, both now closed. **FOUR prereqs total now done — the fn-decl `#foreign`→`extern` +flip is fully de-risked.** +- **Prereq 3 (plain-free):** `isPlainFreeFn`/`isPlainFreeFnDecl` (resolver.zig:178, + generic.zig:815) excluded a `#foreign` body but classified an empty-block `extern` + fn as a plain free fn — so existing extern fns were wrongly counted in the bare-call + ambiguity verdict (example: two same-name `extern libc "abs"` authors errored + ambiguous, while the `#foreign` twin 0729 compiles). Both predicates now also + exclude `extern_export == .extern_`; `export` (real body) stays plain-free. Example + **1230**. +- **Prereq 4 (lib-ref validation):** `checkForeignRefs` (c_import.zig) validated only + `foreign_expr.library_ref`, so a bogus `extern nosuchunit "abs"` compiled silently + while `#foreign nosuchunit` errors (1620). Now reads the lib ref from EITHER spelling + and names the surface keyword in the diagnostic (so 1620 stays byte-unchanged). + Example **1231**. +- Two OTHER classifying sites probed and found BENIGN for extern (no flip prereq): + namespace/qualified dispatch (`registerQualifiedFn` decl.zig:2208, namespace gate + call.zig:729) — a namespaced `extern` fn resolves identically to its `#foreign` twin + (probe: `cm.c_abs(-9)` → 9 both ways; the registered qualified alias resolves to the + same extern symbol). + +### Prior: Phase 5.0 prereq — extern C-variadic tail (xfail `9a2c78d` → fix `0fdc821`) — the SECOND deferred fn-path prerequisite. **BOTH original fn-path prereqs done.** The C-variadic `...` handling was keyed on the `#foreign` (`foreign_expr`) body shape at two sites — the `is_variadic` drop in `declareFunction` (`decl.zig:2097`) and the call-site early-out in `packVariadicCallArgs` (`pack.zig:302`). A variadic `extern` therefore kept its trailing slice param and @@ -111,8 +132,29 @@ AOT), 1227 (export fn rename, AOT), 1348 (objc extern class), 1349 (objc export (jni extern class), 1174/1175 (interplay diagnostics). ## Next step -**PART B — Phase 5.0, fn-decl `#foreign` body-marker migration** (BOTH prereqs — -visibility-gate + variadic — are now DONE). Route the fn-decl `#foreign` path so a +**PART B — Phase 5.0, fn-decl `#foreign` body-marker flip** — fully de-risked (ALL +FOUR prereqs done: visibility, variadic, plain-free classification, lib-ref +validation). The flip itself is now small. **AWAITING ONE DESIGN DECISION** (see +"Open decisions / Decision 7" below) before executing — it determines interim +diagnostic wording + whether one snapshot churns. + +Mechanics of the flip (when greenlit): +1. `parser.zig:~2081` fn-body `#foreign` arm: build the extern shape + (`extern_export = .extern_`, `library_ref→extern_lib`, `c_name→extern_name`, + empty-block body) instead of a `foreign_expr` body. +2. Update parser unit test `parser.zig:4266-4267` (asserts the fn-body `#foreign` + builds a `foreign_expr` body with `library_ref "rl"`) to assert the extern shape. +3. Run the A→B gate (`lower.test.zig`) + full `zig build test`. + +**Verified churn surface (NARROW):** IR is zero-churn (all lowering sites coalesce +`is_foreign`/`extern_export` — confirmed across this stream). The ONLY corpus churn +is example **1620**'s lib-ref message: a `#foreign`-spelled decl becomes the extern +AST, so `checkForeignRefs` would emit "extern library 'X'…" instead of "#foreign +library 'X'…". Parser-surface diagnostics (`parser.zig:484/1429`) and runtime-class +messages are unaffected (they fire on the literal keyword pre-AST). `c_import.zig` +auto-synthesis STILL builds `foreign_expr` bodies (not migrated this step), so +`foreign_expr` does not disappear — both shapes coexist, which is why every reader +had to coalesce. Route the fn-decl `#foreign` path so a `#foreign` fn builds the SAME extern AST that postfix `extern` already produces, instead of a `foreign_expr` body. This is the highest-value path (the bulk of `#foreign` usage). Key sub-questions to resolve before/while routing: @@ -201,8 +243,33 @@ Part A ratified (bare / postfix / `⇒ callconv(.c)` / lib-separate). Part B: - **Decision 6 STILL OPEN**: historical carve-out — keep `issues/*.md` (+ design-doc prose) as provenance & gate only the live tree (recommended) vs purge everything. The user did NOT confirm this at the Part A milestone; confirm before Phase 9. +- **Decision 7 OPEN — interim diagnostic wording for `#foreign`-spelled decls** (gates the + fn-body flip). Once the flip lands, a `#foreign`-spelled fn builds the extern AST, so any + diagnostic that reads the unified AST can no longer tell the user wrote `#foreign` vs + `extern`. Concretely, example 1620's lib-ref error flips "#foreign library…" → + "extern library…". Options: **(A, recommended)** accept the narrow churn — regen 1620 as + intentional; it aligns with Part B's `extern`-only end state and the interim oddity + (`#foreign` source → "extern" message) is cosmetic and short-lived (Phase 8 cutover + removes `#foreign`). **(B)** retain a one-bit surface marker on `FnDecl` (`wrote_foreign`) + so interim diagnostics stay keyword-accurate (zero churn, small extra plumbing, marker + deleted at cutover). Affects only diagnostic wording — IR/behavior identical either way. ## Log +- (5.0 prereq plain-free xfail) Added `1230-ffi-extern-same-name-authors` (two flat + authors of `absval` via `extern libc "abs"`; the `extern` twin of `#foreign` 0729). + RED — extern authors wrongly counted as ambiguous (646/1 fail). `test`/xfail `2706521`. +- (5.0 prereq plain-free fix) `isPlainFreeFn`/`isPlainFreeFnDecl` now also exclude + `extern_export == .extern_` (external C symbol, no sx body; name-keyed first-wins like + `#foreign`); `export` stays plain-free. 1230 green (`absval = 7`). Suite green (646/444). + `fix`/green `3c94c14`. +- (5.0 prereq lib-ref xfail) Added `1231-ffi-extern-undeclared-lib` (`extern nosuchunit + "abs"` — bogus lib ref). RED — compiles silently (extern lib ref unvalidated). + `test`/xfail `38c3240`. +- (5.0 prereq lib-ref fix) `checkForeignRefs` (c_import.zig) now reads the lib ref from + either spelling (foreign_expr.library_ref OR extern_lib) and names the surface keyword, + so 1620 (#foreign) is byte-unchanged and 1231 (extern) gets "extern library … not + declared". 1231 green. Suite green (647/444). `fix`/green `ad6aed3`. **ALL FOUR fn-path + prereqs DONE → fn-body flip de-risked; awaiting Decision 7 (interim wording).** - (5.0 prereq variadic xfail) Added `1229-ffi-extern-cvariadic` (JIT `#source`, int-sum + double-avg, `extern` C-variadic). Expected snapshot pins the DESIRED correct output. RED (variadic `extern` slice-packs extras → garbage: