refactor(ffi-linkage): Phase 5.0 — route #foreign global decl onto extern AST

Part B begins: `#foreign` becomes an alias for `extern`. First of the four
`#foreign` parser paths to migrate — the data-global form
(`name : T #foreign [lib] ["csym"];`). It now builds the SAME extern-named
VarDecl (`is_extern`/`extern_lib`/`extern_name`) that the postfix `extern`
global path already produces, instead of `is_foreign`/`foreign_lib`/`foreign_name`.

Behavior-preserving: lowering coalesces the two forms identically — the symbol
name is `extern_name orelse foreign_name orelse name` (decl.zig:1119), and both
`is_foreign` and `is_extern` feed the same `.is_extern` IR flag + early-return
(decl.zig:1127,1141). The A->B gate already proved fn/global/class lower to
byte-identical IR, so the corpus locks this with zero snapshot churn.

Suite green: 10/10 steps, 444/444 unit, 643 corpus, 0 failed.

The fn-decl, const-with-type, and runtime-class `#foreign` paths still build the
legacy AST; they migrate next (the fn path needs the deferred visibility-gate +
variadic alignment first).
This commit is contained in:
agra
2026-06-14 20:31:50 +03:00
parent 9ad04e2dda
commit e5ddfbe09a

View File

@@ -424,6 +424,10 @@ pub const Parser = struct {
if (self.current.tag == .hash_foreign) {
// name : type #foreign [lib] ["c_name"]; (extern global from libsystem etc.)
// Phase 5.0: `#foreign` is an alias for `extern`. Build the SAME
// extern-named AST the postfix `extern` global path builds below —
// lowering coalesces is_foreign/is_extern identically (decl.zig:1127,
// 1141), so this is behavior-preserving (snapshots unchanged).
self.advance();
var lib_ref: ?[]const u8 = null;
if (self.current.tag == .identifier) {
@@ -442,9 +446,9 @@ pub const Parser = struct {
.name_span = name_span,
.type_annotation = type_node,
.value = null,
.is_foreign = true,
.foreign_lib = lib_ref,
.foreign_name = c_name,
.is_extern = true,
.extern_lib = lib_ref,
.extern_name = c_name,
.is_raw = name_is_raw,
} });
}