From 7d8ba1aabc52e126eb72af006d28663b5d18fe6b Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 14 Jun 2026 20:57:19 +0300 Subject: [PATCH] =?UTF-8?q?fix(ffi-linkage):=20Phase=205.0=20prereq=20?= =?UTF-8?q?=E2=80=94=20police=20lib-less=20extern=20like=20#foreign=20in?= =?UTF-8?q?=20c=5Fimport=5Fbare=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The non-transitive C-import visibility gate (`isVisible(.c_import_bare)`) only recognised the legacy `#foreign` body shape; a bare `extern` fn (empty-block body + extern_export == .extern_) escaped 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 a library-bound `#foreign LIB`) stays unconditionally visible. This makes a future fn-decl `#foreign`->`extern` migration byte-identical at this gate. Greens example 1228. 644 corpus / 444 unit, 0 failed. --- src/ir/lower/decl.zig | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/ir/lower/decl.zig b/src/ir/lower/decl.zig index bc963f5..8457fb3 100644 --- a/src/ir/lower/decl.zig +++ b/src/ir/lower/decl.zig @@ -2247,13 +2247,28 @@ pub fn isVisible(self: *Lowering, name: []const u8, vis: resolver_mod.Visibility // this predicate is single-hop only. .impl_transitive => @panic("isVisible: transitive visibility is owned by findVisibleImpls"), .c_import_bare => { - // Foreign-C gate: only C-import fn_decls without a library_ref - // are policed; a non-foreign body or a library-bound foreign - // decl is unconditionally visible. + // Foreign-C gate: only a lib-less C-import fn_decl is policed; a + // library-bound decl (resolves via the named library, not a + // module edge) or a non-C body is unconditionally visible. The + // legacy `#foreign` form (a `foreign_expr` body) and the new + // `extern` keyword (`extern_export == .extern_`, empty-block body) + // are two spellings of the same lib-less C-symbol import, so BOTH + // route to `visibleOverEdges` here — a migrated `extern` decl must + // get the identical "C function not visible" diagnostic its + // `#foreign` twin did, not the generic top-level-name wording + // (FFI-linkage Part B; example 1228). const fd = self.program_index.fn_ast_map.get(name) orelse return true; - if (fd.body.data != .foreign_expr) return true; - if (fd.body.data.foreign_expr.library_ref != null) return true; - return self.visibleOverEdges(name); + switch (fd.body.data) { + .foreign_expr => |fe| { + if (fe.library_ref != null) return true; + return self.visibleOverEdges(name); + }, + else => { + if (fd.extern_export != .extern_) return true; + if (fd.extern_lib != null) return true; + return self.visibleOverEdges(name); + }, + } }, .user_bare_flat => return self.visibleOverEdges(name), }