docs(ffi-linkage): checkpoint — Phase 5.0 visibility-gate prereq DONE; const-with-type/runtime-class findings
- Mark deferred prereq (b) visibility-gate equivalence CLOSED (1228). - Record const-with-type as a dead path (deferred per user) and the runtime-class prefix as already-coalesced (no Phase 5.0 change). - Next step is the fn-path variadic prerequisite.
This commit is contained in:
@@ -5,15 +5,32 @@ Companion to `current/PLAN-EXTERN-EXPORT.md` — one merged plan: **Part A** add
|
|||||||
every commit, one step at a time per the cadence rule.
|
every commit, one step at a time per the cadence rule.
|
||||||
|
|
||||||
## Last completed step
|
## Last completed step
|
||||||
**Phase 5.0 (global path)** (`refactor` lock, commit `e5ddfbe`) — **PART B STARTED.**
|
**Phase 5.0 prereq — visibility-gate equivalence** (xfail `717c35d` → fix
|
||||||
|
`7d8ba1a`) — the first of the two deferred fn-path prerequisites is now LOCKED.
|
||||||
|
The non-transitive C-import visibility gate (`isVisible(.c_import_bare)`,
|
||||||
|
`decl.zig:2249`) used to recognise only the legacy `#foreign` body shape; a bare
|
||||||
|
`extern` fn (empty-block body + `extern_export == .extern_`) escaped the gate via
|
||||||
|
the `body != foreign_expr → return true` arm and was caught only by the general
|
||||||
|
`isNameVisible` gate — yielding the generic "not visible" wording instead of the
|
||||||
|
C-specific "C function not visible; add #import" one. Now BOTH lib-less spellings
|
||||||
|
route to `visibleOverEdges`, and a library-bound `extern LIB` (like `#foreign LIB`)
|
||||||
|
stays unconditionally visible — so a future fn-decl `#foreign`→`extern` migration
|
||||||
|
is byte-identical at this gate. New cross-module example **1228**
|
||||||
|
(`examples/1228-ffi-extern-c-non-transitive`, main → b → c) pins the equivalence:
|
||||||
|
referencing c's lib-less `#foreign` AND `extern` twins transitively both produce
|
||||||
|
the identical C-specific diagnostic. Suite green (644 corpus / 444 unit, 0 failed).
|
||||||
|
**Empirical finding** (probe, not yet acted on): the bare-extern twin was NEVER a
|
||||||
|
silent visibility hole — the general `isNameVisible` gate already denied it; only
|
||||||
|
the *diagnostic wording* diverged. The fix aligns the wording + gate ownership.
|
||||||
|
|
||||||
|
### Prior: Phase 5.0 (global path) (`refactor` lock, commit `e5ddfbe`) — **PART B STARTED.**
|
||||||
First of the four `#foreign` parser paths migrated onto the extern AST: the
|
First of the four `#foreign` parser paths migrated onto the extern AST: the
|
||||||
data-global form `name : T #foreign [lib] ["csym"];` now builds the same
|
data-global form `name : T #foreign [lib] ["csym"];` now builds the same
|
||||||
extern-named `VarDecl` (`is_extern`/`extern_lib`/`extern_name`) that postfix
|
extern-named `VarDecl` (`is_extern`/`extern_lib`/`extern_name`) that postfix
|
||||||
`extern` already produces, instead of `is_foreign`/`foreign_lib`/`foreign_name`.
|
`extern` already produces, instead of `is_foreign`/`foreign_lib`/`foreign_name`.
|
||||||
Behavior-preserving — lowering coalesces both forms identically
|
Behavior-preserving — lowering coalesces both forms identically
|
||||||
(`decl.zig:1119,1127,1141`), so zero snapshot churn. Suite green (10/10 steps,
|
(`decl.zig:1119,1127,1141`), so zero snapshot churn. The fn-decl, const-with-type,
|
||||||
444/444 unit, 643 corpus, 0 failed). The fn-decl, const-with-type, and
|
and runtime-class `#foreign` paths still build the legacy AST.
|
||||||
runtime-class `#foreign` paths still build the legacy AST.
|
|
||||||
|
|
||||||
### Prior: Phase 4 (green) — **PHASE 4 COMPLETE → PART A DONE; GATE A→B LOCKED.** Four pieces:
|
### Prior: Phase 4 (green) — **PHASE 4 COMPLETE → PART A DONE; GATE A→B LOCKED.** Four pieces:
|
||||||
(1) **GATE A→B unit test** (`lower.test.zig`, `lowerSrcToIr` helper + "GATE A→B" test) —
|
(1) **GATE A→B unit test** (`lower.test.zig`, `lowerSrcToIr` helper + "GATE A→B" test) —
|
||||||
@@ -82,23 +99,37 @@ AOT), 1227 (export fn rename, AOT), 1348 (objc extern class), 1349 (objc export
|
|||||||
(jni extern class), 1174/1175 (interplay diagnostics).
|
(jni extern class), 1174/1175 (interplay diagnostics).
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
**PART B — Phase 5.0, remaining three `#foreign` parser paths.** The global path
|
**PART B — Phase 5.0, fn-path variadic prerequisite** (the SECOND of the two
|
||||||
(`parser.zig:425`) is done (commit `e5ddfbe`). Remaining:
|
deferred fn-path prereqs; visibility-gate equivalence is now DONE). The
|
||||||
- **const-with-type** (`parser.zig:316`, `name :: type_expr #foreign …`) — builds a
|
`is_variadic` drop in `declareFunction` is gated on `is_foreign` only, so a
|
||||||
`const_decl` whose value is a `foreign_expr` node. Lowest-risk of the three; route
|
migrated variadic `extern` (e.g. `printf`) would lose its `...` tail. Extend the
|
||||||
to the extern-named shape used by the fn path. Confirm the value-node lowering path
|
gate to cover `extern_export == .extern_`. Cadence: xfail an `extern` variadic fn
|
||||||
coalesces with extern before committing.
|
losing its `...` (locks the gap), then the next commit fixes the gate. Find the
|
||||||
- **fn-decl body marker** (`parser.zig:2059`) — **needs prerequisites first**: (a) the
|
exact site via `grep -n "is_variadic" src/ir/lower/decl.zig` (the checkpoint's old
|
||||||
deferred **visibility-gate** alignment (`decl.zig:2254` — a lib-less `extern` must be
|
`decl.zig:2097` line ref may have drifted). AFTER both prereqs land, the fn-decl
|
||||||
policed like a lib-less `#foreign`; currently bare `extern` is unconditionally visible),
|
`#foreign` body-marker path (`parser.zig:~2062`) can migrate onto `extern`.
|
||||||
which needs a **cross-module example** to lock; (b) **variadic** handling — the
|
|
||||||
`is_variadic` drop at `decl.zig:2097` is gated on `is_foreign` only, so a migrated
|
**Investigation findings (this session — reorder the remaining paths):**
|
||||||
variadic `#foreign` (e.g. `printf`) would lose its `...` tail unless extended to
|
- **const-with-type** (`parser.zig:316`, `name :: type_expr #foreign …`) is a
|
||||||
`is_extern_decl`. Do these (xfail/lock) BEFORE routing the fn path.
|
**DEAD path**: it builds `const_decl{value = foreign_expr}`, but
|
||||||
- **runtime-class prefix** (`parser.zig:1305`, `#foreign #objc_class/#jni_class`) — route
|
`registerTypedModuleConst` (`decl.zig:848-851`) bails on a `foreign_expr` value
|
||||||
the prefix to the same `is_foreign_eff` the postfix `extern` already feeds
|
(`else => return`), so it registers no const and emits no symbol — a probe
|
||||||
(`parseForeignClassDecl`); the class node keeps its `is_foreign` field (renamed to a
|
(`g_abs :: FP #foreign "abs";`) returns `unresolved 'g_abs'` at the use site, and
|
||||||
`runtime*` axis only in Phase 9.2, not `extern`).
|
the form is used NOWHERE in `library`/`examples`/`issues`. Its migration target is
|
||||||
|
ambiguous because the `foreign_expr` value node is SHARED with the fn-decl path,
|
||||||
|
which isn't migrated yet. **Decision (user, 2026-06-14): defer it — migrate it
|
||||||
|
alongside the fn-decl path once `foreign_expr`'s extern shape is decided.** The
|
||||||
|
checkpoint's old "lowest-risk, route to the extern-named shape" note is wrong: the
|
||||||
|
"confirm the value-node lowering path coalesces" gate can't be met (nothing lowers it).
|
||||||
|
- **runtime-class prefix** (`parser.zig:~1351`, `#foreign #objc_class/#jni_class`) is
|
||||||
|
**ALREADY coalesced**: both prefix `#foreign` and postfix `extern` feed the single
|
||||||
|
`is_foreign_eff`→`is_foreign` field on `foreign_class_decl` (`parser.zig:1421-1432`),
|
||||||
|
so there is NO Phase 5.0 AST change for it — only the Phase 9.2 `Runtime*Class*`
|
||||||
|
rename remains. Drop it from the Phase 5.0 path list.
|
||||||
|
|
||||||
|
So Phase 5.0's real remaining work collapses to: the fn-path variadic prereq, then
|
||||||
|
the fn-decl `#foreign` body-marker migration. const-with-type + runtime-class need
|
||||||
|
no standalone Phase 5.0 commit.
|
||||||
|
|
||||||
Then Phase 5.1 (`lock`): unit test that `#foreign` and `extern` produce identical IR (the
|
Then Phase 5.1 (`lock`): unit test that `#foreign` and `extern` produce identical IR (the
|
||||||
A→B gate already covers fn/global/class — extend or reuse `lowerSrcToIr`). Then Phases 6–7
|
A→B gate already covers fn/global/class — extend or reuse `lowerSrcToIr`). Then Phases 6–7
|
||||||
@@ -127,13 +158,12 @@ milestone, NOT Phase 2/3 scope. The AOT `.aot`-marker corpus mode is the pragmat
|
|||||||
path and works today. Spike fully reverted (target.zig/main.zig at HEAD).
|
path and works today. Spike fully reverted (target.zig/main.zig at HEAD).
|
||||||
|
|
||||||
**Deferred (carry into Part B):** (a) ~~docs~~ — DONE in Phase 4 (`specs.md`/`readme.md`
|
**Deferred (carry into Part B):** (a) ~~docs~~ — DONE in Phase 4 (`specs.md`/`readme.md`
|
||||||
document `extern`/`export`; `#foreign` stays until the Part B cutover); (b) visibility-gate
|
document `extern`/`export`; `#foreign` stays until the Part B cutover); (b) ~~visibility-gate
|
||||||
equivalence — bare `extern` (no `extern_lib`)
|
equivalence~~ — **DONE** (`717c35d`/`7d8ba1a`): the `c_import_bare` gate now polices a
|
||||||
is currently unconditionally visible via the `c_import_bare` gate
|
lib-less `extern` fn identically to its lib-less `#foreign` twin (same C-specific
|
||||||
(`decl.zig:~2241`, `fd.body.data != .foreign_expr → return true`), whereas a lib-less
|
diagnostic); a library-bound `extern LIB` stays unconditionally visible. Locked by the
|
||||||
`#foreign` is policed by `visibleOverEdges`. Single-file examples don't exercise this;
|
cross-module example 1228. (Empirical: the bare-extern twin was never a silent hole — the
|
||||||
verify/align it at the A→B gate (a bare-extern lib-less fn should be policed like its
|
general `isNameVisible` gate already denied it; only the diagnostic wording diverged.)
|
||||||
`#foreign` twin). Adding it now would be untested — needs a cross-module example.
|
|
||||||
|
|
||||||
## Open decisions
|
## Open decisions
|
||||||
Part A ratified (bare / postfix / `⇒ callconv(.c)` / lib-separate). Part B:
|
Part A ratified (bare / postfix / `⇒ callconv(.c)` / lib-separate). Part B:
|
||||||
@@ -144,6 +174,19 @@ Part A ratified (bare / postfix / `⇒ callconv(.c)` / lib-separate). Part B:
|
|||||||
NOT confirm this at the Part A milestone; confirm before Phase 9.
|
NOT confirm this at the Part A milestone; confirm before Phase 9.
|
||||||
|
|
||||||
## Log
|
## Log
|
||||||
|
- (5.0 prereq vis xfail) Added cross-module example `1228-ffi-extern-c-non-transitive`
|
||||||
|
(main → b → c). Main references c's lib-less `#foreign` + `extern` twins
|
||||||
|
transitively; expected snapshot pins the DESIRED equivalent C-specific
|
||||||
|
diagnostic for both. RED (extern twin gets the generic "not visible" wording —
|
||||||
|
443/444). `test`/xfail commit `717c35d`; the fix greens it.
|
||||||
|
- (5.0 prereq vis fix) Extended `isVisible(.c_import_bare)` (`decl.zig:2249`) to
|
||||||
|
switch on the body: a `foreign_expr` body OR an `extern_export == .extern_` decl
|
||||||
|
with no lib both route to `visibleOverEdges`; a library-bound decl stays
|
||||||
|
unconditionally visible. 1228 green — both twins emit "C function not visible".
|
||||||
|
Suite green (644 corpus / 444 unit, 0 failed). `fix`/green commit `7d8ba1a`.
|
||||||
|
**Deferred prereq (b) CLOSED.** Investigation this session also found
|
||||||
|
const-with-type is a DEAD parser path (defer per user) and the runtime-class
|
||||||
|
prefix is already coalesced (no Phase 5.0 change) — see Next step.
|
||||||
- (5.0 global) **PART B STARTED.** Routed the `#foreign` data-global parser path
|
- (5.0 global) **PART B STARTED.** Routed the `#foreign` data-global parser path
|
||||||
(`parser.zig:425`) onto the extern-named `VarDecl` (`is_extern`/`extern_lib`/
|
(`parser.zig:425`) onto the extern-named `VarDecl` (`is_extern`/`extern_lib`/
|
||||||
`extern_name`) — the same AST postfix `extern` builds. Behavior-preserving
|
`extern_name`) — the same AST postfix `extern` builds. Behavior-preserving
|
||||||
|
|||||||
Reference in New Issue
Block a user