test(ir): lock generic substitution + mono keys before A4.1 extraction (A4.1 scaffolding step 1)
Test-first scaffolding ahead of extracting src/ir/generics.zig — no code change
to the refactor targets (buildTypeBindings / mangleGenericName / monomorphize* /
inferGenericReturnType / mangleTypeName).
Adds the first non-FFI generic/pack .ir snapshots (closing the ARCH-SAFETY §3
gap for this phase), each captured surgically via `sx ir | normalize_ir`,
path-free and idempotent:
- 0200-generics-generic generic fn, type-param inference + mono
- 0201-generics-generic-struct generic struct instantiation
- 0507-packs-pack-mono-dedup mono-key dedup (same shape => one mono)
- 0518-packs-pack-value-dispatch pack value dispatch (monomorphizePackFn)
- 0524-packs-generic-fn-pack-state-leak pack-state isolation (issue-0048/0050
class; guards the future scoped-env change)
Adds 2 unit tests via the existing public surface (no new pub exposure,
mirroring the A3.2 sub-step-1 cadence):
- mangleTypeName: pins the mono-key fragment encoding per type shape
(s64 / ptr_X / opt_X / SL_X / mptr_X / AR_n_X / vec_n_X / struct-name / tu_X_Y).
- inferGenericReturnType: explicit type-arg path binds $T and resolves the
-> T return (pair(s64,..) => s64, pair(f64,..) => f64).
The internal substitution/mono-key unit tests (comptime-value mangle,
buildTypeBindings strategies, scoped-env isolation) land with the generics.zig
extraction in sub-step 2, as A3.2's plan-object tests landed with CallPlan.
zig build, zig build test, tests/run_examples.sh (357/0) all green.
This commit is contained in:
3193
examples/expected/0200-generics-generic.ir
Normal file
3193
examples/expected/0200-generics-generic.ir
Normal file
File diff suppressed because it is too large
Load Diff
5029
examples/expected/0201-generics-generic-struct.ir
Normal file
5029
examples/expected/0201-generics-generic-struct.ir
Normal file
File diff suppressed because it is too large
Load Diff
3564
examples/expected/0507-packs-pack-mono-dedup.ir
Normal file
3564
examples/expected/0507-packs-pack-mono-dedup.ir
Normal file
File diff suppressed because it is too large
Load Diff
3269
examples/expected/0518-packs-pack-value-dispatch.ir
Normal file
3269
examples/expected/0518-packs-pack-value-dispatch.ir
Normal file
File diff suppressed because it is too large
Load Diff
3728
examples/expected/0524-packs-generic-fn-pack-state-leak.ir
Normal file
3728
examples/expected/0524-packs-generic-fn-pack-state-leak.ir
Normal file
File diff suppressed because it is too large
Load Diff
@@ -828,3 +828,92 @@ test "E1.4c noreturn typing: divergence shapes + if-else unification + block pro
|
|||||||
defer alloc.destroy(both_div);
|
defer alloc.destroy(both_div);
|
||||||
try std.testing.expectEqual(TypeId.noreturn, lowering.inferExprType(both_div));
|
try std.testing.expectEqual(TypeId.noreturn, lowering.inferExprType(both_div));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── A4.1 test-first scaffolding: generic substitution + mono keys ────
|
||||||
|
// Lock the CURRENT behavior of the generic mono-key building blocks
|
||||||
|
// (`mangleTypeName`) and generic-return substitution (`inferGenericReturnType`)
|
||||||
|
// before they move to `src/ir/generics.zig`. Reached through the existing
|
||||||
|
// public surface — no new exposure (mirrors the A3.2 sub-step-1 cadence).
|
||||||
|
|
||||||
|
test "generics: mangleTypeName encodes the mono-key fragment per type shape" {
|
||||||
|
// Arena: the compound arms allocate fragment strings via the module
|
||||||
|
// allocator (`allocPrint` / ArrayList) and never free them — the real
|
||||||
|
// compiler runs in the compile arena, so an arena keeps the leak checker
|
||||||
|
// clean without changing the encoding under test.
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
var module = ir_mod.Module.init(alloc);
|
||||||
|
defer module.deinit();
|
||||||
|
var l = Lowering.init(&module);
|
||||||
|
const tt = &module.types;
|
||||||
|
|
||||||
|
// Builtins — the leaf fragments `mangleGenericName` concatenates per
|
||||||
|
// bound type param (`base__<frag>...`).
|
||||||
|
try std.testing.expectEqualStrings("s64", l.mangleTypeName(.s64));
|
||||||
|
try std.testing.expectEqualStrings("u8", l.mangleTypeName(.u8));
|
||||||
|
try std.testing.expectEqualStrings("f32", l.mangleTypeName(.f32));
|
||||||
|
try std.testing.expectEqualStrings("bool", l.mangleTypeName(.bool));
|
||||||
|
try std.testing.expectEqualStrings("Any", l.mangleTypeName(.any));
|
||||||
|
try std.testing.expectEqualStrings("string", l.mangleTypeName(.string));
|
||||||
|
|
||||||
|
// Compound shapes — prefix + recursive inner fragment.
|
||||||
|
try std.testing.expectEqualStrings("ptr_s64", l.mangleTypeName(tt.ptrTo(.s64)));
|
||||||
|
try std.testing.expectEqualStrings("opt_s64", l.mangleTypeName(tt.optionalOf(.s64)));
|
||||||
|
try std.testing.expectEqualStrings("ptr_opt_u8", l.mangleTypeName(tt.ptrTo(tt.optionalOf(.u8))));
|
||||||
|
try std.testing.expectEqualStrings("SL_f64", l.mangleTypeName(tt.intern(.{ .slice = .{ .element = .f64 } })));
|
||||||
|
try std.testing.expectEqualStrings("mptr_u8", l.mangleTypeName(tt.intern(.{ .many_pointer = .{ .element = .u8 } })));
|
||||||
|
try std.testing.expectEqualStrings("AR_4_s32", l.mangleTypeName(tt.intern(.{ .array = .{ .element = .s32, .length = 4 } })));
|
||||||
|
try std.testing.expectEqualStrings("vec_3_f32", l.mangleTypeName(tt.intern(.{ .vector = .{ .element = .f32, .length = 3 } })));
|
||||||
|
|
||||||
|
// Named aggregate → its declared name.
|
||||||
|
const pt = tt.intern(.{ .@"struct" = .{ .name = tt.internString("Point"), .fields = &.{} } });
|
||||||
|
try std.testing.expectEqualStrings("Point", l.mangleTypeName(pt));
|
||||||
|
|
||||||
|
// Tuple: "tu" + "_<frag>" per field.
|
||||||
|
const tup = tt.intern(.{ .tuple = .{ .fields = &[_]TypeId{ .s64, .bool }, .names = null } });
|
||||||
|
try std.testing.expectEqualStrings("tu_s64_bool", l.mangleTypeName(tup));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "generics: inferGenericReturnType binds explicit type args, resolves return" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
var module = ir_mod.Module.init(alloc);
|
||||||
|
defer module.deinit();
|
||||||
|
var l = Lowering.init(&module);
|
||||||
|
|
||||||
|
// pair :: ($T: Type, a: T, b: T) -> T — the return type IS the bound `T`.
|
||||||
|
const tps = [_]ast.StructTypeParam{.{ .name = "T", .constraint = typeKeyword(alloc, "Type") }};
|
||||||
|
const params = [_]ast.Param{
|
||||||
|
.{ .name = "T", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeKeyword(alloc, "Type") },
|
||||||
|
.{ .name = "a", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeKeyword(alloc, "T") },
|
||||||
|
.{ .name = "b", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeKeyword(alloc, "T") },
|
||||||
|
};
|
||||||
|
const body = alloc.create(Node) catch unreachable;
|
||||||
|
body.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = &.{} } } };
|
||||||
|
const fd = ast.FnDecl{ .name = "pair", .params = ¶ms, .return_type = typeKeyword(alloc, "T"), .body = body, .type_params = &tps };
|
||||||
|
|
||||||
|
// Explicit type arg in position 0 binds `T`; the inferred return follows it.
|
||||||
|
const mkCall = struct {
|
||||||
|
fn f(a: std.mem.Allocator, type_name: []const u8) ast.Call {
|
||||||
|
const targ = typeKeyword(a, type_name);
|
||||||
|
const x = a.create(Node) catch unreachable;
|
||||||
|
x.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .int_literal = .{ .value = 1 } } };
|
||||||
|
const y = a.create(Node) catch unreachable;
|
||||||
|
y.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .int_literal = .{ .value = 2 } } };
|
||||||
|
const callee = a.create(Node) catch unreachable;
|
||||||
|
callee.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = "pair" } } };
|
||||||
|
const args = a.alloc(*Node, 3) catch unreachable;
|
||||||
|
args[0] = targ;
|
||||||
|
args[1] = x;
|
||||||
|
args[2] = y;
|
||||||
|
return .{ .callee = callee, .args = args };
|
||||||
|
}
|
||||||
|
}.f;
|
||||||
|
|
||||||
|
const c_s64 = mkCall(alloc, "s64");
|
||||||
|
try std.testing.expectEqual(TypeId.s64, l.inferGenericReturnType(&fd, &c_s64));
|
||||||
|
const c_f64 = mkCall(alloc, "f64");
|
||||||
|
try std.testing.expectEqual(TypeId.f64, l.inferGenericReturnType(&fd, &c_f64));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user