fix(ffi-linkage): Phase 5.0 prereq — exclude extern imports from plain-free-fn classification

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.
This commit is contained in:
agra
2026-06-15 03:46:34 +03:00
parent 270652186e
commit 3c94c14b5e
2 changed files with 14 additions and 4 deletions

View File

@@ -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 /// A plain free function: no type params (not generic) and an ordinary sx
/// body (not `#foreign` / `#builtin` / `#compiler`). Only these get an /// body (not `#foreign` / `#builtin` / `#compiler` / `extern`). Only these get
/// out-of-line identity-addressable slot — the bare-call disambiguation /// an out-of-line identity-addressable slot — the bare-call disambiguation
/// and the shadow-author lowering pass leave every other shape /// and the shadow-author lowering pass leave every other shape
/// to the existing name-keyed dispatch. /// to the existing name-keyed dispatch.
pub fn isPlainFreeFn(fd: *const ast.FnDecl) bool { pub fn isPlainFreeFn(fd: *const ast.FnDecl) bool {
if (fd.type_params.len > 0) return false; 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) { return switch (fd.body.data) {
.foreign_expr, .builtin_expr, .compiler_expr => false, .foreign_expr, .builtin_expr, .compiler_expr => false,
else => true, else => true,

View File

@@ -173,10 +173,16 @@ pub fn fnDeclOf(raw: RawDeclRef) ?*const ast.FnDecl {
} }
/// A PLAIN free function — no type params, an ordinary (non-`#foreign`/ /// A PLAIN free function — no type params, an ordinary (non-`#foreign`/
/// `#builtin`/`#compiler`) body — the only callable kind the bare-call verdict /// `#builtin`/`#compiler`/`extern`) body — the only callable kind the bare-call
/// counts. /// verdict counts.
pub fn isPlainFreeFnDecl(fd: *const ast.FnDecl) bool { pub fn isPlainFreeFnDecl(fd: *const ast.FnDecl) bool {
if (fd.type_params.len > 0) return false; 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) { return switch (fd.body.data) {
.foreign_expr, .builtin_expr, .compiler_expr => false, .foreign_expr, .builtin_expr, .compiler_expr => false,
else => true, else => true,