diff --git a/src/core.zig b/src/core.zig index fd0b117f..b104b732 100644 --- a/src/core.zig +++ b/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; diff --git a/src/imports.test.zig b/src/imports.test.zig index 9739f792..fd992939 100644 --- a/src/imports.test.zig +++ b/src/imports.test.zig @@ -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(); diff --git a/src/imports.zig b/src/imports.zig index 668da293..ed15bfa5 100644 --- a/src/imports.zig +++ b/src/imports.zig @@ -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, diff --git a/src/ir/lower.test.zig b/src/ir/lower.test.zig index 956c7d59..c135187a 100644 --- a/src/ir/lower.test.zig +++ b/src/ir/lower.test.zig @@ -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); diff --git a/src/ir/lower.zig b/src/ir/lower.zig index ec772572..df4f9677 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -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. diff --git a/src/ir/program_index.zig b/src/ir/program_index.zig index 6734472f..3e55f647 100644 --- a/src/ir/program_index.zig +++ b/src/ir/program_index.zig @@ -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(); diff --git a/src/ir/resolver.test.zig b/src/ir/resolver.test.zig index e3618cd8..48354aa7 100644 --- a/src/ir/resolver.test.zig +++ b/src/ir/resolver.test.zig @@ -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")); diff --git a/src/ir/resolver.zig b/src/ir/resolver.zig index d7c0c7ee..994f01fa 100644 --- a/src/ir/resolver.zig +++ b/src/ir/resolver.zig @@ -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(