feat(resolver): route plain bare-call author through Phase B collector via SelectedFunc [stdlib C]
Phase C of the unified resolver (R5 §C, §#3). Re-base the plain bare-name
call author onto the Phase B collector behind one shared SelectedFunc, so
every call-path consumer reads ONE author and they can no longer disagree
(fix-0102 F2). Behavior-preserving: 0722-0735 byte-identical, run_examples
stays at 475.
- SelectedFunc {decl, source, materialized?} replaces ResolvedAuthor in
BareCallee.func; CallPlan.Target gains a `selected` arm (calls.zig).
- selectPlainCallableAuthor: resolveBareCallee's body verbatim over
resolver.collectVisibleAuthors (.user_bare_flat) — the ONE graph-walk.
fnDeclOfRaw mirrors imports.fnDeclOf so the collector's all-domain authors
reproduce module_fns' fn-only view; every byte of the negative space is
preserved (own==winner → .none; non-plain-free → .none; filter-before-count;
≥2 distinct → .ambiguous). No eager materialization.
- selectedFuncId materializes the FuncId on demand (shadow-only), caching into
materialized — null until a site needs it (0102d: a shadow taken as a value
never lowers the winner).
- Six consumers route through the one selector: lowerCall variadic packing,
free-fn UFCS, fn-value, closure(fn), resolveCallParamTypes, and
expandCallDefaults (decl-only, no materialization). plan() produces the
SelectedFunc as `.selected`. Generic/comptime/foreign/builtin stay legacy.
- lower.test.zig: wire module_decls; selectPlainCallableAuthor verdicts
(own-winner → .none; ≥2 flat → .ambiguous; own-shadow → decl+source, fid
round-trips, materialized null).
Gate: zig build + zig build test (412 ok) + run_examples (475, byte-identical)
+ m3te ios-sim build exit 0.
This commit is contained in:
@@ -72,6 +72,13 @@ pub const CallPlan = struct {
|
|||||||
/// A callee carried by name — reflection builtin, generic / lazy fn,
|
/// A callee carried by name — reflection builtin, generic / lazy fn,
|
||||||
/// closure / fn-pointer binding, or a not-yet-lowered namespace fn.
|
/// closure / fn-pointer binding, or a not-yet-lowered namespace fn.
|
||||||
named: []const u8,
|
named: []const u8,
|
||||||
|
/// The single bare-call author `selectPlainCallableAuthor` selected for a
|
||||||
|
/// genuine flat same-name collision (R5 §#3). Carries the resolved
|
||||||
|
/// `*FnDecl` + source so `plan` and the lowering call-path read ONE
|
||||||
|
/// author and can no longer disagree (fix-0102 F2); the FuncId is
|
||||||
|
/// materialized on demand. Only set when the bare name reroutes away from
|
||||||
|
/// the first-wins winner; the common path still uses `func` / `named`.
|
||||||
|
selected: Lowering.SelectedFunc,
|
||||||
/// Protocol method, by index in the protocol's method table.
|
/// Protocol method, by index in the protocol's method table.
|
||||||
protocol_method: u32,
|
protocol_method: u32,
|
||||||
/// Foreign-class method (Obj-C / JNI), with its static-ness.
|
/// Foreign-class method (Obj-C / JNI), with its static-ness.
|
||||||
@@ -149,6 +156,28 @@ pub const CallResolver = struct {
|
|||||||
if (std.mem.eql(u8, bare_name, "type_is_unsigned")) return refl(bare_name, .bool);
|
if (std.mem.eql(u8, bare_name, "type_is_unsigned")) return refl(bare_name, .bool);
|
||||||
if (std.mem.eql(u8, bare_name, "type_of")) return refl(bare_name, .any);
|
if (std.mem.eql(u8, bare_name, "type_of")) return refl(bare_name, .any);
|
||||||
if (std.mem.eql(u8, bare_name, "field_value")) return refl(bare_name, .any);
|
if (std.mem.eql(u8, bare_name, "field_value")) return refl(bare_name, .any);
|
||||||
|
// Plain bare same-name flat collision (R5 §C): route through the ONE
|
||||||
|
// selector so `plan` reads the SAME author the lowering call-path
|
||||||
|
// binds — they can no longer disagree (fix-0102 F2). The gate mirrors
|
||||||
|
// `lowerCall`'s: a plain top-level identifier with no scope-mangle /
|
||||||
|
// local shadow. A generic / foreign / builtin author is not plain-free
|
||||||
|
// so the selector returns `.none`; `.ambiguous` / `.none` fall through
|
||||||
|
// to the first-wins path below, byte-for-byte.
|
||||||
|
if (std.mem.eql(u8, name, bare_name) and
|
||||||
|
(if (self.l.scope) |scope| scope.lookup(bare_name) == null else true))
|
||||||
|
{
|
||||||
|
if (self.l.current_source_file) |caller_file| {
|
||||||
|
switch (self.l.selectPlainCallableAuthor(bare_name, caller_file)) {
|
||||||
|
.func => |sf| return .{
|
||||||
|
.kind = .direct_fn,
|
||||||
|
.return_type = if (sf.decl.return_type) |rt| self.l.resolveType(rt) else .void,
|
||||||
|
.target = .{ .selected = sf },
|
||||||
|
.expands_defaults = defaultsFor(sf.decl, c.args.len),
|
||||||
|
},
|
||||||
|
.ambiguous, .none => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Generic function — infer return type via type bindings.
|
// Generic function — infer return type via type bindings.
|
||||||
if (self.l.program_index.fn_ast_map.get(name)) |fd| {
|
if (self.l.program_index.fn_ast_map.get(name)) |fd| {
|
||||||
if (fd.type_params.len > 0) {
|
if (fd.type_params.len > 0) {
|
||||||
|
|||||||
@@ -1361,6 +1361,10 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
|||||||
var module_fns = imports.ModuleFns.init(alloc);
|
var module_fns = imports.ModuleFns.init(alloc);
|
||||||
try imports.buildModuleFns(alloc, main_path, mod, &cache, &module_fns);
|
try imports.buildModuleFns(alloc, main_path, mod, &cache, &module_fns);
|
||||||
|
|
||||||
|
// Phase A raw facts: `selectPlainCallableAuthor` (Phase C) collects authors
|
||||||
|
// over `module_decls`, not `module_fns`. Wired exactly as `core.zig` does.
|
||||||
|
var facts = try imports.buildImportFacts(alloc, main_path, mod, &cache);
|
||||||
|
|
||||||
const resolved_root = try alloc.create(Node);
|
const resolved_root = try alloc.create(Node);
|
||||||
resolved_root.* = .{ .span = root.span, .data = .{ .root = .{ .decls = mod.decls } } };
|
resolved_root.* = .{ .span = root.span, .data = .{ .root = .{ .decls = mod.decls } } };
|
||||||
|
|
||||||
@@ -1375,6 +1379,7 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
|||||||
lowering.program_index.import_graph = &import_graph;
|
lowering.program_index.import_graph = &import_graph;
|
||||||
lowering.program_index.flat_import_graph = &flat_import_graph;
|
lowering.program_index.flat_import_graph = &flat_import_graph;
|
||||||
lowering.program_index.module_fns = &module_fns;
|
lowering.program_index.module_fns = &module_fns;
|
||||||
|
lowering.program_index.module_decls = &facts.decls;
|
||||||
|
|
||||||
lowering.lowerRoot(resolved_root);
|
lowering.lowerRoot(resolved_root);
|
||||||
try std.testing.expect(!diagnostics.hasErrors());
|
try std.testing.expect(!diagnostics.hasErrors());
|
||||||
@@ -1428,19 +1433,27 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
|||||||
try std.testing.expect(shadow_fid != null);
|
try std.testing.expect(shadow_fid != null);
|
||||||
try std.testing.expect(shadow_fid.? != winner_fid.?);
|
try std.testing.expect(shadow_fid.? != winner_fid.?);
|
||||||
|
|
||||||
// fix-0102c: THE bare-name resolver routes per caller file. `main` flat-
|
// fix-0102c / Phase C: THE bare-name selector routes per caller file over the
|
||||||
// imports two `greet` authors and is its own author of neither → a bare
|
// Phase A author collector. `main` flat-imports two `greet` authors and is its
|
||||||
// `greet()` from `main` is ambiguous. a.sx authors the WINNER, so its bare
|
// own author of neither → a bare `greet()` from `main` is ambiguous. a.sx
|
||||||
// `greet` resolves through the existing path (`.none`). b.sx authors the
|
// authors the WINNER, so its bare `greet` resolves through the existing path
|
||||||
// SHADOW, so own-author-wins binds b.sx's distinct FuncId — not first-wins.
|
// (`.none`). b.sx authors the SHADOW, so own-author-wins selects b.sx's
|
||||||
|
// author — its `*FnDecl` + source, NOT first-wins. The selector does NOT
|
||||||
|
// eagerly materialize: it returns the decl, and the FuncId still round-trips
|
||||||
|
// to the shadow slot via the identity map (`fn_decl_fids`).
|
||||||
const a_path = try std.fmt.allocPrint(alloc, "{s}/a.sx", .{absdir});
|
const a_path = try std.fmt.allocPrint(alloc, "{s}/a.sx", .{absdir});
|
||||||
const b_path = try std.fmt.allocPrint(alloc, "{s}/b.sx", .{absdir});
|
const b_path = try std.fmt.allocPrint(alloc, "{s}/b.sx", .{absdir});
|
||||||
try std.testing.expect(lowering.resolveBareCallee("greet", main_path) == .ambiguous);
|
try std.testing.expect(lowering.selectPlainCallableAuthor("greet", main_path) == .ambiguous);
|
||||||
try std.testing.expect(lowering.resolveBareCallee("greet", a_path) == .none);
|
try std.testing.expect(lowering.selectPlainCallableAuthor("greet", a_path) == .none);
|
||||||
switch (lowering.resolveBareCallee("greet", b_path)) {
|
switch (lowering.selectPlainCallableAuthor("greet", b_path)) {
|
||||||
.func => |resolved| try std.testing.expectEqual(shadow_fid.?, resolved.fid),
|
.func => |sf| {
|
||||||
|
try std.testing.expectEqual(shadow_fd.?, sf.decl);
|
||||||
|
try std.testing.expectEqualStrings(b_path, sf.source);
|
||||||
|
try std.testing.expect(sf.materialized == null);
|
||||||
|
try std.testing.expectEqual(shadow_fid.?, lowering.fn_decl_fids.get(sf.decl).?);
|
||||||
|
},
|
||||||
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.
|
||||||
try std.testing.expect(lowering.resolveBareCallee("nonexistent", b_path) == .none);
|
try std.testing.expect(lowering.selectPlainCallableAuthor("nonexistent", b_path) == .none);
|
||||||
}
|
}
|
||||||
|
|||||||
223
src/ir/lower.zig
223
src/ir/lower.zig
@@ -1461,7 +1461,7 @@ pub const Lowering = struct {
|
|||||||
// A `#run` body lowers in its OWN module's source context (fix-0102d
|
// A `#run` body lowers in its OWN module's source context (fix-0102d
|
||||||
// site 4): `NAME :: #run f()` written in an imported module must
|
// site 4): `NAME :: #run f()` written in an imported module must
|
||||||
// resolve a bare `f` from that module's flat imports, not the main
|
// resolve a bare `f` from that module's flat imports, not the main
|
||||||
// file's. Without this, `resolveBareCallee` runs with the main
|
// file's. Without this, `selectPlainCallableAuthor` runs with the main
|
||||||
// file's perspective and reports a genuine per-source author as
|
// file's perspective and reports a genuine per-source author as
|
||||||
// ambiguous. Mirrors `scanDecls` / `lowerDecls`, which already set
|
// ambiguous. Mirrors `scanDecls` / `lowerDecls`, which already set
|
||||||
// the source file per decl.
|
// the source file per decl.
|
||||||
@@ -1552,14 +1552,16 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of bare-call disambiguation (fix-0102c).
|
/// Result of bare-call disambiguation (fix-0102c, now over the Phase B
|
||||||
|
/// author collector).
|
||||||
pub const BareCallee = union(enum) {
|
pub const BareCallee = union(enum) {
|
||||||
/// Bind the call to this specific author — its identity-addressable
|
/// Bind the call to this specific author, carried as the shared
|
||||||
/// FuncId (fix-0102b's `bareAuthorFuncId`) AND its `*FnDecl`. The decl
|
/// `SelectedFunc` (R5 §#3): its `*FnDecl` + authoring source, FuncId
|
||||||
/// travels with the FuncId so every callee-signature decision in the
|
/// materialized on demand. Every callee-signature decision in the call
|
||||||
/// call path (variadic packing, …) reads the RESOLVED author, never a
|
/// path (variadic packing, param typing, default expansion) reads the
|
||||||
/// first-wins re-lookup by name (fix-0102c F1).
|
/// RESOLVED author from this one object — never a first-wins re-lookup
|
||||||
func: ResolvedAuthor,
|
/// by name (fix-0102c F1).
|
||||||
|
func: SelectedFunc,
|
||||||
/// ≥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,
|
||||||
@@ -1568,78 +1570,122 @@ pub const Lowering = struct {
|
|||||||
none,
|
none,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A resolved bare-call author: its FuncId and the `*FnDecl` that defined
|
/// The single bare-call author object (R5 §#3): the `*FnDecl` that defines
|
||||||
/// it, kept together so the call path has ONE source of truth for the
|
/// the call and the SOURCE file that authors it, kept together so the call
|
||||||
/// callee (no re-fetch by name after resolution).
|
/// path has ONE source of truth for the callee. `materialized` holds the
|
||||||
pub const ResolvedAuthor = struct { fid: FuncId, decl: *const ast.FnDecl };
|
/// author's FuncId once a site needs it; it is filled on demand by
|
||||||
|
/// `selectedFuncId` (→ `bareAuthorFuncId`), NOT during selection — so a
|
||||||
|
/// selection that only needs the decl (default-arg expansion), or a shadow
|
||||||
|
/// taken purely as a value, never lowers the first-wins winner (0102d).
|
||||||
|
pub const SelectedFunc = struct {
|
||||||
|
decl: *const ast.FnDecl,
|
||||||
|
source: []const u8,
|
||||||
|
materialized: ?FuncId = null,
|
||||||
|
};
|
||||||
|
|
||||||
/// THE bare-name call resolver (fix-0102c). One canonical traversal over
|
/// THE plain bare-name call selector (fix-0102c, R5 §C). `resolveBareCallee`'s
|
||||||
/// fix-0102a's `module_fns` + `flat_import_graph` that routes a bare
|
/// body verbatim, now over the Phase B author collector
|
||||||
/// identifier call `name` from `caller_file` to the right same-name author
|
/// (`resolver.collectVisibleAuthors` — the ONE graph-walk) instead of a direct
|
||||||
/// when flat imports introduce a genuine collision. Every single-author /
|
/// `module_fns` + `flat_import_graph` traversal. Routes a bare identifier call
|
||||||
/// local / parameter / std / qualified name resolves through the EXISTING
|
/// `name` from `caller_file` to the right same-name author when flat imports
|
||||||
/// path unchanged: the resolver returns `.none` whenever the outcome would
|
/// introduce a genuine collision. Every single-author / local / parameter /
|
||||||
/// match first-wins, so nothing on the common path is perturbed.
|
/// std / qualified name resolves through the EXISTING path unchanged: the
|
||||||
|
/// selector returns `.none` whenever the outcome would match first-wins, so
|
||||||
|
/// nothing on the common path is perturbed.
|
||||||
///
|
///
|
||||||
/// - **own-author wins**: if `caller_file` authors `name` and the bare-name
|
/// The collector returns RAW authors across ALL decl domains; this selector
|
||||||
/// first-wins winner is a DIFFERENT author, bind the caller's own author.
|
/// reproduces `module_fns`' fn-only view by filtering each author through
|
||||||
/// (When the winner already IS the caller's own — the single-author and
|
/// `fnDeclOfRaw` (a `const`-wrapped fn unwraps to its inner fn — the exact
|
||||||
/// first-importer cases — `.none` lets the existing path bind it.)
|
/// `*FnDecl` `module_fns` stored; every other domain drops out), preserving
|
||||||
/// - else collect the authors reachable via `caller_file`'s FLAT import
|
/// resolveBareCallee's negative space byte-for-byte.
|
||||||
|
///
|
||||||
|
/// - **own-author wins**: if `caller_file` authors `name` as a fn and the
|
||||||
|
/// bare-name first-wins winner is a DIFFERENT author, select the caller's
|
||||||
|
/// own author. (When the winner already IS the caller's own — the
|
||||||
|
/// single-author and first-importer cases — `.none` lets the existing path
|
||||||
|
/// bind it.)
|
||||||
|
/// - else select among the authors reachable via `caller_file`'s FLAT import
|
||||||
/// edges (bare `#import` of a file or directory, never a namespaced
|
/// edges (bare `#import` of a file or directory, never a namespaced
|
||||||
/// `ns :: #import`), deduped by `FnDecl` identity (a diamond import of the
|
/// `ns :: #import`), deduped by author identity (a diamond import of the
|
||||||
/// same module is one author): `≥2 distinct` → `.ambiguous`; exactly one
|
/// same module is one author): `≥2 distinct` → `.ambiguous`; exactly one
|
||||||
/// that DIFFERS from the winner → bind it; otherwise `.none`.
|
/// that DIFFERS from the winner → select it; otherwise `.none`.
|
||||||
///
|
///
|
||||||
/// Generic / comptime / foreign / builtin authors are never rerouted — the
|
/// Generic / comptime / foreign / builtin authors are never rerouted — the
|
||||||
/// existing dispatch owns those shapes — so the resolver returns `.none`.
|
/// existing dispatch owns those shapes; `isPlainFreeFn` filters them out
|
||||||
pub fn resolveBareCallee(self: *Lowering, name: []const u8, caller_file: []const u8) BareCallee {
|
/// BEFORE the count gate (so a same-name collision of non-plain authors is
|
||||||
const module_fns = self.program_index.module_fns orelse return .none;
|
/// NOT ambiguous), and the selector returns `.none`. No eager
|
||||||
|
/// materialization: the returned `SelectedFunc` carries decl + source and
|
||||||
|
/// `materialized = null`; a consumer fills the FuncId via `selectedFuncId`
|
||||||
|
/// only when it truly needs it (0102d).
|
||||||
|
pub fn selectPlainCallableAuthor(self: *Lowering, name: []const u8, caller_file: []const u8) BareCallee {
|
||||||
const winner = self.program_index.fn_ast_map.get(name);
|
const winner = self.program_index.fn_ast_map.get(name);
|
||||||
|
var res = self.resolver();
|
||||||
|
const set = res.collectVisibleAuthors(name, caller_file, .user_bare_flat);
|
||||||
|
defer if (set.flat.len > 0) self.alloc.free(set.flat);
|
||||||
|
|
||||||
// own-author wins.
|
// own-author wins. The collector's `own` spans all domains; a non-fn
|
||||||
if (module_fns.get(caller_file)) |own_fns| {
|
// (or a const not bound to a function) means `caller_file` has no fn
|
||||||
if (own_fns.get(name)) |own| {
|
// `name` — fall through to the flat authors, exactly as the fn-only
|
||||||
|
// `module_fns` walk did.
|
||||||
|
if (set.own) |own_author| {
|
||||||
|
if (fnDeclOfRaw(own_author.raw)) |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 = .{ .fid = self.bareAuthorFuncId(own, name, caller_file), .decl = own } };
|
return .{ .func = .{ .decl = own, .source = own_author.source } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caller does not author `name` → collect its flat-reachable authors.
|
// Caller does not author `name` as a fn → its flat-reachable authors.
|
||||||
const flat_graph = self.program_index.flat_import_graph orelse return .none;
|
// Filter to plain free functions BEFORE counting: a same-name collision
|
||||||
const edges = flat_graph.get(caller_file) orelse return .none;
|
// of non-plain authors (e.g. two flat-imported modules each `#foreign`ing
|
||||||
var distinct = std.AutoHashMap(*const ast.FnDecl, []const u8).init(self.alloc);
|
// the same symbol) is NOT counted as ambiguous — it falls through to
|
||||||
defer distinct.deinit();
|
// `.none` and the existing first-wins path.
|
||||||
var edge_it = edges.iterator();
|
var the_one: ?*const ast.FnDecl = null;
|
||||||
while (edge_it.next()) |e| {
|
var the_source: []const u8 = &.{};
|
||||||
const fns = module_fns.get(e.key_ptr.*) orelse continue;
|
var count: usize = 0;
|
||||||
// Only plain free functions are eligible for rerouting; generic /
|
for (set.flat) |fa| {
|
||||||
// foreign / builtin / #compiler authors keep their existing
|
const fd = fnDeclOfRaw(fa.raw) orelse continue;
|
||||||
// dispatch. Filtering BEFORE the count gate means a same-name
|
if (!isPlainFreeFn(fd)) continue;
|
||||||
// collision of non-plain authors (e.g. two flat-imported modules
|
count += 1;
|
||||||
// each `#foreign`ing the same symbol) is NOT counted as ambiguous —
|
if (count >= 2) return .ambiguous;
|
||||||
// it falls through to `.none` and the existing first-wins path.
|
the_one = fd;
|
||||||
if (fns.get(name)) |fd| {
|
the_source = fa.source;
|
||||||
if (!isPlainFreeFn(fd)) continue;
|
|
||||||
distinct.put(fd, e.key_ptr.*) catch {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (distinct.count() == 0) return .none;
|
if (count == 0) return .none;
|
||||||
if (distinct.count() >= 2) return .ambiguous;
|
if (winner != null and winner.? == the_one.?) return .none;
|
||||||
|
return .{ .func = .{ .decl = the_one.?, .source = the_source } };
|
||||||
|
}
|
||||||
|
|
||||||
var one_it = distinct.iterator();
|
/// The `*FnDecl` a raw author wraps, or null when the author is not a
|
||||||
const entry = one_it.next().?;
|
/// function — `imports.fnDeclOf` over a `RawDeclRef` so the collector's
|
||||||
const the_one = entry.key_ptr.*;
|
/// all-domain authors reproduce `module_fns`' fn-only view (a `const`-wrapped
|
||||||
const the_path = entry.value_ptr.*;
|
/// fn unwraps to its inner fn, the same pointer `module_fns` holds; every
|
||||||
if (winner != null and winner.? == the_one) return .none;
|
/// other domain → null).
|
||||||
return .{ .func = .{ .fid = self.bareAuthorFuncId(the_one, name, the_path), .decl = the_one } };
|
fn fnDeclOfRaw(ref: resolver_mod.RawDeclRef) ?*const ast.FnDecl {
|
||||||
|
return switch (ref) {
|
||||||
|
.fn_decl => |fd| fd,
|
||||||
|
.const_decl => |cd| if (cd.value.data == .fn_decl) &cd.value.data.fn_decl else null,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Materialize (lower-on-demand) the FuncId for a selected bare-call author,
|
||||||
|
/// caching into `sf.materialized`. Shadow-only: the winner owns the
|
||||||
|
/// name-keyed slot and lowers through the lazy path, so
|
||||||
|
/// `selectPlainCallableAuthor` returns `.none` for it and this is never asked
|
||||||
|
/// to lower the winner (0102d). `name` is the call name (== the author's
|
||||||
|
/// registered name); `sf.source` pins the author's own visibility context.
|
||||||
|
fn selectedFuncId(self: *Lowering, sf: *SelectedFunc, name: []const u8) FuncId {
|
||||||
|
if (sf.materialized) |fid| return fid;
|
||||||
|
const fid = self.bareAuthorFuncId(sf.decl, name, sf.source);
|
||||||
|
sf.materialized = fid;
|
||||||
|
return fid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
/// Only ever called for a SHADOW (an author that is not the name-keyed
|
/// Only ever called for a SHADOW (an author that is not the name-keyed
|
||||||
/// winner): the winner owns the name-keyed slot and lowers through the
|
/// winner): the winner owns the name-keyed slot and lowers through the
|
||||||
/// normal lazy path, so `resolveBareCallee` returns `.none` for it. A shadow
|
/// normal lazy path, so `selectPlainCallableAuthor` returns `.none` for it. A shadow
|
||||||
/// is declared a fresh same-name FuncId in its OWN module's visibility
|
/// is declared a fresh same-name FuncId in its OWN module's visibility
|
||||||
/// context and its body lowered into that slot via fix-0102b's identity-
|
/// context and its body lowered into that slot via fix-0102b's identity-
|
||||||
/// addressable `lowerFunctionBodyInto`. Idempotent: `lowered_fids` tracks
|
/// addressable `lowerFunctionBodyInto`. Idempotent: `lowered_fids` tracks
|
||||||
@@ -3325,8 +3371,11 @@ pub const Lowering = struct {
|
|||||||
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
||||||
{
|
{
|
||||||
if (self.current_source_file) |caller_file| {
|
if (self.current_source_file) |caller_file| {
|
||||||
switch (self.resolveBareCallee(id.name, caller_file)) {
|
switch (self.selectPlainCallableAuthor(id.name, caller_file)) {
|
||||||
.func => |resolved| break :blk_fv resolved.fid,
|
.func => |sf| {
|
||||||
|
var selected = sf;
|
||||||
|
break :blk_fv self.selectedFuncId(&selected, id.name);
|
||||||
|
},
|
||||||
.ambiguous => {
|
.ambiguous => {
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, node.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{id.name});
|
d.addFmt(.err, node.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{id.name});
|
||||||
@@ -7377,8 +7426,11 @@ pub const Lowering = struct {
|
|||||||
(if (self.scope) |scope| scope.lookup(fn_name) == null else true))
|
(if (self.scope) |scope| scope.lookup(fn_name) == null else true))
|
||||||
{
|
{
|
||||||
if (self.current_source_file) |caller_file| {
|
if (self.current_source_file) |caller_file| {
|
||||||
switch (self.resolveBareCallee(fn_name, caller_file)) {
|
switch (self.selectPlainCallableAuthor(fn_name, caller_file)) {
|
||||||
.func => |resolved| break :blk_cl resolved.fid,
|
.func => |sf| {
|
||||||
|
var selected = sf;
|
||||||
|
break :blk_cl self.selectedFuncId(&selected, fn_name);
|
||||||
|
},
|
||||||
.ambiguous => {
|
.ambiguous => {
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, arg.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fn_name});
|
d.addFmt(.err, arg.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fn_name});
|
||||||
@@ -7682,27 +7734,28 @@ pub const Lowering = struct {
|
|||||||
// author. Only a plain top-level identifier call routes here:
|
// author. Only a plain top-level identifier call routes here:
|
||||||
// scope-mangled / UFCS-aliased / locally-shadowed names and
|
// scope-mangled / UFCS-aliased / locally-shadowed names and
|
||||||
// 0/1-author names fall straight to the existing path below
|
// 0/1-author names fall straight to the existing path below
|
||||||
// (`resolveBareCallee` returns `.none`).
|
// (`selectPlainCallableAuthor` returns `.none`).
|
||||||
if (std.mem.eql(u8, func_name, id.name) and
|
if (std.mem.eql(u8, func_name, id.name) and
|
||||||
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
||||||
{
|
{
|
||||||
if (self.current_source_file) |caller_file| {
|
if (self.current_source_file) |caller_file| {
|
||||||
switch (self.resolveBareCallee(func_name, caller_file)) {
|
switch (self.selectPlainCallableAuthor(func_name, caller_file)) {
|
||||||
.none => {},
|
.none => {},
|
||||||
.ambiguous => {
|
.ambiguous => {
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
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 => |resolved| {
|
.func => |sf| {
|
||||||
const fid = resolved.fid;
|
var selected = sf;
|
||||||
|
const fid = self.selectedFuncId(&selected, func_name);
|
||||||
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;
|
||||||
// The RESOLVED author's decl drives variadic
|
// The RESOLVED author's decl drives variadic
|
||||||
// packing — not a first-wins re-lookup by name,
|
// packing — not a first-wins re-lookup by name,
|
||||||
// whose variadic shape may differ (fix-0102c F1).
|
// whose variadic shape may differ (fix-0102c F1).
|
||||||
self.packVariadicCallArgs(resolved.decl, c, &args);
|
self.packVariadicCallArgs(selected.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);
|
||||||
@@ -8202,8 +8255,11 @@ pub const Lowering = struct {
|
|||||||
// → existing first-wins path.
|
// → existing first-wins path.
|
||||||
const ufcs_fid: ?FuncId = blk_uf: {
|
const ufcs_fid: ?FuncId = blk_uf: {
|
||||||
if (self.current_source_file) |caller_file| {
|
if (self.current_source_file) |caller_file| {
|
||||||
switch (self.resolveBareCallee(fa.field, caller_file)) {
|
switch (self.selectPlainCallableAuthor(fa.field, caller_file)) {
|
||||||
.func => |resolved| break :blk_uf resolved.fid,
|
.func => |sf| {
|
||||||
|
var selected = sf;
|
||||||
|
break :blk_uf self.selectedFuncId(&selected, fa.field);
|
||||||
|
},
|
||||||
.ambiguous => {
|
.ambiguous => {
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fa.field});
|
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fa.field});
|
||||||
@@ -12054,8 +12110,14 @@ pub const Lowering = struct {
|
|||||||
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
||||||
{
|
{
|
||||||
if (self.current_source_file) |caller_file| {
|
if (self.current_source_file) |caller_file| {
|
||||||
switch (self.resolveBareCallee(id.name, caller_file)) {
|
switch (self.selectPlainCallableAuthor(id.name, caller_file)) {
|
||||||
.func => |resolved| break :blk resolved.decl,
|
// Default expansion needs only the author's decl
|
||||||
|
// (its param defaults) — never the FuncId. Reading
|
||||||
|
// `sf.decl` here keeps `materialized` null, so a
|
||||||
|
// bare call whose body is never emitted (e.g. only
|
||||||
|
// its defaults are inspected) does not lower the
|
||||||
|
// author (0102d).
|
||||||
|
.func => |sf| break :blk sf.decl,
|
||||||
.ambiguous => return null,
|
.ambiguous => return null,
|
||||||
.none => {},
|
.none => {},
|
||||||
}
|
}
|
||||||
@@ -12296,9 +12358,11 @@ pub const Lowering = struct {
|
|||||||
(if (self.scope) |scope| scope.lookup(bare_name) == null else true))
|
(if (self.scope) |scope| scope.lookup(bare_name) == null else true))
|
||||||
{
|
{
|
||||||
if (self.current_source_file) |caller_file| {
|
if (self.current_source_file) |caller_file| {
|
||||||
switch (self.resolveBareCallee(bare_name, caller_file)) {
|
switch (self.selectPlainCallableAuthor(bare_name, caller_file)) {
|
||||||
.func => |resolved| {
|
.func => |sf| {
|
||||||
const func = &self.module.functions.items[@intFromEnum(resolved.fid)];
|
var selected = sf;
|
||||||
|
const fid = self.selectedFuncId(&selected, bare_name);
|
||||||
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||||
return self.userParamTypes(func);
|
return self.userParamTypes(func);
|
||||||
},
|
},
|
||||||
.ambiguous, .none => {},
|
.ambiguous, .none => {},
|
||||||
@@ -14548,6 +14612,13 @@ pub const Lowering = struct {
|
|||||||
return .{ .l = self };
|
return .{ .l = self };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `Resolver` facade over the borrowed Phase A import facts (Phase B). Cheap
|
||||||
|
/// by-value; `collectVisibleAuthors`'s `AuthorSet.flat` slice is backed by
|
||||||
|
/// `self.alloc` and owned by the caller (`selectPlainCallableAuthor` frees it).
|
||||||
|
fn resolver(self: *Lowering) resolver_mod.Resolver {
|
||||||
|
return resolver_mod.Resolver.init(&self.program_index, self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn genericResolver(self: *Lowering) GenericResolver {
|
pub fn genericResolver(self: *Lowering) GenericResolver {
|
||||||
return .{ .l = self };
|
return .{ .l = self };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user