fix(ffi-linkage): Phase 5.0 prereq — map extern C-variadic tail to ... like #foreign

Two gates were keyed on the `#foreign` (foreign_expr) body shape only:
- declareFunction: the is_variadic drop (decl.zig) — a variadic extern
  kept its trailing slice param in the IR signature.
- packVariadicCallArgs: the call-site early-out (pack.zig) — extras were
  slice-packed instead of passed through the C `...` slot.

Both now also fire for `extern_export == .extern_`, so a variadic
`extern` drops the trailing `..args: []T`, sets is_variadic, and passes
extras through the C ABI with default argument promotion — byte-identical
to its `#foreign` twin. Greens example 1229.

645 corpus / 444 unit, 0 failed.
This commit is contained in:
agra
2026-06-14 21:05:40 +03:00
parent 9a2c78d6b9
commit 0fdc82154f
2 changed files with 14 additions and 5 deletions

View File

@@ -2094,7 +2094,12 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
const is_extern_decl = fd.extern_export == .extern_;
var is_variadic = false;
var effective_params = fd.params;
if (is_foreign and fd.params.len > 0 and fd.params[fd.params.len - 1].is_variadic) {
// The C-variadic `...` tail applies to BOTH lib-less C-import spellings:
// the legacy `#foreign` (foreign_expr body) and the new `extern` keyword.
// A migrated variadic `extern` must drop the trailing slice param and set
// the flag exactly as its `#foreign` twin did (mirrored at the call site
// by `packVariadicCallArgs`).
if ((is_foreign or is_extern_decl) and fd.params.len > 0 and fd.params[fd.params.len - 1].is_variadic) {
is_variadic = true;
effective_params = fd.params[0 .. fd.params.len - 1];
}

View File

@@ -296,10 +296,14 @@ pub fn lowerVariadicArgs(self: *Lowering, param_name: []const u8, call_args: []c
/// Detects variadic params in the function decl, packs remaining args into a typed slice,
/// and replaces the args list with [fixed_args..., slice_ref].
pub fn packVariadicCallArgs(self: *Lowering, fd: *const ast.FnDecl, c: *const ast.Call, args: *std.ArrayList(Ref)) void {
// `#foreign` variadic uses the C calling convention's `...` tail —
// extras are passed through directly with default argument promotion
// (handled at the call site), not packed into an sx slice.
if (fd.body.data == .foreign_expr and fd.params.len > 0 and fd.params[fd.params.len - 1].is_variadic) {
// A lib-less C-import variadic — `#foreign` (foreign_expr body) OR the new
// `extern` keyword — uses the C calling convention's `...` tail: extras are
// passed through directly with default argument promotion (handled at the
// call site), not packed into an sx slice. Mirrors the `is_variadic` drop
// in `declareFunction`.
if ((fd.body.data == .foreign_expr or fd.extern_export == .extern_) and
fd.params.len > 0 and fd.params[fd.params.len - 1].is_variadic)
{
return;
}
// Find variadic param index. The two surface forms differ in