feat(stdlib/S2.1a): resolver.zig owning pass + ResolvedProgram scaffold + 3 bare-name domains [additive]

Turn src/ir/resolver.zig from a raw author-collection facade into the OWNING
resolution pass: one exhaustive recursive AST walk (exhaustive switch over
ast.Node.Data with NO else arm, so a new node kind is a compile error here
rather than a silently unvisited subtree) populating a ResolvedProgram.

- ResolvedProgram: all 10 node-keyed side tables declared as
  AutoHashMap(*const ast.Node, ResolvedRef) + symbolic TemplateParamId/
  PackParamId registries. ResolvedRef is the S2.1 RAW form — collected author
  identity (AuthorSet, own ∪ flat), NO verdict (own-wins/ambiguity is S2.2).
- Populate the 3 bare-name domains (type / value-const / callable heads) via
  collectVisibleAuthors(.user_bare_flat); record $T / ..$Ts / $pack[i] as
  SYMBOLIC template/pack refs, never TypeIds. The 7 head/qualified/foreign
  domains stay declared-but-empty (S2.1b/c own them).
- Slot via Compilation.resolveProgram() after the program_index facts are
  wired and before lowerRoot; ResolvedProgram owned on Compilation, borrowed
  *ResolvedProgram lent to ProgramIndex (lowerToIR signature unchanged).
- Population proof unit test over real Phase A facts: the 3 tables are
  non-empty, keyed by node identity, and carry symbolic template/pack refs.

ADDITIVE / PARALLEL / UNCONSUMED: lowering still reads the OLD selectors, so
single-author output is byte-identical. Gate green: zig build; zig build test
(425/425, LSP smoke 574 files no crash); run_examples (540 passed, 0 failed,
byte-identical incl. FFI 12xx-14xx + 1615 ios-sim); resolver-target (18 xfail
unchanged).
This commit is contained in:
agra
2026-06-09 12:29:27 +03:00
parent b29037b257
commit b46ad8b7a7
4 changed files with 753 additions and 0 deletions

View File

@@ -39,6 +39,11 @@ pub const Compilation = struct {
/// `imports.buildDeclTable` in parallel with the import facts. Borrowed by
/// `ProgramIndex.decl_table`.
decl_table: imports.DeclTable,
/// The owning resolution pass's output (Fork C S2.1a), built by
/// `resolveProgram` before lowering and borrowed by
/// `ProgramIndex.resolved_program`. ADDITIVE / PARALLEL / UNCONSUMED — nothing
/// in lowering reads it yet, so generated output is byte-identical.
resolved_program: ?ir.resolver.ResolvedProgram = null,
ir_emitter: ?ir.LLVMEmitter = null,
/// Lowered IR module, kept alive past `generateCode` so post-link
/// callbacks can re-enter the interpreter to invoke sx functions
@@ -80,6 +85,7 @@ pub const Compilation = struct {
m.deinit();
self.allocator.destroy(m);
}
if (self.resolved_program) |*rp| rp.deinit();
self.diagnostics.deinit();
}
@@ -295,6 +301,18 @@ pub const Compilation = struct {
return null;
}
/// Run the owning resolution pass (Fork C S2.1a) over the resolved root,
/// storing its `ResolvedProgram` on `self` and lending a borrowed pointer to
/// `index`. Slotted after the `program_index` import facts are wired and
/// before `lowerRoot`. ADDITIVE / PARALLEL / UNCONSUMED — nothing in lowering
/// reads the result yet, so this changes no generated byte; it is the clean
/// pass seam future consumers (S3) cut over to.
fn resolveProgram(self: *Compilation, index: *ir.ProgramIndex, root: *const Node) void {
if (self.resolved_program) |*rp| rp.deinit();
self.resolved_program = ir.resolver.resolve(root, index, self.file_path, self.allocator);
index.resolved_program = &self.resolved_program.?;
}
/// Lower the parsed AST to the sx IR module (shadow pipeline).
pub fn lowerToIR(self: *Compilation) !ir.Module {
const root = self.resolved_root orelse self.root orelse return ir.Module.init(self.allocator);
@@ -314,6 +332,7 @@ pub const Compilation = struct {
lowering.program_index.module_decls = &self.module_decls;
lowering.program_index.namespace_edges = &self.namespace_edges;
lowering.program_index.decl_table = &self.decl_table;
self.resolveProgram(&lowering.program_index, root);
lowering.lowerRoot(root);
if (self.diagnostics.hasErrors()) return error.CompileError;