From 8f9c00dcdbde09d357b0046211f6b5918efb40a9 Mon Sep 17 00:00:00 2001 From: agra Date: Sat, 6 Jun 2026 14:28:00 +0300 Subject: [PATCH] fix(lower): resolved author drives variadic packing in bare call [0102c F1] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attempt-2 fix for the F1 review finding. After `resolveBareCallee` picks a shadowed same-name author's FuncId at a normal call site, the call path still re-fetched the FIRST-WINS function AST by name to drive variadic argument packing. When the resolved (shadow) author's variadic shape differs from the first-wins author's, arguments were packed against the WRONG signature — a fixed-arity shadow packed as if variadic, or a variadic shadow not packed at all — producing IR with the wrong argument count (LLVM verification failure). The `.func` arm now carries the resolved `*FnDecl` alongside its FuncId (`BareCallee.func: ResolvedAuthor`), so `packVariadicCallArgs` reads THE resolved author's signature. The rest of the arm already used the resolved FuncId's IR function (ret/params/ctx/coercion), so the callee now has one source of truth in the whole call lowering — no re-fetch by name after resolution. Default-arg / closure / UFCS / comptime *sites* remain first-wins (fix-0102d); `expandCallDefaults` runs before resolution and is a default site. Regression: examples/0726-modules-flat-same-name-variadic — two flat file imports each author `combine` and `pick` with OPPOSITE variadic shapes (a.sx fixed `combine` / variadic `pick`; b.sx variadic `combine` / fixed `pick`). Each module's bare call must pack against ITS OWN author. Fails on the pre-fix re-lookup (LLVM "Incorrect number of arguments passed to called function" for both `combine.1` and `pick.2`); passes after. Gate: zig build, zig build test (400/400), bash tests/run_examples.sh (463 passed) all green. Matrix 0722-0725/0727 unchanged; single-author / local resolution byte-for-byte unchanged (the `.func` arm never runs for them). --- .../0726-modules-flat-same-name-variadic.sx | 23 +++++++++++++++ .../0726-modules-flat-same-name-variadic/a.sx | 11 ++++++++ .../0726-modules-flat-same-name-variadic/b.sx | 12 ++++++++ .../0726-modules-flat-same-name-variadic.exit | 1 + ...726-modules-flat-same-name-variadic.stderr | 1 + ...726-modules-flat-same-name-variadic.stdout | 4 +++ src/ir/lower.test.zig | 2 +- src/ir/lower.zig | 28 +++++++++++++------ 8 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 examples/0726-modules-flat-same-name-variadic.sx create mode 100644 examples/0726-modules-flat-same-name-variadic/a.sx create mode 100644 examples/0726-modules-flat-same-name-variadic/b.sx create mode 100644 examples/expected/0726-modules-flat-same-name-variadic.exit create mode 100644 examples/expected/0726-modules-flat-same-name-variadic.stderr create mode 100644 examples/expected/0726-modules-flat-same-name-variadic.stdout diff --git a/examples/0726-modules-flat-same-name-variadic.sx b/examples/0726-modules-flat-same-name-variadic.sx new file mode 100644 index 0000000..55f84ee --- /dev/null +++ b/examples/0726-modules-flat-same-name-variadic.sx @@ -0,0 +1,23 @@ +// fix-0102c F1 (issue 0102): two flat FILE imports each author same-name free +// functions whose VARIADIC SHAPE differs. `combine` is fixed-arity in a.sx +// (the first-wins winner) but variadic in b.sx (the shadow); `pick` is the +// reverse. Each module's bare call must pack arguments against ITS OWN +// author's signature — not the first-wins author's. Pre-fix the call path +// re-fetched the first-wins AST by name to drive variadic packing, so b.sx's +// variadic `combine` was packed as if fixed (and its fixed `pick` as if +// variadic) → wrong lowering. Regression for the F1 review finding. +#import "modules/std.sx"; +#import "0726-modules-flat-same-name-variadic/a.sx"; +#import "0726-modules-flat-same-name-variadic/b.sx"; + +report :: (label: string, ok: bool) { + if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); } +} + +main :: () -> s32 { + report("from_a combine fixed", from_a_combine() == 30); + report("from_b combine variadic", from_b_combine() == 10); + report("from_a pick variadic", from_a_pick() == 6); + report("from_b pick fixed", from_b_pick() == 5); + 0 +} diff --git a/examples/0726-modules-flat-same-name-variadic/a.sx b/examples/0726-modules-flat-same-name-variadic/a.sx new file mode 100644 index 0000000..55a1456 --- /dev/null +++ b/examples/0726-modules-flat-same-name-variadic/a.sx @@ -0,0 +1,11 @@ +// a.sx is the first-wins winner for both names. `combine` is FIXED arity; +// `pick` is VARIADIC. `from_a_*` call them bare — a authors the winner, so +// they resolve through the existing path and pack against a's own shapes. +combine :: (x: s64, y: s64) -> s64 { return x + y; } +pick :: (..xs: []s64) -> s64 { + result := 0; + for xs: (it) { result = result + it; } + result +} +from_a_combine :: () -> s64 { return combine(10, 20); } +from_a_pick :: () -> s64 { return pick(1, 2, 3); } diff --git a/examples/0726-modules-flat-same-name-variadic/b.sx b/examples/0726-modules-flat-same-name-variadic/b.sx new file mode 100644 index 0000000..654f21c --- /dev/null +++ b/examples/0726-modules-flat-same-name-variadic/b.sx @@ -0,0 +1,12 @@ +// b.sx is the SHADOW author for both names, with the OPPOSITE shapes: +// `combine` is VARIADIC, `pick` is FIXED. Each `from_b_*` bare call must pack +// against b's OWN author's signature (the F1 fix) — combine sums its variadic +// pack, pick subtracts its two fixed args. +combine :: (..xs: []s64) -> s64 { + result := 0; + for xs: (it) { result = result + it; } + result +} +pick :: (a: s64, b: s64) -> s64 { return b - a; } +from_b_combine :: () -> s64 { return combine(1, 2, 3, 4); } +from_b_pick :: () -> s64 { return pick(2, 7); } diff --git a/examples/expected/0726-modules-flat-same-name-variadic.exit b/examples/expected/0726-modules-flat-same-name-variadic.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0726-modules-flat-same-name-variadic.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0726-modules-flat-same-name-variadic.stderr b/examples/expected/0726-modules-flat-same-name-variadic.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0726-modules-flat-same-name-variadic.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0726-modules-flat-same-name-variadic.stdout b/examples/expected/0726-modules-flat-same-name-variadic.stdout new file mode 100644 index 0000000..2942265 --- /dev/null +++ b/examples/expected/0726-modules-flat-same-name-variadic.stdout @@ -0,0 +1,4 @@ +from_a combine fixed: ok +from_b combine variadic: ok +from_a pick variadic: ok +from_b pick fixed: ok diff --git a/src/ir/lower.test.zig b/src/ir/lower.test.zig index 8a853f2..d827be3 100644 --- a/src/ir/lower.test.zig +++ b/src/ir/lower.test.zig @@ -1438,7 +1438,7 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102 try std.testing.expect(lowering.resolveBareCallee("greet", main_path) == .ambiguous); try std.testing.expect(lowering.resolveBareCallee("greet", a_path) == .none); switch (lowering.resolveBareCallee("greet", b_path)) { - .func => |fid| try std.testing.expectEqual(shadow_fid.?, fid), + .func => |resolved| try std.testing.expectEqual(shadow_fid.?, resolved.fid), else => return error.TestUnexpectedResult, } // A name no module authors (and no flat import provides) never routes. diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 03209d1..afc9bb1 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -1521,9 +1521,12 @@ pub const Lowering = struct { /// Result of bare-call disambiguation (fix-0102c). pub const BareCallee = union(enum) { - /// Bind the call to this specific author's FuncId — the identity- - /// addressable body lowered by `bareAuthorFuncId` (fix-0102b). - func: FuncId, + /// Bind the call to this specific author — its identity-addressable + /// FuncId (fix-0102b's `bareAuthorFuncId`) AND its `*FnDecl`. The decl + /// travels with the FuncId so every callee-signature decision in the + /// call path (variadic packing, …) reads the RESOLVED author, never a + /// first-wins re-lookup by name (fix-0102c F1). + func: ResolvedAuthor, /// ≥2 distinct flat authors are reachable from the caller and none is /// the caller's own — the bare call can't pick one; require a qualifier. ambiguous, @@ -1532,6 +1535,11 @@ pub const Lowering = struct { none, }; + /// A resolved bare-call author: its FuncId and the `*FnDecl` that defined + /// it, kept together so the call path has ONE source of truth for the + /// callee (no re-fetch by name after resolution). + pub const ResolvedAuthor = struct { fid: FuncId, decl: *const ast.FnDecl }; + /// THE bare-name call resolver (fix-0102c). One canonical traversal over /// fix-0102a's `module_fns` + `flat_import_graph` that routes a bare /// identifier call `name` from `caller_file` to the right same-name author @@ -1561,7 +1569,7 @@ pub const Lowering = struct { if (own_fns.get(name)) |own| { if (winner != null and winner.? == own) return .none; if (!isPlainFreeFn(own)) return .none; - return .{ .func = self.bareAuthorFuncId(own, name, caller_file) }; + return .{ .func = .{ .fid = self.bareAuthorFuncId(own, name, caller_file), .decl = own } }; } } @@ -1584,7 +1592,7 @@ pub const Lowering = struct { const the_path = entry.value_ptr.*; if (winner != null and winner.? == the_one) return .none; if (!isPlainFreeFn(the_one)) return .none; - return .{ .func = self.bareAuthorFuncId(the_one, name, the_path) }; + return .{ .func = .{ .fid = self.bareAuthorFuncId(the_one, name, the_path), .decl = the_one } }; } /// The FuncId for a resolved bare-call author, ensuring its body is lowered. @@ -7560,13 +7568,15 @@ pub const Lowering = struct { d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{func_name}); return Ref.none; }, - .func => |fid| { + .func => |resolved| { + const fid = resolved.fid; const func = &self.module.functions.items[@intFromEnum(fid)]; const ret_ty = func.ret; const params = func.params; - if (self.program_index.fn_ast_map.get(func_name)) |fd| { - self.packVariadicCallArgs(fd, c, &args); - } + // The RESOLVED author's decl drives variadic + // packing — not a first-wins re-lookup by name, + // whose variadic shape may differ (fix-0102c F1). + self.packVariadicCallArgs(resolved.decl, c, &args); const final_args = self.prependCtxIfNeeded(func, args.items); self.coerceCallArgs(final_args, params); if (func.is_variadic) self.promoteCVariadicArgs(final_args, params.len);