From fb262e9e594c7828050322fff1bb785af300c5ac Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 2 Jun 2026 12:30:11 +0300 Subject: [PATCH] refactor(ir): move declaration maps into ProgramIndex (A1.1b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture phase A1.1b — mechanical storage relocation. Move the 9 declaration-fact maps out of the Lowering state bag into ProgramIndex: high-fanout: fn_ast_map, foreign_class_map, global_names, type_alias_map medium-fanout: struct_template_map, protocol_decl_map, protocol_ast_map, module_const_map, ufcs_alias_map 168 self. sites in lower.zig repointed to self.program_index.; external readers repointed too (core.zig foreign_class_map iteration; lower.test.zig fn_ast_map / foreign_class_map). No duplicate storage, no fallback path; zig build enforces no missed reference. The four maps whose value types were Lowering-private pull those types into program_index.zig as pub (GlobalInfo, StructTemplate + TemplateParam, ProtocolDeclInfo + ProtocolMethodInfo, ModuleConstInfo); lower.zig aliases them at file scope so call sites are unchanged. Behavior is preserved exactly: - per-map allocator unchanged — import_flags/fn_ast_map/global_names use the lowering allocator (ProgramIndex.init), the other 7 keep their page_allocator inline defaults; - ProgramIndex.deinit frees only the 10 owned maps, never the borrowed module_scopes / import_graph; - TypeTable.aliases still borrows &self.program_index.type_alias_map, loaned at lowerRoot with the same late-binding lifetime. Extends program_index.test.zig with declaration-map round-trips (fn AST, type alias, global, module const, foreign class, protocol decl/AST, struct template, ufcs alias). Registration logic (registerStructDecl / registerProtocolDecl / registerForeignClassDecl, ...) stays in Lowering, writing through the index. Gate green: zig build, zig build test, bash tests/run_examples.sh (350 passed, 0 failed). lower.zig 19433 -> 19393 lines. --- src/core.zig | 6 +- src/ir/lower.test.zig | 8 +- src/ir/lower.zig | 392 +++++++++++++++------------------- src/ir/program_index.test.zig | 60 +++++- src/ir/program_index.zig | 115 ++++++++-- 5 files changed, 337 insertions(+), 244 deletions(-) diff --git a/src/core.zig b/src/core.zig index 6a6c2ee..5c88d98 100644 --- a/src/core.zig +++ b/src/core.zig @@ -328,7 +328,7 @@ pub const Compilation = struct { return module; } - /// Walk `lowering.foreign_class_map` and render Java sources for every + /// Walk `lowering.program_index.foreign_class_map` and render Java sources for every /// `#jni_main #jni_class("...")` declaration. Renders happen here so the /// AST + class-registry snapshot stay confined to the lowering pass; the /// downstream APK pipeline only needs `{foreign_path, java_source}` pairs. @@ -342,7 +342,7 @@ pub const Compilation = struct { // and `#extends Alias` resolution. var registry = std.StringHashMap([]const u8).init(self.allocator); defer registry.deinit(); - var it_reg = lowering.foreign_class_map.iterator(); + var it_reg = lowering.program_index.foreign_class_map.iterator(); while (it_reg.next()) |entry| { try registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path); } @@ -353,7 +353,7 @@ pub const Compilation = struct { // .so loading via another class. const lib_name = libNameFromOutputPath(self.target_config.output_path); - var it = lowering.foreign_class_map.iterator(); + var it = lowering.program_index.foreign_class_map.iterator(); while (it.next()) |entry| { const fcd = entry.value_ptr.*; if (!fcd.is_main) continue; diff --git a/src/ir/lower.test.zig b/src/ir/lower.test.zig index 0050f92..c3a4ecc 100644 --- a/src/ir/lower.test.zig +++ b/src/ir/lower.test.zig @@ -479,7 +479,7 @@ test "lower: objcTypeEncodingFromSignature emits @ for Obj-C class pointers" { .is_foreign = true, .is_main = false, }; - try lowering.foreign_class_map.put("NSString", &ns_fcd); + try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd); // Return *NSString, no args: "@@:" const e1 = try lowering.objcTypeEncodingFromSignature(ns_ptr, &.{}, null); @@ -510,7 +510,7 @@ test "lower: objcTypeEncodingFromSignature unwraps optional to wire type" { .is_foreign = true, .is_main = false, }; - try lowering.foreign_class_map.put("NSString", &ns_fcd); + try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd); // `?s64 -> ?*NSString` collapses to `q -> @` at the Obj-C boundary. const opt_s64 = module.types.optionalOf(.s64); @@ -750,8 +750,8 @@ test "E1.4b converge inferred error sets: empty -> warning, raising -> converged r_body.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = r_stmts } } }; const raiser_fd = ast.FnDecl{ .name = "raiser", .params = &.{}, .return_type = r_rt, .body = r_body }; - lowering.fn_ast_map.put("stub", &stub_fd) catch unreachable; - lowering.fn_ast_map.put("raiser", &raiser_fd) catch unreachable; + lowering.program_index.fn_ast_map.put("stub", &stub_fd) catch unreachable; + lowering.program_index.fn_ast_map.put("raiser", &raiser_fd) catch unreachable; lowering.convergeInferredErrorSets(); diff --git a/src/ir/lower.zig b/src/ir/lower.zig index e67e102..416abde 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -11,7 +11,14 @@ 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 program_index_mod = @import("program_index.zig"); +const ProgramIndex = program_index_mod.ProgramIndex; +const GlobalInfo = program_index_mod.GlobalInfo; +const StructTemplate = program_index_mod.StructTemplate; +const TemplateParam = program_index_mod.TemplateParam; +const ProtocolDeclInfo = program_index_mod.ProtocolDeclInfo; +const ProtocolMethodInfo = program_index_mod.ProtocolMethodInfo; +const ModuleConstInfo = program_index_mod.ModuleConstInfo; const TypeId = types.TypeId; const StringId = types.StringId; @@ -101,7 +108,6 @@ pub const Lowering = struct { main_file: ?[]const u8 = null, // path of the main file; imported functions are declared extern resolved_root: ?*const Node = null, // full AST root (for building comptime modules) comptime_param_nodes: ?std.StringHashMap(*const Node) = null, // active comptime substitutions - fn_ast_map: std.StringHashMap(*const ast.FnDecl), 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 @@ -131,7 +137,6 @@ pub const Lowering = struct { trace_clear_fid: ?FuncId = null, // extern `sx_trace_clear` needs_trace_runtime: bool = false, // set when lowering emits a trace push/clear; signals Compilation to auto-link sx_trace.c chain_fail_target: ?ChainFailTarget = null, // ERR E2.4: when set, a failable `or` chain routes its TOTAL failure here (an absorbing consumer like `catch`) instead of propagating to the function - foreign_class_map: std.StringHashMap(*const ast.ForeignClassDecl) = std.StringHashMap(*const ast.ForeignClassDecl).init(std.heap.page_allocator), // sx alias → ForeignClassDecl (jni_class / objc_class / swift_class / ... — registered in scan pass) current_foreign_class: ?*const ast.ForeignClassDecl = null, // set while lowering a `#jni_main` (or any sx-defined `#jni_class`) bodied method — `super.method(args)` dispatch resolves the parent class against this fcd's `#extends` current_foreign_method: ?ast.ForeignMethodDecl = null, // the specific method whose body is being lowered; `super.(...)` reuses its signature type_bindings: ?std.StringHashMap(TypeId) = null, // generic type param bindings ($T → concrete TypeId) @@ -141,16 +146,12 @@ pub const Lowering = struct { in_lambda_body: bool = false, // true while lowering a closure-literal body; sharpens the `raise`-not-failable diagnostic (ERR E5.1: tell the user to annotate `-> (T, !)`) defer_stack: std.ArrayList(CleanupEntry) = std.ArrayList(CleanupEntry).empty, // block-scoped defer + onfail cleanup stack func_defer_base: usize = 0, // defer stack base for current function (lowerReturn drains to this) - global_names: std.StringHashMap(GlobalInfo) = std.StringHashMap(GlobalInfo).init(std.heap.page_allocator), // #run global name → GlobalId deferred_type_fns: std.ArrayList([]const u8) = std.ArrayList([]const u8).empty, // functions deferred until all types registered processing_deferred: bool = false, // true when processing deferred functions (prevents re-deferral) - struct_template_map: std.StringHashMap(StructTemplate) = std.StringHashMap(StructTemplate).init(std.heap.page_allocator), // generic struct name → template struct_defaults_map: std.StringHashMap([]const ?*const Node) = std.StringHashMap([]const ?*const Node).init(std.heap.page_allocator), // struct name → field defaults struct_instance_bindings: std.StringHashMap(std.StringHashMap(TypeId)) = std.StringHashMap(std.StringHashMap(TypeId)).init(std.heap.page_allocator), // mangled struct name → type param bindings struct_instance_template: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // mangled struct name → template name comptime_value_bindings: ?std.StringHashMap(i64) = null, // comptime value bindings ($N → integer value) - protocol_decl_map: std.StringHashMap(ProtocolDeclInfo) = std.StringHashMap(ProtocolDeclInfo).init(std.heap.page_allocator), // protocol name → protocol info - protocol_ast_map: std.StringHashMap(*const ast.ProtocolDecl) = std.StringHashMap(*const ast.ProtocolDecl).init(std.heap.page_allocator), // protocol name → AST node protocol_thunk_map: std.StringHashMap([]const FuncId) = std.StringHashMap([]const FuncId).init(std.heap.page_allocator), // "Proto\x00Type" → thunk FuncIds protocol_vtable_type_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // protocol name → vtable struct TypeId protocol_vtable_global_map: std.StringHashMap(inst_mod.GlobalId) = std.StringHashMap(inst_mod.GlobalId).init(std.heap.page_allocator), // "Proto\x00Type" → vtable GlobalId @@ -204,10 +205,7 @@ pub const Lowering = struct { /// Null / absent for the comptime `..$args` pack (no constraint). pack_constraint: ?std.StringHashMap([]const u8) = null, struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info - module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), // module-level value constants (e.g. AF_INET :s32: 2) foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames - type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // type alias name → target TypeId - ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // UFCS alias name → target function name target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if) comptime_constants: std.StringHashMap(ComptimeValue) = std.StringHashMap(ComptimeValue).init(std.heap.page_allocator), // compile-time known constants (e.g. OS, ARCH) diagnostics: ?*errors.DiagnosticList = null, // error reporting with source locations @@ -237,27 +235,6 @@ pub const Lowering = struct { ty: ?TypeId, // null if no type annotation (inferred) }; - const ModuleConstInfo = struct { - value: *const Node, - ty: TypeId, - }; - - const ProtocolDeclInfo = struct { - name: []const u8, - is_inline: bool, - methods: []const ProtocolMethodInfo, - }; - - const ProtocolMethodInfo = struct { - name: []const u8, - param_types: []const TypeId, // excluding self - ret_type: TypeId, - // True when the AST return type was `Self` (encoded here as *void). - // Lets the dispatcher distinguish Self-disguised-as-*void (auto-unbox - // on the caller side) from a literal `-> *void` (return as-is). - ret_is_self: bool = false, - }; - /// One impl block for a parameterised protocol (e.g. `impl Into(Block) for Closure() -> void`). /// Stored in `param_impl_map` keyed by (protocol_name, target_args_mangled, source_mangled). /// `defining_module` enables import-scoped visibility + cross-module duplicate diagnostics. @@ -293,30 +270,13 @@ pub const Lowering = struct { ret_var_name: ?[]const u8, }; - /// Owned copy of a generic struct template (AST pointers are copied/interned to survive imports) - const StructTemplate = struct { - name: []const u8, - type_params: []const TemplateParam, - field_names: []const []const u8, - field_type_nodes: []const *const Node, // raw AST pointers — must be copied from heap nodes - }; - const TemplateParam = struct { - name: []const u8, - is_type_param: bool, // true for $T: Type, false for $N: u32 - is_variadic: bool = false, // `..$Ts: []Type` — binds remaining type args as a pack - }; - - const GlobalInfo = struct { id: inst_mod.GlobalId, ty: TypeId }; - pub fn init(module: *Module) Lowering { return .{ .module = module, .builder = Builder.init(module), .alloc = module.alloc, - .fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(module.alloc), .lowered_functions = std.StringHashMap(void).init(module.alloc), .program_index = ProgramIndex.init(module.alloc), - .global_names = std.StringHashMap(GlobalInfo).init(module.alloc), }; } @@ -327,11 +287,11 @@ pub const Lowering = struct { /// Pass 2: Lower only `main` (everything else is lowered lazily on demand). pub fn lowerRoot(self: *Lowering, root: *const Node) void { // Loan our alias map to the TypeTable. Done here (not in - // init) because `init` returns by value and `&self.type_alias_map` + // init) because `init` returns by value and `&self.program_index.type_alias_map` // wouldn't survive the return. `lowerRoot` runs on the // caller's stable Lowering, so the borrow stays valid for // every subsequent `resolveAstType` / `resolveTypeName` call. - self.module.types.aliases = &self.type_alias_map; + self.module.types.aliases = &self.program_index.type_alias_map; const decls = switch (root.data) { .root => |r| r.decls, else => return, @@ -411,7 +371,7 @@ pub const Lowering = struct { /// shape (`-> string`, `-> f64`, a non-failable tuple, …) is a clean /// diagnostic rather than a silent miscompile. fn validateMainSignature(self: *Lowering) void { - const fd = self.fn_ast_map.get("main") orelse return; + const fd = self.program_index.fn_ast_map.get("main") orelse return; if (fd.params.len != 0) { if (self.diagnostics) |diags| { @@ -576,15 +536,15 @@ pub const Lowering = struct { else => {}, } } - var it_fc = self.foreign_class_map.keyIterator(); + var it_fc = self.program_index.foreign_class_map.keyIterator(); while (it_fc.next()) |k| out.put(k.*, {}) catch {}; - var it_tmpl = self.struct_template_map.keyIterator(); + var it_tmpl = self.program_index.struct_template_map.keyIterator(); while (it_tmpl.next()) |k| out.put(k.*, {}) catch {}; - var it_pd = self.protocol_decl_map.keyIterator(); + var it_pd = self.program_index.protocol_decl_map.keyIterator(); while (it_pd.next()) |k| out.put(k.*, {}) catch {}; - var it_pa = self.protocol_ast_map.keyIterator(); + var it_pa = self.program_index.protocol_ast_map.keyIterator(); while (it_pa.next()) |k| out.put(k.*, {}) catch {}; - var it_al = self.type_alias_map.keyIterator(); + var it_al = self.program_index.type_alias_map.keyIterator(); while (it_al.next()) |k| out.put(k.*, {}) catch {}; } @@ -1270,7 +1230,7 @@ pub const Lowering = struct { const tc = self.target_config orelse return; if (!tc.isAndroid()) return; - var it = self.foreign_class_map.iterator(); + var it = self.program_index.foreign_class_map.iterator(); while (it.next()) |entry| { const fcd = entry.value_ptr.*; if (fcd.is_main and !fcd.is_foreign and fcd.runtime == .jni_class) return; @@ -1370,12 +1330,12 @@ pub const Lowering = struct { false; switch (decl.data) { .fn_decl => |fd| { - self.fn_ast_map.put(fd.name, &decl.data.fn_decl) catch {}; + self.program_index.fn_ast_map.put(fd.name, &decl.data.fn_decl) catch {}; self.lowerFunction(&fd, fd.name, is_imported); }, .const_decl => |cd| { if (cd.value.data == .fn_decl) { - self.fn_ast_map.put(cd.name, &cd.value.data.fn_decl) catch {}; + self.program_index.fn_ast_map.put(cd.name, &cd.value.data.fn_decl) catch {}; self.lowerFunction(&cd.value.data.fn_decl, cd.name, is_imported); } else if (cd.value.data == .struct_decl) { self.registerStructDecl(&cd.value.data.struct_decl); @@ -1481,14 +1441,14 @@ pub const Lowering = struct { false; switch (decl.data) { .fn_decl => |fd| { - self.fn_ast_map.put(fd.name, &decl.data.fn_decl) catch {}; + self.program_index.fn_ast_map.put(fd.name, &decl.data.fn_decl) 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.program_index.fn_ast_map.put(cd.name, &cd.value.data.fn_decl) 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) { @@ -1509,7 +1469,7 @@ pub const Lowering = struct { { // Type alias: MyFloat :: f64; Ptr :: *u8; Cb :: (s32) -> s32; const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types); - self.type_alias_map.put(cd.name, target_ty) catch {}; + self.program_index.type_alias_map.put(cd.name, target_ty) catch {}; } else if (cd.value.data == .identifier) { // Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide; // Chase through type_alias_map, then look up named types @@ -1517,12 +1477,12 @@ pub const Lowering = struct { // the .identifier branch of resolveTypeArg also consults // type_alias_map at use time. const rhs_name = cd.value.data.identifier.name; - if (self.type_alias_map.get(rhs_name)) |chained| { - self.type_alias_map.put(cd.name, chained) catch {}; + if (self.program_index.type_alias_map.get(rhs_name)) |chained| { + self.program_index.type_alias_map.put(cd.name, chained) catch {}; } else { const name_id = self.module.types.internString(rhs_name); if (self.module.types.findByName(name_id)) |tid| { - self.type_alias_map.put(cd.name, tid) catch {}; + self.program_index.type_alias_map.put(cd.name, tid) catch {}; } } } @@ -1536,7 +1496,7 @@ pub const Lowering = struct { else => "", }; if (callee_name.len > 0) { - if (self.struct_template_map.getPtr(callee_name)) |tmpl| { + if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| { const inst_id = self.instantiateGenericStruct(tmpl, call_data.args); // Register under the alias name const alias_name_id = self.module.types.internString(cd.name); @@ -1559,13 +1519,13 @@ pub const Lowering = struct { // hard-codes the vector layout. const result_ty = self.resolveTypeCallWithBindings(call_data); if (result_ty != .void) { - self.type_alias_map.put(cd.name, result_ty) catch {}; + self.program_index.type_alias_map.put(cd.name, result_ty) catch {}; } - } else if (self.fn_ast_map.get(callee_name)) |fd| { + } 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.type_alias_map.put(cd.name, result_ty) catch {}; + self.program_index.type_alias_map.put(cd.name, result_ty) catch {}; } } } @@ -1574,7 +1534,7 @@ pub const Lowering = struct { // Type alias for generic struct (from type_bridge path) const pt = &cd.value.data.parameterized_type_expr; const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name; - if (self.struct_template_map.getPtr(base_name)) |tmpl| { + if (self.program_index.struct_template_map.getPtr(base_name)) |tmpl| { const inst_id = self.instantiateGenericStruct(tmpl, pt.args); const alias_name_id = self.module.types.internString(cd.name); const inst_info = self.module.types.get(inst_id); @@ -1593,7 +1553,7 @@ pub const Lowering = struct { // position can `const_type()`. const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types); if (result_ty != .void and result_ty != .unresolved) { - self.type_alias_map.put(cd.name, result_ty) catch {}; + self.program_index.type_alias_map.put(cd.name, result_ty) catch {}; } } } @@ -1604,21 +1564,21 @@ pub const Lowering = struct { switch (cd.value.data) { .int_literal, .float_literal, .bool_literal, .string_literal, .undef_literal, .null_literal => { const ty = self.resolveType(ta); - self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = ty }) catch {}; + self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = ty }) catch {}; }, else => {}, } } else { // Untyped literal constants (e.g. UI_VERT_SRC :: #string GLSL...GLSL;) switch (cd.value.data) { - .string_literal => self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .string }) catch {}, - .int_literal => self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .s64 }) catch {}, - .float_literal => self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .f64 }) catch {}, - .bool_literal => self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .bool }) catch {}, + .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 {}, // Complex constant expressions (e.g. COLOR_WHITE :: Color.{ r = 255, ... }) .struct_literal => { const inferred_ty = self.inferExprType(cd.value); - self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = inferred_ty }) catch {}; + self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = inferred_ty }) catch {}; }, else => {}, } @@ -1654,7 +1614,7 @@ pub const Lowering = struct { } }, .ufcs_alias => |ua| { - self.ufcs_alias_map.put(ua.name, ua.target) catch {}; + self.program_index.ufcs_alias_map.put(ua.name, ua.target) catch {}; }, .var_decl => |vd| { // Top-level mutable global (e.g., `context : Context = ---;`) @@ -1694,7 +1654,7 @@ pub const Lowering = struct { .is_const = false, .is_extern = vd.is_foreign, }); - self.global_names.put(vd.name, .{ .id = gid, .ty = var_ty }) catch {}; + self.program_index.global_names.put(vd.name, .{ .id = gid, .ty = var_ty }) catch {}; }, else => {}, } @@ -1872,7 +1832,7 @@ pub const Lowering = struct { /// Check if a C-imported function is visible from the current source file. /// Returns true for non-C functions (always visible) or if no scoping info available. fn isCImportVisible(self: *Lowering, fn_name: []const u8) bool { - const fd = self.fn_ast_map.get(fn_name) orelse return true; + const fd = self.program_index.fn_ast_map.get(fn_name) orelse return true; // Only restrict C import fn_decls: foreign_expr with no library_ref if (fd.body.data != .foreign_expr) return true; if (fd.body.data.foreign_expr.library_ref != null) return true; @@ -1927,7 +1887,7 @@ pub const Lowering = struct { self.current_foreign_class = fcd; } // No AST? (builtins, foreign functions, or imported functions not in this file) - const fd = self.fn_ast_map.get(name) orelse return; + const fd = self.program_index.fn_ast_map.get(name) orelse return; // Foreign declarations stay as extern stubs but need to be REGISTERED // in the current module so callers get a real FuncId. Without this, // a comptime-lowered function (e.g. `concat` from std.sx pulled into @@ -2091,7 +2051,7 @@ pub const Lowering = struct { // but their bodies may call ctx-aware sx functions / fn-ptrs // and need a real Context to forward. if (!wants_ctx and self.implicit_ctx_enabled) { - if (self.global_names.get("__sx_default_context")) |dctx_gi| { + if (self.program_index.global_names.get("__sx_default_context")) |dctx_gi| { self.current_ctx_ref = self.builder.emit(.{ .global_addr = dctx_gi.id }, self.module.types.ptrTo(.void)); } } @@ -2235,7 +2195,7 @@ pub const Lowering = struct { // current_ctx_ref to &__sx_default_context. See companion comment // in `lowerFunction` for the same case. if (!wants_ctx_lf and self.implicit_ctx_enabled) { - if (self.global_names.get("__sx_default_context")) |dctx_gi| { + if (self.program_index.global_names.get("__sx_default_context")) |dctx_gi| { self.current_ctx_ref = self.builder.emit(.{ .global_addr = dctx_gi.id }, self.module.types.ptrTo(.void)); } } @@ -2464,7 +2424,7 @@ pub const Lowering = struct { }, .error_set_decl => self.registerErrorSetDecl(node), .ufcs_alias => |ua| { - self.ufcs_alias_map.put(ua.name, ua.target) catch {}; + self.program_index.ufcs_alias_map.put(ua.name, ua.target) catch {}; }, // Expression statement else => { @@ -2588,7 +2548,7 @@ pub const Lowering = struct { scope.fn_names.put(fd.name, mangled) catch {}; break :blk mangled; } else fd.name; - self.fn_ast_map.put(name, fd) catch {}; + self.program_index.fn_ast_map.put(name, fd) catch {}; self.lazyLowerFunction(name); } @@ -2607,7 +2567,7 @@ pub const Lowering = struct { break :blk mangled; } else cd.name; // Register in fn_ast_map so it can be resolved by lowerCall - self.fn_ast_map.put(name, fd) catch {}; + self.program_index.fn_ast_map.put(name, fd) catch {}; // Lower the function body (saves/restores builder state) self.lazyLowerFunction(name); return; @@ -2735,7 +2695,7 @@ pub const Lowering = struct { } } if (!found_local) { - if (self.global_names.get(asgn.target.data.identifier.name)) |gi| { + if (self.program_index.global_names.get(asgn.target.data.identifier.name)) |gi| { self.target_type = gi.ty; } } @@ -2802,7 +2762,7 @@ pub const Lowering = struct { } // Fallback: global variable assignment if (!handled) { - if (self.global_names.get(id.name)) |gi| { + if (self.program_index.global_names.get(id.name)) |gi| { if (asgn.op == .assign) { const val_ty = self.builder.getRefType(val); const store_val = if (val_ty != gi.ty and val_ty != .void and gi.ty != .void) @@ -3272,11 +3232,11 @@ pub const Lowering = struct { break :blk self.builder.load(self.current_ctx_ref, ctx_ty); } // Check globals (#run constants) - if (self.global_names.get(id.name)) |gi| { + if (self.program_index.global_names.get(id.name)) |gi| { break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty); } // Check module-level value constants (e.g. AF_INET :s32: 2) - if (self.module_const_map.get(id.name)) |ci| { + if (self.program_index.module_const_map.get(id.name)) |ci| { if (!self.isNameVisible(id.name)) { if (self.diagnostics) |d| d.addFmt(.err, node.span, "'{s}' is not visible; #import the module that declares it", .{id.name}); @@ -3287,13 +3247,13 @@ pub const Lowering = struct { // Check if it's a function name — produce function pointer reference // Resolve mangled name for block-local functions const eff_fn_name = if (self.scope) |scope| scope.lookupFn(id.name) orelse id.name else id.name; - if (self.fn_ast_map.contains(eff_fn_name)) { + if (self.program_index.fn_ast_map.contains(eff_fn_name)) { // Visibility check only for user-typed bare names (id.name // == eff_fn_name) without a UFCS alias. Mangled local- // scope names and UFCS rewrites are compiler indirections // and stay exempt. if (std.mem.eql(u8, eff_fn_name, id.name) and - self.ufcs_alias_map.get(id.name) == null and + self.program_index.ufcs_alias_map.get(id.name) == null and !self.isNameVisible(eff_fn_name)) { if (self.diagnostics) |d| @@ -3302,7 +3262,7 @@ pub const Lowering = struct { } // Type-as-value: if target is Any (Type variable), produce a type name string if (self.target_type == .any) { - const fd = self.fn_ast_map.get(eff_fn_name).?; + const fd = self.program_index.fn_ast_map.get(eff_fn_name).?; const fn_type_str = self.formatFnTypeString(fd); const sid = self.module.types.internString(fn_type_str); const str = self.builder.constString(sid); @@ -3365,7 +3325,7 @@ pub const Lowering = struct { if (self.type_bindings) |tb| { if (tb.get(id.name)) |t| break :blk_ty t; } - if (self.type_alias_map.get(id.name)) |t| break :blk_ty t; + if (self.program_index.type_alias_map.get(id.name)) |t| break :blk_ty t; if (type_bridge.resolveTypePrimitive(id.name)) |t| break :blk_ty t; const name_id = self.module.types.internString(id.name); if (self.module.types.findByName(name_id)) |t| break :blk_ty t; @@ -3426,7 +3386,7 @@ pub const Lowering = struct { } } // address_of(global) → emit global_addr (pointer to global, not load) - if (self.global_names.get(id_name)) |gi| { + if (self.program_index.global_names.get(id_name)) |gi| { const ptr_ty = self.module.types.ptrTo(gi.ty); break :blk self.builder.emit(.{ .global_addr = gi.id }, ptr_ty); } @@ -3536,7 +3496,7 @@ pub const Lowering = struct { break :blk binding.ref; } } - if (self.global_names.get(te.name)) |gi| { + if (self.program_index.global_names.get(te.name)) |gi| { break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty); } // Type literal in expression position → first-class @@ -4241,7 +4201,7 @@ pub const Lowering = struct { if (info == .@"struct" and info.@"struct".is_protocol and std.mem.eql(u8, self.module.types.getString(info.@"struct".name), p_name)) return true; } - const pd = self.protocol_ast_map.get(p_name) orelse return false; + const pd = self.program_index.protocol_ast_map.get(p_name) orelse return false; if (pd.type_params.len > 0) { const prefix = std.fmt.allocPrint(self.alloc, "{s}\x00", .{p_name}) catch return false; const suffix = std.fmt.allocPrint(self.alloc, "\x00{s}", .{self.mangleTypeName(ty)}) catch return false; @@ -4256,7 +4216,7 @@ pub const Lowering = struct { for (pd.methods) |m| { if (m.default_body != null) continue; const q = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ty_name, m.name }) catch return false; - if (!self.fn_ast_map.contains(q)) return false; + if (!self.program_index.fn_ast_map.contains(q)) return false; } return true; } @@ -6069,7 +6029,7 @@ pub const Lowering = struct { } } // Try as generic struct - if (self.struct_template_map.getPtr(callee_name)) |tmpl| { + if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| { return self.instantiateGenericStruct(tmpl, cl.args); } return .unresolved; @@ -6723,7 +6683,7 @@ pub const Lowering = struct { // resolve `*Foo` cross-class refs to their foreign paths. var registry = jni_descriptor.ClassRegistry.init(self.alloc); defer registry.deinit(); - var it = self.foreign_class_map.iterator(); + var it = self.program_index.foreign_class_map.iterator(); while (it.next()) |entry| { registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path) catch {}; } @@ -6931,7 +6891,7 @@ pub const Lowering = struct { const is_objc_obj = blk: { if (pointee_info != .@"struct") break :blk false; const name = self.module.types.getString(pointee_info.@"struct".name); - break :blk self.foreign_class_map.get(name) != null; + break :blk self.program_index.foreign_class_map.get(name) != null; }; if (is_objc_obj) { try out.append(self.alloc, '@'); @@ -7226,7 +7186,7 @@ pub const Lowering = struct { else blk: { // Fallback: no current ctx (e.g. compiler-internal callers). // Use the default context — same as the IMP would. - const default_ctx_gi = self.global_names.get("__sx_default_context") orelse { + const default_ctx_gi = self.program_index.global_names.get("__sx_default_context") orelse { if (self.diagnostics) |d| { d.addFmt(.err, span, "Cls.alloc() on sx-defined class '{s}': no current context and __sx_default_context missing", .{fcd.name}); } @@ -7311,7 +7271,7 @@ pub const Lowering = struct { // Build class registry snapshot for `*Foo` cross-class refs. var registry = jni_descriptor.ClassRegistry.init(self.alloc); defer registry.deinit(); - var it = self.foreign_class_map.iterator(); + var it = self.program_index.foreign_class_map.iterator(); while (it.next()) |entry| { registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path) catch {}; } @@ -7400,7 +7360,7 @@ pub const Lowering = struct { var parent_path: []const u8 = "android/app/Activity"; for (fcd.members) |m| switch (m) { .extends => |alias| { - if (self.foreign_class_map.get(alias)) |parent_fcd| { + if (self.program_index.foreign_class_map.get(alias)) |parent_fcd| { parent_path = parent_fcd.foreign_path; } else { parent_path = alias; @@ -7422,7 +7382,7 @@ pub const Lowering = struct { } if (resolved_method == null) { const parent_fcd = blk: for (fcd.members) |m| switch (m) { - .extends => |alias| if (self.foreign_class_map.get(alias)) |pf| break :blk pf else continue, + .extends => |alias| if (self.program_index.foreign_class_map.get(alias)) |pf| break :blk pf else continue, else => {}, } else null; if (parent_fcd) |pf| { @@ -7444,7 +7404,7 @@ pub const Lowering = struct { // for `*Self` resolution). var registry = jni_descriptor.ClassRegistry.init(self.alloc); defer registry.deinit(); - var it = self.foreign_class_map.iterator(); + var it = self.program_index.foreign_class_map.iterator(); while (it.next()) |entry| { registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path) catch {}; } @@ -7509,7 +7469,7 @@ pub const Lowering = struct { const id_name = c.callee.data.identifier.name; const eff_name = blk: { const scoped = if (self.scope) |scope| scope.lookupFn(id_name) orelse id_name else id_name; - if (self.ufcs_alias_map.get(id_name)) |target| { + if (self.program_index.ufcs_alias_map.get(id_name)) |target| { break :blk if (self.scope) |scope| scope.lookupFn(target) orelse target else target; } break :blk scoped; @@ -7525,15 +7485,15 @@ pub const Lowering = struct { // mangling (eff_name != id_name) and UFCS alias rewriting are // compiler indirections and stay exempt. if (std.mem.eql(u8, eff_name, id_name) and - self.ufcs_alias_map.get(id_name) == null and - self.fn_ast_map.contains(eff_name) and + self.program_index.ufcs_alias_map.get(id_name) == null and + self.program_index.fn_ast_map.contains(eff_name) and !self.isNameVisible(eff_name)) { if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "'{s}' is not visible; #import the module that declares it", .{eff_name}); return Ref.none; } - if (self.fn_ast_map.get(eff_name)) |fd| { + if (self.program_index.fn_ast_map.get(eff_name)) |fd| { if (self.current_match_tags) |tags| { if (tags.len > 0 and self.hasCastWithRuntimeType(c)) { return self.lowerRuntimeDispatchCall(fd, eff_name, c, tags); @@ -7579,12 +7539,12 @@ pub const Lowering = struct { const early_name = blk: { const id_name = c.callee.data.identifier.name; const scoped = if (self.scope) |scope| scope.lookupFn(id_name) orelse id_name else id_name; - if (self.ufcs_alias_map.get(id_name)) |target| { + if (self.program_index.ufcs_alias_map.get(id_name)) |target| { break :blk if (self.scope) |scope| scope.lookupFn(target) orelse target else target; } break :blk scoped; }; - if (self.fn_ast_map.get(early_name)) |fd| { + if (self.program_index.fn_ast_map.get(early_name)) |fd| { if (isPackFn(fd)) { // Protocol packs (`..xs: P`) and comptime type-packs // (`..$args`) both monomorphize per call shape. @@ -7747,7 +7707,7 @@ pub const Lowering = struct { // First try scope lookup for mangled local fn names const scoped = if (self.scope) |scope| scope.lookupFn(id.name) orelse id.name else id.name; // Then try UFCS alias on bare name - if (self.ufcs_alias_map.get(id.name)) |target| { + if (self.program_index.ufcs_alias_map.get(id.name)) |target| { // Resolve the alias target through scope too (target may be mangled) break :blk if (self.scope) |scope| scope.lookupFn(target) orelse target else target; } @@ -7825,7 +7785,7 @@ pub const Lowering = struct { } } // Check for comptime-expanded or generic functions - if (self.fn_ast_map.get(func_name)) |fd| { + if (self.program_index.fn_ast_map.get(func_name)) |fd| { if (hasComptimeParams(fd)) { return self.lowerComptimeCall(fd, c); } @@ -7835,7 +7795,7 @@ pub const Lowering = struct { } } // Check for #compiler free functions - if (self.fn_ast_map.get(func_name)) |fd_check| { + if (self.program_index.fn_ast_map.get(func_name)) |fd_check| { if (fd_check.body.data == .compiler_expr) { const ret_ty = if (fd_check.return_type) |rt| type_bridge.resolveAstType(rt, &self.module.types) else TypeId.void; return self.builder.compilerCall(func_name, args.items, ret_ty); @@ -7846,7 +7806,7 @@ pub const Lowering = struct { { // First attempt: function may already be declared (from scanDecls) // but not yet lowered. Try lazy lowering if needed. - if (self.fn_ast_map.contains(func_name) and !self.lowered_functions.contains(func_name)) { + if (self.program_index.fn_ast_map.contains(func_name) and !self.lowered_functions.contains(func_name)) { self.lazyLowerFunction(func_name); } if (self.resolveFuncByName(func_name)) |fid| { @@ -7854,7 +7814,7 @@ pub const Lowering = struct { const ret_ty = func.ret; const params = func.params; // Pack variadic args into a slice if the function has a variadic param - if (self.fn_ast_map.get(func_name)) |fd| { + if (self.program_index.fn_ast_map.get(func_name)) |fd| { self.packVariadicCallArgs(fd, c, &args); } const final_args = self.prependCtxIfNeeded(func, args.items); @@ -7883,7 +7843,7 @@ pub const Lowering = struct { } } // May be a global variable holding a function pointer - if (self.global_names.get(id.name)) |gi| { + if (self.program_index.global_names.get(id.name)) |gi| { if (!gi.ty.isBuiltin()) { const gti = self.module.types.get(gi.ty); if (gti == .function) { @@ -7950,7 +7910,7 @@ pub const Lowering = struct { // NewObject. Falls through to existing paths when no match. if (fa.object.data == .identifier) { const alias = fa.object.data.identifier.name; - if (self.foreign_class_map.get(alias)) |fcd| { + if (self.program_index.foreign_class_map.get(alias)) |fcd| { for (fcd.members) |m| switch (m) { .method => |md| if (md.is_static and std.mem.eql(u8, md.name, fa.field)) { return self.lowerForeignStaticCall(fcd, md, args.items, c.callee.span); @@ -7968,13 +7928,13 @@ pub const Lowering = struct { const resolved = if (self.scope) |scope| (scope.lookupFn(inner_name) orelse inner_name) else inner_name; // Generic struct static method: Animated(Size).make(...) - if (self.struct_template_map.getPtr(resolved)) |tmpl| { + if (self.program_index.struct_template_map.getPtr(resolved)) |tmpl| { const inst_ty = self.instantiateGenericStruct(tmpl, inner_call.args); const inst_name = self.formatTypeName(inst_ty); // Look up template method, monomorphize, and call if (self.struct_instance_template.get(inst_name)) |tmpl_name| { const tmpl_qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tmpl_name, fa.field }) catch fa.field; - if (self.fn_ast_map.get(tmpl_qualified)) |fd| { + if (self.program_index.fn_ast_map.get(tmpl_qualified)) |fd| { if (self.struct_instance_bindings.getPtr(inst_name)) |bindings| { const mangled = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ inst_name, fa.field }) catch fa.field; if (!self.lowered_functions.contains(mangled)) { @@ -7991,7 +7951,7 @@ pub const Lowering = struct { } } - if (self.fn_ast_map.get(resolved)) |fd| { + if (self.program_index.fn_ast_map.get(resolved)) |fd| { if (fd.type_params.len > 0) { // Try instantiate as type function if (self.instantiateTypeFunction(inner_name, inner_name, fd, inner_call.args)) |result_ty| { @@ -8038,7 +7998,7 @@ pub const Lowering = struct { if (scope.lookup(name) != null) break :blk false; } // Check global variables (e.g., g_font : *FontAtlas) - if (self.global_names.contains(name)) break :blk false; + if (self.program_index.global_names.contains(name)) break :blk false; // Not a local or global variable → namespace prefix break :blk true; } @@ -8059,8 +8019,8 @@ pub const Lowering = struct { else func_name; // Check for comptime-expanded or generic functions (try both names) - const effective_name = if (self.fn_ast_map.get(qualified_name) != null) qualified_name else func_name; - if (self.fn_ast_map.get(effective_name)) |fd| { + const effective_name = if (self.program_index.fn_ast_map.get(qualified_name) != null) qualified_name else func_name; + if (self.program_index.fn_ast_map.get(effective_name)) |fd| { if (hasComptimeParams(fd)) { return self.lowerComptimeCall(fd, c); } @@ -8068,14 +8028,14 @@ pub const Lowering = struct { return self.lowerGenericCall(fd, effective_name, c, args.items); } } - if (self.fn_ast_map.contains(effective_name) and !self.lowered_functions.contains(effective_name)) { + if (self.program_index.fn_ast_map.contains(effective_name) and !self.lowered_functions.contains(effective_name)) { self.lazyLowerFunction(effective_name); } if (self.resolveFuncByName(effective_name)) |fid| { const func = &self.module.functions.items[@intFromEnum(fid)]; const ret_ty = func.ret; const params = func.params; - if (self.fn_ast_map.get(effective_name)) |fd| { + if (self.program_index.fn_ast_map.get(effective_name)) |fd| { self.packVariadicCallArgs(fd, c, &args); } const final_args = self.prependCtxIfNeeded(func, args.items); @@ -8191,7 +8151,7 @@ pub const Lowering = struct { // shape, descriptor derived from the sx signature. const struct_name = self.getStructTypeName(obj_ty); if (struct_name) |sname_for_foreign| { - if (self.foreign_class_map.get(sname_for_foreign)) |fcd| { + if (self.program_index.foreign_class_map.get(sname_for_foreign)) |fcd| { return self.lowerForeignMethodCall(fcd, fa.field, obj, args.items, c.callee.span); } } @@ -8202,7 +8162,7 @@ pub const Lowering = struct { const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, fa.field }) catch fa.field; // Generic #compiler method dispatch - if (self.fn_ast_map.get(qualified)) |method_fd| { + if (self.program_index.fn_ast_map.get(qualified)) |method_fd| { if (method_fd.body.data == .compiler_expr) { const ret_ty = if (method_fd.return_type) |rt| type_bridge.resolveAstType(rt, &self.module.types) @@ -8216,7 +8176,7 @@ pub const Lowering = struct { if (self.struct_instance_template.get(sname)) |tmpl_name| { // This is an instantiated generic struct — look up template method const tmpl_qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tmpl_name, fa.field }) catch fa.field; - if (self.fn_ast_map.get(tmpl_qualified)) |fd| { + if (self.program_index.fn_ast_map.get(tmpl_qualified)) |fd| { // Get the stored type bindings for this instance if (self.struct_instance_bindings.getPtr(sname)) |bindings| { // Monomorphize the method with the struct's type bindings @@ -8240,7 +8200,7 @@ pub const Lowering = struct { // Generic method on a non-template struct: `obj.method($T, ...)` // or inferred form `obj.method(val)` where val's type pins $T. - if (self.fn_ast_map.get(qualified)) |gen_fd| { + if (self.program_index.fn_ast_map.get(qualified)) |gen_fd| { if (gen_fd.type_params.len > 0 and gen_fd.body.data != .compiler_expr) { // Effective AST args: prepend receiver so positions // line up with fd.params (which has self at index 0). @@ -8287,7 +8247,7 @@ pub const Lowering = struct { } // Try non-generic qualified method - if (self.fn_ast_map.get(qualified)) |fd| { + if (self.program_index.fn_ast_map.get(qualified)) |fd| { if (!self.lowered_functions.contains(qualified)) { self.lazyLowerFunction(qualified); } @@ -8319,7 +8279,7 @@ pub const Lowering = struct { // `recv.fn(args)` → `fn(recv, args)`). Lazily lower the body — // a function reached ONLY via UFCS would otherwise be declared // but never emitted (issue 0063: undefined symbol at link). - if (self.fn_ast_map.get(fa.field)) |_| { + if (self.program_index.fn_ast_map.get(fa.field)) |_| { if (!self.lowered_functions.contains(fa.field)) { self.lazyLowerFunction(fa.field); } @@ -8349,7 +8309,7 @@ pub const Lowering = struct { if (target_info == .@"struct") { const struct_name = self.module.types.typeName(tgt); const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ struct_name, el.name }) catch el.name; - if (self.fn_ast_map.get(qualified)) |fd| { + if (self.program_index.fn_ast_map.get(qualified)) |fd| { if (fd.type_params.len > 0) { return self.lowerGenericCall(fd, qualified, c, args.items); } @@ -9027,9 +8987,9 @@ pub const Lowering = struct { // Skip lambda params if (param_names.contains(id.name)) return; // Skip function names - if (self.fn_ast_map.contains(id.name)) return; + if (self.program_index.fn_ast_map.contains(id.name)) return; // Skip type names - if (self.struct_template_map.contains(id.name)) return; + if (self.program_index.struct_template_map.contains(id.name)) return; // Check if it's a variable in the parent scope if (self.scope) |scope| { if (scope.lookup(id.name)) |binding| { @@ -9548,7 +9508,7 @@ pub const Lowering = struct { }); // Register for runtime lookup: identifier resolution emits global_get - self.global_names.put(name, .{ .id = gid, .ty = global_ty }) catch {}; + self.program_index.global_names.put(name, .{ .id = gid, .ty = global_ty }) catch {}; } /// Lower a standalone `#run expr;` at the top level (side-effect only). @@ -9660,7 +9620,7 @@ pub const Lowering = struct { if (expr.data.call.callee.data != .identifier) return; const name = expr.data.call.callee.data.identifier.name; if (resolveBuiltin(name) != null) return; - if (self.fn_ast_map.get(name)) |fd| { + if (self.program_index.fn_ast_map.get(name)) |fd| { if (ct.resolveFuncByName(name) == null) { ct.lowerFunction(fd, name, false); } @@ -10814,7 +10774,7 @@ pub const Lowering = struct { // protocol. Only enforced for a known protocol constraint — an unknown // name (e.g. a plain type used as a pack constraint) is left alone. if (pack_protocol) |proto| { - if (self.protocol_ast_map.contains(proto)) { + if (self.program_index.protocol_ast_map.contains(proto)) { for (call_node.args[pack_start..], pack_arg_types.items) |arg_node, arg_ty| { if (!self.packArgConformsTo(proto, arg_ty)) { if (self.diagnostics) |diags| { @@ -11596,7 +11556,7 @@ pub const Lowering = struct { } return self.isKnownTypeName(te.name) or self.module.types.findByName(self.module.types.internString(te.name)) != null or - self.type_alias_map.get(te.name) != null; + self.program_index.type_alias_map.get(te.name) != null; }, .identifier => |id| { if (self.scope) |scope| { @@ -11604,7 +11564,7 @@ pub const Lowering = struct { } return self.isKnownTypeName(id.name) or self.module.types.findByName(self.module.types.internString(id.name)) != null or - self.type_alias_map.get(id.name) != null; + self.program_index.type_alias_map.get(id.name) != null; }, .pointer_type_expr, .many_pointer_type_expr, @@ -11677,7 +11637,7 @@ pub const Lowering = struct { if (self.type_bindings) |tb| { if (tb.get(id.name)) |ty| return ty; } - if (self.type_alias_map.get(id.name)) |alias_ty| return alias_ty; + if (self.program_index.type_alias_map.get(id.name)) |alias_ty| return alias_ty; const name_id = self.module.types.internString(id.name); if (self.module.types.findByName(name_id)) |t| return t; if (self.diagnostics) |diags| { @@ -11686,7 +11646,7 @@ pub const Lowering = struct { return .unresolved; }, .type_expr => |te| { - if (self.type_alias_map.get(te.name)) |alias_ty| return alias_ty; + if (self.program_index.type_alias_map.get(te.name)) |alias_ty| return alias_ty; return type_bridge.resolveAstType(node, &self.module.types); }, .call => |cl| { @@ -12220,12 +12180,12 @@ pub const Lowering = struct { .identifier => |id| { const eff_name = blk2: { const scoped = if (self.scope) |scope| scope.lookupFn(id.name) orelse id.name else id.name; - if (self.ufcs_alias_map.get(id.name)) |target| { + if (self.program_index.ufcs_alias_map.get(id.name)) |target| { break :blk2 if (self.scope) |scope| scope.lookupFn(target) orelse target else target; } break :blk2 scoped; }; - break :blk self.fn_ast_map.get(eff_name) orelse return null; + break :blk self.program_index.fn_ast_map.get(eff_name) orelse return null; }, // Namespace call `mod.fn(args)` — args map directly to params // (no `self` prepend), so default expansion is the same shape as @@ -12242,9 +12202,9 @@ pub const Lowering = struct { if (self.scope) |scope| { if (scope.lookup(name) != null) return null; // method call on a value } - if (self.global_names.contains(name)) return null; + if (self.program_index.global_names.contains(name)) return null; const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ name, fa.field }) catch fa.field; - break :blk self.fn_ast_map.get(qualified) orelse self.fn_ast_map.get(fa.field) orelse return null; + break :blk self.program_index.fn_ast_map.get(qualified) orelse self.program_index.fn_ast_map.get(fa.field) orelse return null; }, else => return null, } @@ -12311,7 +12271,7 @@ pub const Lowering = struct { if (self.scope) |scope| { if (scope.lookup(obj_name) != null) break :blk true; } - if (self.global_names.contains(obj_name)) break :blk true; + if (self.program_index.global_names.contains(obj_name)) break :blk true; break :blk false; }; if (!is_value) { @@ -12320,7 +12280,7 @@ pub const Lowering = struct { const func = &self.module.functions.items[@intFromEnum(fid)]; return self.userParamTypes(func); } - if (self.fn_ast_map.get(qualified)) |fd| { + if (self.program_index.fn_ast_map.get(qualified)) |fd| { var types_list = std.ArrayList(TypeId).empty; for (fd.params) |p| { types_list.append(self.alloc, self.resolveParamType(&p)) catch unreachable; @@ -12371,7 +12331,7 @@ pub const Lowering = struct { // whatever `self.target_type` was on entry — typically the // enclosing fn's return type — which silently truncates `xx ptr` // casts inside e.g. a `BOOL`-returning method body. - if (self.foreign_class_map.get(sname)) |fcd| { + if (self.program_index.foreign_class_map.get(sname)) |fcd| { if (self.findForeignMethodInChain(fcd, fa.field)) |found| { const md = found.method; const saved_fc = self.current_foreign_class; @@ -12405,7 +12365,7 @@ pub const Lowering = struct { } } // Try AST map (not yet lowered) - if (self.fn_ast_map.get(qualified)) |fd| { + if (self.program_index.fn_ast_map.get(qualified)) |fd| { if (fd.params.len > 0) { var types_list = std.ArrayList(TypeId).empty; for (fd.params[1..]) |p| { @@ -12418,7 +12378,7 @@ pub const Lowering = struct { // with type bindings from the struct instantiation if (self.struct_instance_template.get(sname)) |tmpl_name| { const tmpl_qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tmpl_name, fa.field }) catch return &.{}; - if (self.fn_ast_map.get(tmpl_qualified)) |fd| { + if (self.program_index.fn_ast_map.get(tmpl_qualified)) |fd| { if (fd.params.len > 0) { // Temporarily set type_bindings so resolveParamType can substitute T → concrete type const saved_bindings = self.type_bindings; @@ -12441,7 +12401,7 @@ pub const Lowering = struct { const bare_name = c.callee.data.identifier.name; const name = blk: { const scoped = if (self.scope) |scope| scope.lookupFn(bare_name) orelse bare_name else bare_name; - if (self.ufcs_alias_map.get(bare_name)) |target| { + if (self.program_index.ufcs_alias_map.get(bare_name)) |target| { break :blk if (self.scope) |scope| scope.lookupFn(target) orelse target else target; } break :blk scoped; @@ -12454,7 +12414,7 @@ pub const Lowering = struct { } // Check AST map for function signatures - if (self.fn_ast_map.get(name)) |fd| { + if (self.program_index.fn_ast_map.get(name)) |fd| { var types_list = std.ArrayList(TypeId).empty; for (fd.params) |p| { types_list.append(self.alloc, self.resolveParamType(&p)) catch unreachable; @@ -12463,7 +12423,7 @@ pub const Lowering = struct { } // Check global function pointer variables - if (self.global_names.get(bare_name)) |gi| { + if (self.program_index.global_names.get(bare_name)) |gi| { if (!gi.ty.isBuiltin()) { const ti = self.module.types.get(gi.ty); if (ti == .function) { @@ -13167,13 +13127,13 @@ pub const Lowering = struct { return self.module.types.vectorOf(elem, length); } // User-defined generic struct - if (self.struct_template_map.getPtr(callee_name)) |tmpl| { + if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| { return self.instantiateGenericStruct(tmpl, cl.args); } // User-defined type-returning function: Complex(u32), Sx(f32) // Also resolve via scope fn_names (local functions get mangled names) const resolved_name = if (self.scope) |scope| (scope.lookupFn(callee_name) orelse callee_name) else callee_name; - if (self.fn_ast_map.get(resolved_name)) |fd| { + if (self.program_index.fn_ast_map.get(resolved_name)) |fd| { if (fd.type_params.len > 0) { if (self.instantiateTypeFunction(callee_name, callee_name, fd, cl.args)) |ty| { return ty; @@ -13218,13 +13178,13 @@ pub const Lowering = struct { } // User-defined generic struct: look up template and instantiate - if (self.struct_template_map.getPtr(base_name)) |tmpl| { + if (self.program_index.struct_template_map.getPtr(base_name)) |tmpl| { return self.instantiateGenericStruct(tmpl, pt.args); } // Parameterized protocol used as a value type (`VL(s64)`): materialize a // 16-byte protocol value with the type-arg bound (not a 0-field stub). - if (self.protocol_ast_map.get(base_name)) |pd| { + if (self.program_index.protocol_ast_map.get(base_name)) |pd| { if (pd.type_params.len > 0) { return self.instantiateParamProtocol(pd, pt.args); } @@ -13587,8 +13547,8 @@ pub const Lowering = struct { const cname = tp.constraint.data.type_expr.name; // "Type" or a protocol name → type param break :blk std.mem.eql(u8, cname, "Type") or - self.protocol_decl_map.contains(cname) or - self.protocol_ast_map.contains(cname); + self.program_index.protocol_decl_map.contains(cname) or + self.program_index.protocol_ast_map.contains(cname); } else false), .is_variadic = tp.is_variadic, }; @@ -13604,7 +13564,7 @@ pub const Lowering = struct { // Copy the slice of pointers (the nodes themselves are heap-allocated). const ftype_nodes = self.alloc.dupe(*const Node, sd.field_types) catch return; - self.struct_template_map.put(owned_name, .{ + self.program_index.struct_template_map.put(owned_name, .{ .name = owned_name, .type_params = tps, .field_names = fnames, @@ -13616,7 +13576,7 @@ pub const Lowering = struct { if (method_node.data == .fn_decl) { const method_fd = &method_node.data.fn_decl; const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, method_fd.name }) catch continue; - self.fn_ast_map.put(qualified, method_fd) catch {}; + self.program_index.fn_ast_map.put(qualified, method_fd) catch {}; } } return; @@ -13699,7 +13659,7 @@ pub const Lowering = struct { const method_fd = &method_node.data.fn_decl; // Build qualified name: StructName.method const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, method_fd.name }) catch continue; - self.fn_ast_map.put(qualified, method_fd) catch {}; + self.program_index.fn_ast_map.put(qualified, method_fd) catch {}; // Declare extern stub (body is lowered lazily on demand) self.declareFunction(method_fd, qualified); } @@ -13793,7 +13753,7 @@ pub const Lowering = struct { // (Source, Target) pair at xx resolution time. Stash the AST so // `param_impl_map` lookup can resolve method signatures lazily. if (pd.type_params.len > 0) { - self.protocol_ast_map.put(pd.name, pd) catch {}; + self.program_index.protocol_ast_map.put(pd.name, pd) catch {}; return; } @@ -13860,12 +13820,12 @@ pub const Lowering = struct { .ret_is_self = ret_is_self, }) catch unreachable; } - self.protocol_decl_map.put(pd.name, .{ + self.program_index.protocol_decl_map.put(pd.name, .{ .name = pd.name, .is_inline = pd.is_inline, .methods = self.alloc.dupe(ProtocolMethodInfo, method_infos.items) catch unreachable, }) catch {}; - self.protocol_ast_map.put(pd.name, pd) catch {}; + self.program_index.protocol_ast_map.put(pd.name, pd) catch {}; // For vtable protocols, create the vtable struct type if (!pd.is_inline) { @@ -13955,7 +13915,7 @@ pub const Lowering = struct { self.type_bindings = saved_tb; const owned = self.alloc.dupe(u8, mangled) catch return id; - self.protocol_decl_map.put(owned, .{ + self.program_index.protocol_decl_map.put(owned, .{ .name = owned, .is_inline = pd.is_inline, .methods = self.alloc.dupe(ProtocolMethodInfo, method_infos.items) catch unreachable, @@ -13996,7 +13956,7 @@ pub const Lowering = struct { /// Find `name` in `protocol_name`'s type-arg namespace (`protocol($T,...)`). /// Returns the `type_params` index, or null (also for unknown protocols). pub fn lookupProtocolArg(self: *Lowering, protocol_name: []const u8, name: []const u8) ?u32 { - const pd = self.protocol_ast_map.get(protocol_name) orelse return null; + const pd = self.program_index.protocol_ast_map.get(protocol_name) orelse return null; for (pd.type_params, 0..) |tp, i| { if (std.mem.eql(u8, tp.name, name)) return @intCast(i); } @@ -14006,7 +13966,7 @@ pub const Lowering = struct { /// Find `name` in `protocol_name`'s runtime-accessor namespace (its methods /// — protocols have no fields). Returns the `methods` index, or null. pub fn lookupProtocolField(self: *Lowering, protocol_name: []const u8, name: []const u8) ?u32 { - const pd = self.protocol_ast_map.get(protocol_name) orelse return null; + const pd = self.program_index.protocol_ast_map.get(protocol_name) orelse return null; for (pd.methods, 0..) |m, i| { if (std.mem.eql(u8, m.name, name)) return @intCast(i); } @@ -14048,7 +14008,7 @@ pub const Lowering = struct { /// then handles the body via the standard path; `*Self` is /// substituted to `*State` during body lowering (M1.2 A.2b). fn registerForeignClassDecl(self: *Lowering, fcd: *const ast.ForeignClassDecl) void { - self.foreign_class_map.put(fcd.name, fcd) catch {}; + self.program_index.foreign_class_map.put(fcd.name, fcd) catch {}; if (!fcd.is_foreign and fcd.runtime == .objc_class) { if (self.module.lookupObjcDefinedClass(fcd.name) == null) { self.module.appendObjcDefinedClass(fcd.name, fcd); @@ -14083,7 +14043,7 @@ pub const Lowering = struct { fn resolveObjcParentName(self: *Lowering, fcd: *const ast.ForeignClassDecl) []const u8 { for (fcd.members) |m| switch (m) { .extends => |alias| { - if (self.foreign_class_map.get(alias)) |parent_fcd| { + if (self.program_index.foreign_class_map.get(alias)) |parent_fcd| { if (parent_fcd.is_foreign) return parent_fcd.foreign_path; // Sx-defined parent — its alias IS its Obj-C name. return parent_fcd.name; @@ -14154,7 +14114,7 @@ pub const Lowering = struct { const body = method.body orelse continue; const fd = self.synthesizeFnDeclFromObjcMethod(method, body) orelse continue; const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ fcd.name, method.name }) catch continue; - self.fn_ast_map.put(qualified, fd) catch {}; + self.program_index.fn_ast_map.put(qualified, fd) catch {}; self.declareFunction(fd, qualified); // Selector mangling — A.1's deriveObjcSelector handles @@ -14337,7 +14297,7 @@ pub const Lowering = struct { if (inner.data == .foreign_class_decl) { const fcd = &inner.data.foreign_class_decl; const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ns.name, fcd.name }) catch fcd.name; - self.foreign_class_map.put(qualified, fcd) catch {}; + self.program_index.foreign_class_map.put(qualified, fcd) catch {}; } else if (inner.data == .namespace_decl) { // Nested namespaces — qualify with both prefixes. self.registerNamespacedForeignClasses(inner.data.namespace_decl); @@ -14362,20 +14322,20 @@ pub const Lowering = struct { if (method_node.data == .fn_decl) { 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.program_index.fn_ast_map.put(qualified, method_fd) catch {}; self.program_index.import_flags.put(qualified, is_imported) catch {}; self.declareFunction(method_fd, qualified); impl_methods.put(method_fd.name, {}) catch {}; } } // Synthesize default methods from protocol declaration - if (self.protocol_ast_map.get(ib.protocol_name)) |pd| { + if (self.program_index.protocol_ast_map.get(ib.protocol_name)) |pd| { for (pd.methods) |method| { if (method.default_body != null and !impl_methods.contains(method.name)) { // Create a synthesized fn_decl for the default method 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.program_index.fn_ast_map.put(qualified, synth_fd) catch {}; self.program_index.import_flags.put(qualified, is_imported) catch {}; self.declareFunction(synth_fd, qualified); } @@ -14478,11 +14438,11 @@ pub const Lowering = struct { // standalone function would emit garbage (an unresolved return // type). Concrete instances are monomorphized per-erasure by // createProtocolThunk via this same fn_ast_map entry. - const is_generic_src = self.struct_template_map.contains(src_name); + const is_generic_src = self.program_index.struct_template_map.contains(src_name); for (methods.items) |mfd| { 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 {}; + if (self.program_index.fn_ast_map.contains(q)) continue; // first impl wins + self.program_index.fn_ast_map.put(q, mfd) catch {}; self.program_index.import_flags.put(q, is_imported) catch {}; if (!is_generic_src) self.declareFunction(mfd, q); } @@ -14580,7 +14540,7 @@ pub const Lowering = struct { /// Check if a type name is a registered protocol. fn isProtocolType(self: *Lowering, type_name: []const u8) bool { - return self.protocol_decl_map.contains(type_name); + return self.program_index.protocol_decl_map.contains(type_name); } /// Get protocol info for a TypeId (if it's a protocol type). @@ -14589,7 +14549,7 @@ pub const Lowering = struct { const info = self.module.types.get(ty); if (info != .@"struct") return null; const name = self.module.types.getString(info.@"struct".name); - return self.protocol_decl_map.get(name); + return self.program_index.protocol_decl_map.get(name); } /// Get or create thunks for a (protocol, concrete_type) pair. @@ -14599,7 +14559,7 @@ pub const Lowering = struct { const key = std.fmt.allocPrint(self.alloc, "{s}\x00{s}", .{ proto_name, concrete_type_name }) catch return &.{}; if (self.protocol_thunk_map.get(key)) |thunks| return thunks; - const pd = self.protocol_decl_map.get(proto_name) orelse return &.{}; + const pd = self.program_index.protocol_decl_map.get(proto_name) orelse return &.{}; var thunk_ids = std.ArrayList(FuncId).empty; defer thunk_ids.deinit(self.alloc); @@ -14658,7 +14618,7 @@ pub const Lowering = struct { .init_val = .{ .aggregate = ctx_fields }, .is_const = true, }); - self.global_names.put(global_name, .{ .id = gid, .ty = ctx_ty }) catch {}; + self.program_index.global_names.put(global_name, .{ .id = gid, .ty = ctx_ty }) catch {}; } /// Create a thunk function: __thunk_ConcreteType_Protocol_method(ctx: *void, args...) -> ret @@ -14707,7 +14667,7 @@ pub const Lowering = struct { // Ensure the concrete method is lowered const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ concrete_type_name, method.name }) catch method.name; if (!self.lowered_functions.contains(qualified)) { - if (self.fn_ast_map.contains(qualified)) { + if (self.program_index.fn_ast_map.contains(qualified)) { self.lazyLowerFunction(qualified); } else if (self.struct_instance_template.get(concrete_type_name)) |tmpl_name| { // Generic-struct instance (`Combined__s64_s64`): the impl method @@ -14715,7 +14675,7 @@ pub const Lowering = struct { // Monomorphize it for this instance's bindings so the thunk has a // concrete `Combined__s64_s64.get` to call. const tmpl_qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tmpl_name, method.name }) catch method.name; - if (self.fn_ast_map.get(tmpl_qualified)) |fd| { + if (self.program_index.fn_ast_map.get(tmpl_qualified)) |fd| { if (self.struct_instance_bindings.getPtr(concrete_type_name)) |bindings| { self.monomorphizeFunction(fd, qualified, bindings); } @@ -14811,7 +14771,7 @@ pub const Lowering = struct { /// outlives the current stack frame (used when source is a value, not an explicit pointer). /// When false, the pointer is used directly (user manages the pointee's lifetime). fn buildProtocolValue(self: *Lowering, concrete_ptr: Ref, proto_name: []const u8, concrete_type_name: []const u8, proto_ty: TypeId, concrete_ty: TypeId, heap_copy: bool) Ref { - const pd = self.protocol_decl_map.get(proto_name) orelse return concrete_ptr; + const pd = self.program_index.protocol_decl_map.get(proto_name) orelse return concrete_ptr; const thunks = self.getOrCreateThunks(proto_name, concrete_type_name); if (thunks.len != pd.methods.len) return concrete_ptr; @@ -15074,7 +15034,7 @@ pub const Lowering = struct { // Resolve local function name (bare → mangled) and UFCS aliases const name = blk: { const scoped = if (self.scope) |scope| scope.lookupFn(bare_name) orelse bare_name else bare_name; - if (self.ufcs_alias_map.get(bare_name)) |target| { + if (self.program_index.ufcs_alias_map.get(bare_name)) |target| { break :blk if (self.scope) |scope| scope.lookupFn(target) orelse target else target; } break :blk scoped; @@ -15113,7 +15073,7 @@ pub const Lowering = struct { if (std.mem.eql(u8, bare_name, "type_of")) return .any; if (std.mem.eql(u8, bare_name, "field_value")) return .any; // Check if it's a generic function — infer return type via type bindings - if (self.fn_ast_map.get(name)) |fd| { + if (self.program_index.fn_ast_map.get(name)) |fd| { if (fd.type_params.len > 0) { return self.inferGenericReturnType(fd, &c); } @@ -15125,7 +15085,7 @@ pub const Lowering = struct { // Not lowered yet (lazy lowering): take the return type from // the declared AST. A void/return-less fn is void — not an // `.unresolved` guess. - if (self.fn_ast_map.get(name)) |fd| { + if (self.program_index.fn_ast_map.get(name)) |fd| { if (fd.return_type) |rt| return self.resolveType(rt); return .void; } @@ -15166,7 +15126,7 @@ pub const Lowering = struct { const inner_info = self.module.types.get(recv_inner); if (inner_info == .@"struct") { const sn = self.module.types.getString(inner_info.@"struct".name); - if (self.foreign_class_map.get(sn)) |fcd| { + if (self.program_index.foreign_class_map.get(sn)) |fcd| { for (fcd.members) |m| switch (m) { .method => |md| if (!md.is_static and std.mem.eql(u8, md.name, cfa.field)) { return self.resolveForeignMethodReturnType(fcd, md); @@ -15190,7 +15150,7 @@ pub const Lowering = struct { const struct_name = self.module.types.getString(oi.@"struct".name); const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ struct_name, cfa.field }) catch cfa.field; // Generic #compiler method dispatch — return type from declaration - if (self.fn_ast_map.get(qualified)) |method_fd| { + if (self.program_index.fn_ast_map.get(qualified)) |method_fd| { if (method_fd.body.data == .compiler_expr) { if (method_fd.return_type) |rt| return type_bridge.resolveAstType(rt, &self.module.types); return .void; @@ -15210,7 +15170,7 @@ pub const Lowering = struct { }; if (type_name) |tn| { // Foreign-class static method: `Alias.static_method(args)`. - if (self.foreign_class_map.get(tn)) |fcd| { + if (self.program_index.foreign_class_map.get(tn)) |fcd| { for (fcd.members) |m| switch (m) { .method => |md| if (md.is_static and std.mem.eql(u8, md.name, cfa.field)) { return self.resolveForeignMethodReturnType(fcd, md); @@ -15236,14 +15196,14 @@ pub const Lowering = struct { if (self.resolveFuncByName(qualified)) |fid| { return self.module.functions.items[@intFromEnum(fid)].ret; } - if (self.fn_ast_map.get(qualified)) |qfd| { + if (self.program_index.fn_ast_map.get(qualified)) |qfd| { if (qfd.return_type) |rt| return self.resolveType(rt); return .void; } // Namespace aliases sometimes register the function // under its bare name (matches `lowerCall`'s effective- // name resolution order). - if (self.fn_ast_map.get(cfa.field)) |bfd| { + if (self.program_index.fn_ast_map.get(cfa.field)) |bfd| { if (bfd.return_type) |rt| return self.resolveType(rt); return .void; } @@ -15374,11 +15334,11 @@ pub const Lowering = struct { if (self.module.types.findByName(self.module.types.internString("Context"))) |ty| return ty; } // Check global variables (e.g., `context : Context`) - if (self.global_names.get(id.name)) |gi| { + if (self.program_index.global_names.get(id.name)) |gi| { return gi.ty; } // Check module-level value constants (e.g., WIDTH :f32: 800) - if (self.module_const_map.get(id.name)) |ci| { + if (self.program_index.module_const_map.get(id.name)) |ci| { return ci.ty; } // A bare type name (alias like `Vec4`, struct name, or @@ -15762,7 +15722,7 @@ pub const Lowering = struct { var bindings = std.StringHashMap(TypeId).init(self.alloc); defer bindings.deinit(); - const pd = self.protocol_ast_map.get(proto_name) orelse return null; + const pd = self.program_index.protocol_ast_map.get(proto_name) orelse return null; bindings.put(pd.type_params[0].name, dst_ty) catch return null; if (entry.ret_var_name) |rv| bindings.put(rv, src_ret) catch return null; @@ -15818,7 +15778,7 @@ pub const Lowering = struct { // parameterised protocols would walk protocol_decl_map looking for // protocols that take a single type-param and have a `convert` method. const proto_name = "Into"; - const pd = self.protocol_ast_map.get(proto_name) orelse return null; + const pd = self.program_index.protocol_ast_map.get(proto_name) orelse return null; if (pd.type_params.len != 1) return null; var key_buf = std.ArrayList(u8).empty; @@ -16183,7 +16143,7 @@ pub const Lowering = struct { if (self.type_bindings) |bindings| { if (bindings.get(name) != null) return true; } - if (self.type_alias_map.get(name) != null) return true; + if (self.program_index.type_alias_map.get(name) != null) return true; const name_id = self.module.types.internString(name); return self.module.types.findByName(name_id) != null; } @@ -17466,7 +17426,7 @@ pub const Lowering = struct { defer work.deinit(); // Seed each bare-`!` function with its direct escape sites. - var it = self.fn_ast_map.iterator(); + var it = self.program_index.fn_ast_map.iterator(); while (it.next()) |e| { const fd = e.value_ptr.*; if (!astIsPureBareInferred(fd.return_type)) continue; @@ -17485,7 +17445,7 @@ pub const Lowering = struct { for (we.value_ptr.edges.items) |callee| { const callee_tags: []const u32 = blk: { if (work.getPtr(callee)) |cc| break :blk cc.tags.items; - if (self.fn_ast_map.get(callee)) |cfd| { + if (self.program_index.fn_ast_map.get(callee)) |cfd| { if (astPureNamedSet(cfd.return_type)) |nm| { break :blk self.namedSetTags(nm) orelse &.{}; } @@ -17530,7 +17490,7 @@ pub const Lowering = struct { /// by all occurrences of its value-signature shape. A `try slot(x)` against /// any matching-shape slot then widens against this union. pub fn convergeClosureShapeSets(self: *Lowering) void { - var it = self.fn_ast_map.iterator(); + var it = self.program_index.fn_ast_map.iterator(); while (it.next()) |e| { self.collectClosureShapes(e.value_ptr.*.body); } @@ -17645,7 +17605,7 @@ pub const Lowering = struct { /// a bare-`!` callee's converged set, or a `-> !Named` callee's declared set. fn calleeEscapeTags(self: *Lowering, callee: []const u8) []const u32 { if (self.inferred_error_sets.get(callee)) |t| return t; - if (self.fn_ast_map.get(callee)) |cfd| { + if (self.program_index.fn_ast_map.get(callee)) |cfd| { if (astPureNamedSet(cfd.return_type)) |nm| return self.namedSetTags(nm) orelse &.{}; } return &.{}; @@ -17877,7 +17837,7 @@ pub const Lowering = struct { const pointee_info = self.module.types.get(ptr_info.pointer.pointee); if (pointee_info != .@"struct") return false; const struct_name = self.module.types.getString(pointee_info.@"struct".name); - const fcd = self.foreign_class_map.get(struct_name) orelse return false; + const fcd = self.program_index.foreign_class_map.get(struct_name) orelse return false; return fcd.runtime == .objc_class or fcd.runtime == .objc_protocol; } @@ -17893,7 +17853,7 @@ pub const Lowering = struct { const pointee_info = self.module.types.get(ptr_info.pointer.pointee); if (pointee_info != .@"struct") return null; const struct_name = self.module.types.getString(pointee_info.@"struct".name); - const fcd = self.foreign_class_map.get(struct_name) orelse return null; + const fcd = self.program_index.foreign_class_map.get(struct_name) orelse return null; if (fcd.runtime != .objc_class and fcd.runtime != .objc_protocol) return null; return self.findForeignPropertyInChain(fcd, field_name); } @@ -17918,7 +17878,7 @@ pub const Lowering = struct { }; break :blk null; } orelse return null; - current = self.foreign_class_map.get(parent) orelse return null; + current = self.program_index.foreign_class_map.get(parent) orelse return null; } return null; } @@ -17940,7 +17900,7 @@ pub const Lowering = struct { }; break :blk null; } orelse return null; - current = self.foreign_class_map.get(parent) orelse return null; + current = self.program_index.foreign_class_map.get(parent) orelse return null; } return null; } @@ -17964,7 +17924,7 @@ pub const Lowering = struct { const pointee_info = self.module.types.get(ptr_info.pointer.pointee); if (pointee_info != .@"struct") return null; const struct_name = self.module.types.getString(pointee_info.@"struct".name); - const fcd = self.foreign_class_map.get(struct_name) orelse return null; + const fcd = self.program_index.foreign_class_map.get(struct_name) orelse return null; // Only sx-defined Obj-C classes have a state struct. Foreign // classes' fields are purely declaration metadata (no state). if (fcd.is_foreign or fcd.runtime != .objc_class) return null; @@ -18247,7 +18207,7 @@ pub const Lowering = struct { const pointee_info = self.module.types.get(pointee); if (pointee_info != .@"struct") break :blk false; const struct_name = self.module.types.getString(pointee_info.@"struct".name); - const fcd = self.foreign_class_map.get(struct_name) orelse break :blk false; + const fcd = self.program_index.foreign_class_map.get(struct_name) orelse break :blk false; break :blk fcd.runtime == .objc_class or fcd.runtime == .objc_protocol; }; @@ -18686,7 +18646,7 @@ pub const Lowering = struct { const ctx_ref: ?Ref = blk: { if (!self.implicit_ctx_enabled) break :blk null; - const dctx_gi = self.global_names.get("__sx_default_context") orelse break :blk null; + const dctx_gi = self.program_index.global_names.get("__sx_default_context") orelse break :blk null; break :blk self.builder.emit(.{ .global_addr = dctx_gi.id }, ptr_void); }; @@ -18777,7 +18737,7 @@ pub const Lowering = struct { // default allocator. Sx-side callers bypass this IMP entirely // (compiler intercepts Cls.alloc()) and use their own // `context.allocator`. - const default_ctx_gi = self.global_names.get("__sx_default_context") orelse { + const default_ctx_gi = self.program_index.global_names.get("__sx_default_context") orelse { if (self.diagnostics) |d| { d.addFmt(.err, ast.Span{ .start = 0, .end = 0 }, "emitObjcDefinedClassAllocImp: __sx_default_context global missing for class '{s}' (compiler bug — scan pass did not register the default context)", .{fcd.name}); } @@ -18949,7 +18909,7 @@ pub const Lowering = struct { const ctx_ref: ?Ref = blk: { if (!self.implicit_ctx_enabled) break :blk null; - const dctx_gi = self.global_names.get("__sx_default_context") orelse break :blk null; + const dctx_gi = self.program_index.global_names.get("__sx_default_context") orelse break :blk null; break :blk self.builder.emit(.{ .global_addr = dctx_gi.id }, ptr_void); }; @@ -19130,7 +19090,7 @@ pub const Lowering = struct { // Default-context address for the implicit __sx_ctx the dealloc // fn-ptr takes as its first arg (the dealloc body might allocate // internally; default GPA is the safe baseline). - const default_ctx_gi = self.global_names.get("__sx_default_context") orelse { + const default_ctx_gi = self.program_index.global_names.get("__sx_default_context") orelse { if (self.diagnostics) |d| { d.addFmt(.err, ast.Span{ .start = 0, .end = 0 }, "emitObjcDefinedClassDeallocImp: __sx_default_context global missing for class '{s}'", .{fcd.name}); } @@ -19242,7 +19202,7 @@ pub const Lowering = struct { var seen = std.StringHashMap(void).init(self.alloc); defer seen.deinit(); - var it = self.foreign_class_map.iterator(); + var it = self.program_index.foreign_class_map.iterator(); while (it.next()) |entry| { const fcd = entry.value_ptr.*; if (!fcd.is_main) continue; @@ -19346,7 +19306,7 @@ pub const Lowering = struct { const saved_ctx_ref_jni = self.current_ctx_ref; defer self.current_ctx_ref = saved_ctx_ref_jni; if (self.implicit_ctx_enabled) { - if (self.global_names.get("__sx_default_context")) |dctx_gi| { + if (self.program_index.global_names.get("__sx_default_context")) |dctx_gi| { self.current_ctx_ref = self.builder.emit(.{ .global_addr = dctx_gi.id }, ptr_void); } } diff --git a/src/ir/program_index.test.zig b/src/ir/program_index.test.zig index 22d502f..34acb5d 100644 --- a/src/ir/program_index.test.zig +++ b/src/ir/program_index.test.zig @@ -1,5 +1,9 @@ const std = @import("std"); -const ProgramIndex = @import("program_index.zig").ProgramIndex; +const pi = @import("program_index.zig"); +const ProgramIndex = pi.ProgramIndex; +const ast = @import("../ast.zig"); +const types = @import("types.zig"); +const inst = @import("inst.zig"); test "ProgramIndex.init starts empty with unset borrowed views" { var idx = ProgramIndex.init(std.testing.allocator); @@ -38,3 +42,57 @@ test "ProgramIndex borrows module_scopes / import_graph without owning them" { try std.testing.expect(idx.import_graph.? == &graph); try std.testing.expectEqual(@as(u32, 0), idx.module_scopes.?.count()); } + +test "ProgramIndex declaration maps round-trip (A1.1b)" { + var idx = ProgramIndex.init(std.testing.allocator); + defer idx.deinit(); + + // Minimal AST node reused wherever a *Node is required. + var blk = ast.Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = &.{} } } }; + + // fn_ast_map: function name → AST decl. + const fd = ast.FnDecl{ .name = "main", .params = &.{}, .return_type = null, .body = &blk }; + try idx.fn_ast_map.put("main", &fd); + try std.testing.expect(idx.fn_ast_map.get("main").? == &fd); + + // type_alias_map: alias name → target TypeId. + try idx.type_alias_map.put("ShaderHandle", .s64); + try std.testing.expectEqual(@as(?types.TypeId, .s64), idx.type_alias_map.get("ShaderHandle")); + + // global_names: #run global name → GlobalInfo. + try idx.global_names.put("g", .{ .id = inst.GlobalId.fromIndex(0), .ty = .s64 }); + try std.testing.expect(idx.global_names.get("g").?.id == inst.GlobalId.fromIndex(0)); + + // module_const_map: const name → ModuleConstInfo. + try idx.module_const_map.put("AF_INET", .{ .value = &blk, .ty = .s32 }); + try std.testing.expect(idx.module_const_map.get("AF_INET").?.value == &blk); + + // foreign_class_map: sx alias → ForeignClassDecl. + const fcd = ast.ForeignClassDecl{ + .name = "NSString", + .foreign_path = "NSString", + .runtime = .objc_class, + .members = &.{}, + .is_foreign = true, + .is_main = false, + }; + try idx.foreign_class_map.put("NSString", &fcd); + try std.testing.expect(idx.foreign_class_map.get("NSString").? == &fcd); + + // protocol_decl_map: protocol name → ProtocolDeclInfo. + try idx.protocol_decl_map.put("Show", .{ .name = "Show", .is_inline = false, .methods = &.{} }); + try std.testing.expectEqualStrings("Show", idx.protocol_decl_map.get("Show").?.name); + + // protocol_ast_map: protocol name → AST decl. + const pd = ast.ProtocolDecl{ .name = "Show", .methods = &.{} }; + try idx.protocol_ast_map.put("Show", &pd); + try std.testing.expect(idx.protocol_ast_map.get("Show").? == &pd); + + // struct_template_map: generic struct name → template. + try idx.struct_template_map.put("List", .{ .name = "List", .type_params = &.{}, .field_names = &.{}, .field_type_nodes = &.{} }); + try std.testing.expectEqualStrings("List", idx.struct_template_map.get("List").?.name); + + // ufcs_alias_map: alias name → target function name. + try idx.ufcs_alias_map.put("len", "list_len"); + try std.testing.expectEqualStrings("list_len", idx.ufcs_alias_map.get("len").?); +} diff --git a/src/ir/program_index.zig b/src/ir/program_index.zig index c5f4800..77ff9b7 100644 --- a/src/ir/program_index.zig +++ b/src/ir/program_index.zig @@ -1,36 +1,111 @@ const std = @import("std"); +const ast = @import("../ast.zig"); +const types = @import("types.zig"); +const inst = @import("inst.zig"); -/// Single storage owner for declaration-name facts: declarations, imports, -/// visibility, names, aliases, templates, protocols, foreign classes, and -/// module constants. The architecture stream (`current/PLAN-ARCH.md`, phase -/// A1) extracts these out of the `Lowering` state bag incrementally; this is -/// the A1.1a slice — the low-fanout import/visibility facts. `Lowering` embeds -/// one `ProgramIndex` by value and reaches every moved fact through it; later -/// phases hand collaborator modules a `*ProgramIndex` instead of `*Lowering`. +const Node = ast.Node; +const TypeId = types.TypeId; + +/// Owned copy of a generic struct template (AST pointers are copied/interned to survive imports) +pub const StructTemplate = struct { + name: []const u8, + type_params: []const TemplateParam, + field_names: []const []const u8, + field_type_nodes: []const *const Node, // raw AST pointers — must be copied from heap nodes +}; +pub const TemplateParam = struct { + name: []const u8, + is_type_param: bool, // true for $T: Type, false for $N: u32 + is_variadic: bool = false, // `..$Ts: []Type` — binds remaining type args as a pack +}; + +pub const ProtocolMethodInfo = struct { + name: []const u8, + param_types: []const TypeId, // excluding self + ret_type: TypeId, + // True when the AST return type was `Self` (encoded here as *void). + // Lets the dispatcher distinguish Self-disguised-as-*void (auto-unbox + // on the caller side) from a literal `-> *void` (return as-is). + ret_is_self: bool = false, +}; + +pub const ProtocolDeclInfo = struct { + name: []const u8, + is_inline: bool, + methods: []const ProtocolMethodInfo, +}; + +pub const ModuleConstInfo = struct { + value: *const Node, + ty: TypeId, +}; + +pub const GlobalInfo = struct { id: inst.GlobalId, ty: TypeId }; + +/// Single lowering access point for declaration-name / import / visibility +/// facts. The architecture stream (`current/PLAN-ARCH.md`, phase A1) extracts +/// these out of the `Lowering` state bag incrementally. `Lowering` embeds one +/// `ProgramIndex` by value and reaches every moved fact through +/// `self.program_index.`; later phases hand collaborator modules a +/// `*ProgramIndex` instead of `*Lowering`. /// -/// `import_flags` is owned here. `module_scopes` / `import_graph` are borrowed -/// views into maps owned by the compilation driver (`core.zig`); this index -/// only reads through them. +/// OWNS the declaration maps below. BORROWS `module_scopes` / `import_graph` +/// (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 +/// (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 { + // ── Import / visibility ── /// Declaration name → is the function imported (declared `extern`)? - /// Populated by the declaration scan / registration code in `Lowering`. import_flags: std.StringHashMap(bool), - - /// Per-module visible names, keyed by source file (from import - /// resolution). Borrowed — the backing map lives in the compilation - /// driver, so this index does not free it. + /// Per-module visible names, keyed by source file. Borrowed view. module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null, - - /// Module path → set of directly imported paths; drives the - /// `param_impl_map` cross-module visibility filter. Borrowed — owned by - /// the compilation driver. + /// Module path → set of directly imported paths (param_impl visibility + /// filter). Borrowed view. import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null, + // ── Declaration maps ── + /// Function name → AST decl. + fn_ast_map: std.StringHashMap(*const ast.FnDecl), + /// sx alias → ForeignClassDecl (jni_class / objc_class / swift_class / ... — registered in scan pass). + foreign_class_map: std.StringHashMap(*const ast.ForeignClassDecl) = std.StringHashMap(*const ast.ForeignClassDecl).init(std.heap.page_allocator), + /// `#run` global name → GlobalId. + global_names: std.StringHashMap(GlobalInfo), + /// Type alias name → target TypeId. Loaned to `TypeTable.aliases`. + type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), + /// Generic struct name → template. + struct_template_map: std.StringHashMap(StructTemplate) = std.StringHashMap(StructTemplate).init(std.heap.page_allocator), + /// Protocol name → protocol info. + protocol_decl_map: std.StringHashMap(ProtocolDeclInfo) = std.StringHashMap(ProtocolDeclInfo).init(std.heap.page_allocator), + /// Protocol name → AST node. + protocol_ast_map: std.StringHashMap(*const ast.ProtocolDecl) = std.StringHashMap(*const ast.ProtocolDecl).init(std.heap.page_allocator), + /// Module-level value constants (e.g. AF_INET :s32: 2). + module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), + /// UFCS alias name → target function name. + ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), + pub fn init(alloc: std.mem.Allocator) ProgramIndex { - return .{ .import_flags = std.StringHashMap(bool).init(alloc) }; + return .{ + .import_flags = std.StringHashMap(bool).init(alloc), + .fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(alloc), + .global_names = std.StringHashMap(GlobalInfo).init(alloc), + }; } pub fn deinit(self: *ProgramIndex) void { + // Owned maps only — module_scopes / import_graph are borrowed. self.import_flags.deinit(); + self.fn_ast_map.deinit(); + self.foreign_class_map.deinit(); + self.global_names.deinit(); + self.type_alias_map.deinit(); + self.struct_template_map.deinit(); + self.protocol_decl_map.deinit(); + self.protocol_ast_map.deinit(); + self.module_const_map.deinit(); + self.ufcs_alias_map.deinit(); } };