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:
agra
2026-06-07 14:17:08 +03:00
parent c839c60233
commit 662142c388
4 changed files with 288 additions and 14 deletions

View File

@@ -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