diff --git a/current/CHECKPOINT-EXTERN-EXPORT.md b/current/CHECKPOINT-EXTERN-EXPORT.md index 8e5efd3..99cd652 100644 --- a/current/CHECKPOINT-EXTERN-EXPORT.md +++ b/current/CHECKPOINT-EXTERN-EXPORT.md @@ -5,11 +5,26 @@ 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 prereqs 3 & 4 — plain-free classification + extern lib-ref validation** +**Phase 5.0 — fn-decl `#foreign` body-marker FLIP** (`refactor` commit `6b94bb6`). +**PHASE 5.0 PARSER ROUTING COMPLETE.** The fn-body `#foreign [LIB] ["csym"]` marker +now builds the SAME extern AST postfix `extern` produces (`extern_export = .extern_` ++ `extern_lib`/`extern_name` + empty-block body) instead of a `foreign_expr` body. +Behavior-preserving — all four prereqs (visibility, variadic, plain-free, lib-ref) +ensure every downstream reader coalesces `is_foreign` with `extern_export`, so IR + +runtime are byte-identical (full corpus + A→B gate green). Decision 7 churn realised: +example 1620's lib-ref error flips "#foreign library" → "extern library" (the only +snapshot moved; hand-edited, not regen). Parser unit test updated to assert the extern +shape. Spot-checked 1219/1218/0729 (foreign rename / cvariadic / same-name) end-to-end. +**All four `#foreign` parser paths now resolved:** global (`e5ddfbe`) + fn-body +(`6b94bb6`) flipped onto extern; const-with-type is dead (deferred); runtime-class is +already coalesced (`is_foreign_eff`). `c_import.zig` auto-synthesis STILL emits +`foreign_expr` bodies (Phase 6+), so both shapes coexist — every reader stays dual. +Suite green (647 corpus / 444 unit, 0 failed). + +### Prior: 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.** +flip, both now closed. **FOUR prereqs total done — the fn-decl flip 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 @@ -132,29 +147,34 @@ 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 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. +**PART B — Phase 5.1: lock `#foreign` ≡ `extern` IR, then Phases 6–7 (migrate +stdlib + examples).** Phase 5.0 parser routing is COMPLETE. -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`. +- **Phase 5.1** (`lock`/`test`): the A→B gate (`lower.test.zig`) already asserts + `#foreign` ≡ `extern`/`export` byte-identical IR for fn / global / Obj-C class, and + it's now TRIVIALLY true for the fn path (both build the same AST). Decide whether 5.1 + needs anything beyond the existing gate — likely just confirm/annotate it covers the + post-flip world (the fn path no longer has two distinct shapes to compare). Could fold + into a doc note rather than a new test. +- **Phases 6–7** (`refactor` batches, empty snapshot diff per batch): migrate the + stdlib + examples from `#foreign` spelling to `extern`. Because the AST is already + unified, this is a pure SOURCE rename (`… #foreign LIB "sym";` → `… extern LIB "sym";` + for fns; the global/const forms similarly), and IR/output must be byte-identical per + batch. NOTE: `c_import.zig` auto-synthesis (`#import c {#include}`) still BUILDS + `foreign_expr` bodies internally — that's a compiler-internal path, migrated separately + (likely Phase 8/9 area), not a source-spelling change. +- **Then Phase 8** (cutover: hard-reject the `#foreign` keyword) and **Phase 9** (purge + all `foreign` identifiers — needs Decision 5 [done, `Runtime*Class*`] + Decision 6 + [open, historical carve-out]). -**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 +**Watch items carried forward:** +- `c_import.zig:262` auto-synthesis still emits `foreign_expr` — both shapes coexist + until that path is migrated; keep every `body.data == .foreign_expr` reader dual + (checked exhaustively this stream). +- const-with-type `#foreign` parser path (`parser.zig:316`) is still on `foreign_expr` + but DEAD (registers no const); migrate or delete it at the Phase 8 cutover. +- The `decl.zig:2055` "foreign symbol … already bound" dedupe message is keyword-neutral + and fires for both forms — no churn, but reword to "extern" at cutover for consistency. 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: @@ -243,8 +263,11 @@ 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 +- **Decision 7 RATIFIED** (user, 2026-06-15): **accept the churn** — `#foreign`-spelled + decls produce `extern`-worded diagnostics; example 1620 regenerated (only snapshot moved). + Aligns with Part B's extern-only end state; the interim oddity is cosmetic and removed at + the Phase 8 cutover. Landed in the fn-body flip `6b94bb6`. (Original framing below.) + — interim diagnostic wording for `#foreign`-spelled decls (gated 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 @@ -255,6 +278,13 @@ Part A ratified (bare / postfix / `⇒ callconv(.c)` / lib-separate). Part B: deleted at cutover). Affects only diagnostic wording — IR/behavior identical either way. ## Log +- (5.0 fn-body flip) **PHASE 5.0 PARSER ROUTING COMPLETE.** Flipped the fn-body + `#foreign` parser arm (`parser.zig:~2062`) onto the extern AST (empty-block body + + `extern_export = .extern_` + extern_lib/extern_name); `extern_export` made `var` so + the body arm can route onto it. Updated the parser unit test to assert the extern + shape. Behavior-preserving via the four prereqs; only example 1620's lib-ref message + churned ("#foreign library"→"extern library", Decision 7, hand-edited). Suite green + (647 corpus / 444 unit). `refactor` `6b94bb6`. - (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`.