fix(lower): route early pack/comptime dispatch through SelectedFunc [stdlib C attempt-3]
lowerCall's early pack/comptime/generic dispatch keyed off the first-wins
winner (`fn_ast_map.get(early_name)`) BEFORE the main dispatch consumes the
selected same-name author. Under a genuine flat same-name collision where the
caller's own author is a plain free fn but the first-wins winner is a comptime
pack `(..$args)` (or comptime-param / generic), the early path invoked the
WINNER — so `CallResolver.plan` (which selects the own plain author) and
lowering disagreed about which function a bare call names.
Confirms reviewer finding C-review-1. The earlier manager ground-truth got
`show_b=2` because it used a slice variadic `(..xs: []s64)` — NOT a pack fn
(`isPackParam` false), so it never hit the early dispatch. The reviewer used a
comptime pack `(..$args)` (`isPackFn` true), which does. Both observations are
correct for their respective shapes; the bug is real for the comptime-pack
winner.
Fix: the early dispatch reads the SAME author the selector chose
(`sel_author.decl`) when a collision rerouted the call, else the winner
(common path, byte-identical). The selector only ever returns a plain free fn
(`isPlainFreeFn` excludes type-params / comptime / pack), so a selected author
falls through to the main dispatch that binds it via `SelectedFunc`.
Regression: examples/0741-modules-flat-same-name-bare-pack-winner — a.sx
(imported first) authors `f` as a comptime pack (first-wins winner); b.sx
authors its own plain `f`; b's bare `f()` must return 2 (own author), not 1
(the pack). Fails on 2dd6c3c (b: f() = 1), passes after.
Gate: zig build + zig build test (412/412) + run_examples (477/0) +
m3te ios-sim exit 0.
This commit is contained in:
18
examples/0741-modules-flat-same-name-bare-pack-winner.sx
Normal file
18
examples/0741-modules-flat-same-name-bare-pack-winner.sx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Regression (issue 0102, Phase C): a BARE call whose own author is a plain free
|
||||||
|
// fn must DISPATCH to that author, not the first-wins winner — even when the
|
||||||
|
// winner is a comptime PACK (`..$args`) of the same name. a.sx (imported first)
|
||||||
|
// authors `f` as a pack → first-wins winner; b.sx authors its OWN plain `f`. In
|
||||||
|
// b.sx, `f()` must reach b.f (returns 2). Before the fix, lowerCall's early
|
||||||
|
// pack/comptime/generic dispatch keyed off the first-wins winner (a's pack) and
|
||||||
|
// invoked it (returns 1) BEFORE consuming the selected author — so plan-selected
|
||||||
|
// author and lowered+dispatched author disagreed. The early dispatch now reads
|
||||||
|
// the SAME `SelectedFunc` the main dispatch binds (fix-0102 F2).
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "0741-modules-flat-same-name-bare-pack-winner/a.sx";
|
||||||
|
#import "0741-modules-flat-same-name-bare-pack-winner/b.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
show_a(); // a-side: own == winner (the pack) → returns 1, byte-for-byte unchanged
|
||||||
|
show_b(); // b-side: selected own plain author → returns 2, not the pack winner
|
||||||
|
0
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
// a.sx authors `f` as a comptime pack; imported first → first-wins winner.
|
||||||
|
// `show_a`'s bare `f()` is the caller's OWN author (own == winner → existing
|
||||||
|
// pack path, byte-for-byte unchanged): dispatched as a.f (the pack → 1).
|
||||||
|
f :: (..$args) -> s64 { return 1; }
|
||||||
|
show_a :: () { print("a: f() = {}\n", f()); }
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
// b.sx authors its OWN plain `f`. `show_b`'s bare `f()` must dispatch b.f (2),
|
||||||
|
// not the first-wins pack winner from a.sx (1). The selector picks b.f; the
|
||||||
|
// early pack/comptime dispatch must NOT hijack it with the winner.
|
||||||
|
f :: () -> s64 { return 2; }
|
||||||
|
show_b :: () { print("b: f() = {}\n", f()); }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
a: f() = 1
|
||||||
|
b: f() = 2
|
||||||
@@ -7494,7 +7494,21 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
break :blk scoped;
|
break :blk scoped;
|
||||||
};
|
};
|
||||||
if (self.program_index.fn_ast_map.get(early_name)) |fd| {
|
// fix-0102 F2 / R5 §C: the early pack/comptime/generic dispatch reads
|
||||||
|
// the SAME author the call resolver SELECTED — not the first-wins
|
||||||
|
// winner — whenever a genuine flat same-name collision rerouted the
|
||||||
|
// call (`sel_author != null`). The selector only ever returns a plain
|
||||||
|
// free fn (`isPlainFreeFn` rejects type-params / comptime / pack), so
|
||||||
|
// `sel_author.decl` matches none of the arms below and the early path
|
||||||
|
// falls through to the main dispatch, which CONSUMES `sel_author` and
|
||||||
|
// binds that author. Without this the early path would dispatch the
|
||||||
|
// first-wins winner (e.g. a pack `(..$args)`) and disagree with the
|
||||||
|
// main dispatch — the selected plain author's bare call would invoke
|
||||||
|
// the wrong function. On the common path (`sel_author == null`) this
|
||||||
|
// reads the winner exactly as before — byte-identical, since the
|
||||||
|
// selector reroutes nothing there.
|
||||||
|
const early_fd: ?*const ast.FnDecl = if (sel_author) |sf| sf.decl else self.program_index.fn_ast_map.get(early_name);
|
||||||
|
if (early_fd) |fd| {
|
||||||
if (isPackFn(fd)) {
|
if (isPackFn(fd)) {
|
||||||
// Protocol packs (`..xs: P`) and comptime type-packs
|
// Protocol packs (`..xs: P`) and comptime type-packs
|
||||||
// (`..$args`) both monomorphize per call shape.
|
// (`..$args`) both monomorphize per call shape.
|
||||||
@@ -7505,7 +7519,9 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
// Early detection of generic function calls — skip arg lowering for type params
|
// Early detection of generic function calls — skip arg lowering for type params
|
||||||
// because lowerGenericCall resolves type params from AST nodes, not lowered refs.
|
// because lowerGenericCall resolves type params from AST nodes, not lowered refs.
|
||||||
// Only if the name is NOT shadowed by a local variable (closure, fn ptr, etc.)
|
// Only if the name is NOT shadowed by a local variable (closure, fn ptr, etc.).
|
||||||
|
// A selected author is never generic (`isPlainFreeFn` excludes
|
||||||
|
// `type_params > 0`), so this branch fires only on the winner.
|
||||||
const shadowed = if (self.scope) |scope| scope.lookup(c.callee.data.identifier.name) != null else false;
|
const shadowed = if (self.scope) |scope| scope.lookup(c.callee.data.identifier.name) != null else false;
|
||||||
if (fd.type_params.len > 0 and !shadowed) {
|
if (fd.type_params.len > 0 and !shadowed) {
|
||||||
// Types are explicit when call args match param count (e.g., are_equal(Point, p1, p2))
|
// Types are explicit when call args match param count (e.g., are_equal(Point, p1, p2))
|
||||||
|
|||||||
Reference in New Issue
Block a user