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:
@@ -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 => {},
|
||||
}
|
||||
|
||||
@@ -97,24 +97,36 @@ pub const PackResolver = struct {
|
||||
} });
|
||||
}
|
||||
|
||||
/// Resolve a `(Params...) -> Ret` function type expression with the
|
||||
/// active type/pack bindings applied. Mirrors
|
||||
/// `resolveClosureTypeWithBindings` but for `function_type_expr`.
|
||||
/// Unlocks `$args[$i]` in fn-pointer type literals like
|
||||
/// `fp : (*void, $args[0]) -> $args[1] = ...` — used in step 5's
|
||||
/// generic trampoline body.
|
||||
pub fn resolveFunctionTypeWithBindings(self: PackResolver, ft: *const ast.FunctionTypeExpr) TypeId {
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
defer param_ids.deinit(self.l.alloc);
|
||||
for (ft.param_types) |pt| {
|
||||
param_ids.append(self.l.alloc, self.l.resolveTypeWithBindings(pt)) catch return .unresolved;
|
||||
/// Resolve a tuple LITERAL used in a type position whose elements include a
|
||||
/// pack spread (`(..$Ts)` / `(..xs.T)` — these parse as a tuple literal, not
|
||||
/// a `tuple_type_expr`). Returns null when no element is a spread, so the
|
||||
/// caller falls through to ordinary name/type resolution. A failed
|
||||
/// allocation yields `.unresolved` (never a real `.void`).
|
||||
pub fn resolveTupleLiteralType(self: PackResolver, tl: *const ast.TupleLiteral) ?TypeId {
|
||||
var any_spread = false;
|
||||
for (tl.elements) |el| {
|
||||
if (el.value.data == .spread_expr) {
|
||||
any_spread = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const ret_ty = if (ft.return_type) |rt| self.l.resolveTypeWithBindings(rt) else .void;
|
||||
const cc: types.TypeInfo.CallConv = switch (ft.call_conv) {
|
||||
.default => .default,
|
||||
.c => .c,
|
||||
};
|
||||
return self.l.module.types.functionTypeCC(param_ids.items, ret_ty, cc);
|
||||
if (!any_spread) return null;
|
||||
var field_ids = std.ArrayList(TypeId).empty;
|
||||
defer field_ids.deinit(self.l.alloc);
|
||||
for (tl.elements) |el| {
|
||||
if (el.value.data == .spread_expr) {
|
||||
if (self.packTypeElems(el.value.data.spread_expr.operand)) |elems| {
|
||||
defer self.l.alloc.free(elems);
|
||||
for (elems) |e| field_ids.append(self.l.alloc, e) catch return .unresolved;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
field_ids.append(self.l.alloc, self.l.resolveTypeWithBindings(el.value)) catch return .unresolved;
|
||||
}
|
||||
return self.l.module.types.intern(.{ .tuple = .{
|
||||
.fields = self.l.alloc.dupe(TypeId, field_ids.items) catch return .unresolved,
|
||||
.names = null,
|
||||
} });
|
||||
}
|
||||
|
||||
/// TYPE-position pack expansion: given a spread operand, return the
|
||||
|
||||
@@ -18,6 +18,19 @@ const type_resolver = @import("type_resolver.zig");
|
||||
/// `null` for contexts that never see aliases, e.g. unit tests).
|
||||
pub const AliasMap = ?*const std.StringHashMap(TypeId);
|
||||
|
||||
/// Binding-free element-recursion adapter for `TypeResolver.resolveCompound`:
|
||||
/// nested element types resolve through `type_bridge.resolveAstType` (the
|
||||
/// registration-time path — no generic/pack bindings). Lets type_bridge reuse
|
||||
/// the single canonical structural-shape constructor instead of carrying its
|
||||
/// own compound algorithm (A2.3b).
|
||||
const StatelessInner = struct {
|
||||
table: *TypeTable,
|
||||
alias_map: AliasMap,
|
||||
pub fn resolveInner(self: StatelessInner, node: *const Node) TypeId {
|
||||
return resolveAstType(node, self.table, self.alias_map);
|
||||
}
|
||||
};
|
||||
|
||||
// ── AST Node → TypeId ───────────────────────────────────────────────────
|
||||
// Resolve an AST type node into an IR TypeId. Used during lowering when
|
||||
// we only have the parsed AST (no codegen type registry).
|
||||
@@ -29,17 +42,31 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
||||
// `.s64` here would silently fabricate an 8-byte int. Surface it via the
|
||||
// `.unresolved` sentinel (trips the sizeOf/toLLVMType panic at codegen).
|
||||
const n = node orelse return .unresolved;
|
||||
const si = StatelessInner{ .table = table, .alias_map = alias_map };
|
||||
return switch (n.data) {
|
||||
.type_expr => |te| resolveTypeName(te.name, table, alias_map),
|
||||
.identifier => |id| resolveTypeName(id.name, table, alias_map),
|
||||
.array_type_expr => |at| resolveArrayType(&at, table, alias_map),
|
||||
.slice_type_expr => |st| resolveSliceType(&st, table, alias_map),
|
||||
.pointer_type_expr => |pt| resolvePointerType(&pt, table, alias_map),
|
||||
.many_pointer_type_expr => |mpt| resolveManyPointerType(&mpt, table, alias_map),
|
||||
.optional_type_expr => |ot| resolveOptionalType(&ot, table, alias_map),
|
||||
.function_type_expr => |ft| resolveFunctionType(&ft, table, alias_map),
|
||||
.closure_type_expr => |ct| resolveClosureType(&ct, table, alias_map),
|
||||
.tuple_type_expr => |tt| resolveTupleType(&tt, table, alias_map),
|
||||
// Structural shapes (`*T`/`[*]T`/`[]T`/`?T`/`[N]T`, functions, plain
|
||||
// closures, plain tuples) are owned by the single canonical
|
||||
// `TypeResolver.resolveCompound` — no independent compound algorithm
|
||||
// lives here (A2.3b). resolveCompound never returns null for these
|
||||
// kinds, so `.?` is total.
|
||||
.pointer_type_expr,
|
||||
.many_pointer_type_expr,
|
||||
.slice_type_expr,
|
||||
.optional_type_expr,
|
||||
.array_type_expr,
|
||||
.function_type_expr,
|
||||
=> type_resolver.TypeResolver.resolveCompound(table, n, si).?,
|
||||
// Plain closures/tuples are owned by resolveCompound (above). It returns
|
||||
// null for the PACK-shaped forms — `Closure(..p)` and spread tuples —
|
||||
// because expanding a pack needs bindings. type_bridge has none, so it
|
||||
// preserves the pack SHAPE statelessly (e.g. `Into(Block)` resolves a
|
||||
// `Closure(..p)` field type at registration time). These tiny fallbacks
|
||||
// are the only stateless-specific shape code left; the stateful expand
|
||||
// lives in PackResolver.
|
||||
.closure_type_expr => |ct| type_resolver.TypeResolver.resolveCompound(table, n, si) orelse resolveClosurePackShape(&ct, table, alias_map),
|
||||
.tuple_type_expr => |tt| type_resolver.TypeResolver.resolveCompound(table, n, si) orelse resolveTupleSpreadShape(&tt, table, alias_map),
|
||||
.pack_index_type_expr => {
|
||||
// Pack-index `$args[N]` in a type position must be resolved
|
||||
// against an active pack binding — `type_bridge` has no access
|
||||
@@ -208,61 +235,27 @@ fn resolveTypeName(name: []const u8, table: *TypeTable, alias_map: AliasMap) Typ
|
||||
/// retired (A2.2). Single source of truth: the table is defined once, there.
|
||||
pub const resolveTypePrimitive = type_resolver.TypeResolver.resolvePrimitive;
|
||||
|
||||
fn resolveArrayType(at: *const ast.ArrayTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
const elem = resolveAstType(at.element_type, table, alias_map);
|
||||
const length: u32 = switch (at.length.data) {
|
||||
.int_literal => |lit| @intCast(@as(u64, @bitCast(lit.value))),
|
||||
else => 0,
|
||||
};
|
||||
return table.arrayOf(elem, length);
|
||||
}
|
||||
|
||||
fn resolveSliceType(st: *const ast.SliceTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
const elem = resolveAstType(st.element_type, table, alias_map);
|
||||
return table.sliceOf(elem);
|
||||
}
|
||||
|
||||
fn resolvePointerType(pt: *const ast.PointerTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
const pointee = resolveAstType(pt.pointee_type, table, alias_map);
|
||||
return table.ptrTo(pointee);
|
||||
}
|
||||
|
||||
fn resolveManyPointerType(mpt: *const ast.ManyPointerTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
const elem = resolveAstType(mpt.element_type, table, alias_map);
|
||||
return table.manyPtrTo(elem);
|
||||
}
|
||||
|
||||
fn resolveOptionalType(ot: *const ast.OptionalTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
const child = resolveAstType(ot.inner_type, table, alias_map);
|
||||
return table.optionalOf(child);
|
||||
}
|
||||
|
||||
fn resolveFunctionType(ft: *const ast.FunctionTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
for (ft.param_types) |pt| {
|
||||
param_ids.append(alloc, resolveAstType(pt, table, alias_map)) catch unreachable;
|
||||
}
|
||||
const ret_id = if (ft.return_type) |rt| resolveAstType(rt, table, alias_map) else TypeId.void;
|
||||
const cc: ir_types.TypeInfo.CallConv = if (ft.call_conv == .c) .c else .default;
|
||||
return table.functionTypeCC(param_ids.items, ret_id, cc);
|
||||
}
|
||||
|
||||
fn resolveClosureType(ct: *const ast.ClosureTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
/// Pack-shaped `Closure(..p)` resolved without bindings: the canonical
|
||||
/// `resolveCompound` builds plain closures and defers pack-shaped ones (returns
|
||||
/// null). type_bridge can't expand the pack (no state), so it preserves the
|
||||
/// pack SHAPE — a `closureTypePack` whose prefix is the fixed params. The
|
||||
/// stateful expand lives in `PackResolver.resolveClosureTypeWithBindings`.
|
||||
fn resolveClosurePackShape(ct: *const ast.ClosureTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
for (ct.param_types) |pt| {
|
||||
param_ids.append(alloc, resolveAstType(pt, table, alias_map)) catch unreachable;
|
||||
}
|
||||
const ret_id = if (ct.return_type) |rt| resolveAstType(rt, table, alias_map) else TypeId.void;
|
||||
if (ct.pack_name != null) {
|
||||
// Pack-variadic shape: fixed prefix in params, pack-start at end.
|
||||
return table.closureTypePack(param_ids.items, ret_id, @intCast(param_ids.items.len));
|
||||
}
|
||||
return table.closureType(param_ids.items, ret_id);
|
||||
return table.closureTypePack(param_ids.items, ret_id, @intCast(param_ids.items.len));
|
||||
}
|
||||
|
||||
fn resolveTupleType(tt: *const ast.TupleTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
/// Spread tuple `(..xs)` resolved without bindings: `resolveCompound` builds
|
||||
/// plain tuples and defers spread ones. type_bridge can't expand the pack, so
|
||||
/// each field resolves individually (a spread field is not a type → resolves to
|
||||
/// `.unresolved`). The stateful expand lives in
|
||||
/// `PackResolver.resolveTupleTypeWithBindings`.
|
||||
fn resolveTupleSpreadShape(tt: *const ast.TupleTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
var field_ids = std.ArrayList(TypeId).empty;
|
||||
for (tt.field_types) |ft| {
|
||||
|
||||
@@ -42,12 +42,12 @@ test "TypeResolver.resolvePrimitive maps builtin keywords, null otherwise" {
|
||||
}
|
||||
|
||||
test "TypeResolver.resolveCompound builds structural compound types" {
|
||||
const alloc = std.testing.allocator;
|
||||
// Arena-backed: interned tuple field slices are owned by the type table and
|
||||
// reclaimed in bulk by the real compiler's arena (never freed individually).
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
var index = ProgramIndex.init(alloc);
|
||||
defer index.deinit();
|
||||
const tr = TypeResolver{ .alloc = alloc, .types = &table, .diagnostics = null, .index = &index };
|
||||
const inner = PrimInner{};
|
||||
|
||||
var s64n = typeExpr("s64");
|
||||
@@ -57,25 +57,51 @@ test "TypeResolver.resolveCompound builds structural compound types" {
|
||||
var s32n = typeExpr("s32");
|
||||
|
||||
var ptr = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .pointer_type_expr = .{ .pointee_type = &s64n } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.ptrTo(.s64)), tr.resolveCompound(&ptr, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.ptrTo(.s64)), TypeResolver.resolveCompound(&table, &ptr, inner));
|
||||
|
||||
var mptr = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .many_pointer_type_expr = .{ .element_type = &u8n } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.manyPtrTo(.u8)), tr.resolveCompound(&mptr, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.manyPtrTo(.u8)), TypeResolver.resolveCompound(&table, &mptr, inner));
|
||||
|
||||
var slice = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .slice_type_expr = .{ .element_type = &f32n } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.sliceOf(.f32)), tr.resolveCompound(&slice, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.sliceOf(.f32)), TypeResolver.resolveCompound(&table, &slice, inner));
|
||||
|
||||
var opt = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .optional_type_expr = .{ .inner_type = &booln } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.optionalOf(.bool)), tr.resolveCompound(&opt, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.optionalOf(.bool)), TypeResolver.resolveCompound(&table, &opt, inner));
|
||||
|
||||
var len = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .int_literal = .{ .value = 3 } } };
|
||||
var arr = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .array_type_expr = .{ .length = &len, .element_type = &s32n } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.arrayOf(.s32, 3)), tr.resolveCompound(&arr, inner));
|
||||
try std.testing.expectEqual(@as(?TypeId, table.arrayOf(.s32, 3)), TypeResolver.resolveCompound(&table, &arr, inner));
|
||||
|
||||
// Non-compound nodes are not this resolver's responsibility → null, so the
|
||||
// caller continues with name / tuple / closure / generic resolution.
|
||||
// Function type `(s64) -> bool` — resolveCompound owns it (A2.3b).
|
||||
const fparams = [_]*Node{&s64n};
|
||||
var fnode = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .function_type_expr = .{ .param_types = &fparams, .return_type = &booln } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.functionTypeCC(&[_]TypeId{.s64}, .bool, .default)), TypeResolver.resolveCompound(&table, &fnode, inner));
|
||||
|
||||
// Plain closure `Closure(s64) -> bool` (no pack) — owned here.
|
||||
const cparams = [_]*Node{&s64n};
|
||||
var cnode = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .closure_type_expr = .{ .param_types = &cparams, .return_type = &booln } } };
|
||||
try std.testing.expectEqual(@as(?TypeId, table.closureType(&[_]TypeId{.s64}, .bool)), TypeResolver.resolveCompound(&table, &cnode, inner));
|
||||
|
||||
// Plain positional tuple `(s64, bool)` — owned here.
|
||||
const tfields = [_]*Node{ &s64n, &booln };
|
||||
var tnode = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .tuple_type_expr = .{ .field_types = &tfields, .field_names = null } } };
|
||||
const want_tuple = table.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .s64, .bool }, .names = null } });
|
||||
try std.testing.expectEqual(@as(?TypeId, want_tuple), TypeResolver.resolveCompound(&table, &tnode, inner));
|
||||
|
||||
// Pack-shaped `Closure(..p)` → null (needs caller pack state → PackResolver).
|
||||
var cpack = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .closure_type_expr = .{ .param_types = &.{}, .return_type = &booln, .pack_name = "p" } } };
|
||||
try std.testing.expect(TypeResolver.resolveCompound(&table, &cpack, inner) == null);
|
||||
|
||||
// Spread tuple `(..xs)` → null (a spread field needs pack expansion).
|
||||
var spread_op = typeExpr("xs");
|
||||
var spread = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .spread_expr = .{ .operand = &spread_op } } };
|
||||
const sfields = [_]*Node{&spread};
|
||||
var snode = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .tuple_type_expr = .{ .field_types = &sfields, .field_names = null } } };
|
||||
try std.testing.expect(TypeResolver.resolveCompound(&table, &snode, inner) == null);
|
||||
|
||||
// Names / parameterized types are not this resolver's responsibility → null.
|
||||
var name = typeExpr("List");
|
||||
try std.testing.expect(tr.resolveCompound(&name, inner) == null);
|
||||
try std.testing.expect(TypeResolver.resolveCompound(&table, &name, inner) == null);
|
||||
}
|
||||
|
||||
test "ResolveEnv default-constructs with all-null context" {
|
||||
|
||||
@@ -7,6 +7,7 @@ const program_index_mod = @import("program_index.zig");
|
||||
const Node = ast.Node;
|
||||
const TypeId = types.TypeId;
|
||||
const TypeTable = types.TypeTable;
|
||||
const StringId = types.StringId;
|
||||
const ProgramIndex = program_index_mod.ProgramIndex;
|
||||
|
||||
/// Explicit, caller-supplied resolution context (architecture Principle 2):
|
||||
@@ -66,23 +67,77 @@ pub const TypeResolver = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Structural compound types whose meaning is fully determined by their
|
||||
/// node kind and element type(s): `*T`, `[*]T`, `[]T`, `?T`, `[N]T`.
|
||||
/// Element types are resolved via `inner.resolveInner(node)` so generic
|
||||
/// structs / bindings / aliases in element position keep their full
|
||||
/// (caller-side, stateful) resolution. Returns `null` for any other node
|
||||
/// kind — names, tuples, closures/functions (pack-aware, A2.3),
|
||||
/// parameterized types, pack-index, `Self` — which the caller handles.
|
||||
pub fn resolveCompound(self: TypeResolver, node: *const Node, inner: anytype) ?TypeId {
|
||||
/// Single owner of structural AST-type-shape construction. Builds the
|
||||
/// shapes whose `TypeId` is fully determined by their node kind plus their
|
||||
/// element types resolved through `inner.resolveInner`: `*T`, `[*]T`, `[]T`,
|
||||
/// `?T`, `[N]T`, `(P...) -> R` functions, plain `Closure(P...) -> R`, and
|
||||
/// plain positional/named tuples. Element recursion goes through `inner`, so
|
||||
/// the caller's resolution mode is preserved — the compiler's stateful path
|
||||
/// passes `*Lowering` (generic/pack-binding aware), `type_bridge` passes a
|
||||
/// binding-free adapter. Both call THIS; there is no second compound/shape
|
||||
/// algorithm (architecture A2.3b — `resolveCompound` is the single owner).
|
||||
///
|
||||
/// Namespaced (no `self`): only the `TypeTable` is needed, so `type_bridge`
|
||||
/// (which has no `ProgramIndex`/diagnostics) can call it too.
|
||||
///
|
||||
/// Returns `null` for shapes that depend on caller pack/binding STATE and so
|
||||
/// can't be built here: pack-shaped `Closure(..p)` and spread tuples
|
||||
/// `(..xs)` (the stateful caller routes these to `PackResolver`), plus
|
||||
/// names, parameterized types, pack-index, and `Self`. OOM yields the
|
||||
/// `.unresolved` sentinel, never a fabricated type.
|
||||
pub fn resolveCompound(table: *TypeTable, node: *const Node, inner: anytype) ?TypeId {
|
||||
return switch (node.data) {
|
||||
.pointer_type_expr => |pt| self.types.ptrTo(inner.resolveInner(pt.pointee_type)),
|
||||
.many_pointer_type_expr => |mp| self.types.manyPtrTo(inner.resolveInner(mp.element_type)),
|
||||
.slice_type_expr => |st| self.types.sliceOf(inner.resolveInner(st.element_type)),
|
||||
.optional_type_expr => |ot| self.types.optionalOf(inner.resolveInner(ot.inner_type)),
|
||||
.pointer_type_expr => |pt| table.ptrTo(inner.resolveInner(pt.pointee_type)),
|
||||
.many_pointer_type_expr => |mp| table.manyPtrTo(inner.resolveInner(mp.element_type)),
|
||||
.slice_type_expr => |st| table.sliceOf(inner.resolveInner(st.element_type)),
|
||||
.optional_type_expr => |ot| table.optionalOf(inner.resolveInner(ot.inner_type)),
|
||||
.array_type_expr => |at| blk: {
|
||||
const elem = inner.resolveInner(at.element_type);
|
||||
const len: u32 = if (at.length.data == .int_literal) @intCast(at.length.data.int_literal.value) else 0;
|
||||
break :blk self.types.arrayOf(elem, len);
|
||||
break :blk table.arrayOf(elem, len);
|
||||
},
|
||||
.function_type_expr => |ft| blk: {
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
defer param_ids.deinit(table.alloc);
|
||||
for (ft.param_types) |pt| param_ids.append(table.alloc, inner.resolveInner(pt)) catch return .unresolved;
|
||||
const ret_ty = if (ft.return_type) |rt| inner.resolveInner(rt) else TypeId.void;
|
||||
const cc: types.TypeInfo.CallConv = switch (ft.call_conv) {
|
||||
.default => .default,
|
||||
.c => .c,
|
||||
};
|
||||
break :blk table.functionTypeCC(param_ids.items, ret_ty, cc);
|
||||
},
|
||||
.closure_type_expr => |ct| blk: {
|
||||
// Pack-shaped `Closure(..p)` needs caller pack state to expand —
|
||||
// defer to PackResolver (stateful) by returning null.
|
||||
if (ct.pack_name != null) break :blk null;
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
defer param_ids.deinit(table.alloc);
|
||||
for (ct.param_types) |pt| param_ids.append(table.alloc, inner.resolveInner(pt)) catch return .unresolved;
|
||||
const ret_ty = if (ct.return_type) |rt| inner.resolveInner(rt) else TypeId.void;
|
||||
break :blk table.closureType(param_ids.items, ret_ty);
|
||||
},
|
||||
.tuple_type_expr => |tt| blk: {
|
||||
// A spread field `(..xs)` expands to many fields via the pack
|
||||
// state — defer to PackResolver by returning null.
|
||||
for (tt.field_types) |ft| if (ft.data == .spread_expr) break :blk null;
|
||||
var field_ids = std.ArrayList(TypeId).empty;
|
||||
defer field_ids.deinit(table.alloc);
|
||||
for (tt.field_types) |ft| field_ids.append(table.alloc, inner.resolveInner(ft)) catch return .unresolved;
|
||||
// Preserve field names for a named tuple `(x: T, y: U)` when the
|
||||
// name and field counts agree (so `t.x` resolves).
|
||||
var name_ids: ?[]const StringId = null;
|
||||
if (tt.field_names) |names| {
|
||||
if (names.len == field_ids.items.len) {
|
||||
var ids = std.ArrayList(StringId).empty;
|
||||
for (names) |n| ids.append(table.alloc, table.internString(n)) catch return .unresolved;
|
||||
name_ids = ids.toOwnedSlice(table.alloc) catch null;
|
||||
}
|
||||
}
|
||||
break :blk table.intern(.{ .tuple = .{
|
||||
.fields = table.alloc.dupe(TypeId, field_ids.items) catch return .unresolved,
|
||||
.names = name_ids,
|
||||
} });
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user