fix(ffi-linkage): Phase 5.0 prereq — validate extern LIB refs like #foreign

checkForeignRefs now reads a library reference from either spelling — the
legacy #foreign body (foreign_expr.library_ref) or the new extern keyword
(extern_lib) — and validates both against the declared #library / #import c
units. The diagnostic names the surface keyword the user wrote (#foreign vs
extern), so example 1620 (#foreign) is byte-unchanged and example 1231
(extern) gets the parallel 'extern library ... not declared'. Greens 1231.

647 corpus / 444 unit, 0 failed.
This commit is contained in:
agra
2026-06-15 03:53:03 +03:00
parent 38c32400f5
commit ad6aed3d7a

View File

@@ -332,10 +332,20 @@ fn checkForeignRefs(valid: *const std.StringHashMap(void), decls: []const *Node,
for (decls) |d| {
switch (d.data) {
.fn_decl => |fd| {
if (fd.body.data != .foreign_expr) continue;
const ref = fd.body.data.foreign_expr.library_ref orelse continue;
// 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,
};
if (!valid.contains(ref)) {
diags.addFmt(.err, d.span, "#foreign library '{s}' is not declared; expected a #library constant or a named '#import c' unit", .{ref});
diags.addFmt(.err, d.span, "{s} library '{s}' is not declared; expected a #library constant or a named '#import c' unit", .{ kw, ref });
}
},
.namespace_decl => |ns| checkForeignRefs(valid, ns.decls, diags),