refactor(ffi-linkage): Phase 5.0 — flip fn-decl #foreign body marker onto the extern AST
The fn-body `#foreign [LIB] ["csym"]` marker now builds the SAME shape postfix `extern` produces — extern_export = .extern_ + extern_lib/extern_name + an empty-block body — instead of a `foreign_expr` body. With all four prereqs landed (visibility, variadic, plain-free classification, lib-ref validation), every downstream reader coalesces is_foreign with extern_export, so the IR and runtime behavior are byte-identical (full corpus + the A->B gate stay green). The surface keyword is no longer on the AST, so a `#foreign`-spelled decl now yields `extern`-worded diagnostics — the single accepted churn (Decision 7): example 1620's lib-ref error flips '#foreign library' -> 'extern library'. Parser-surface diagnostics (conflict/expected-token) fire on the literal keyword and are unaffected. c_import auto-synthesis still emits foreign_expr bodies (not this step), so both shapes still coexist. Parser unit test updated to assert the extern shape. 647 corpus / 444 unit, 0 failed. The const-with-type (dead) + runtime-class (already coalesced) paths need no flip — Phase 5.0 parser routing is complete.
This commit is contained in:
@@ -2004,7 +2004,9 @@ pub const Parser = struct {
|
||||
const call_conv = try self.parseOptionalCallConv();
|
||||
|
||||
// Optional postfix linkage modifier: `extern` (import) / `export` (define).
|
||||
const extern_export = self.parseOptionalExternExport();
|
||||
// `var` because the fn-body `#foreign` marker (an alias for postfix
|
||||
// `extern`) routes onto `.extern_` from inside the body chain below.
|
||||
var extern_export = self.parseOptionalExternExport();
|
||||
|
||||
// `extern` and `export` are mutually exclusive — one declaration is either
|
||||
// an import or a definition, never both. Reject the redundant second keyword
|
||||
@@ -2061,27 +2063,33 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
break :blk try self.createNode(ci_start, .{ .compiler_expr = {} });
|
||||
} else if (self.current.tag == .hash_foreign) blk: {
|
||||
// Phase 5.0 (Part B): the fn-body `#foreign` marker is an alias for
|
||||
// postfix `extern` — build the SAME extern shape (empty-block body +
|
||||
// `extern_export = .extern_` carrying the optional `[LIB] ["csym"]`
|
||||
// on extern_lib/extern_name) the `extern` keyword produces above, so
|
||||
// every downstream reader sees ONE form. Lowering + all gates
|
||||
// coalesce `is_foreign` with `extern_export` (decl.zig declareFunction
|
||||
// / visibility, pack.zig variadic, resolver/generic plain-free,
|
||||
// c_import lib-ref). The surface keyword is gone from the AST here, so
|
||||
// a `#foreign`-spelled decl now yields `extern`-worded diagnostics
|
||||
// (Decision 7 — accepted interim churn until the Phase 8 cutover).
|
||||
const fi_start = self.current.loc.start;
|
||||
self.advance();
|
||||
// Required: library reference (identifier)
|
||||
// Optional: library reference (identifier).
|
||||
var lib_ref: ?[]const u8 = null;
|
||||
if (self.current.tag == .identifier) {
|
||||
lib_ref = self.tokenSlice(self.current);
|
||||
extern_lib = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
}
|
||||
// Optional: C symbol name (string literal)
|
||||
var c_name: ?[]const u8 = null;
|
||||
// Optional: C symbol name (string literal).
|
||||
if (self.current.tag == .string_literal) {
|
||||
const raw = self.tokenSlice(self.current);
|
||||
c_name = raw[1 .. raw.len - 1];
|
||||
extern_name = raw[1 .. raw.len - 1];
|
||||
self.advance();
|
||||
}
|
||||
try self.expect(.semicolon);
|
||||
break :blk try self.createNode(fi_start, .{ .foreign_expr = .{
|
||||
.library_ref = lib_ref,
|
||||
.c_name = c_name,
|
||||
} });
|
||||
extern_export = .extern_;
|
||||
const stmts = try self.allocator.alloc(*Node, 0);
|
||||
break :blk try self.createNode(fi_start, .{ .block = .{ .stmts = stmts, .produces_value = false } });
|
||||
} else if (self.current.tag == .fat_arrow) blk: {
|
||||
is_arrow = true;
|
||||
self.advance();
|
||||
@@ -4254,6 +4262,9 @@ test "parse void function with builtin body" {
|
||||
}
|
||||
|
||||
test "parse void function with foreign body" {
|
||||
// Phase 5.0 (Part B): the fn-body `#foreign` marker now builds the unified
|
||||
// `extern` shape (extern_export = .extern_ + extern_lib, empty-block body),
|
||||
// NOT a `foreign_expr` body — an alias for postfix `extern LIB`.
|
||||
const source = "InitWindow :: (width: i32, height: i32, title: *u8) -> void #foreign rl;";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -4263,8 +4274,10 @@ test "parse void function with foreign body" {
|
||||
const decl = root.data.root.decls[0];
|
||||
try std.testing.expect(decl.data == .fn_decl);
|
||||
try std.testing.expectEqualStrings("InitWindow", decl.data.fn_decl.name);
|
||||
try std.testing.expect(decl.data.fn_decl.body.data == .foreign_expr);
|
||||
try std.testing.expectEqualStrings("rl", decl.data.fn_decl.body.data.foreign_expr.library_ref.?);
|
||||
try std.testing.expectEqual(ast.ExternExportModifier.extern_, decl.data.fn_decl.extern_export);
|
||||
try std.testing.expectEqualStrings("rl", decl.data.fn_decl.extern_lib.?);
|
||||
try std.testing.expect(decl.data.fn_decl.body.data == .block);
|
||||
try std.testing.expectEqual(@as(usize, 0), decl.data.fn_decl.body.data.block.stmts.len);
|
||||
try std.testing.expectEqual(@as(usize, 3), decl.data.fn_decl.params.len);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user