From 3c94c14b5e3742c0a4c15a0fb011e902a75d7351 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 15 Jun 2026 03:46:34 +0300 Subject: [PATCH] =?UTF-8?q?fix(ffi-linkage):=20Phase=205.0=20prereq=20?= =?UTF-8?q?=E2=80=94=20exclude=20extern=20imports=20from=20plain-free-fn?= =?UTF-8?q?=20classification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isPlainFreeFn / isPlainFreeFnDecl excluded a #foreign body but classified an empty-block extern fn as a plain free function, so existing extern fns were wrongly counted in the bare-call ambiguity verdict (and eligible for the out-of-line-slot / shadow-author pass). Both predicates now also exclude extern_export == .extern_ (an external C symbol with no sx-lowerable body, name-keyed first-wins dispatch like #foreign); export keeps a real body and stays plain-free. Greens example 1230 — same-name extern authors compile like their #foreign twins (0729). 646 corpus / 444 unit, 0 failed. --- src/ir/lower/generic.zig | 8 ++++++-- src/ir/resolver.zig | 10 ++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ir/lower/generic.zig b/src/ir/lower/generic.zig index 212d46e..cba26cc 100644 --- a/src/ir/lower/generic.zig +++ b/src/ir/lower/generic.zig @@ -808,12 +808,16 @@ pub fn hasComptimeParams(fd: *const ast.FnDecl) bool { } /// A plain free function: no type params (not generic) and an ordinary sx -/// body (not `#foreign` / `#builtin` / `#compiler`). Only these get an -/// out-of-line identity-addressable slot — the bare-call disambiguation +/// body (not `#foreign` / `#builtin` / `#compiler` / `extern`). Only these get +/// an out-of-line identity-addressable slot — the bare-call disambiguation /// and the shadow-author lowering pass leave every other shape /// to the existing name-keyed dispatch. pub fn isPlainFreeFn(fd: *const ast.FnDecl) bool { if (fd.type_params.len > 0) return false; + // An `extern` import is an external C symbol with no sx-lowerable body — + // name-keyed first-wins dispatch like a `#foreign` body, never a plain free + // fn. `export` DEFINES a real body, so it stays plain-free. + if (fd.extern_export == .extern_) return false; return switch (fd.body.data) { .foreign_expr, .builtin_expr, .compiler_expr => false, else => true, diff --git a/src/ir/resolver.zig b/src/ir/resolver.zig index 11f0001..25fcc2f 100644 --- a/src/ir/resolver.zig +++ b/src/ir/resolver.zig @@ -173,10 +173,16 @@ pub fn fnDeclOf(raw: RawDeclRef) ?*const ast.FnDecl { } /// A PLAIN free function — no type params, an ordinary (non-`#foreign`/ -/// `#builtin`/`#compiler`) body — the only callable kind the bare-call verdict -/// counts. +/// `#builtin`/`#compiler`/`extern`) body — the only callable kind the bare-call +/// verdict counts. pub fn isPlainFreeFnDecl(fd: *const ast.FnDecl) bool { if (fd.type_params.len > 0) return false; + // An `extern` import is an external C symbol with no sx-lowerable body — + // dispatched name-keyed first-wins, exactly like a `#foreign` body, so it + // is NOT a plain free fn (excluded from the bare-call ambiguity verdict and + // the out-of-line-slot / shadow-author pass). `export` DEFINES a real sx + // body, so it stays plain-free. + if (fd.extern_export == .extern_) return false; return switch (fd.body.data) { .foreign_expr, .builtin_expr, .compiler_expr => false, else => true,