refactor(ir): move generic-binding + alias-aware name resolution into TypeResolver (A2.2)

Architecture phase A2.2 -- behavior-preserving. TypeResolver gains the
generic-binding and bare-name resolution it now owns:

- resolveBinding(node, env): $T / bare return-type T lookup via an explicit
  ResolveEnv (no hidden Lowering state).
- resolveNamed(name, table, alias_map): the full bare-name algorithm (primitive
  -> arbitrary-width int -> string-prefix [*]/*/?/[:0]u8 -> already-registered
  -> alias(alias_map) -> empty-struct stub), MOVED from
  type_bridge.resolveTypeName so it is single-sourced.
- resolveName(self, name): resolves through the canonical alias source
  ProgramIndex.type_alias_map -- the compiler path no longer reads the
  TypeTable.aliases borrow.

Lowering.resolveTypeWithBindings: the `if (self.type_bindings)` block (the $T
lookup plus parameterized/call/closure/function arms that were redundant with
the unconditional handling below) collapses to one resolveBinding delegation via
a new resolveEnv() snapshot; the bare-name fallback routes type_expr/identifier
to resolveName (index-based alias), other node kinds still to resolveAstType.

type_bridge.resolveTypeName becomes a 1-line delegate to resolveNamed, passing
its TypeTable.aliases borrow as the alias source. Single algorithm; the alias
map stays single-sourced in ProgramIndex.

Deferred to A2.3: removing the TypeTable.aliases borrow (its ~30 resolveAstType
callers must converge onto TypeResolver first) and type_bridge's stateless
compound resolvers. A2.2 #3 (templates/protocols/type-fns via ProgramIndex) was
already satisfied by A1.1b.

Tests: resolveBinding ($T bound/unbound/no-env), resolveName (alias->primitive,
alias->pointer via ProgramIndex), resolveNamed (width-int, string-prefix,
unknown->stub).

No new fallback path; no duplicate truth. Gate green: zig build, zig build test,
bash tests/run_examples.sh (350 passed, 0 failed).
lower.zig 19372->19367; type_bridge.zig 647->592; type_resolver.zig 90->159.
This commit is contained in:
agra
2026-06-02 13:56:32 +03:00
parent 9eb85cf9e3
commit dd16bab2c2
4 changed files with 147 additions and 94 deletions

View File

@@ -20,6 +20,7 @@ const ProtocolDeclInfo = program_index_mod.ProtocolDeclInfo;
const ProtocolMethodInfo = program_index_mod.ProtocolMethodInfo;
const ModuleConstInfo = program_index_mod.ModuleConstInfo;
const TypeResolver = @import("type_resolver.zig").TypeResolver;
const ResolveEnv = @import("type_resolver.zig").ResolveEnv;
const TypeId = types.TypeId;
const StringId = types.StringId;
@@ -12742,6 +12743,16 @@ pub const Lowering = struct {
};
}
/// Snapshot the active resolution context (Principle 2) for `TypeResolver`.
/// A2.2 wires the type bindings + literal target; the pack/comptime fields
/// are populated as A2.3 moves the cases that consume them.
fn resolveEnv(self: *Lowering) ResolveEnv {
return .{
.type_bindings = if (self.type_bindings) |*tb| tb else null,
.target_type = self.target_type,
};
}
/// Inner-type recursion hook for `TypeResolver.resolveCompound`: resolves a
/// child type node through the full stateful resolver, so generic structs /
/// bindings / aliases in element position keep their resolution.
@@ -12798,34 +12809,12 @@ pub const Lowering = struct {
// stateful resolver (`resolveInner` → here) so generic structs /
// bindings in element position keep their resolution.
if (self.typeResolver().resolveCompound(node, self)) |t| return t;
if (self.type_bindings) |tb| {
switch (node.data) {
.type_expr => |te| {
// Check bindings for any type_expr name — not just those
// marked is_generic. The return type `T` in `-> T` may
// not have the `$` prefix, so is_generic is false, but
// it still refers to the type param.
if (tb.get(te.name)) |ty| return ty;
},
.identifier => |id| {
if (tb.get(id.name)) |ty| return ty;
},
.parameterized_type_expr => |pt| {
return self.resolveParameterizedWithBindings(&pt);
},
.call => |cl| {
// Handle List(T), Vector(N, T) etc. as type constructor calls
return self.resolveTypeCallWithBindings(&cl);
},
.closure_type_expr => |ct| {
return self.resolveClosureTypeWithBindings(&ct);
},
.function_type_expr => |ft| {
return self.resolveFunctionTypeWithBindings(&ft);
},
else => {},
}
}
// Generic type-param binding (`$T`, or a bare return-type `T` without
// the `$` prefix) — owned by TypeResolver via the explicit ResolveEnv.
// The parameterized / call / closure / function arms that used to live
// here were redundant with the unconditional handling just below (both
// read the active bindings through the same resolvers), so they're gone.
if (TypeResolver.resolveBinding(node, self.resolveEnv())) |t| return t;
// Even without active type_bindings, handle parameterized types with struct templates
if (node.data == .parameterized_type_expr) {
return self.resolveParameterizedWithBindings(&node.data.parameterized_type_expr);
@@ -12885,10 +12874,16 @@ pub const Lowering = struct {
if (node.data == .type_expr and node.data.type_expr.is_generic) {
return .unresolved;
}
// Alias resolution (`ShaderHandle :: u32`, `Vec4 ::
// Vector(4,f32)`) is now handled inside `resolveTypeName`
// via the `TypeTable.aliases` borrow loaned at lowerRoot.
return type_bridge.resolveAstType(node, &self.module.types);
// Bare type names resolve through TypeResolver, which reads the
// canonical alias table directly (`ProgramIndex.type_alias_map`) — this
// path no longer depends on the `TypeTable.aliases` borrow. Other node
// kinds (inline type decls, error types) still route through type_bridge
// (A2.3 converges its remaining `resolveAstType` callers).
switch (node.data) {
.type_expr => |te| return self.typeResolver().resolveName(te.name),
.identifier => |id| return self.typeResolver().resolveName(id.name),
else => return type_bridge.resolveAstType(node, &self.module.types),
}
}
/// Resolve a `Closure(...)` type expression with the active type/pack