fix(lower): resolved author drives variadic packing in bare call [0102c F1]

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).
This commit is contained in:
agra
2026-06-06 14:28:00 +03:00
parent ea35a05b26
commit 8f9c00dcdb
8 changed files with 72 additions and 10 deletions

View File

@@ -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
}

View File

@@ -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); }

View File

@@ -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); }

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,4 @@
from_a combine fixed: ok
from_b combine variadic: ok
from_a pick variadic: ok
from_b pick fixed: ok

View File

@@ -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", main_path) == .ambiguous);
try std.testing.expect(lowering.resolveBareCallee("greet", a_path) == .none); try std.testing.expect(lowering.resolveBareCallee("greet", a_path) == .none);
switch (lowering.resolveBareCallee("greet", b_path)) { 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, else => return error.TestUnexpectedResult,
} }
// A name no module authors (and no flat import provides) never routes. // A name no module authors (and no flat import provides) never routes.

View File

@@ -1521,9 +1521,12 @@ pub const Lowering = struct {
/// Result of bare-call disambiguation (fix-0102c). /// Result of bare-call disambiguation (fix-0102c).
pub const BareCallee = union(enum) { pub const BareCallee = union(enum) {
/// Bind the call to this specific author's FuncId — the identity- /// Bind the call to this specific author — its identity-addressable
/// addressable body lowered by `bareAuthorFuncId` (fix-0102b). /// FuncId (fix-0102b's `bareAuthorFuncId`) AND its `*FnDecl`. The decl
func: FuncId, /// 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 /// ≥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. /// the caller's own — the bare call can't pick one; require a qualifier.
ambiguous, ambiguous,
@@ -1532,6 +1535,11 @@ pub const Lowering = struct {
none, 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 /// THE bare-name call resolver (fix-0102c). One canonical traversal over
/// fix-0102a's `module_fns` + `flat_import_graph` that routes a bare /// fix-0102a's `module_fns` + `flat_import_graph` that routes a bare
/// identifier call `name` from `caller_file` to the right same-name author /// 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 (own_fns.get(name)) |own| {
if (winner != null and winner.? == own) return .none; if (winner != null and winner.? == own) return .none;
if (!isPlainFreeFn(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.*; const the_path = entry.value_ptr.*;
if (winner != null and winner.? == the_one) return .none; if (winner != null and winner.? == the_one) return .none;
if (!isPlainFreeFn(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. /// 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}); d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{func_name});
return Ref.none; return Ref.none;
}, },
.func => |fid| { .func => |resolved| {
const fid = resolved.fid;
const func = &self.module.functions.items[@intFromEnum(fid)]; const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret; const ret_ty = func.ret;
const params = func.params; const params = func.params;
if (self.program_index.fn_ast_map.get(func_name)) |fd| { // The RESOLVED author's decl drives variadic
self.packVariadicCallArgs(fd, c, &args); // 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); const final_args = self.prependCtxIfNeeded(func, args.items);
self.coerceCallArgs(final_args, params); self.coerceCallArgs(final_args, params);
if (func.is_variadic) self.promoteCVariadicArgs(final_args, params.len); if (func.is_variadic) self.promoteCVariadicArgs(final_args, params.len);