fix(lower): route remaining bare-name sites through resolver + close 0102 [0102d]
Final 0102 sub-step. fix-0102c landed resolveBareCallee and routed the primary call path + parameter target typing through it, leaving four other bare-name consumer sites on the old first-wins path. Route the SAME resolver through all four, gated exactly as the call path (plain top-level identifier, no scope-mangle / UFCS alias / local shadow; act on .func / .ambiguous, fall through on .none so single-author / local / std / qualified / foreign-single resolution is byte-for-byte unchanged): 1. Default-argument expansion (expandCallDefaults): omitted trailing args fill from the RESOLVED author's defaults, not the winner's. 2. Function-value conversion (closure(fn) and the bare-fn-as-value func_ref / fn-ptr / closure-coercion path): captures the resolved author's FuncId. 3. Free-function UFCS (recv.fn() -> fn(recv, ...)): dispatches the resolved author for the receiver's source. 4. Comptime #run of a bare call: lowerMainAndComptime now sets current_source_file per decl, so a `NAME :: #run f()` in an imported module resolves f from THAT module's flat imports (own-author wins) instead of the main file's perspective (which made it spuriously ambiguous). Regression tests: examples/0730-0734 (default-arg, closure+fn-value, UFCS, comptime #run, UFCS-ambiguity), each fails on pre-fix code and passes after. issues/0102-flat-import-same-signature-collision.md written RESOLVED with the 4-sub-step root cause and regression-test paths.
This commit is contained in:
113
src/ir/lower.zig
113
src/ir/lower.zig
@@ -1433,6 +1433,14 @@ pub const Lowering = struct {
|
||||
/// Pass 2: Lower main function body and comptime side-effects.
|
||||
fn lowerMainAndComptime(self: *Lowering, decls: []const *const Node) void {
|
||||
for (decls) |decl| {
|
||||
// A `#run` body lowers in its OWN module's source context (fix-0102d
|
||||
// site 4): `NAME :: #run f()` written in an imported module must
|
||||
// resolve a bare `f` from that module's flat imports, not the main
|
||||
// file's. Without this, `resolveBareCallee` runs with the main
|
||||
// file's perspective and reports a genuine per-source author as
|
||||
// ambiguous. Mirrors `scanDecls` / `lowerDecls`, which already set
|
||||
// the source file per decl.
|
||||
self.setCurrentSourceFile(decl.source_file);
|
||||
switch (decl.data) {
|
||||
.const_decl => |cd| {
|
||||
if (cd.value.data == .fn_decl) {
|
||||
@@ -3245,7 +3253,31 @@ pub const Lowering = struct {
|
||||
if (!self.lowered_functions.contains(eff_fn_name)) {
|
||||
self.lazyLowerFunction(eff_fn_name);
|
||||
}
|
||||
if (self.resolveFuncByName(eff_fn_name)) |fid| {
|
||||
// fix-0102d site 2: taking a bare same-name fn as a VALUE
|
||||
// (func_ref, fn-ptr / closure coercion) must capture the
|
||||
// RESOLVED author's FuncId for a genuine flat collision, not
|
||||
// the first-wins winner's. Plain bare name only; `.ambiguous`
|
||||
// → loud diagnostic; `.none` → existing first-wins path.
|
||||
const value_fid: ?FuncId = blk_fv: {
|
||||
if (std.mem.eql(u8, eff_fn_name, id.name) and
|
||||
self.program_index.ufcs_alias_map.get(id.name) == null and
|
||||
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
||||
{
|
||||
if (self.current_source_file) |caller_file| {
|
||||
switch (self.resolveBareCallee(id.name, caller_file)) {
|
||||
.func => |resolved| break :blk_fv resolved.fid,
|
||||
.ambiguous => {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, node.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{id.name});
|
||||
break :blk self.emitError(id.name, node.span);
|
||||
},
|
||||
.none => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
break :blk_fv self.resolveFuncByName(eff_fn_name);
|
||||
};
|
||||
if (value_fid) |fid| {
|
||||
// Auto-promote bare function → closure when target_type is closure
|
||||
if (self.target_type) |tt| {
|
||||
if (!tt.isBuiltin()) {
|
||||
@@ -7272,10 +7304,32 @@ pub const Lowering = struct {
|
||||
// If argument is a bare function name, create a proper closure from it
|
||||
if (arg.data == .identifier) {
|
||||
const fn_name = arg.data.identifier.name;
|
||||
if (!self.lowered_functions.contains(fn_name)) {
|
||||
self.lazyLowerFunction(fn_name);
|
||||
}
|
||||
if (self.resolveFuncByName(fn_name)) |fid| {
|
||||
// fix-0102d site 2: `closure(fn)` over a genuine flat same-name
|
||||
// collision must capture the RESOLVED author's FuncId, not the
|
||||
// first-wins winner's. Plain bare name only; `.ambiguous`
|
||||
// → loud diagnostic; `.none` → existing first-wins path.
|
||||
const closure_fid: ?FuncId = blk_cl: {
|
||||
if (self.program_index.ufcs_alias_map.get(fn_name) == null and
|
||||
(if (self.scope) |scope| scope.lookup(fn_name) == null else true))
|
||||
{
|
||||
if (self.current_source_file) |caller_file| {
|
||||
switch (self.resolveBareCallee(fn_name, caller_file)) {
|
||||
.func => |resolved| break :blk_cl resolved.fid,
|
||||
.ambiguous => {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, arg.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fn_name});
|
||||
return Ref.none;
|
||||
},
|
||||
.none => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!self.lowered_functions.contains(fn_name)) {
|
||||
self.lazyLowerFunction(fn_name);
|
||||
}
|
||||
break :blk_cl self.resolveFuncByName(fn_name);
|
||||
};
|
||||
if (closure_fid) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
// Build closure type from user-visible params only —
|
||||
// skip the implicit __sx_ctx param.
|
||||
@@ -8075,12 +8129,33 @@ pub const Lowering = struct {
|
||||
// `recv.fn(args)` → `fn(recv, args)`). Lazily lower the body —
|
||||
// a function reached ONLY via UFCS would otherwise be declared
|
||||
// but never emitted (issue 0063: undefined symbol at link).
|
||||
if (self.program_index.fn_ast_map.get(fa.field)) |_| {
|
||||
if (!self.lowered_functions.contains(fa.field)) {
|
||||
self.lazyLowerFunction(fa.field);
|
||||
//
|
||||
// fix-0102d site 3: a free-function UFCS target with a genuine
|
||||
// flat same-name collision must dispatch to the RESOLVED author
|
||||
// for the receiver's source, not the first-wins winner. The
|
||||
// field name is never scope-mangled, so the only gate is a
|
||||
// known source file; `.ambiguous` → loud diagnostic; `.none`
|
||||
// → existing first-wins path.
|
||||
const ufcs_fid: ?FuncId = blk_uf: {
|
||||
if (self.current_source_file) |caller_file| {
|
||||
switch (self.resolveBareCallee(fa.field, caller_file)) {
|
||||
.func => |resolved| break :blk_uf resolved.fid,
|
||||
.ambiguous => {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fa.field});
|
||||
return Ref.none;
|
||||
},
|
||||
.none => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.resolveFuncByName(fa.field)) |fid| {
|
||||
if (self.program_index.fn_ast_map.get(fa.field)) |_| {
|
||||
if (!self.lowered_functions.contains(fa.field)) {
|
||||
self.lazyLowerFunction(fa.field);
|
||||
}
|
||||
}
|
||||
break :blk_uf self.resolveFuncByName(fa.field);
|
||||
};
|
||||
if (ufcs_fid) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
@@ -11866,6 +11941,24 @@ pub const Lowering = struct {
|
||||
}
|
||||
break :blk2 scoped;
|
||||
};
|
||||
// fix-0102d site 1: for a genuine flat same-name collision the
|
||||
// omitted trailing args must be filled from the RESOLVED
|
||||
// author's defaults, not the first-wins winner's. Only a plain
|
||||
// top-level identifier with no scope-mangle / UFCS alias /
|
||||
// local shadow routes here; `.ambiguous` declines to expand
|
||||
// (the call path emits the single diagnostic); `.none` keeps
|
||||
// the existing first-wins winner, byte-for-byte.
|
||||
if (std.mem.eql(u8, eff_name, id.name) and
|
||||
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
||||
{
|
||||
if (self.current_source_file) |caller_file| {
|
||||
switch (self.resolveBareCallee(id.name, caller_file)) {
|
||||
.func => |resolved| break :blk resolved.decl,
|
||||
.ambiguous => return null,
|
||||
.none => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
break :blk self.program_index.fn_ast_map.get(eff_name) orelse return null;
|
||||
},
|
||||
// Namespace call `mod.fn(args)` — args map directly to params
|
||||
|
||||
Reference in New Issue
Block a user