Files
sx/src/ir/packs.test.zig
agra 3ed1b3a7a0 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).
2026-06-02 14:43:47 +03:00

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());
}