refactor(ffi-linkage): Phase 9.1d — eliminate the foreign_expr AST node

The last linkage-family 'foreign' carrier. Migrated c_import.zig auto-synthesis
(#import c {#include}) to build the extern shape (empty-block body + extern_export
= .extern_) instead of a foreign_expr body — the Phase 5.0 fn-body flip applied to
auto-synth. With nothing left building it, deleted the foreign_expr union variant +
ForeignExpr struct (ast.zig) and every reader: the dead-arm switch cases (sema,
resolver, generic, call, semantic_diagnostics, lsp), the coalescing reads in
decl.zig (is_foreign local, cc/rename/dedup/variadic/visibility gates) + pack.zig,
and checkForeignRefs (now reads extern_lib only). 9.1 LINKAGE PURGE COMPLETE — all
that remains in src/ is the runtime-class family (9.2) + comments. Snapshot-neutral
(the #import c examples 1215/1216/1217 + sqlite 1624 exercise the synth path); suite
green (646 corpus / 444 unit, 0 failed).
This commit is contained in:
agra
2026-06-15 08:54:56 +03:00
parent 98264b8640
commit 7ffdc7d2a2
10 changed files with 55 additions and 88 deletions

View File

@@ -259,11 +259,13 @@ pub fn processCImport(
else
try mapCTypeToSxNode(allocator, ret_str);
// Create foreign_expr body (no library_ref — symbols resolved at runtime)
const foreign_body = try allocator.create(Node);
foreign_body.* = .{
// Extern-import body: an empty block + `extern_export = .extern_` (no
// LIB / csym — symbols resolve at runtime). Same shape the postfix
// `extern` keyword produces; lowering reads `extern_export`.
const extern_body = try allocator.create(Node);
extern_body.* = .{
.span = .{ .start = 0, .end = 0 },
.data = .{ .foreign_expr = .{ .library_ref = null, .c_name = null } },
.data = .{ .block = .{ .stmts = &.{}, .produces_value = false } },
};
const fn_node = try allocator.create(Node);
@@ -273,9 +275,10 @@ pub fn processCImport(
.name = name,
.params = try params.toOwnedSlice(allocator),
.return_type = ret_node,
.body = foreign_body,
// A foreign C function whose own NAME collides with a reserved
// type spelling (`int i2(int);`) is RAW — exempt from the
.body = extern_body,
.extern_export = .extern_,
// A C function whose own NAME collides with a reserved type
// spelling (`int i2(int);`) is RAW — exempt from the
// reserved-type-name decl check so generated bindings import
// without hand-edits.
.is_raw = true,
@@ -332,20 +335,12 @@ fn checkForeignRefs(valid: *const std.StringHashMap(void), decls: []const *Node,
for (decls) |d| {
switch (d.data) {
.fn_decl => |fd| {
// A library reference rides on the legacy `#foreign` body
// (foreign_expr.library_ref) OR the new `extern LIB` keyword
// (extern_lib); both must name a declared #library / #import c
// unit. The diagnostic names the surface keyword the user wrote
// so the two spellings stay self-describing.
const kw: []const u8, const ref: []const u8 = switch (fd.body.data) {
.foreign_expr => |fe| .{ "#foreign", fe.library_ref orelse continue },
else => if (fd.extern_export == .extern_)
.{ "extern", fd.extern_lib orelse continue }
else
continue,
};
// A library reference rides on the `extern LIB` keyword
// (extern_lib); it must name a declared #library / #import c unit.
if (fd.extern_export != .extern_) continue;
const ref = fd.extern_lib orelse continue;
if (!valid.contains(ref)) {
diags.addFmt(.err, d.span, "{s} library '{s}' is not declared; expected a #library constant or a named '#import c' unit", .{ kw, ref });
diags.addFmt(.err, d.span, "extern library '{s}' is not declared; expected a #library constant or a named '#import c' unit", .{ref});
}
},
.namespace_decl => |ns| checkForeignRefs(valid, ns.decls, diags),