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);
|
||||
}
|
||||
|
||||
@@ -765,6 +765,31 @@ pub const Lowering = struct {
|
||||
return ti.function.call_conv != .c;
|
||||
}
|
||||
|
||||
// ── Source-keyed cache writers (R5 §#4) ──
|
||||
// Mirror each global `type_alias_map` / `module_const_map` / `global_names`
|
||||
// write into its source-partitioned analogue, keyed by the registering
|
||||
// decl's source. Behavior-preserving for now: the global maps stay the only
|
||||
// readers; the per-source maps exist for the later read-side cutover. A null
|
||||
// source (unreachable for a scanned top-level decl post-import-resolution)
|
||||
// falls back to the main file; if even that is absent the write is skipped
|
||||
// rather than recorded under a fabricated key.
|
||||
fn recordTypeAliasBySource(self: *Lowering, source: ?[]const u8, name: []const u8, tid: TypeId) void {
|
||||
const src = source orelse self.main_file orelse return;
|
||||
self.program_index.putTypeAliasBySource(src, name, tid);
|
||||
}
|
||||
fn recordModuleConstBySource(self: *Lowering, source: ?[]const u8, name: []const u8, info: program_index_mod.ModuleConstInfo) void {
|
||||
const src = source orelse self.main_file orelse return;
|
||||
self.program_index.putModuleConstBySource(src, name, info);
|
||||
}
|
||||
fn recordGlobalBySource(self: *Lowering, source: ?[]const u8, name: []const u8, info: program_index_mod.GlobalInfo) void {
|
||||
const src = source orelse self.main_file orelse return;
|
||||
self.program_index.putGlobalBySource(src, name, info);
|
||||
}
|
||||
fn dropModuleConstBySource(self: *Lowering, source: ?[]const u8, name: []const u8) void {
|
||||
const src = source orelse self.main_file orelse return;
|
||||
self.program_index.removeModuleConstBySource(src, name);
|
||||
}
|
||||
|
||||
/// Pass 1: Scan declarations — register ASTs and extern stubs, but don't lower bodies.
|
||||
fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
// Pass 0: register every numeric-literal module const (`N :: 16` and the
|
||||
@@ -785,15 +810,27 @@ pub const Lowering = struct {
|
||||
if (decl.data != .const_decl) continue;
|
||||
const cd = decl.data.const_decl;
|
||||
switch (cd.value.data) {
|
||||
.int_literal => self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .s64 }) catch {},
|
||||
.float_literal => self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .f64 }) catch {},
|
||||
.int_literal => {
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = .s64 };
|
||||
self.program_index.module_const_map.put(cd.name, info) catch {};
|
||||
self.recordModuleConstBySource(decl.source_file, cd.name, info);
|
||||
},
|
||||
.float_literal => {
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = .f64 };
|
||||
self.program_index.module_const_map.put(cd.name, info) catch {};
|
||||
self.recordModuleConstBySource(decl.source_file, cd.name, info);
|
||||
},
|
||||
// A const whose RHS is an integer EXPRESSION over other consts
|
||||
// (`M :: 2; N :: M + 1`) is itself a usable count: register it so
|
||||
// `moduleConstInt` can fold the RHS through `evalConstIntExpr`
|
||||
// (issue 0083). Placeholder `.s64` type — the count consumers read
|
||||
// only the value; if the expression doesn't fold (references a
|
||||
// non-const), `moduleConstInt` yields null and the use diagnoses.
|
||||
.binary_op, .unary_op => self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .s64 }) catch {},
|
||||
.binary_op, .unary_op => {
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = .s64 };
|
||||
self.program_index.module_const_map.put(cd.name, info) catch {};
|
||||
self.recordModuleConstBySource(decl.source_file, cd.name, info);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
@@ -877,6 +914,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
self.program_index.type_alias_map.put(cd.name, target_ty) catch {};
|
||||
self.recordTypeAliasBySource(self.current_source_file, cd.name, target_ty);
|
||||
} else if (cd.value.data == .identifier) {
|
||||
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide;
|
||||
// Chase through type_alias_map, then look up named types
|
||||
@@ -886,10 +924,12 @@ pub const Lowering = struct {
|
||||
const rhs_name = cd.value.data.identifier.name;
|
||||
if (self.program_index.type_alias_map.get(rhs_name)) |chained| {
|
||||
self.program_index.type_alias_map.put(cd.name, chained) catch {};
|
||||
self.recordTypeAliasBySource(self.current_source_file, cd.name, chained);
|
||||
} else {
|
||||
const name_id = self.module.types.internString(rhs_name);
|
||||
if (self.module.types.findByName(name_id)) |tid| {
|
||||
self.program_index.type_alias_map.put(cd.name, tid) catch {};
|
||||
self.recordTypeAliasBySource(self.current_source_file, cd.name, tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -927,12 +967,14 @@ pub const Lowering = struct {
|
||||
const result_ty = self.resolveTypeCallWithBindings(call_data);
|
||||
if (result_ty != .void) {
|
||||
self.program_index.type_alias_map.put(cd.name, result_ty) catch {};
|
||||
self.recordTypeAliasBySource(self.current_source_file, cd.name, result_ty);
|
||||
}
|
||||
} else if (self.program_index.fn_ast_map.get(callee_name)) |fd| {
|
||||
// Type-returning function: Foo :: Complex(u32)
|
||||
if (fd.type_params.len > 0) {
|
||||
if (self.instantiateTypeFunction(cd.name, callee_name, fd, call_data.args)) |result_ty| {
|
||||
self.program_index.type_alias_map.put(cd.name, result_ty) catch {};
|
||||
self.recordTypeAliasBySource(self.current_source_file, cd.name, result_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -961,6 +1003,7 @@ pub const Lowering = struct {
|
||||
const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
if (result_ty != .void and result_ty != .unresolved) {
|
||||
self.program_index.type_alias_map.put(cd.name, result_ty) catch {};
|
||||
self.recordTypeAliasBySource(self.current_source_file, cd.name, result_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -974,17 +1017,19 @@ pub const Lowering = struct {
|
||||
// stay here (their type comes from the literal / inference).
|
||||
if (cd.type_annotation == null) {
|
||||
// Untyped literal constants (e.g. UI_VERT_SRC :: #string GLSL...GLSL;)
|
||||
switch (cd.value.data) {
|
||||
.string_literal => self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .string }) catch {},
|
||||
.int_literal => self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .s64 }) catch {},
|
||||
.float_literal => self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .f64 }) catch {},
|
||||
.bool_literal => self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .bool }) catch {},
|
||||
const lit_ty: ?TypeId = switch (cd.value.data) {
|
||||
.string_literal => .string,
|
||||
.int_literal => .s64,
|
||||
.float_literal => .f64,
|
||||
.bool_literal => .bool,
|
||||
// Complex constant expressions (e.g. COLOR_WHITE :: Color.{ r = 255, ... })
|
||||
.struct_literal => {
|
||||
const inferred_ty = self.inferExprType(cd.value);
|
||||
self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = inferred_ty }) catch {};
|
||||
},
|
||||
else => {},
|
||||
.struct_literal => self.inferExprType(cd.value),
|
||||
else => null,
|
||||
};
|
||||
if (lit_ty) |ty| {
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = ty };
|
||||
self.program_index.module_const_map.put(cd.name, info) catch {};
|
||||
self.recordModuleConstBySource(self.current_source_file, cd.name, info);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1065,6 +1110,7 @@ pub const Lowering = struct {
|
||||
// placeholder behind as a usable const.
|
||||
if (ty == .unresolved) {
|
||||
_ = self.program_index.module_const_map.remove(cd.name);
|
||||
self.dropModuleConstBySource(self.current_source_file, cd.name);
|
||||
return;
|
||||
}
|
||||
// Validate the initializer against the explicit annotation BY TYPE, so a
|
||||
@@ -1084,6 +1130,7 @@ pub const Lowering = struct {
|
||||
if (program_index_mod.evalConstFloatExpr(cd.value, self)) |fv| {
|
||||
self.diagNonIntegralNarrow(cd.value.span, fv, ty);
|
||||
_ = self.program_index.module_const_map.remove(cd.name);
|
||||
self.dropModuleConstBySource(self.current_source_file, cd.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1096,12 +1143,15 @@ pub const Lowering = struct {
|
||||
// `N : string : M + 2` are both pre-registered as `.s64` in scanDecls
|
||||
// pass 0); leaving it would let a count use still fold `N`.
|
||||
_ = self.program_index.module_const_map.remove(cd.name);
|
||||
self.dropModuleConstBySource(self.current_source_file, cd.name);
|
||||
return;
|
||||
}
|
||||
// Reconcile the registration with the resolved annotation (pass 0 stored
|
||||
// a literal/expression placeholder type), so the const folds and emits at
|
||||
// its declared type — the same `put` the literal path always did.
|
||||
self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = ty }) catch {};
|
||||
const info = program_index_mod.ModuleConstInfo{ .value = cd.value, .ty = ty };
|
||||
self.program_index.module_const_map.put(cd.name, info) catch {};
|
||||
self.recordModuleConstBySource(self.current_source_file, cd.name, info);
|
||||
}
|
||||
|
||||
/// True iff a literal initializer of `value`'s kind is faithfully
|
||||
@@ -1218,6 +1268,7 @@ pub const Lowering = struct {
|
||||
.is_extern = vd.is_foreign,
|
||||
});
|
||||
self.program_index.global_names.put(vd.name, .{ .id = gid, .ty = var_ty }) catch {};
|
||||
self.recordGlobalBySource(self.current_source_file, vd.name, .{ .id = gid, .ty = var_ty });
|
||||
}
|
||||
|
||||
/// Serialize a top-level global's initializer into a static `ConstantValue`.
|
||||
@@ -1313,11 +1364,13 @@ pub const Lowering = struct {
|
||||
const rhs_name = cd.value.data.identifier.name;
|
||||
if (self.program_index.type_alias_map.get(rhs_name)) |chained| {
|
||||
self.program_index.type_alias_map.put(cd.name, chained) catch {};
|
||||
self.recordTypeAliasBySource(decl.source_file, cd.name, chained);
|
||||
progressed = true;
|
||||
} else {
|
||||
const name_id = self.module.types.internString(rhs_name);
|
||||
if (self.module.types.findByName(name_id)) |tid| {
|
||||
self.program_index.type_alias_map.put(cd.name, tid) catch {};
|
||||
self.recordTypeAliasBySource(decl.source_file, cd.name, tid);
|
||||
progressed = true;
|
||||
}
|
||||
}
|
||||
@@ -9533,6 +9586,7 @@ pub const Lowering = struct {
|
||||
|
||||
// Register for runtime lookup: identifier resolution emits global_get
|
||||
self.program_index.global_names.put(name, .{ .id = gid, .ty = global_ty }) catch {};
|
||||
self.recordGlobalBySource(self.current_source_file, name, .{ .id = gid, .ty = global_ty });
|
||||
}
|
||||
|
||||
/// Lower a standalone `#run expr;` at the top level (side-effect only).
|
||||
@@ -14266,6 +14320,7 @@ pub const Lowering = struct {
|
||||
.is_const = true,
|
||||
});
|
||||
self.program_index.global_names.put(global_name, .{ .id = gid, .ty = ctx_ty }) catch {};
|
||||
self.recordGlobalBySource(self.current_source_file, global_name, .{ .id = gid, .ty = ctx_ty });
|
||||
}
|
||||
|
||||
/// Create a thunk function: __thunk_ConcreteType_Protocol_method(ctx: *void, args...) -> ret
|
||||
|
||||
@@ -97,6 +97,54 @@ test "ProgramIndex declaration maps round-trip (A1.1b)" {
|
||||
try std.testing.expectEqualStrings("list_len", idx.ufcs_alias_map.get("len").?);
|
||||
}
|
||||
|
||||
// E0 (R5 §#4): the source-keyed caches partition by declaring source, so the
|
||||
// SAME name authored in two different modules lands two DISTINCT entries under
|
||||
// two source keys — never last-wins. The legacy global maps stay single-keyed
|
||||
// by name (one entry per name), so the compat readers are untouched.
|
||||
test "ProgramIndex source-keyed caches partition same-name authors by source" {
|
||||
var idx = ProgramIndex.init(std.testing.allocator);
|
||||
defer idx.deinit();
|
||||
|
||||
var blk_a = ast.Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = &.{} } } };
|
||||
var blk_b = ast.Node{ .span = .{ .start = 1, .end = 1 }, .data = .{ .block = .{ .stmts = &.{} } } };
|
||||
|
||||
// SAME alias name `Foo` authored in two modules → two distinct TypeIds.
|
||||
idx.putTypeAliasBySource("a.sx", "Foo", .s64);
|
||||
idx.putTypeAliasBySource("b.sx", "Foo", .f64);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .s64), idx.type_aliases_by_source.get("a.sx").?.get("Foo"));
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .f64), idx.type_aliases_by_source.get("b.sx").?.get("Foo"));
|
||||
try std.testing.expectEqual(@as(u32, 2), idx.type_aliases_by_source.count());
|
||||
|
||||
// SAME const name `K` authored in two modules → two distinct ModuleConstInfos.
|
||||
idx.putModuleConstBySource("a.sx", "K", .{ .value = &blk_a, .ty = .s32 });
|
||||
idx.putModuleConstBySource("b.sx", "K", .{ .value = &blk_b, .ty = .f32 });
|
||||
try std.testing.expect(idx.module_consts_by_source.get("a.sx").?.get("K").?.value == &blk_a);
|
||||
try std.testing.expect(idx.module_consts_by_source.get("b.sx").?.get("K").?.value == &blk_b);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .s32), idx.module_consts_by_source.get("a.sx").?.get("K").?.ty);
|
||||
try std.testing.expectEqual(@as(?types.TypeId, .f32), idx.module_consts_by_source.get("b.sx").?.get("K").?.ty);
|
||||
|
||||
// SAME global name `g` authored in two modules → two distinct GlobalInfos.
|
||||
idx.putGlobalBySource("a.sx", "g", .{ .id = inst.GlobalId.fromIndex(0), .ty = .s64 });
|
||||
idx.putGlobalBySource("b.sx", "g", .{ .id = inst.GlobalId.fromIndex(1), .ty = .f64 });
|
||||
try std.testing.expect(idx.globals_by_source.get("a.sx").?.get("g").?.id == inst.GlobalId.fromIndex(0));
|
||||
try std.testing.expect(idx.globals_by_source.get("b.sx").?.get("g").?.id == inst.GlobalId.fromIndex(1));
|
||||
|
||||
// Compat readers: the legacy global maps stay keyed by NAME alone, so a
|
||||
// same-name author is last-wins there — exactly ONE entry for `Foo` / `K`,
|
||||
// unchanged by the source-keyed writes above.
|
||||
idx.type_alias_map.put("Foo", .s64) catch unreachable;
|
||||
idx.type_alias_map.put("Foo", .f64) catch unreachable;
|
||||
try std.testing.expectEqual(@as(u32, 1), idx.type_alias_map.count());
|
||||
idx.module_const_map.put("K", .{ .value = &blk_a, .ty = .s32 }) catch unreachable;
|
||||
idx.module_const_map.put("K", .{ .value = &blk_b, .ty = .f32 }) catch unreachable;
|
||||
try std.testing.expectEqual(@as(u32, 1), idx.module_const_map.count());
|
||||
|
||||
// removeModuleConstBySource drops only the named entry under its source.
|
||||
idx.removeModuleConstBySource("a.sx", "K");
|
||||
try std.testing.expect(idx.module_consts_by_source.get("a.sx").?.get("K") == null);
|
||||
try std.testing.expect(idx.module_consts_by_source.get("b.sx").?.get("K").?.value == &blk_b);
|
||||
}
|
||||
|
||||
/// Stand-in for the leaf-name lookup both array-dimension resolvers pass to the
|
||||
/// shared `evalConstIntExpr`: `M`/`N` resolve to integers, everything else is
|
||||
/// genuinely non-comptime.
|
||||
|
||||
@@ -581,6 +581,11 @@ pub const GlobalInfo = struct { id: inst.GlobalId, ty: TypeId };
|
||||
/// (set in `init`); the rest default to `page_allocator`. Written only by the
|
||||
/// declaration scan / registration code in `Lowering`; read everywhere else.
|
||||
pub const ProgramIndex = struct {
|
||||
/// The lowering/compilation allocator (`module.alloc`), retained so the
|
||||
/// source-keyed caches below can lazily create their inner per-source maps.
|
||||
/// Lives for the whole compilation; the inner maps are freed in `deinit`.
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
// ── Import / visibility ──
|
||||
/// Declaration name → is the function imported (declared `extern`)?
|
||||
import_flags: std.StringHashMap(bool),
|
||||
@@ -637,12 +642,30 @@ pub const ProgramIndex = struct {
|
||||
/// UFCS alias name → target function name.
|
||||
ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator),
|
||||
|
||||
// ── Source-keyed semantic caches (R5 §#4) ──
|
||||
// The source-partitioned analogues of `type_alias_map` / `module_const_map`
|
||||
// / `global_names`, keyed `source path → name → X`. Written by the same scan
|
||||
// (`scanDecls` in lower.zig), keyed by the registering decl's source. The
|
||||
// global maps above stay the ONLY readers for now; the read-side cutover to
|
||||
// `selectedAuthor.source` lands in a later phase. These maps OWN their inner
|
||||
// per-source maps and free them in `deinit`.
|
||||
/// Type alias name → target TypeId, partitioned by declaring source.
|
||||
type_aliases_by_source: std.StringHashMap(std.StringHashMap(TypeId)),
|
||||
/// Module-level value const → info, partitioned by declaring source.
|
||||
module_consts_by_source: std.StringHashMap(std.StringHashMap(ModuleConstInfo)),
|
||||
/// `#run` / top-level global name → GlobalInfo, partitioned by declaring source.
|
||||
globals_by_source: std.StringHashMap(std.StringHashMap(GlobalInfo)),
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator) ProgramIndex {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.import_flags = std.StringHashMap(bool).init(alloc),
|
||||
.fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(alloc),
|
||||
.qualified_fn_source = std.StringHashMap([]const u8).init(alloc),
|
||||
.global_names = std.StringHashMap(GlobalInfo).init(alloc),
|
||||
.type_aliases_by_source = std.StringHashMap(std.StringHashMap(TypeId)).init(alloc),
|
||||
.module_consts_by_source = std.StringHashMap(std.StringHashMap(ModuleConstInfo)).init(alloc),
|
||||
.globals_by_source = std.StringHashMap(std.StringHashMap(GlobalInfo)).init(alloc),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -660,5 +683,42 @@ pub const ProgramIndex = struct {
|
||||
self.protocol_ast_map.deinit();
|
||||
self.module_const_map.deinit();
|
||||
self.ufcs_alias_map.deinit();
|
||||
deinitBySource(TypeId, &self.type_aliases_by_source);
|
||||
deinitBySource(ModuleConstInfo, &self.module_consts_by_source);
|
||||
deinitBySource(GlobalInfo, &self.globals_by_source);
|
||||
}
|
||||
|
||||
/// Free every inner per-source map, then the outer map.
|
||||
fn deinitBySource(comptime V: type, outer: *std.StringHashMap(std.StringHashMap(V))) void {
|
||||
var it = outer.valueIterator();
|
||||
while (it.next()) |inner| inner.deinit();
|
||||
outer.deinit();
|
||||
}
|
||||
|
||||
/// Insert `name → value` into the per-source map for `source`, creating the
|
||||
/// inner map on first use. OOM is swallowed to mirror the `catch {}` global
|
||||
/// writes this shadows.
|
||||
fn putBySource(comptime V: type, outer: *std.StringHashMap(std.StringHashMap(V)), alloc: std.mem.Allocator, source: []const u8, name: []const u8, value: V) void {
|
||||
const gop = outer.getOrPut(source) catch return;
|
||||
if (!gop.found_existing) gop.value_ptr.* = std.StringHashMap(V).init(alloc);
|
||||
gop.value_ptr.put(name, value) catch {};
|
||||
}
|
||||
|
||||
pub fn putTypeAliasBySource(self: *ProgramIndex, source: []const u8, name: []const u8, tid: TypeId) void {
|
||||
putBySource(TypeId, &self.type_aliases_by_source, self.alloc, source, name, tid);
|
||||
}
|
||||
|
||||
pub fn putModuleConstBySource(self: *ProgramIndex, source: []const u8, name: []const u8, info: ModuleConstInfo) void {
|
||||
putBySource(ModuleConstInfo, &self.module_consts_by_source, self.alloc, source, name, info);
|
||||
}
|
||||
|
||||
pub fn putGlobalBySource(self: *ProgramIndex, source: []const u8, name: []const u8, info: GlobalInfo) void {
|
||||
putBySource(GlobalInfo, &self.globals_by_source, self.alloc, source, name, info);
|
||||
}
|
||||
|
||||
/// Mirror a `module_const_map.remove` into the per-source map: drop `name`
|
||||
/// from `source`'s inner map (a no-op if the source/name is absent).
|
||||
pub fn removeModuleConstBySource(self: *ProgramIndex, source: []const u8, name: []const u8) void {
|
||||
if (self.module_consts_by_source.getPtr(source)) |inner| _ = inner.remove(name);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user