refactor(ir): extract ProgramIndex, move low-fanout decl facts (A1.1a)

Architecture phase A1.1a. Introduce src/ir/program_index.zig as the single
storage owner for declaration-name / import / visibility facts, and move the
three low-fanout maps out of the Lowering state bag:

- import_flags     (owned by ProgramIndex)
- module_scopes    (borrowed pointer into a core.zig-owned map)
- import_graph     (borrowed pointer into a core.zig-owned map)

Lowering embeds one ProgramIndex by value and reaches every moved fact through
self.program_index.<field>; later phases hand collaborator modules a
*ProgramIndex instead of *Lowering. 8 call sites in lower.zig + 2 setters in
core.zig repointed. No duplicate storage, no fallback path; zig build enforces
no missed reference.

Mutation-heavy registration (registerStructDecl etc.) stays in Lowering and
now writes import_flags through the index. High-fanout maps are deferred to
A1.1b.

Adds src/ir/program_index.test.zig (init-empty, import_flags round-trip,
borrowed-view ownership) wired into the ir.zig barrel.

Behavior-preserving: zig build, zig build test, and bash tests/run_examples.sh
(350 passed, 0 failed) all green.
This commit is contained in:
agra
2026-06-02 12:04:31 +03:00
parent 795ce3dc7d
commit 90520eefeb
5 changed files with 96 additions and 14 deletions

View File

@@ -11,6 +11,7 @@ const parser_mod = @import("../parser.zig");
const interp_mod = @import("interp.zig");
const errors = @import("../errors.zig");
const jni_descriptor = @import("jni_descriptor.zig");
const ProgramIndex = @import("program_index.zig").ProgramIndex;
const TypeId = types.TypeId;
const StringId = types.StringId;
@@ -104,9 +105,11 @@ pub const Lowering = struct {
target_type: ?TypeId = null, // target type for struct/enum literals without explicit names
lowered_functions: std.StringHashMap(void), // tracks which functions have been fully lowered
local_fn_counter: u32 = 0, // unique counter for mangling local function names
import_flags: std.StringHashMap(bool), // tracks whether each function is imported
module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null, // per-module visible names (from import resolution)
import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null, // module path → set of directly imported paths (used by param_impl_map visibility filter)
/// Declaration-name / import / visibility facts (architecture phase A1,
/// `ProgramIndex`). Owns `import_flags`; borrows `module_scopes` /
/// `import_graph` from the compilation driver. Reached via
/// `self.program_index.<field>`; populated by scan/registration code.
program_index: ProgramIndex,
current_source_file: ?[]const u8 = null, // source file of function currently being lowered
// Implicit Context parameter machinery. When the program imports
// `std.sx` (and therefore declares `Context :: struct {...}`), every
@@ -312,7 +315,7 @@ pub const Lowering = struct {
.alloc = module.alloc,
.fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(module.alloc),
.lowered_functions = std.StringHashMap(void).init(module.alloc),
.import_flags = std.StringHashMap(bool).init(module.alloc),
.program_index = ProgramIndex.init(module.alloc),
.global_names = std.StringHashMap(GlobalInfo).init(module.alloc),
};
}
@@ -1479,14 +1482,14 @@ pub const Lowering = struct {
switch (decl.data) {
.fn_decl => |fd| {
self.fn_ast_map.put(fd.name, &decl.data.fn_decl) catch {};
self.import_flags.put(fd.name, is_imported) catch {};
self.program_index.import_flags.put(fd.name, is_imported) catch {};
// Declare extern stub for all functions (bodies lowered lazily)
self.declareFunction(&fd, fd.name);
},
.const_decl => |cd| {
if (cd.value.data == .fn_decl) {
self.fn_ast_map.put(cd.name, &cd.value.data.fn_decl) catch {};
self.import_flags.put(cd.name, is_imported) catch {};
self.program_index.import_flags.put(cd.name, is_imported) catch {};
self.declareFunction(&cd.value.data.fn_decl, cd.name);
} else if (cd.value.data == .struct_decl) {
self.registerStructDecl(&cd.value.data.struct_decl);
@@ -1893,11 +1896,11 @@ pub const Lowering = struct {
/// responsible for restricting the call to names that ARE known
/// top-level decls; otherwise every local variable would be policed.
fn isNameVisible(self: *Lowering, name: []const u8) bool {
const scopes = self.module_scopes orelse return true;
const scopes = self.program_index.module_scopes orelse return true;
const source = self.current_source_file orelse return true;
const own_scope = scopes.get(source) orelse return true;
if (own_scope.contains(name)) return true;
const graph = self.import_graph orelse return true;
const graph = self.program_index.import_graph orelse return true;
const direct = graph.get(source) orelse return true;
var it = direct.iterator();
while (it.next()) |kv| {
@@ -11984,7 +11987,7 @@ pub const Lowering = struct {
out.appendSlice(self.alloc, entries) catch {};
return;
};
const graph = self.import_graph orelse {
const graph = self.program_index.import_graph orelse {
out.appendSlice(self.alloc, entries) catch {};
return;
};
@@ -14360,7 +14363,7 @@ pub const Lowering = struct {
const method_fd = &method_node.data.fn_decl;
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ib.target_type, method_fd.name }) catch continue;
self.fn_ast_map.put(qualified, method_fd) catch {};
self.import_flags.put(qualified, is_imported) catch {};
self.program_index.import_flags.put(qualified, is_imported) catch {};
self.declareFunction(method_fd, qualified);
impl_methods.put(method_fd.name, {}) catch {};
}
@@ -14373,7 +14376,7 @@ pub const Lowering = struct {
const synth_fd = self.synthesizeDefaultMethod(method, ib.target_type);
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ib.target_type, method.name }) catch continue;
self.fn_ast_map.put(qualified, synth_fd) catch {};
self.import_flags.put(qualified, is_imported) catch {};
self.program_index.import_flags.put(qualified, is_imported) catch {};
self.declareFunction(synth_fd, qualified);
}
}
@@ -14480,7 +14483,7 @@ pub const Lowering = struct {
const q = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ src_name, mfd.name }) catch continue;
if (self.fn_ast_map.contains(q)) continue; // first impl wins
self.fn_ast_map.put(q, mfd) catch {};
self.import_flags.put(q, is_imported) catch {};
self.program_index.import_flags.put(q, is_imported) catch {};
if (!is_generic_src) self.declareFunction(mfd, q);
}
}