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).
89 lines
3.7 KiB
Zig
89 lines
3.7 KiB
Zig
// Tests for packs.zig (PackResolver) — pack-aware TYPE-position resolution.
|
|
const std = @import("std");
|
|
const ast = @import("../ast.zig");
|
|
const errors = @import("../errors.zig");
|
|
|
|
const ir_mod = @import("ir.zig");
|
|
const TypeId = ir_mod.TypeId;
|
|
const Lowering = ir_mod.Lowering;
|
|
const PackResolver = ir_mod.PackResolver;
|
|
|
|
test "PackResolver.packTypeArgs: bound pack → element types; unbound → null" {
|
|
const alloc = std.testing.allocator;
|
|
var module = ir_mod.Module.init(alloc);
|
|
defer module.deinit();
|
|
var lowering = Lowering.init(&module);
|
|
|
|
var pat = std.StringHashMap([]const TypeId).init(alloc);
|
|
defer pat.deinit();
|
|
const elems = [_]TypeId{ .s32, .s64 };
|
|
try pat.put("xs", &elems);
|
|
lowering.pack_arg_types = pat;
|
|
|
|
const pr = PackResolver{ .l = &lowering };
|
|
|
|
// Bound pack, no projection → a fresh copy of its element types.
|
|
const got = pr.packTypeArgs("xs", null) orelse return error.TestUnexpectedResult;
|
|
defer alloc.free(got);
|
|
try std.testing.expectEqualSlices(TypeId, &elems, got);
|
|
|
|
// Unbound pack name → null (caller continues with other resolution).
|
|
try std.testing.expect(pr.packTypeArgs("ys", null) == null);
|
|
|
|
// A projection (`xs.T`) with no constraint map → null: there is no
|
|
// protocol to project the type-arg through.
|
|
try std.testing.expect(pr.packTypeArgs("xs", "T") == null);
|
|
}
|
|
|
|
test "PackResolver.packTypeArgs: no active pack_arg_types → null" {
|
|
const alloc = std.testing.allocator;
|
|
var module = ir_mod.Module.init(alloc);
|
|
defer module.deinit();
|
|
var lowering = Lowering.init(&module);
|
|
// pack_arg_types stays null (no active pack binding).
|
|
const pr = PackResolver{ .l = &lowering };
|
|
try std.testing.expect(pr.packTypeArgs("xs", null) == null);
|
|
}
|
|
|
|
test "PackResolver.packTypeArgs: missing projection → diagnostic + .unresolved (never silent .void)" {
|
|
// Arena-backed: the projection path allocates mangle/key buffers the
|
|
// arena-style compiler never frees individually.
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
|
|
var module = ir_mod.Module.init(alloc);
|
|
var lowering = Lowering.init(&module);
|
|
|
|
// Protocol `P(T: Type)` so `lookupProtocolArg("P", "T")` resolves to arg 0 —
|
|
// but with NO `impl P(...) for <elem>` registered, the per-element
|
|
// projection finds no type for the slot.
|
|
var constraint = ast.Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "Type" } } };
|
|
const tparams = [_]ast.StructTypeParam{.{ .name = "T", .constraint = &constraint }};
|
|
const pd = ast.ProtocolDecl{ .name = "P", .methods = &.{}, .type_params = &tparams };
|
|
try lowering.program_index.protocol_ast_map.put("P", &pd);
|
|
|
|
var pat = std.StringHashMap([]const TypeId).init(alloc);
|
|
const elems = [_]TypeId{.s64};
|
|
try pat.put("xs", &elems);
|
|
lowering.pack_arg_types = pat;
|
|
|
|
var pcon = std.StringHashMap([]const u8).init(alloc);
|
|
try pcon.put("xs", "P");
|
|
lowering.pack_constraint = pcon;
|
|
|
|
var diags = errors.DiagnosticList.init(alloc, "", "<test>");
|
|
lowering.diagnostics = &diags;
|
|
|
|
const pr = PackResolver{ .l = &lowering };
|
|
const got = pr.packTypeArgs("xs", "T") orelse return error.TestUnexpectedResult;
|
|
|
|
// The unfilled slot is the dedicated failure sentinel — never a real
|
|
// `.void`, which would read as a legitimate type and silently corrupt.
|
|
try std.testing.expectEqual(@as(usize, 1), got.len);
|
|
try std.testing.expectEqual(TypeId.unresolved, got[0]);
|
|
try std.testing.expect(TypeId.unresolved != TypeId.void);
|
|
// And the failure was surfaced loudly, not swallowed.
|
|
try std.testing.expect(diags.hasErrors());
|
|
}
|