refactor(ir): converge structural type-shape resolution onto resolveCompound (A2.3b)

Codex corrective step before the A2 merge gate: A2.3 left type_bridge with a
parallel structural type-resolution algorithm and an inline tuple-literal-spread
shape in lower.zig with a `.void` fallback.

Finding 1 — single owner for structural shapes:
- TypeResolver.resolveCompound is now the sole structural type-shape
  constructor. Namespaced on `table` (so the stateless type_bridge can call it)
  and extended to own function types, plain `Closure(P...) -> R`, and plain
  positional/named tuples (it already owned *T/[*]T/[]T/?T/[N]T). It returns
  null only for the pack-shaped forms that need caller state (`Closure(..p)`,
  spread tuples); OOM yields `.unresolved`.
- type_bridge: deleted its 8 independent structural resolvers
  (resolveArray/Slice/Pointer/ManyPointer/Optional/Function/Closure/TupleType).
  resolveAstType delegates those node kinds to resolveCompound via a binding-free
  StatelessInner adapter. The only residual stateless shape code is two tiny
  fallbacks for the pack-shaped forms resolveCompound defers
  (resolveClosurePackShape — used by Into(Block) at registration time —
  and resolveTupleSpreadShape) plus resolveParameterizedType (kept:
  generic-instantiation convergence is A4.1 per PLAN-ARCH).
- lower.zig: stateful resolveTypeWithBindings uses resolveCompound; the
  `.function_type_expr` switch arm is gone. PackResolver.resolveFunctionTypeWithBindings
  deleted (subsumed). Plain closures/tuples now resolve via resolveCompound in
  both paths; only pack closures / spread tuples reach PackResolver.

Finding 2 — no `.void` failure fallback in lower.zig pack handling:
- the inline tuple_literal-with-spread type assembly moved into
  PackResolver.resolveTupleLiteralType (returns ?TypeId; OOM `catch return .void`
  became `catch return .unresolved`).

Alias result preserved: TypeTable.aliases stays gone; no table.aliases reads;
ProgramIndex.type_alias_map threaded explicitly.

type_resolver.test.zig: resolveCompound test rewritten (namespaced + new
function/closure/tuple/pack-shape arms, arena-backed). Gate green: zig build,
zig build test, run_examples 350/0.
This commit is contained in:
agra
2026-06-02 15:20:31 +03:00
parent 3ed1b3a7a0
commit 9b50aacbe4
5 changed files with 199 additions and 136 deletions

View File

@@ -12799,11 +12799,13 @@ pub const Lowering = struct {
}
}
}
// Structural compound types (`*T`, `[*]T`, `[]T`, `?T`, `[N]T`) are
// owned by TypeResolver (A2.1). Element types recurse through the full
// stateful resolver (`resolveInner` → here) so generic structs /
// bindings in element position keep their resolution.
if (self.typeResolver().resolveCompound(node, self)) |t| return t;
// Structural type shapes — `*T`, `[*]T`, `[]T`, `?T`, `[N]T`, functions,
// PLAIN closures, and PLAIN tuples — are owned by
// `TypeResolver.resolveCompound` (A2.3b). Element types recurse through
// the full stateful resolver (`resolveInner` → here) so generic structs
// / bindings keep their resolution. resolveCompound returns null only
// for the pack-shaped forms (`Closure(..p)`, spread tuples) below.
if (TypeResolver.resolveCompound(&self.module.types, node, self)) |t| return t;
// 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
@@ -12817,48 +12819,23 @@ pub const Lowering = struct {
if (node.data == .call) {
return self.resolveTypeCallWithBindings(&node.data.call);
}
// Pointers / slices / many-pointers / optionals / arrays are owned by
// TypeResolver (handled above). The pack-aware tuple / closure /
// function shapes are owned by `PackResolver` (packs.zig, A2.3).
// Plain structural shapes were handled by resolveCompound above. What
// reaches here is the PACK-shaped subset, owned by `PackResolver`
// (packs.zig): pack-shaped `Closure(..p)` and spread tuples. (Functions
// are never pack-shaped at the type level — resolveCompound owns them
// all, so there is no function arm here.)
switch (node.data) {
.closure_type_expr => |ct| {
return self.packResolver().resolveClosureTypeWithBindings(&ct);
},
.function_type_expr => |ft| {
return self.packResolver().resolveFunctionTypeWithBindings(&ft);
},
.tuple_type_expr => |tt| {
return self.packResolver().resolveTupleTypeWithBindings(&tt);
},
// `(..$Ts)` in a type position (e.g. a struct field) parses as a
// tuple LITERAL whose elements include a pack spread; expand it to
// the bound pack's element types, same as `resolveTupleTypeWithBindings`.
// tuple LITERAL whose elements include a pack spread; PackResolver
// expands it (returns null when no spread, so we fall through).
.tuple_literal => |tl| {
var any_spread = false;
for (tl.elements) |el| {
if (el.value.data == .spread_expr) {
any_spread = true;
break;
}
}
if (any_spread) {
var field_ids = std.ArrayList(TypeId).empty;
defer field_ids.deinit(self.alloc);
for (tl.elements) |el| {
if (el.value.data == .spread_expr) {
if (self.packResolver().packTypeElems(el.value.data.spread_expr.operand)) |elems| {
defer self.alloc.free(elems);
for (elems) |e| field_ids.append(self.alloc, e) catch return .void;
continue;
}
}
field_ids.append(self.alloc, self.resolveTypeWithBindings(el.value)) catch return .void;
}
return self.module.types.intern(.{ .tuple = .{
.fields = self.alloc.dupe(TypeId, field_ids.items) catch return .void,
.names = null,
} });
}
if (self.packResolver().resolveTupleLiteralType(&tl)) |t| return t;
},
else => {},
}