ffi issue-0049: resolveParamType + packVariadicCallArgs unwrap new-form slice

Both helpers now detect when a variadic param's declared type is
already a slice (`..name: []T`) and use it as the element-shape
container directly, instead of wrapping it once more. The legacy
form (`name: ..T`) still wraps as before. Without the unwrap, the
new-form `..parts: []string` ends up with a callee-side slot type
of `[]([]string)`, while the call-site marshal pack emits a
`[N x string]` array, and downstream LLVM emission crashes on
the resulting null Refs (`LLVMBuildExtractValue` inside
`emitStrCmp`).

`examples/121-ios-sim-bundle.sx` (which exercises stdlib's
migrated `path_join`) and the focused regression
`examples/174-new-form-variadic-cross-module.sx` both flip green;
suite stays at 214/214. The remaining stdlib decls (`format` /
`print` / `open`) and example fixtures land in the follow-up
migration commit.
This commit is contained in:
agra
2026-05-27 21:29:53 +03:00
parent 64dcbca06a
commit b5301c4228

View File

@@ -7677,13 +7677,22 @@ pub const Lowering = struct {
if (fd.body.data == .foreign_expr and fd.params.len > 0 and fd.params[fd.params.len - 1].is_variadic) {
return;
}
// Find variadic param index
// Find variadic param index. The two surface forms differ in
// what `p.type_expr` resolves to: legacy `name: ..T` declares T
// (element type), new `..name: []T` declares []T (already a
// slice). Unwrap the latter so the per-element packing below
// sees T in both cases.
var variadic_idx: ?usize = null;
var elem_ty: TypeId = .any;
for (fd.params, 0..) |p, i| {
if (p.is_variadic) {
variadic_idx = i;
elem_ty = self.resolveTypeWithBindings(p.type_expr);
const declared = self.resolveTypeWithBindings(p.type_expr);
elem_ty = declared;
if (!declared.isBuiltin()) {
const info = self.module.types.get(declared);
if (info == .slice) elem_ty = info.slice.element;
}
break;
}
}
@@ -9999,12 +10008,23 @@ pub const Lowering = struct {
}
fn resolveParamType(self: *Lowering, p: *const ast.Param) TypeId {
const elem_ty = self.resolveTypeWithBindings(p.type_expr);
const declared_ty = self.resolveTypeWithBindings(p.type_expr);
if (p.is_variadic) {
// Variadic param (..T) → receives a []T slice
return self.module.types.sliceOf(elem_ty);
// Two surface forms:
// - legacy `name: ..T` — declared_ty is the element type;
// wrap to receive a `[]T` slice.
// - new `..name: []T` — declared_ty is already the slice
// type; use it as-is. Wrapping here would double up to
// `[][]T` and downstream LLVM emission crashes when the
// caller's argument-marshal pack produces a `[]T` that
// doesn't match the callee's stored param shape.
if (!declared_ty.isBuiltin()) {
const info = self.module.types.get(declared_ty);
if (info == .slice) return declared_ty;
}
return self.module.types.sliceOf(declared_ty);
}
return elem_ty;
return declared_ty;
}
fn resolveType(self: *Lowering, type_ann: *const Node) TypeId {