feat(resolver): source-keyed alias/const/global caches, write-side only [stdlib E0]
Phase E0 of the unified resolver (R5 §#4): add the source-partitioned
analogues of the global `type_alias_map` / `module_const_map` /
`global_names`, keyed `source path -> name -> X`, and POPULATE them from
the existing scan. Purely additive and behavior-preserving — the global
maps remain the ONLY readers; the read-side cutover to
`selectedAuthor.source` is E1.
ProgramIndex:
- type_aliases_by_source / module_consts_by_source / globals_by_source
(StringHashMap of inner StringHashMap), owned + freed on deinit.
- put{TypeAlias,ModuleConst,Global}BySource + removeModuleConstBySource
helpers; retain `module.alloc` to lazily create inner per-source maps.
lower.zig scan: every global `type_alias_map`/`module_const_map`/
`global_names` write (and each module_const_map.remove) now mirrors into
its by-source analogue, keyed by the registering decl's source
(decl.source_file / current_source_file), the analogue of module_fns.
Tests:
- program_index.test.zig: same alias/const/global name under two sources
lands two distinct entries (not last-wins); compat globals stay
single-keyed; removeModuleConstBySource scoped to its source.
- lower.test.zig: end-to-end two-source namespace fixture — the scan
populates the by-source caches per declaring source while the global
maps stay single-keyed by name.
Gate: zig build + zig build test (423, incl. 2 new) + run_examples
(477, byte-identical) + m3te ios-sim build, all exit 0.
This commit is contained in:
@@ -1457,3 +1457,114 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
||||
// A name no module authors (and no flat import provides) never routes.
|
||||
try std.testing.expect(lowering.selectPlainCallableAuthor("nonexistent", b_path) == .none);
|
||||
}
|
||||
|
||||
// E0 (R5 §#4): the scan populates the source-keyed caches partitioned by the
|
||||
// registering decl's source. Two namespaced modules each author the SAME alias
|
||||
// name `Color` AND the SAME const name `K`; the scan recurses into each
|
||||
// namespace's decls (per-source). After lowering, the by-source maps hold TWO
|
||||
// distinct entries under the two source keys (not last-wins), while the legacy
|
||||
// global maps stay single-keyed by name — the compat readers are unchanged.
|
||||
test "lower: scan populates source-keyed caches per declaring source (E0)" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
const io = lowerTestIo();
|
||||
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "Color :: *u8;\nK :: 5;\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "Color :: *u16;\nK :: 7;\n" });
|
||||
const main_src =
|
||||
\\na :: #import "a.sx";
|
||||
\\nb :: #import "b.sx";
|
||||
\\main :: () -> s32 { 0 }
|
||||
\\
|
||||
;
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = main_src });
|
||||
|
||||
var dirbuf: [4096]u8 = undefined;
|
||||
const dirlen = try tmp.dir.realPath(io, &dirbuf);
|
||||
const absdir = dirbuf[0..dirlen];
|
||||
|
||||
const main_path = try std.fmt.allocPrint(alloc, "{s}/main.sx", .{absdir});
|
||||
const main_bytes = try std.Io.Dir.readFileAlloc(.cwd(), io, main_path, alloc, .limited(1 << 20));
|
||||
const main_source = try alloc.dupeZ(u8, main_bytes);
|
||||
var p = parser.Parser.init(alloc, main_source);
|
||||
const root = p.parse() catch return error.ParseFailed;
|
||||
|
||||
var chain = std.StringHashMap(void).init(alloc);
|
||||
var cache = imports.ModuleCache.init(alloc);
|
||||
var import_graph = std.StringHashMap(std.StringHashMap(void)).init(alloc);
|
||||
var flat_import_graph = std.StringHashMap(std.StringHashMap(void)).init(alloc);
|
||||
const stdlib_paths = [_][]const u8{};
|
||||
|
||||
const mod = try imports.resolveImports(
|
||||
alloc,
|
||||
io,
|
||||
root,
|
||||
absdir,
|
||||
main_path,
|
||||
&chain,
|
||||
&cache,
|
||||
null,
|
||||
null,
|
||||
&stdlib_paths,
|
||||
&import_graph,
|
||||
&flat_import_graph,
|
||||
.{},
|
||||
);
|
||||
|
||||
var module_scopes = std.StringHashMap(std.StringHashMap(void)).init(alloc);
|
||||
try module_scopes.put(main_path, mod.scope);
|
||||
var cache_it = cache.iterator();
|
||||
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);
|
||||
resolved_root.* = .{ .span = root.span, .data = .{ .root = .{ .decls = mod.decls } } };
|
||||
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
var diagnostics = errors.DiagnosticList.init(alloc, main_source, main_path);
|
||||
var lowering = Lowering.init(&module);
|
||||
lowering.main_file = main_path;
|
||||
lowering.resolved_root = resolved_root;
|
||||
lowering.diagnostics = &diagnostics;
|
||||
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);
|
||||
try std.testing.expect(!diagnostics.hasErrors());
|
||||
|
||||
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 idx = &lowering.program_index;
|
||||
|
||||
// SAME alias name `Color` lands a DISTINCT entry under each source key.
|
||||
const color_a = idx.type_aliases_by_source.get(a_path).?.get("Color").?;
|
||||
const color_b = idx.type_aliases_by_source.get(b_path).?.get("Color").?;
|
||||
try std.testing.expect(color_a != color_b); // *u8 vs *u16 — source-partitioned
|
||||
|
||||
// SAME const name `K` lands a DISTINCT entry (distinct value node) per source.
|
||||
const k_a = idx.module_consts_by_source.get(a_path).?.get("K").?;
|
||||
const k_b = idx.module_consts_by_source.get(b_path).?.get("K").?;
|
||||
try std.testing.expect(k_a.value != k_b.value);
|
||||
|
||||
// Compat readers: the legacy global maps stay keyed by NAME alone — a
|
||||
// hashmap key holds exactly one value, so a same-name author is last-wins
|
||||
// there (one entry for `Color` / `K`), unchanged by the by-source writes.
|
||||
// The single global `Color` is one of the two source-keyed authors (not a
|
||||
// merged/duplicated value).
|
||||
const global_color = idx.type_alias_map.get("Color").?;
|
||||
try std.testing.expect(global_color == color_a or global_color == color_b);
|
||||
const global_k = idx.module_const_map.get("K").?;
|
||||
try std.testing.expect(global_k.value == k_a.value or global_k.value == k_b.value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user