docs(ffi-linkage): checkpoint — Phase 5.0 parser routing COMPLETE (fn-body flip landed)
fn-decl #foreign body marker now builds the unified extern AST. All four #foreign parser paths resolved (global + fn-body flipped; const-with-type dead; runtime-class already coalesced). Decision 7 ratified (accept churn). Next: Phase 5.1 (confirm A->B gate covers post-flip) then Phases 6-7 (source #foreign->extern rename in stdlib + examples).
This commit is contained in:
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user