refactor(stdlib/S1.2): delete module_fns; dissolve legacy_direct_any [additive]
Delete module_fns as a separate function-author fact source. Its authors already live in the module_decls raw facts, so lowerRetainedSameNameAuthors now reads function authors straight out of module_decls (filtered to *FnDecl via fnDeclOfRaw) — the same path → name → RawDeclRef store, fn-filtered. Remove imports.ModuleFns / FnIndex / indexModuleFns / buildModuleFns / fnDeclOf, the Compilation.module_fns field + its build + wiring, and ProgramIndex.module_fns. Remove VisibilityMode.legacy_direct_any (the quarantined own-scope-plus-full- import_graph mode): no production caller passed it, so the collectVisibleAuthors and isVisible switch arms that handled it are dead and go too, collapsing VisEdgeSet to the single flat-import walk. No semantic fallback is introduced; import_graph stays the transitive-visibility source for findVisibleImpls. Additive: the old maps stay active and lowering still consumes them — no lowering consumer is cut over to the DeclTable (that is S3), and no resolution behavior changes. Tests that drove the removed symbols are rerouted through module_decls / the flat-edge walk. Gate over the baseline-green corpus: zig build, zig build test (424/424), bash tests/run_examples.sh (540 passed) — all exit 0; single-author output byte-identical; multi-author 0722–0740 stdout/exit unchanged.
This commit is contained in:
10
src/core.zig
10
src/core.zig
@@ -29,9 +29,6 @@ pub const Compilation = struct {
|
||||
/// Flat-only subset of `import_graph` (bare `#import` edges, no namespaced
|
||||
/// `ns :: #import`). Borrowed by `ProgramIndex.flat_import_graph`.
|
||||
flat_import_graph: std.StringHashMap(std.StringHashMap(void)),
|
||||
/// Per-module authored-function index (`path → name → *const FnDecl`).
|
||||
/// Borrowed by `ProgramIndex.module_fns`.
|
||||
module_fns: imports.ModuleFns,
|
||||
/// Per-module scalar raw-decl index (`path → name → RawDeclRef`), built by
|
||||
/// `imports.buildImportFacts`. Borrowed by `ProgramIndex.module_decls`.
|
||||
module_decls: imports.ModuleDecls,
|
||||
@@ -69,7 +66,6 @@ pub const Compilation = struct {
|
||||
.module_scopes = std.StringHashMap(std.StringHashMap(void)).init(allocator),
|
||||
.import_graph = std.StringHashMap(std.StringHashMap(void)).init(allocator),
|
||||
.flat_import_graph = std.StringHashMap(std.StringHashMap(void)).init(allocator),
|
||||
.module_fns = imports.ModuleFns.init(allocator),
|
||||
.module_decls = imports.ModuleDecls.init(allocator),
|
||||
.namespace_edges = imports.NamespaceEdges.init(allocator),
|
||||
.decl_table = imports.DeclTable.init(allocator),
|
||||
@@ -133,11 +129,6 @@ pub const Compilation = struct {
|
||||
self.module_scopes.put(entry.key_ptr.*, entry.value_ptr.scope) catch {};
|
||||
}
|
||||
|
||||
// Per-module authored-function index, built from the SAME modules as
|
||||
// `module_scopes` (main + every cache entry). Keyed by path; same-name
|
||||
// cross-module authors stay distinct under their own paths.
|
||||
imports.buildModuleFns(self.allocator, self.file_path, mod, &cache, &self.module_fns) catch {};
|
||||
|
||||
// Raw import facts (the unified-resolver store): scalar per-module
|
||||
// raw-decl index + namespace edges, built from the SAME modules. Nothing
|
||||
// consumes these yet — they are borrowed by `ProgramIndex` for later
|
||||
@@ -320,7 +311,6 @@ pub const Compilation = struct {
|
||||
lowering.program_index.module_scopes = &self.module_scopes;
|
||||
lowering.program_index.import_graph = &self.import_graph;
|
||||
lowering.program_index.flat_import_graph = &self.flat_import_graph;
|
||||
lowering.program_index.module_fns = &self.module_fns;
|
||||
lowering.program_index.module_decls = &self.module_decls;
|
||||
lowering.program_index.namespace_edges = &self.namespace_edges;
|
||||
lowering.program_index.decl_table = &self.decl_table;
|
||||
|
||||
@@ -70,11 +70,11 @@ fn hasErr(diags: *const errors.DiagnosticList, needle: []const u8) bool {
|
||||
}
|
||||
|
||||
// Two flat-imported modules each author `greet`; a third is namespaced. The
|
||||
// step retains BOTH `greet` authors under their own paths in `module_fns` and
|
||||
// records the namespaced import in `import_graph` but NOT in `flat_import_graph`
|
||||
// — WITHOUT touching the merged scope: `mod.decls` stays byte-for-byte
|
||||
// first-wins (one `greet`, a.sx's), exactly as on `wt-fix-0102-base`.
|
||||
test "imports: module_fns retains same-name cross-module fns; flat_import_graph excludes namespaced edge" {
|
||||
// raw facts retain BOTH `greet` authors under their own paths in `module_decls`
|
||||
// (function authors flow through it) and record the namespaced import in
|
||||
// `import_graph` but NOT in `flat_import_graph` — WITHOUT touching the merged
|
||||
// scope: `mod.decls` stays byte-for-byte first-wins (one `greet`, a.sx's).
|
||||
test "imports: module_decls retains same-name cross-module fns; flat_import_graph excludes namespaced edge" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
@@ -131,8 +131,7 @@ test "imports: module_fns retains same-name cross-module fns; flat_import_graph
|
||||
.{},
|
||||
);
|
||||
|
||||
var module_fns = imports.ModuleFns.init(alloc);
|
||||
try imports.buildModuleFns(alloc, main_path, mod, &cache, &module_fns);
|
||||
var facts = try imports.buildImportFacts(alloc, main_path, mod, &cache);
|
||||
|
||||
// The MERGED scope the first-wins resolver consumes is unchanged: mergeFlat
|
||||
// still drops the second `greet`, so `mod.decls` carries exactly ONE — and
|
||||
@@ -147,12 +146,18 @@ test "imports: module_fns retains same-name cross-module fns; flat_import_graph
|
||||
}
|
||||
try std.testing.expectEqual(@as(usize, 1), greet_count);
|
||||
|
||||
// module_fns retains BOTH authors of `greet`, keyed by their own paths —
|
||||
// module_decls retains BOTH authors of `greet`, keyed by their own paths —
|
||||
// the dropped author is recorded here (side index), not in the merged scope.
|
||||
const a_fns = module_fns.get(a_path) orelse return error.MissingAFns;
|
||||
const b_fns = module_fns.get(b_path) orelse return error.MissingBFns;
|
||||
const a_greet = a_fns.get("greet") orelse return error.MissingAGreet;
|
||||
const b_greet = b_fns.get("greet") orelse return error.MissingBGreet;
|
||||
const a_idx = facts.decls.get(a_path) orelse return error.MissingAIndex;
|
||||
const b_idx = facts.decls.get(b_path) orelse return error.MissingBIndex;
|
||||
const a_greet = switch (a_idx.names.get("greet") orelse return error.MissingAGreet) {
|
||||
.fn_decl => |fd| fd,
|
||||
else => return error.AGreetNotFn,
|
||||
};
|
||||
const b_greet = switch (b_idx.names.get("greet") orelse return error.MissingBGreet) {
|
||||
.fn_decl => |fd| fd,
|
||||
else => return error.BGreetNotFn,
|
||||
};
|
||||
// Distinct authoring decls — not the same node deduped down to one.
|
||||
try std.testing.expect(a_greet != b_greet);
|
||||
// First-wins: the surviving merged-scope `greet` is a.sx's author.
|
||||
@@ -175,9 +180,9 @@ test "imports: module_fns retains same-name cross-module fns; flat_import_graph
|
||||
|
||||
// Mixed collision: a.sx authors `Widget` as a STRUCT (non-fn), b.sx authors it
|
||||
// as a FUNCTION. fix-0102a must NOT let the function-author retention shift the
|
||||
// merged scope — first-wins keeps a.sx's struct and drops b.sx's function,
|
||||
// exactly as on `wt-fix-0102-base`. (The fn author may still be indexed in
|
||||
// module_fns; resolution is what must be untouched.)
|
||||
// merged scope — first-wins keeps a.sx's struct and drops b.sx's function.
|
||||
// (The fn author may still be indexed in `module_decls`; resolution is what
|
||||
// must be untouched.)
|
||||
test "imports: mixed non-fn/fn same-name collision stays first-wins in merged scope" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
@@ -5,18 +5,6 @@ const errors = @import("errors.zig");
|
||||
const c_import = @import("c_import.zig");
|
||||
const Node = ast.Node;
|
||||
|
||||
/// The `*const ast.FnDecl` a function-authoring decl carries, or null when the
|
||||
/// decl is not a function — either a bare `fn_decl` (`f :: (…) -> T { … }`) or a
|
||||
/// `const_decl` whose value is a function. Drives the per-module `module_fns`
|
||||
/// identity index (fix-0102a).
|
||||
fn fnDeclOf(decl: *const Node) ?*const ast.FnDecl {
|
||||
return switch (decl.data) {
|
||||
.fn_decl => &decl.data.fn_decl,
|
||||
.const_decl => |cd| if (cd.value.data == .fn_decl) &cd.value.data.fn_decl else null,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Comptime evaluation context for the inline-if hoisting pass below.
|
||||
/// Mirrors the values `injectComptimeConstants` will later push into the
|
||||
/// lowering's `comptime_constants` map (OS / ARCH / POINTER_SIZE), but
|
||||
@@ -451,50 +439,15 @@ pub const ResolvedModule = struct {
|
||||
/// Module cache: maps resolved file paths to their ResolvedModules.
|
||||
pub const ModuleCache = std.StringHashMap(ResolvedModule);
|
||||
|
||||
/// Per-module function identity index: function NAME → the `*const FnDecl` that
|
||||
/// module AUTHORS. Mirrors a single module's slice of `module_scopes`.
|
||||
pub const FnIndex = std.StringHashMap(*const ast.FnDecl);
|
||||
|
||||
/// `path → name → *const FnDecl`, mirroring `module_scopes`. One entry per
|
||||
/// resolved module keyed by its path (a directory's combined module keyed by
|
||||
/// `dir_path`); each entry indexes only what that module AUTHORS. Two modules
|
||||
/// each authoring `f` are retained under their own paths — the identity index
|
||||
/// fix-0102c's bare-name disambiguation consults to bind a flat call to the
|
||||
/// right author.
|
||||
pub const ModuleFns = std.StringHashMap(FnIndex);
|
||||
|
||||
/// Index a single module's authored functions (`own_decls`) into `out[path]`.
|
||||
/// First-wins WITHIN a module mirrors the scan pass; cross-module same-name
|
||||
/// authors live under their own `path` keys.
|
||||
fn indexModuleFns(allocator: std.mem.Allocator, out: *ModuleFns, path: []const u8, own_decls: []const *Node) !void {
|
||||
const gop = try out.getOrPut(path);
|
||||
if (!gop.found_existing) gop.value_ptr.* = FnIndex.init(allocator);
|
||||
for (own_decls) |decl| {
|
||||
const fd = fnDeclOf(decl) orelse continue;
|
||||
const name = decl.data.declName() orelse continue;
|
||||
if (gop.value_ptr.contains(name)) continue;
|
||||
try gop.value_ptr.put(name, fd);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the per-module function index from a resolved program: the main module
|
||||
/// (keyed by `main_path`) plus every cached module (keyed by its own path).
|
||||
/// Mirrors how `core.zig` fills `module_scopes` from `mod.scope` + the cache.
|
||||
pub fn buildModuleFns(allocator: std.mem.Allocator, main_path: []const u8, main_mod: ResolvedModule, cache: *const ModuleCache, out: *ModuleFns) !void {
|
||||
try indexModuleFns(allocator, out, main_path, main_mod.own_decls);
|
||||
var it = cache.iterator();
|
||||
while (it.next()) |entry| {
|
||||
try indexModuleFns(allocator, out, entry.key_ptr.*, entry.value_ptr.own_decls);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Raw import facts (the unified-resolver store; nothing consumes it yet) ──
|
||||
// ── Raw import facts (the unified-resolver store) ──
|
||||
//
|
||||
// `buildImportFacts` produces two source-keyed views over the resolved program,
|
||||
// callable WITHOUT IR lowering (the LSP reuses it later): a scalar per-module
|
||||
// raw-decl index (`name → RawDeclRef`) and the namespace import edges
|
||||
// (`importer → alias → NamespaceTarget`). Both are built from each module's
|
||||
// `own_decls` — exactly the same modules `buildModuleFns` walks.
|
||||
// `own_decls` (the main module plus every cache entry). Function authors are
|
||||
// read out of `name → RawDeclRef` directly (`fnDeclOfRaw`), so there is no
|
||||
// separate function-only index.
|
||||
|
||||
/// A named top-level declaration the resolver may select, kept as the raw AST
|
||||
/// node pointer (NOT pre-classified — a `const_decl` whose value is a function
|
||||
@@ -566,9 +519,9 @@ pub fn rawDeclRefOf(decl: *const Node) ?RawDeclRef {
|
||||
}
|
||||
|
||||
/// Index one module's authored decls (`own_decls`) into `decls[path]` and record
|
||||
/// any namespace aliases into `ns_edges[path]`. First-wins WITHIN a module
|
||||
/// mirrors `indexModuleFns`; `own_decls` is already name-deduped by `addOwnDecl`,
|
||||
/// so the first-wins guard never actually fires here.
|
||||
/// any namespace aliases into `ns_edges[path]`. First-wins WITHIN a module;
|
||||
/// `own_decls` is already name-deduped by `addOwnDecl`, so the first-wins guard
|
||||
/// never actually fires here.
|
||||
fn indexModuleDecls(
|
||||
allocator: std.mem.Allocator,
|
||||
decls: *ModuleDecls,
|
||||
@@ -601,8 +554,8 @@ fn indexModuleDecls(
|
||||
}
|
||||
|
||||
/// Build the raw import facts from a resolved program: the main module (keyed by
|
||||
/// `main_path`) plus every cached module (keyed by its own path). The same module
|
||||
/// set `buildModuleFns` walks. No IR lowering required.
|
||||
/// `main_path`) plus every cached module (keyed by its own path). No IR lowering
|
||||
/// required.
|
||||
pub fn buildImportFacts(
|
||||
allocator: std.mem.Allocator,
|
||||
main_path: []const u8,
|
||||
|
||||
@@ -1291,7 +1291,7 @@ fn countRealBodies(module: *ir_mod.Module, name: []const u8) usize {
|
||||
|
||||
// fix-0102b: two flat-imported modules each author `greet`. The first-wins merge
|
||||
// keeps a.sx's author in the merged decl list (the WINNER) and drops b.sx's,
|
||||
// which `module_fns` still retains (0102a). `main` itself can't bare-call `greet`
|
||||
// which the `module_decls` raw facts still retain (0102a). `main` itself can't bare-call `greet`
|
||||
// — under fix-0102c two flat authors make that ambiguous — so it calls a.sx's
|
||||
// `use_greet` wrapper, whose own-author call to `greet` binds a.sx's winner.
|
||||
// BEFORE the identity-addressable pass, only the winner has a real body — the
|
||||
@@ -1358,11 +1358,9 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
||||
while (cache_it.next()) |entry| {
|
||||
try module_scopes.put(entry.key_ptr.*, entry.value_ptr.scope);
|
||||
}
|
||||
var module_fns = imports.ModuleFns.init(alloc);
|
||||
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.
|
||||
// Phase A raw facts: both `selectPlainCallableAuthor` (Phase C) and
|
||||
// `lowerRetainedSameNameAuthors` read function authors out of `module_decls`.
|
||||
// Wired exactly as `core.zig` does.
|
||||
var facts = try imports.buildImportFacts(alloc, main_path, mod, &cache);
|
||||
|
||||
const resolved_root = try alloc.create(Node);
|
||||
@@ -1378,7 +1376,6 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
||||
lowering.program_index.module_scopes = &module_scopes;
|
||||
lowering.program_index.import_graph = &import_graph;
|
||||
lowering.program_index.flat_import_graph = &flat_import_graph;
|
||||
lowering.program_index.module_fns = &module_fns;
|
||||
lowering.program_index.module_decls = &facts.decls;
|
||||
|
||||
lowering.lowerRoot(resolved_root);
|
||||
@@ -1409,23 +1406,23 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
||||
|
||||
// F1 (attempt-2): the identity map must be keyed by the STABLE AST field
|
||||
// pointer for BOTH same-name authors — the exact pointers `fn_ast_map` and
|
||||
// `module_fns` carry — not a per-iteration switch-capture temporary. If the
|
||||
// winner were keyed by `&fd` (the scanDecls bug), this lookup by the stable
|
||||
// `fn_ast_map` pointer would miss (null). fix-0102c routes calls through
|
||||
// exactly these pointers, so the round-trip must hold here.
|
||||
// the `module_decls` raw facts carry — not a per-iteration switch-capture
|
||||
// temporary. If the winner were keyed by `&fd` (the scanDecls bug), this
|
||||
// lookup by the stable `fn_ast_map` pointer would miss (null). fix-0102c
|
||||
// routes calls through exactly these pointers, so the round-trip must hold.
|
||||
const winner_fd = lowering.program_index.fn_ast_map.get("greet").?;
|
||||
const winner_fid = lowering.fn_decl_fids.get(winner_fd);
|
||||
try std.testing.expect(winner_fid != null);
|
||||
// Round-trips to the first-wins winner FuncId (resolveFuncByName's pick).
|
||||
try std.testing.expectEqual(lowering.resolveFuncByName("greet").?, winner_fid.?);
|
||||
|
||||
// The shadowed author's stable pointer lives in `module_fns`; find the one
|
||||
// The shadowed author's stable pointer lives in `module_decls`; find the one
|
||||
// that is NOT the winner and confirm IT round-trips to a DISTINCT FuncId.
|
||||
var shadow_fd: ?*const ast.FnDecl = null;
|
||||
var mf_it = module_fns.iterator();
|
||||
while (mf_it.next()) |path_entry| {
|
||||
if (path_entry.value_ptr.get("greet")) |fd| {
|
||||
if (fd != winner_fd) shadow_fd = fd;
|
||||
var md_it = facts.decls.iterator();
|
||||
while (md_it.next()) |path_entry| {
|
||||
if (path_entry.value_ptr.names.get("greet")) |ref| {
|
||||
if (ref == .fn_decl and ref.fn_decl != winner_fd) shadow_fd = ref.fn_decl;
|
||||
}
|
||||
}
|
||||
try std.testing.expect(shadow_fd != null);
|
||||
@@ -1521,8 +1518,6 @@ test "lower: scan populates source-keyed caches per declaring source (E0)" {
|
||||
while (cache_it.next()) |entry| {
|
||||
try module_scopes.put(entry.key_ptr.*, entry.value_ptr.scope);
|
||||
}
|
||||
var module_fns = imports.ModuleFns.init(alloc);
|
||||
try imports.buildModuleFns(alloc, main_path, mod, &cache, &module_fns);
|
||||
var facts = try imports.buildImportFacts(alloc, main_path, mod, &cache);
|
||||
|
||||
const resolved_root = try alloc.create(Node);
|
||||
@@ -1538,7 +1533,6 @@ test "lower: scan populates source-keyed caches per declaring source (E0)" {
|
||||
lowering.program_index.module_scopes = &module_scopes;
|
||||
lowering.program_index.import_graph = &import_graph;
|
||||
lowering.program_index.flat_import_graph = &flat_import_graph;
|
||||
lowering.program_index.module_fns = &module_fns;
|
||||
lowering.program_index.module_decls = &facts.decls;
|
||||
|
||||
lowering.lowerRoot(resolved_root);
|
||||
|
||||
@@ -986,10 +986,10 @@ pub const Lowering = struct {
|
||||
// Declare extern stub for all functions (bodies lowered
|
||||
// lazily). Key the identity map (`fn_decl_fids`, inside
|
||||
// `declareFunction`) by the STABLE AST field pointer — the
|
||||
// same `&decl.data.fn_decl` stored in `fn_ast_map` and
|
||||
// `module_fns` — not the switch-capture copy `fd`, whose
|
||||
// address is a per-iteration stack temporary that no later
|
||||
// decl-identity lookup can reproduce.
|
||||
// same `&decl.data.fn_decl` stored in `fn_ast_map` and the
|
||||
// `module_decls` raw facts — not the switch-capture copy `fd`,
|
||||
// whose address is a per-iteration stack temporary that no
|
||||
// later decl-identity lookup can reproduce.
|
||||
self.declareFunction(&decl.data.fn_decl, fd.name);
|
||||
},
|
||||
.const_decl => |cd| {
|
||||
@@ -1741,35 +1741,36 @@ pub const Lowering = struct {
|
||||
/// The first-wins flat/directory merge keeps exactly one author per name in
|
||||
/// the merged decl list; `scanDecls` declares that WINNER (lowered on demand
|
||||
/// through the name-keyed `lazyLowerFunction`). fix-0102a retained every
|
||||
/// dropped same-name author in `module_fns` (path → name → `*FnDecl`) without
|
||||
/// touching resolution; this walks that index and gives each shadowed author
|
||||
/// its own slot: `declareFunction` (identity-mapped to a fresh same-name
|
||||
/// FuncId) + `lowerFunctionBodyInto` (its body, in its own module's
|
||||
/// dropped same-name author in the `module_decls` raw facts (path → name →
|
||||
/// `RawDeclRef`) without touching resolution; this walks that index, filters
|
||||
/// each author to its `*FnDecl` (`fnDeclOfRaw`), and gives each shadowed
|
||||
/// author its own slot: `declareFunction` (identity-mapped to a fresh
|
||||
/// same-name FuncId) + `lowerFunctionBodyInto` (its body, in its own module's
|
||||
/// visibility context). Two same-name authors then carry distinct FuncIds and
|
||||
/// distinct bodies, while `resolveFuncByName` still returns the first (winner)
|
||||
/// author so existing calls bind first-wins.
|
||||
///
|
||||
/// Scoped to DIRECT flat imports of the main file: a `module_fns` entry whose
|
||||
/// path is the main file or one of its bare `#import` edges. A namespaced
|
||||
/// (`ns :: #import`) author has no bare-name winner and is excluded both by
|
||||
/// that flat-edge gate and by the `fn_ast_map` winner lookup below.
|
||||
/// Scoped to DIRECT flat imports of the main file: a `module_decls` entry
|
||||
/// whose path is the main file or one of its bare `#import` edges. A
|
||||
/// namespaced (`ns :: #import`) author has no bare-name winner and is excluded
|
||||
/// both by that flat-edge gate and by the `fn_ast_map` winner lookup below.
|
||||
pub fn lowerRetainedSameNameAuthors(self: *Lowering) void {
|
||||
const module_fns = self.program_index.module_fns orelse return;
|
||||
const module_decls = self.program_index.module_decls orelse return;
|
||||
const main_file = self.main_file orelse return;
|
||||
const flat_graph = self.program_index.flat_import_graph orelse return;
|
||||
const main_flat_edges = flat_graph.get(main_file);
|
||||
|
||||
var path_it = module_fns.iterator();
|
||||
var path_it = module_decls.iterator();
|
||||
while (path_it.next()) |path_entry| {
|
||||
const path = path_entry.key_ptr.*;
|
||||
const is_eligible = std.mem.eql(u8, path, main_file) or
|
||||
(main_flat_edges != null and main_flat_edges.?.contains(path));
|
||||
if (!is_eligible) continue;
|
||||
|
||||
var fn_it = path_entry.value_ptr.iterator();
|
||||
var fn_it = path_entry.value_ptr.names.iterator();
|
||||
while (fn_it.next()) |fn_entry| {
|
||||
const name = fn_entry.key_ptr.*;
|
||||
const fd = fn_entry.value_ptr.*;
|
||||
const fd = fnDeclOfRaw(fn_entry.value_ptr.*) orelse continue;
|
||||
|
||||
// A name with no bare winner is namespaced-only (`ns.fn`) — it
|
||||
// never participated in the flat merge, so it has no shadow to
|
||||
@@ -1871,18 +1872,18 @@ pub const Lowering = struct {
|
||||
/// THE plain bare-name call selector (fix-0102c, R5 §C). `resolveBareCallee`'s
|
||||
/// body verbatim, now over the Phase B author collector
|
||||
/// (`resolver.collectVisibleAuthors` — the ONE graph-walk) instead of a direct
|
||||
/// `module_fns` + `flat_import_graph` traversal. Routes a bare identifier call
|
||||
/// `name` from `caller_file` to the right same-name author when flat imports
|
||||
/// introduce a genuine collision. Every single-author / local / parameter /
|
||||
/// 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.
|
||||
/// `module_decls` + `flat_import_graph` traversal. Routes a bare identifier
|
||||
/// call `name` from `caller_file` to the right same-name author when flat
|
||||
/// imports introduce a genuine collision. Every single-author / local /
|
||||
/// parameter / 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.
|
||||
///
|
||||
/// The collector returns RAW authors across ALL decl domains; this selector
|
||||
/// reproduces `module_fns`' fn-only view by filtering each author through
|
||||
/// `fnDeclOfRaw` (a `const`-wrapped fn unwraps to its inner fn — the exact
|
||||
/// `*FnDecl` `module_fns` stored; every other domain drops out), preserving
|
||||
/// resolveBareCallee's negative space byte-for-byte.
|
||||
/// reproduces a fn-only author view by filtering each author through
|
||||
/// `fnDeclOfRaw` (a `const`-wrapped fn unwraps to its inner fn; every other
|
||||
/// domain drops out), preserving 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
|
||||
@@ -1910,8 +1911,8 @@ pub const Lowering = struct {
|
||||
|
||||
// own-author wins. The collector's `own` spans all domains; a non-fn
|
||||
// (or a const not bound to a function) means `caller_file` has no fn
|
||||
// `name` — fall through to the flat authors, exactly as the fn-only
|
||||
// `module_fns` walk did.
|
||||
// `name` — fall through to the flat authors, exactly as a fn-only walk
|
||||
// would.
|
||||
if (set.own) |own_author| {
|
||||
if (fnDeclOfRaw(own_author.raw)) |own| {
|
||||
if (winner != null and winner.? == own) return .none;
|
||||
@@ -2378,10 +2379,10 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
/// The `*FnDecl` a raw author wraps, or null when the author is not a
|
||||
/// function — `imports.fnDeclOf` over a `RawDeclRef` so the collector's
|
||||
/// all-domain authors reproduce `module_fns`' fn-only view (a `const`-wrapped
|
||||
/// fn unwraps to its inner fn, the same pointer `module_fns` holds; every
|
||||
/// other domain → null).
|
||||
/// function — unwraps a `RawDeclRef` so the collector's all-domain authors
|
||||
/// yield a fn-only view (a `const`-wrapped fn unwraps to its inner fn; every
|
||||
/// other domain → null). The single place function authors are read out of
|
||||
/// the `module_decls` raw facts.
|
||||
fn fnDeclOfRaw(ref: resolver_mod.RawDeclRef) ?*const ast.FnDecl {
|
||||
return switch (ref) {
|
||||
.fn_decl => |fd| fd,
|
||||
@@ -2633,27 +2634,20 @@ pub const Lowering = struct {
|
||||
const fd = self.program_index.fn_ast_map.get(name) orelse return true;
|
||||
if (fd.body.data != .foreign_expr) return true;
|
||||
if (fd.body.data.foreign_expr.library_ref != null) return true;
|
||||
return self.visibleOverEdges(name, .flat);
|
||||
return self.visibleOverEdges(name);
|
||||
},
|
||||
.user_bare_flat => return self.visibleOverEdges(name, .flat),
|
||||
.legacy_direct_any => return self.visibleOverEdges(name, .all),
|
||||
.user_bare_flat => return self.visibleOverEdges(name),
|
||||
}
|
||||
}
|
||||
|
||||
const VisEdgeSet = enum { flat, all };
|
||||
|
||||
/// Resolve the mode's edge set and run the per-file visibility walk. Falls
|
||||
/// Run the per-file visibility walk over the flat-import edge set. Falls
|
||||
/// open (visible) when the scoping infrastructure isn't wired (comptime
|
||||
/// callers, directory imports without main_file, etc.). The caller is
|
||||
/// responsible for restricting the check to names that ARE known top-level
|
||||
/// decls; otherwise every local variable would be policed.
|
||||
fn visibleOverEdges(self: *Lowering, name: []const u8, edges: VisEdgeSet) bool {
|
||||
fn visibleOverEdges(self: *Lowering, name: []const u8) bool {
|
||||
const source = self.current_source_file orelse return true;
|
||||
const graph = switch (edges) {
|
||||
.flat => self.program_index.flat_import_graph,
|
||||
.all => self.program_index.import_graph,
|
||||
};
|
||||
return nameVisibleOverEdges(self.program_index.module_scopes, graph, source, name);
|
||||
return nameVisibleOverEdges(self.program_index.module_scopes, self.program_index.flat_import_graph, source, name);
|
||||
}
|
||||
|
||||
/// Check if a C-imported function is visible from the current source file.
|
||||
|
||||
@@ -588,9 +588,9 @@ pub const GlobalInfo = struct { id: inst.GlobalId, ty: TypeId };
|
||||
/// `*ProgramIndex` instead of `*Lowering`.
|
||||
///
|
||||
/// OWNS the declaration maps below. BORROWS `module_scopes` / `import_graph` /
|
||||
/// `flat_import_graph` / `module_fns` (pointers into maps owned by the
|
||||
/// compilation driver, `core.zig`) — those are read-only views and are never
|
||||
/// freed here.
|
||||
/// `flat_import_graph` / `module_decls` / `namespace_edges` / `decl_table`
|
||||
/// (pointers into maps owned by the compilation driver, `core.zig`) — those are
|
||||
/// read-only views and are never freed here.
|
||||
///
|
||||
/// Per-map allocators are preserved exactly as they were on `Lowering`:
|
||||
/// `import_flags` / `fn_ast_map` / `global_names` use the lowering allocator
|
||||
@@ -615,11 +615,6 @@ pub const ProgramIndex = struct {
|
||||
/// `ns :: #import`). fix-0102c's bare-name disambiguation walks this to
|
||||
/// decide which same-name authors a flat importer can reach. Borrowed view.
|
||||
flat_import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null,
|
||||
/// Module path → (function name → authoring `*const FnDecl`), mirroring
|
||||
/// `module_scopes`. Retains every same-name author under its own path so
|
||||
/// fix-0102c can resolve a flat call to the right module's function.
|
||||
/// Borrowed view.
|
||||
module_fns: ?*imports.ModuleFns = null,
|
||||
/// Per-module scalar raw-decl index (`path → name → RawDeclRef`), built by
|
||||
/// `imports.buildImportFacts`. The unified resolver's raw-fact store.
|
||||
/// Borrowed view.
|
||||
@@ -696,7 +691,7 @@ pub const ProgramIndex = struct {
|
||||
|
||||
pub fn deinit(self: *ProgramIndex) void {
|
||||
// Owned maps only — module_scopes / import_graph / flat_import_graph /
|
||||
// module_fns / module_decls / namespace_edges / decl_table are borrowed.
|
||||
// module_decls / namespace_edges / decl_table are borrowed.
|
||||
self.import_flags.deinit();
|
||||
self.fn_ast_map.deinit();
|
||||
self.qualified_fn_source.deinit();
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
// resolveImports → buildImportFacts, the exact path core.zig drives) plus one
|
||||
// synthetic diamond fixture for pointer-identity dedup. The visibility-adapter
|
||||
// tests pin the nameVisibleOverEdges edge-walk that isNameVisible /
|
||||
// isCImportVisible run on top of — including the user_bare_flat vs the
|
||||
// over-permissive legacy_direct_any distinction.
|
||||
// isCImportVisible run on top of — the flat-edge set vs the full import_graph.
|
||||
|
||||
const std = @import("std");
|
||||
const ast = @import("../ast.zig");
|
||||
@@ -82,8 +81,8 @@ fn tag(ref: resolver.RawDeclRef) std.meta.Tag(resolver.RawDeclRef) {
|
||||
// ── collectVisibleAuthors ────────────────────────────────────────────────
|
||||
|
||||
// own author present; two distinct flat authors both returned RAW; and the
|
||||
// user_bare_flat edge set EXCLUDES a namespaced-only import that the quarantined
|
||||
// legacy_direct_any set still reaches.
|
||||
// user_bare_flat edge set EXCLUDES a namespaced-only import (reachable only over
|
||||
// a non-flat edge).
|
||||
test "resolver: collectVisibleAuthors — own author, two distinct flat authors, namespaced edge excluded" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -129,16 +128,10 @@ test "resolver: collectVisibleAuthors — own author, two distinct flat authors,
|
||||
try std.testing.expect(dup_set.flat[0].raw.fn_decl != dup_set.flat[1].raw.fn_decl);
|
||||
|
||||
// `secret` is authored only in p.sx, imported NAMESPACED (`g :: #import`).
|
||||
// user_bare_flat must NOT see it (p.sx is not a flat edge)...
|
||||
// user_bare_flat must NOT see it (p.sx is not a flat edge).
|
||||
const flat_secret = r.collectVisibleAuthors("secret", main_path, .user_bare_flat);
|
||||
try std.testing.expect(flat_secret.own == null);
|
||||
try std.testing.expectEqual(@as(usize, 0), flat_secret.flat.len);
|
||||
|
||||
// ...but the quarantined legacy_direct_any set (import_graph) still reaches
|
||||
// it — the exact over-permissiveness user_bare_flat tightens away.
|
||||
const any_secret = r.collectVisibleAuthors("secret", main_path, .legacy_direct_any);
|
||||
try std.testing.expect(any_secret.own == null);
|
||||
try std.testing.expectEqual(@as(usize, 1), any_secret.flat.len);
|
||||
}
|
||||
|
||||
// Diamond: the SAME author node is reachable over two flat edges. It must
|
||||
@@ -234,10 +227,10 @@ test "resolver: collectNamespaceAuthors — returns target members, walks no gra
|
||||
|
||||
// ── visibility predicate (the isNameVisible / isCImportVisible core) ──────
|
||||
|
||||
// nameVisibleOverEdges is what isVisible(.user_bare_flat) (=> .flat graph) and
|
||||
// the quarantined legacy_direct_any (=> import_graph) run on. They agree on own
|
||||
// + flat names and differ ONLY on a namespaced-only name — the byte-identical
|
||||
// behavior the adapters preserve vs the over-permissive set they avoid.
|
||||
// nameVisibleOverEdges is the edge-walk isVisible(.user_bare_flat) runs on (the
|
||||
// flat graph). Walked over the flat set vs the full import_graph, the two agree
|
||||
// on own + flat names and differ ONLY on a namespaced-only name — the flat set
|
||||
// the bare-name predicate uses, contrasted with the over-permissive full set.
|
||||
test "resolver: visibility edge-walk — own + flat visible; namespaced-only only under import_graph" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -275,7 +268,7 @@ test "resolver: visibility edge-walk — own + flat visible; namespaced-only onl
|
||||
try std.testing.expect(lower.nameVisibleOverEdges(&scopes, &all, "main", "dup"));
|
||||
|
||||
// Namespaced-only name: NOT visible under the flat set (user_bare_flat),
|
||||
// but visible under the import_graph set (legacy_direct_any).
|
||||
// but visible under the full import_graph set.
|
||||
try std.testing.expect(!lower.nameVisibleOverEdges(&scopes, &flat, "main", "secret"));
|
||||
try std.testing.expect(lower.nameVisibleOverEdges(&scopes, &all, "main", "secret"));
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
//! A read-only facade over the borrowed Phase A import facts on a
|
||||
//! `*ProgramIndex` (`module_decls` / `namespace_edges`) and the existing
|
||||
//! `import_graph` / `flat_import_graph` views. It OWNS nothing import-derived;
|
||||
//! those maps live in `imports.zig`/`core.zig` and are borrowed here, exactly
|
||||
//! like `module_fns`.
|
||||
//! those maps live in `imports.zig`/`core.zig` and are borrowed here.
|
||||
//!
|
||||
//! Two collectors sit on top of these facts (R5 §1 #1):
|
||||
//! - `collectVisibleAuthors` — own author ∪ the flat-import edge walk. THE one
|
||||
@@ -69,10 +68,6 @@ pub const VisibilityMode = enum {
|
||||
/// Registration / lazy lowering: falls open (visible), emits no user
|
||||
/// diagnostic, performs no graph walk.
|
||||
lowering_internal,
|
||||
/// own scope ∪ `import_graph` (flat AND namespaced edges) — an
|
||||
/// over-permissive set. QUARANTINE: reserved for sites PROVEN to be internal
|
||||
/// scans, never a user-facing lookup. Deleted in Phase K.
|
||||
legacy_direct_any,
|
||||
};
|
||||
|
||||
/// Read-only facade over the borrowed import facts. `alloc` backs the
|
||||
@@ -91,10 +86,10 @@ pub const Resolver = struct {
|
||||
/// this. `from` is the querying module's source path.
|
||||
///
|
||||
/// Edge set by mode: `flat_import_graph` for `user_bare_flat`/
|
||||
/// `c_import_bare`; `import_graph` for the quarantined `legacy_direct_any`.
|
||||
/// `impl_transitive` (a transitive closure owned by `findVisibleImpls`) and
|
||||
/// `lowering_internal` (no graph walk) are not single-hop author walks —
|
||||
/// reaching them here is a wiring bug, so we trip loudly.
|
||||
/// `c_import_bare`. `impl_transitive` (a transitive closure owned by
|
||||
/// `findVisibleImpls`) and `lowering_internal` (no graph walk) are not
|
||||
/// single-hop author walks — reaching them here is a wiring bug, so we trip
|
||||
/// loudly.
|
||||
pub fn collectVisibleAuthors(
|
||||
self: *Resolver,
|
||||
name: []const u8,
|
||||
@@ -111,7 +106,6 @@ pub const Resolver = struct {
|
||||
|
||||
const graph = (switch (vis) {
|
||||
.user_bare_flat, .c_import_bare => self.index.flat_import_graph,
|
||||
.legacy_direct_any => self.index.import_graph,
|
||||
// findVisibleImpls owns transitive visibility; lowering_internal
|
||||
// performs no graph walk. Neither selects a single-hop edge set.
|
||||
.impl_transitive, .lowering_internal => @panic(
|
||||
|
||||
Reference in New Issue
Block a user