From b5301c42288106a7051f73deadf2f8de56ae46ff Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 27 May 2026 21:29:53 +0300 Subject: [PATCH] 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. --- src/ir/lower.zig | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/ir/lower.zig b/src/ir/lower.zig index bc89881..ed82ab9 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -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 {