refactor(ir): pack projections → PackResolver + retire the alias borrow (A2.3)
A2-merge gate: both parts in one commit, behavior-preserving (350/0). Part 1 — retire the TypeTable.aliases borrow (build-enforced): - type_bridge.zig: add `AliasMap` and thread it as an explicit param through every name-resolving fn (resolveAstType, bridgeType, resolveTypeName, the compound resolvers, resolveTupleLiteralAsType, resolveParameterizedType, the inline enum/struct/union + error resolvers). resolveTypeName now forwards the threaded map to TypeResolver.resolveNamed instead of reading table.aliases. - lower.zig: all 31 resolveAstType callers pass &self.program_index.type_alias_map; drop the lowerRoot loan. - types.zig: remove the now-unused TypeTable.aliases field. - type_bridge.test.zig: alias test passes alias_map explicitly; other calls pass null. Part 2 — pack projections get one owner + no .void failure sentinel: - New packs.zig (PackResolver, a *Lowering facade): moves resolveClosure/Tuple/FunctionTypeWithBindings, packTypeElems, packTypeArgs, elementProtocolTypeArg out of Lowering. Call sites route through Lowering.packResolver(); barrel-wired in ir.zig. - The missing-projection `orelse .void` in packTypeArgs now emits a diagnostic and fills the slot with .unresolved (the tripwire sentinel), never a real .void; OOM `catch return .void` in the moved fns became .unresolved too. Legitimate no-return-type `else .void` defaults are preserved. - packs.test.zig: packTypeArgs bound/unbound/no-constraint/no-state cases + the missing-projection backstop (diagnostic + .unresolved slot).
This commit is contained in:
292
src/ir/lower.zig
292
src/ir/lower.zig
@@ -21,6 +21,7 @@ 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 PackResolver = @import("packs.zig").PackResolver;
|
||||
|
||||
const TypeId = types.TypeId;
|
||||
const StringId = types.StringId;
|
||||
@@ -288,12 +289,6 @@ pub const Lowering = struct {
|
||||
/// Pass 1: Scan all declarations (register ASTs, types, extern stubs).
|
||||
/// 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.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.program_index.type_alias_map;
|
||||
const decls = switch (root.data) {
|
||||
.root => |r| r.decls,
|
||||
else => return,
|
||||
@@ -1342,9 +1337,9 @@ pub const Lowering = struct {
|
||||
} else if (cd.value.data == .struct_decl) {
|
||||
self.registerStructDecl(&cd.value.data.struct_decl);
|
||||
} else if (cd.value.data == .enum_decl) {
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
} else if (cd.value.data == .union_decl) {
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
} else if (cd.value.data == .comptime_expr) {
|
||||
self.lowerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, cd.type_annotation);
|
||||
}
|
||||
@@ -1356,10 +1351,10 @@ pub const Lowering = struct {
|
||||
self.registerStructDecl(&sd);
|
||||
},
|
||||
.enum_decl => {
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
|
||||
},
|
||||
.union_decl => {
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
|
||||
},
|
||||
.error_set_decl => {
|
||||
self.registerErrorSetDecl(decl);
|
||||
@@ -1457,10 +1452,10 @@ pub const Lowering = struct {
|
||||
self.registerStructDecl(&cd.value.data.struct_decl);
|
||||
} else if (cd.value.data == .enum_decl) {
|
||||
// Register enum/tagged-union types in the type table
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
} else if (cd.value.data == .union_decl) {
|
||||
// Register plain union types in the type table
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
} else if (cd.value.data == .type_expr or
|
||||
cd.value.data == .pointer_type_expr or
|
||||
cd.value.data == .many_pointer_type_expr or
|
||||
@@ -1470,7 +1465,7 @@ pub const Lowering = struct {
|
||||
cd.value.data == .function_type_expr)
|
||||
{
|
||||
// Type alias: MyFloat :: f64; Ptr :: *u8; Cb :: (s32) -> s32;
|
||||
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
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;
|
||||
@@ -1553,7 +1548,7 @@ pub const Lowering = struct {
|
||||
// resolve via type_bridge and register the result
|
||||
// under the alias name so `Vec4` in expression
|
||||
// position can `const_type(<vector tid>)`.
|
||||
const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
if (result_ty != .void and result_ty != .unresolved) {
|
||||
self.program_index.type_alias_map.put(cd.name, result_ty) catch {};
|
||||
}
|
||||
@@ -1591,11 +1586,11 @@ pub const Lowering = struct {
|
||||
},
|
||||
.enum_decl => {
|
||||
// Register enum/tagged-union types in the type table
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
|
||||
},
|
||||
.union_decl => {
|
||||
// Register plain union types in the type table
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
|
||||
},
|
||||
.error_set_decl => {
|
||||
self.registerErrorSetDecl(decl);
|
||||
@@ -2422,7 +2417,7 @@ pub const Lowering = struct {
|
||||
// Block-local type declarations
|
||||
.struct_decl => |sd| self.registerStructDecl(&sd),
|
||||
.enum_decl, .union_decl => {
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
},
|
||||
.error_set_decl => self.registerErrorSetDecl(node),
|
||||
.ufcs_alias => |ua| {
|
||||
@@ -2581,7 +2576,7 @@ pub const Lowering = struct {
|
||||
return;
|
||||
}
|
||||
if (cd.value.data == .enum_decl or cd.value.data == .union_decl) {
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3506,7 +3501,7 @@ pub const Lowering = struct {
|
||||
// `t : Type = f64;` store a real TypeId; lets
|
||||
// `t == f64` icmp at runtime against the same TypeId.
|
||||
if (self.isKnownTypeName(te.name)) {
|
||||
const ty = type_bridge.resolveAstType(node, &self.module.types);
|
||||
const ty = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
break :blk self.builder.constType(ty);
|
||||
}
|
||||
break :blk self.emitError(te.name, node.span);
|
||||
@@ -6041,7 +6036,7 @@ pub const Lowering = struct {
|
||||
const name_id = self.module.types.internString(id.name);
|
||||
return self.module.types.findByName(name_id) orelse .unresolved;
|
||||
},
|
||||
.type_expr => return type_bridge.resolveAstType(te, &self.module.types),
|
||||
.type_expr => return type_bridge.resolveAstType(te, &self.module.types, &self.program_index.type_alias_map),
|
||||
.field_access => |fa| {
|
||||
// Module.Type — try to resolve the field as a type name
|
||||
const name_id = self.module.types.internString(fa.field);
|
||||
@@ -7799,7 +7794,7 @@ pub const Lowering = struct {
|
||||
// Check for #compiler free functions
|
||||
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;
|
||||
const ret_ty = if (fd_check.return_type) |rt| type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map) else TypeId.void;
|
||||
return self.builder.compilerCall(func_name, args.items, ret_ty);
|
||||
}
|
||||
}
|
||||
@@ -8167,7 +8162,7 @@ pub const Lowering = struct {
|
||||
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)
|
||||
type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map)
|
||||
else
|
||||
.void;
|
||||
return self.builder.compilerCall(qualified, method_args.items, ret_ty);
|
||||
@@ -8605,7 +8600,7 @@ pub const Lowering = struct {
|
||||
|
||||
const ret_ty = blk: {
|
||||
if (lam.return_type) |rt| {
|
||||
break :blk type_bridge.resolveAstType(rt, &self.module.types);
|
||||
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map);
|
||||
}
|
||||
// Use target closure return type if available — but only when it's
|
||||
// a resolved type. An `.unresolved` ret comes from an unbound
|
||||
@@ -9162,7 +9157,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn resolveReturnType2(self: *Lowering, rt: ?*const Node) TypeId {
|
||||
if (rt) |r| return type_bridge.resolveAstType(r, &self.module.types);
|
||||
if (rt) |r| return type_bridge.resolveAstType(r, &self.module.types, &self.program_index.type_alias_map);
|
||||
return .void;
|
||||
}
|
||||
|
||||
@@ -10300,8 +10295,8 @@ pub const Lowering = struct {
|
||||
const ret_ty: TypeId = blk: {
|
||||
if (fd.return_type) |rt| {
|
||||
if (rt.data == .type_expr) {
|
||||
if (type_bridge.resolveAstType(rt, &self.module.types) != .unresolved) {
|
||||
break :blk type_bridge.resolveAstType(rt, &self.module.types);
|
||||
if (type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map) != .unresolved) {
|
||||
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11649,7 +11644,7 @@ pub const Lowering = struct {
|
||||
},
|
||||
.type_expr => |te| {
|
||||
if (self.program_index.type_alias_map.get(te.name)) |alias_ty| return alias_ty;
|
||||
return type_bridge.resolveAstType(node, &self.module.types);
|
||||
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
},
|
||||
.call => |cl| {
|
||||
// `type_of(x)` resolves to `inferExprType(x)` at lower
|
||||
@@ -11674,7 +11669,7 @@ pub const Lowering = struct {
|
||||
.optional_type_expr,
|
||||
.function_type_expr,
|
||||
.tuple_literal,
|
||||
=> return type_bridge.resolveAstType(node, &self.module.types),
|
||||
=> return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map),
|
||||
else => return .unresolved,
|
||||
}
|
||||
}
|
||||
@@ -11874,7 +11869,7 @@ pub const Lowering = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn mangleTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
pub fn mangleTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
// Builtin types
|
||||
if (ty == .s8) return "s8";
|
||||
if (ty == .s16) return "s16";
|
||||
@@ -12761,7 +12756,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
/// Resolve a type node, checking type_bindings first for generic type params.
|
||||
fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId {
|
||||
pub fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId {
|
||||
// Pack-index in a type position: `$<pack>[<lit>]` resolves to the
|
||||
// i-th element type of the active pack binding (step 3 of the
|
||||
// variadic heterogeneous type packs feature). Unblocks parametric
|
||||
@@ -12824,16 +12819,16 @@ pub const Lowering = struct {
|
||||
}
|
||||
// Pointers / slices / many-pointers / optionals / arrays are owned by
|
||||
// TypeResolver (handled above). The pack-aware tuple / closure /
|
||||
// function shapes resolve here — A2.3 owns their pack projection logic.
|
||||
// function shapes are owned by `PackResolver` (packs.zig, A2.3).
|
||||
switch (node.data) {
|
||||
.closure_type_expr => |ct| {
|
||||
return self.resolveClosureTypeWithBindings(&ct);
|
||||
return self.packResolver().resolveClosureTypeWithBindings(&ct);
|
||||
},
|
||||
.function_type_expr => |ft| {
|
||||
return self.resolveFunctionTypeWithBindings(&ft);
|
||||
return self.packResolver().resolveFunctionTypeWithBindings(&ft);
|
||||
},
|
||||
.tuple_type_expr => |tt| {
|
||||
return self.resolveTupleTypeWithBindings(&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
|
||||
@@ -12851,7 +12846,7 @@ pub const Lowering = struct {
|
||||
defer field_ids.deinit(self.alloc);
|
||||
for (tl.elements) |el| {
|
||||
if (el.value.data == .spread_expr) {
|
||||
if (self.packTypeElems(el.value.data.spread_expr.operand)) |elems| {
|
||||
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;
|
||||
@@ -12875,207 +12870,22 @@ pub const Lowering = struct {
|
||||
return .unresolved;
|
||||
}
|
||||
// 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).
|
||||
// canonical alias table directly (`ProgramIndex.type_alias_map`). Other
|
||||
// node kinds (inline type decls, error types) still route through
|
||||
// type_bridge, which now takes the alias map as an explicit argument
|
||||
// (the `TypeTable.aliases` borrow is gone, A2.3).
|
||||
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),
|
||||
else => return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a `Closure(...)` type expression with the active type/pack
|
||||
/// bindings applied. Pack-shaped closure exprs (`Closure(Prefix..., ..$pack)`)
|
||||
/// substitute `pack` from `self.pack_bindings`, producing a concrete
|
||||
/// closure type — used when monomorphising a pack-variadic impl body
|
||||
/// against a concrete source signature.
|
||||
fn resolveClosureTypeWithBindings(self: *Lowering, ct: *const ast.ClosureTypeExpr) TypeId {
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
defer param_ids.deinit(self.alloc);
|
||||
for (ct.param_types) |pt| {
|
||||
param_ids.append(self.alloc, self.resolveTypeWithBindings(pt)) catch return .void;
|
||||
}
|
||||
if (ct.pack_name) |pn| {
|
||||
// Protocol pack (`Closure(..sources.T)` / `Closure(..sources)`):
|
||||
// expand the bound pack's per-element type-args.
|
||||
if (self.packTypeArgs(pn, ct.pack_projection)) |elems| {
|
||||
defer self.alloc.free(elems);
|
||||
for (elems) |t| param_ids.append(self.alloc, t) catch return .void;
|
||||
const ret_ty = if (ct.return_type) |rt| self.resolveTypeWithBindings(rt) else .void;
|
||||
return self.module.types.closureType(param_ids.items, ret_ty);
|
||||
}
|
||||
if (self.pack_bindings) |pb| {
|
||||
if (pb.get(pn)) |pack_tys| {
|
||||
for (pack_tys) |t| param_ids.append(self.alloc, t) catch return .void;
|
||||
// Fully bound — emit a concrete closure type, no pack_start.
|
||||
const ret_ty = if (ct.return_type) |rt| self.resolveTypeWithBindings(rt) else .void;
|
||||
return self.module.types.closureType(param_ids.items, ret_ty);
|
||||
}
|
||||
}
|
||||
// Pack name in scope but no binding — preserve the pack-shape
|
||||
// so downstream code can still see it's variadic. (Hit during
|
||||
// impl-block parsing before any concrete monomorphisation.)
|
||||
const ret_ty = if (ct.return_type) |rt| self.resolveTypeWithBindings(rt) else .void;
|
||||
return self.module.types.closureTypePack(param_ids.items, ret_ty, @intCast(param_ids.items.len));
|
||||
}
|
||||
const ret_ty = if (ct.return_type) |rt| self.resolveTypeWithBindings(rt) else .void;
|
||||
return self.module.types.closureType(param_ids.items, ret_ty);
|
||||
}
|
||||
|
||||
/// Resolve a tuple type expression with active pack bindings: a spread field
|
||||
/// `(..xs)` / `(..xs.T)` expands to the pack's per-element types via
|
||||
/// `packTypeElems`. Non-spread fields resolve normally.
|
||||
fn resolveTupleTypeWithBindings(self: *Lowering, tt: *const ast.TupleTypeExpr) TypeId {
|
||||
var field_ids = std.ArrayList(TypeId).empty;
|
||||
defer field_ids.deinit(self.alloc);
|
||||
var had_spread = false;
|
||||
for (tt.field_types) |ft| {
|
||||
if (ft.data == .spread_expr) {
|
||||
if (self.packTypeElems(ft.data.spread_expr.operand)) |elems| {
|
||||
defer self.alloc.free(elems);
|
||||
for (elems) |e| field_ids.append(self.alloc, e) catch return .void;
|
||||
had_spread = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
field_ids.append(self.alloc, self.resolveTypeWithBindings(ft)) catch return .void;
|
||||
}
|
||||
// Preserve field names for a named tuple `(x: T, y: U)` so `t.x` resolves
|
||||
// (matches type_bridge.resolveTupleType). A spread expands to unnamed
|
||||
// pack elements, so names only apply when there was no spread.
|
||||
var name_ids: ?[]const types.StringId = null;
|
||||
if (!had_spread) {
|
||||
if (tt.field_names) |names| {
|
||||
if (names.len == field_ids.items.len) {
|
||||
var ids = std.ArrayList(types.StringId).empty;
|
||||
for (names) |n| ids.append(self.alloc, self.module.types.internString(n)) catch return .void;
|
||||
name_ids = ids.toOwnedSlice(self.alloc) catch null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.module.types.intern(.{ .tuple = .{
|
||||
.fields = self.alloc.dupe(TypeId, field_ids.items) catch return .void,
|
||||
.names = name_ids,
|
||||
} });
|
||||
}
|
||||
|
||||
/// TYPE-position pack expansion: given a spread operand, return the
|
||||
/// per-element types. `..xs` → the pack's element types (`pack_arg_types`).
|
||||
/// `..xs.T` → each element's protocol type-arg `T` (from its
|
||||
/// `impl P(args) for elem` in `param_impl_map`). Null when not a pack spread.
|
||||
/// Caller owns the returned slice.
|
||||
fn packTypeElems(self: *Lowering, operand: *const Node) ?[]TypeId {
|
||||
const pat = self.pack_arg_types orelse return null;
|
||||
// `..F(Ts)` — apply a parameterized type `F` to each pack element:
|
||||
// `(..VL(Ts))` → `(VL(T0), VL(T1), …)`. Per element, temporarily bind
|
||||
// the pack name to that single element type and resolve `F(elem)`.
|
||||
if (operand.data == .parameterized_type_expr) {
|
||||
const pt = operand.data.parameterized_type_expr;
|
||||
var pack_name_p: []const u8 = "";
|
||||
for (pt.args) |a| {
|
||||
const nm = switch (a.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
else => continue,
|
||||
};
|
||||
if (pat.contains(nm)) {
|
||||
pack_name_p = nm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pack_name_p.len == 0) return null;
|
||||
const elems = pat.get(pack_name_p) orelse return null;
|
||||
if (self.type_bindings == null) return null;
|
||||
var out = std.ArrayList(TypeId).empty;
|
||||
for (elems) |ti| {
|
||||
const had = self.type_bindings.?.get(pack_name_p);
|
||||
self.type_bindings.?.put(pack_name_p, ti) catch {};
|
||||
out.append(self.alloc, self.resolveTypeWithBindings(operand)) catch return null;
|
||||
if (had) |h| self.type_bindings.?.put(pack_name_p, h) catch {} else _ = self.type_bindings.?.remove(pack_name_p);
|
||||
}
|
||||
return out.toOwnedSlice(self.alloc) catch null;
|
||||
}
|
||||
// In type position `xs` / `xs.T` parse to a (possibly dotted) type_expr
|
||||
// name; `field_access` covers any value-shaped form.
|
||||
var pack_name: []const u8 = "";
|
||||
var projection: ?[]const u8 = null;
|
||||
switch (operand.data) {
|
||||
.type_expr, .identifier => {
|
||||
const full = if (operand.data == .type_expr) operand.data.type_expr.name else operand.data.identifier.name;
|
||||
if (std.mem.indexOfScalar(u8, full, '.')) |dot| {
|
||||
pack_name = full[0..dot];
|
||||
projection = full[dot + 1 ..];
|
||||
} else {
|
||||
pack_name = full;
|
||||
}
|
||||
},
|
||||
.field_access => |fa| {
|
||||
pack_name = switch (fa.object.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
else => return null,
|
||||
};
|
||||
projection = fa.field;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
return self.packTypeArgs(pack_name, projection);
|
||||
}
|
||||
|
||||
/// Per-element types for a bound protocol pack: `pack_name` alone → the
|
||||
/// element types; with `projection` (`xs.T`) → each element's protocol
|
||||
/// type-arg. Null when `pack_name` isn't a bound pack. Caller owns the slice.
|
||||
fn packTypeArgs(self: *Lowering, pack_name: []const u8, projection: ?[]const u8) ?[]TypeId {
|
||||
const pat = self.pack_arg_types orelse return null;
|
||||
const elems = pat.get(pack_name) orelse return null;
|
||||
if (projection == null) return self.alloc.dupe(TypeId, elems) catch null;
|
||||
const proto = if (self.pack_constraint) |pc| (pc.get(pack_name) orelse return null) else return null;
|
||||
const arg_idx = self.lookupProtocolArg(proto, projection.?) orelse return null;
|
||||
var out = std.ArrayList(TypeId).empty;
|
||||
for (elems) |elem| {
|
||||
out.append(self.alloc, self.elementProtocolTypeArg(proto, elem, arg_idx) orelse .void) catch return null;
|
||||
}
|
||||
return out.toOwnedSlice(self.alloc) catch null;
|
||||
}
|
||||
|
||||
/// For a concrete `elem` conforming to parameterised `proto`, return the
|
||||
/// `arg_idx`-th protocol type-arg from its `impl proto(args) for elem`
|
||||
/// (scans `param_impl_map` for `proto\x00…\x00mangle(elem)`).
|
||||
fn elementProtocolTypeArg(self: *Lowering, proto: []const u8, elem: TypeId, arg_idx: u32) ?TypeId {
|
||||
const prefix = std.fmt.allocPrint(self.alloc, "{s}\x00", .{proto}) catch return null;
|
||||
const suffix = std.fmt.allocPrint(self.alloc, "\x00{s}", .{self.mangleTypeName(elem)}) catch return null;
|
||||
var it = self.param_impl_map.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const k = entry.key_ptr.*;
|
||||
if (std.mem.startsWith(u8, k, prefix) and std.mem.endsWith(u8, k, suffix)) {
|
||||
for (entry.value_ptr.items) |impl| {
|
||||
if (arg_idx < impl.target_args.len) return impl.target_args[arg_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn resolveFunctionTypeWithBindings(self: *Lowering, ft: *const ast.FunctionTypeExpr) TypeId {
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
defer param_ids.deinit(self.alloc);
|
||||
for (ft.param_types) |pt| {
|
||||
param_ids.append(self.alloc, self.resolveTypeWithBindings(pt)) catch return .void;
|
||||
}
|
||||
const ret_ty = if (ft.return_type) |rt| self.resolveTypeWithBindings(rt) else .void;
|
||||
const cc: types.TypeInfo.CallConv = switch (ft.call_conv) {
|
||||
.default => .default,
|
||||
.c => .c,
|
||||
};
|
||||
return self.module.types.functionTypeCC(param_ids.items, ret_ty, cc);
|
||||
/// Bind a `PackResolver` to this Lowering for pack-aware TYPE-position
|
||||
/// resolution (`Closure(..p)` / `(Params...) -> R` / `(..xs)` tuples and
|
||||
/// their `..xs.T` projections). A2.3 moved that logic into `packs.zig`.
|
||||
fn packResolver(self: *Lowering) PackResolver {
|
||||
return .{ .l = self };
|
||||
}
|
||||
|
||||
/// Resolve a .call node that represents a type constructor (e.g., List(T), Vector(N, T)).
|
||||
@@ -13197,7 +13007,7 @@ pub const Lowering = struct {
|
||||
// A spread arg `..sources.T` expands to the source pack's
|
||||
// per-element (projected) types; a plain arg is one type.
|
||||
if (a.data == .spread_expr) {
|
||||
if (self.packTypeElems(a.data.spread_expr.operand)) |elems| {
|
||||
if (self.packResolver().packTypeElems(a.data.spread_expr.operand)) |elems| {
|
||||
defer self.alloc.free(elems);
|
||||
for (elems) |ty| {
|
||||
pack_tys.append(self.alloc, ty) catch {};
|
||||
@@ -13498,7 +13308,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types);
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
}
|
||||
|
||||
fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl) void {
|
||||
@@ -13644,7 +13454,7 @@ pub const Lowering = struct {
|
||||
if (const_node.data == .const_decl) {
|
||||
const cd = const_node.data.const_decl;
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, cd.name }) catch continue;
|
||||
const ty: ?TypeId = if (cd.type_annotation) |ta| type_bridge.resolveAstType(ta, table) else null;
|
||||
const ty: ?TypeId = if (cd.type_annotation) |ta| type_bridge.resolveAstType(ta, table, &self.program_index.type_alias_map) else null;
|
||||
self.struct_const_map.put(qualified, .{ .value = cd.value, .ty = ty }) catch {};
|
||||
}
|
||||
}
|
||||
@@ -13769,13 +13579,13 @@ pub const Lowering = struct {
|
||||
var ptypes = std.ArrayList(TypeId).empty;
|
||||
for (method.params) |p| {
|
||||
// Self → *void for protocol context; everything else
|
||||
// goes through `resolveAstType`, which now consults
|
||||
// the alias map via `TypeTable.aliases`.
|
||||
// goes through `resolveAstType`, threaded with the canonical
|
||||
// alias map (`ProgramIndex.type_alias_map`).
|
||||
const pty = blk: {
|
||||
if (p.data == .type_expr and std.mem.eql(u8, p.data.type_expr.name, "Self")) {
|
||||
break :blk void_ptr_ty;
|
||||
}
|
||||
break :blk type_bridge.resolveAstType(p, table);
|
||||
break :blk type_bridge.resolveAstType(p, table, &self.program_index.type_alias_map);
|
||||
};
|
||||
ptypes.append(self.alloc, pty) catch unreachable;
|
||||
}
|
||||
@@ -13785,7 +13595,7 @@ pub const Lowering = struct {
|
||||
ret_is_self = true;
|
||||
break :blk void_ptr_ty;
|
||||
}
|
||||
break :blk type_bridge.resolveAstType(rt, table);
|
||||
break :blk type_bridge.resolveAstType(rt, table, &self.program_index.type_alias_map);
|
||||
} else .void;
|
||||
method_infos.append(self.alloc, .{
|
||||
.name = method.name,
|
||||
@@ -14333,7 +14143,7 @@ pub const Lowering = struct {
|
||||
// Resolve the protocol's type-arg list to concrete TypeIds.
|
||||
var arg_tys = std.ArrayList(TypeId).empty;
|
||||
for (ib.protocol_type_args) |arg_node| {
|
||||
const t = type_bridge.resolveAstType(arg_node, table);
|
||||
const t = type_bridge.resolveAstType(arg_node, table, &self.program_index.type_alias_map);
|
||||
arg_tys.append(self.alloc, t) catch return;
|
||||
}
|
||||
|
||||
@@ -14341,9 +14151,9 @@ pub const Lowering = struct {
|
||||
// parameterised impls (back-compat `target_type` string is kept for
|
||||
// simple cases but the canonical form is the TypeExpr).
|
||||
const src_ty: TypeId = if (ib.target_type_expr) |te|
|
||||
type_bridge.resolveAstType(te, table)
|
||||
type_bridge.resolveAstType(te, table, &self.program_index.type_alias_map)
|
||||
else if (ib.target_type.len > 0)
|
||||
type_bridge.resolveAstType(&.{ .span = decl.span, .data = .{ .type_expr = .{ .name = ib.target_type } } }, table)
|
||||
type_bridge.resolveAstType(&.{ .span = decl.span, .data = .{ .type_expr = .{ .name = ib.target_type } } }, table, &self.program_index.type_alias_map)
|
||||
else
|
||||
return;
|
||||
|
||||
@@ -15126,7 +14936,7 @@ pub const Lowering = struct {
|
||||
// Generic #compiler method dispatch — return type from declaration
|
||||
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);
|
||||
if (method_fd.return_type) |rt| return type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map);
|
||||
return .void;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user