diff --git a/src/check/test/cross_module_mono_test.zig b/src/check/test/cross_module_mono_test.zig index c4ee0255c01..b3800aeccf3 100644 --- a/src/check/test/cross_module_mono_test.zig +++ b/src/check/test/cross_module_mono_test.zig @@ -562,6 +562,82 @@ test "cross-module mono: static dispatch with chained method calls" { try testing.expect(main_ident != null); } +test "cross-module mono: recursive nominal type with self-referencing children" { + // Module A defines a recursive nominal type where children reference + // the type itself (Elem contains List(Elem)). This pattern is the key + // scenario where `remapMonotypeBetweenModules` in MIR Lower.zig does + // meaningful work: the TypeId for List(Elem) may contain a `.rec` + // placeholder indirection internally, which must be resolved when used + // from a different module for TypeId comparisons to succeed. + const source_a = + \\Elem := [Div(List(Elem)), Text(Str)].{ + \\ div : List(Elem) -> Elem + \\ div = |children| Div(children) + \\ + \\ text : Str -> Elem + \\ text = |content| Text(content) + \\} + ; + var env_a = try MonoTestEnv.init("Elem", source_a); + defer env_a.deinit(); + + // Module B imports Elem and uses both constructors + const source_b = + \\import Elem + \\ + \\main : Elem + \\main = Elem.div([Elem.text("hello")]) + ; + var env_b = try MonoTestEnv.initWithImport("B", source_b, "Elem", &env_a); + defer env_b.deinit(); + + // Type-check should succeed — the recursive nominal type is usable cross-module + const main_ident = env_b.module_env.common.findIdent("main"); + try testing.expect(main_ident != null); +} + +test "cross-module mono: recursive nominal through 3-module chain" { + // Recursive nominal type used transitively: A defines the type, + // B wraps it, C uses B's wrapper. This exercises cross-module + // TypeId canonicalization across multiple module boundaries. + const source_a = + \\Tree := [Leaf(U64), Branch(List(Tree))].{ + \\ leaf : U64 -> Tree + \\ leaf = |n| Leaf(n) + \\ + \\ branch : List(Tree) -> Tree + \\ branch = |children| Branch(children) + \\} + ; + var env_a = try MonoTestEnv.init("Tree", source_a); + defer env_a.deinit(); + + const source_b = + \\import Tree + \\ + \\make_pair : U64, U64 -> Tree + \\make_pair = |a, b| Tree.branch([Tree.leaf(a), Tree.leaf(b)]) + ; + var env_b = try MonoTestEnv.initWithImport("B", source_b, "Tree", &env_a); + defer env_b.deinit(); + + const source_c = + \\import B + \\import Tree + \\ + \\main : Tree + \\main = B.make_pair(1, 2) + ; + var env_c = try MonoTestEnv.initWithImports("C", source_c, &.{ + .{ .name = "B", .env = &env_b }, + .{ .name = "Tree", .env = &env_a }, + }); + defer env_c.deinit(); + + const main_ident = env_c.module_env.common.findIdent("main"); + try testing.expect(main_ident != null); +} + test "type checker catches polymorphic recursion (infinite type)" { // This test verifies that polymorphic recursion (f = |x| f([x])) is caught // during type checking as a circular/infinite type. diff --git a/src/cli/main.zig b/src/cli/main.zig index ca9417712d2..3dd82f40049 100644 --- a/src/cli/main.zig +++ b/src/cli/main.zig @@ -4349,12 +4349,12 @@ fn rocBuildNative(ctx: *CliContext, args: cli_args.BuildArgs) !void { const arg_vars = platform_types.sliceVars(func.args); if (arg_vars.len == 0) { const func_mono_idx = mir_store.typeOf(mir_expr_id); - const func_mono = mir_store.monotype_store.getMonotype(func_mono_idx); - if (func_mono == .func) { + const resolved_func = mir_store.monotype_store.resolve(func_mono_idx); + if (resolved_func.kind == .func) { mir_expr_id = try mir_store.addExpr(ctx.gpa, .{ .call = .{ .func = mir_expr_id, .args = MIR.ExprSpan.empty(), - } }, func_mono.func.ret, base.Region.zero()); + } }, mir_store.monotype_store.funcRet(resolved_func), base.Region.zero()); } } } diff --git a/src/eval/dev_evaluator.zig b/src/eval/dev_evaluator.zig index 379eff3116f..1e295798869 100644 --- a/src/eval/dev_evaluator.zig +++ b/src/eval/dev_evaluator.zig @@ -893,12 +893,12 @@ pub const DevEvaluator = struct { // as calls, not as first-class function values. if (arg_layouts.len == 0) { const func_mono_idx = mir_store.typeOf(mir_expr_id); - const func_mono = mir_store.monotype_store.getMonotype(func_mono_idx); - if (func_mono == .func) { + const resolved_func = mir_store.monotype_store.resolve(func_mono_idx); + if (resolved_func.kind == .func) { mir_expr_id = mir_store.addExpr(self.allocator, .{ .call = .{ .func = mir_expr_id, .args = MIR.ExprSpan.empty(), - } }, func_mono.func.ret, base.Region.zero()) catch return error.OutOfMemory; + } }, mir_store.monotype_store.funcRet(resolved_func), base.Region.zero()) catch return error.OutOfMemory; } } diff --git a/src/layout/mir_monotype_resolver.zig b/src/layout/mir_monotype_resolver.zig index 6ed4e111086..27264f0e717 100644 --- a/src/layout/mir_monotype_resolver.zig +++ b/src/layout/mir_monotype_resolver.zig @@ -53,7 +53,7 @@ pub const Resolver = struct { overrides: ?*const std.AutoHashMap(u32, layout.Idx), ) Allocator.Error!layout.Idx { std.debug.assert(!mono_idx.isNone()); - const key = @intFromEnum(mono_idx); + const key: u32 = @bitCast(mono_idx); if (overrides) |override_map| { if (override_map.get(key)) |layout_idx| return layout_idx; @@ -99,7 +99,7 @@ pub const Resolver = struct { graph: *LayoutGraph, refs_by_mono: *std.AutoHashMap(u32, GraphRef), ) Allocator.Error!GraphRef { - const mono_key = @intFromEnum(mono_idx); + const mono_key: u32 = @bitCast(mono_idx); if (overrides) |override_map| { if (override_map.get(mono_key)) |layout_idx| { return .{ .canonical = layout_idx }; @@ -107,63 +107,63 @@ pub const Resolver = struct { } if (refs_by_mono.get(mono_key)) |cached| return cached; - const mono = self.monotype_store.getMonotype(mono_idx); - const resolved_ref: GraphRef = switch (mono) { - .recursive_placeholder => unreachable, - .unit => GraphRef{ .canonical = .zst }, - .prim => |p| GraphRef{ .canonical = switch (p) { - .str => layout.Idx.str, - .u8 => layout.Idx.u8, - .i8 => layout.Idx.i8, - .u16 => layout.Idx.u16, - .i16 => layout.Idx.i16, - .u32 => layout.Idx.u32, - .i32 => layout.Idx.i32, - .u64 => layout.Idx.u64, - .i64 => layout.Idx.i64, - .u128 => layout.Idx.u128, - .i128 => layout.Idx.i128, - .f32 => layout.Idx.f32, - .f64 => layout.Idx.f64, - .dec => layout.Idx.dec, - } }, + const resolved_id = self.monotype_store.resolve(mono_idx); + const resolved_ref: GraphRef = switch (resolved_id.kind) { + .builtin => blk: { + if (resolved_id.builtinPrim()) |p| { + break :blk GraphRef{ .canonical = switch (p) { + .str => layout.Idx.str, + .u8 => layout.Idx.u8, + .i8 => layout.Idx.i8, + .u16 => layout.Idx.u16, + .i16 => layout.Idx.i16, + .u32 => layout.Idx.u32, + .i32 => layout.Idx.i32, + .u64 => layout.Idx.u64, + .i64 => layout.Idx.i64, + .u128 => layout.Idx.u128, + .i128 => layout.Idx.i128, + .f32 => layout.Idx.f32, + .f64 => layout.Idx.f64, + .dec => layout.Idx.dec, + } }; + } + // unit + break :blk GraphRef{ .canonical = .zst }; + }, .func => blk: { const empty_captures = try self.layout_store.getEmptyRecordLayout(); break :blk GraphRef{ .canonical = try self.layout_store.insertLayout(layout.Layout.closure(empty_captures)) }; }, - .box => |b| blk: { + .box => blk: { const node_id = try graph.reserveNode(self.allocator); const local_ref = GraphRef{ .local = node_id }; try refs_by_mono.put(mono_key, local_ref); - const child_ref = try self.buildRefForMonotype(b.inner, overrides, graph, refs_by_mono); + const child_ref = try self.buildRefForMonotype(self.monotype_store.boxInner(resolved_id), overrides, graph, refs_by_mono); graph.setNode(node_id, .{ .box = child_ref }); break :blk local_ref; }, - .list => |l| blk: { + .list => blk: { const node_id = try graph.reserveNode(self.allocator); const local_ref = GraphRef{ .local = node_id }; try refs_by_mono.put(mono_key, local_ref); - const child_ref = try self.buildRefForMonotype(l.elem, overrides, graph, refs_by_mono); + const child_ref = try self.buildRefForMonotype(self.monotype_store.listElem(resolved_id), overrides, graph, refs_by_mono); graph.setNode(node_id, .{ .list = child_ref }); break :blk local_ref; }, - .record => |r| try self.buildStructFromFields(self.monotype_store.getFields(r.fields), overrides, graph, refs_by_mono), - .tuple => |t| try self.buildStructFromElems(self.monotype_store.getIdxSpan(t.elems), overrides, graph, refs_by_mono), - .tag_union => |tu| try self.buildTagUnionRef(self.monotype_store.getTags(tu.tags), overrides, graph, refs_by_mono), + .record => try self.buildStructFromFields(self.monotype_store.recordFields(resolved_id), overrides, graph, refs_by_mono), + .tuple => try self.buildStructFromElems(self.monotype_store.tupleElems(resolved_id), overrides, graph, refs_by_mono), + .tag_union => try self.buildTagUnionRef(self.monotype_store.tagUnionTags(resolved_id), overrides, graph, refs_by_mono), + .rec => unreachable, // already resolved above }; - if (findEquivalentMonotypeRef(self.monotype_store, mono_idx, refs_by_mono, mono_key)) |equivalent| { - try refs_by_mono.put(mono_key, equivalent); - return equivalent; - } - try refs_by_mono.put(mono_key, resolved_ref); return resolved_ref; } fn buildStructFromElems( self: *Resolver, - elems: []const Monotype.Idx, + elems: []const Monotype.TypeId, overrides: ?*const std.AutoHashMap(u32, layout.Idx), graph: *LayoutGraph, refs_by_mono: *std.AutoHashMap(u32, GraphRef), @@ -184,7 +184,7 @@ pub const Resolver = struct { fn buildStructFromFields( self: *Resolver, - fields_slice: []const Monotype.Field, + fields_slice: []const Monotype.FieldKey, overrides: ?*const std.AutoHashMap(u32, layout.Idx), graph: *LayoutGraph, refs_by_mono: *std.AutoHashMap(u32, GraphRef), @@ -197,7 +197,7 @@ pub const Resolver = struct { for (fields_slice, 0..) |field, i| { fields.appendAssumeCapacity(.{ .index = @intCast(i), - .child = try self.buildRefForMonotype(field.type_idx, overrides, graph, refs_by_mono), + .child = try self.buildRefForMonotype(field.ty, overrides, graph, refs_by_mono), }); } return self.buildStructNode(fields.items, graph); @@ -218,7 +218,7 @@ pub const Resolver = struct { fn buildTagUnionRef( self: *Resolver, - tags: []const Monotype.Tag, + tags: []const Monotype.TagKey, overrides: ?*const std.AutoHashMap(u32, layout.Idx), graph: *LayoutGraph, refs_by_mono: *std.AutoHashMap(u32, GraphRef), @@ -230,7 +230,7 @@ pub const Resolver = struct { try variants.ensureTotalCapacity(self.allocator, tags.len); for (tags) |tag| { variants.appendAssumeCapacity(try self.buildPayloadRef( - self.monotype_store.getIdxSpan(tag.payloads), + self.monotype_store.getIdxListItems(tag.payloads), overrides, graph, refs_by_mono, @@ -245,7 +245,7 @@ pub const Resolver = struct { fn buildPayloadRef( self: *Resolver, - payloads: []const Monotype.Idx, + payloads: []const Monotype.TypeId, overrides: ?*const std.AutoHashMap(u32, layout.Idx), graph: *LayoutGraph, refs_by_mono: *std.AutoHashMap(u32, GraphRef), @@ -255,26 +255,6 @@ pub const Resolver = struct { } }; -fn findEquivalentMonotypeRef( - monotype_store: *const Monotype.Store, - mono_idx: Monotype.Idx, - refs_by_mono: *const std.AutoHashMap(u32, GraphRef), - mono_key: u32, -) ?GraphRef { - const mono = monotype_store.getMonotype(mono_idx); - - var iter = refs_by_mono.iterator(); - while (iter.next()) |entry| { - if (entry.key_ptr.* == mono_key) continue; - const other_idx: Monotype.Idx = @enumFromInt(entry.key_ptr.*); - if (std.meta.eql(monotype_store.getMonotype(other_idx), mono)) { - return entry.value_ptr.*; - } - } - - return null; -} - fn findEquivalentRootNode( allocator: Allocator, graph: *const LayoutGraph, diff --git a/src/layout/store_test.zig b/src/layout/store_test.zig index 5508e60b4c9..1094640ea01 100644 --- a/src/layout/store_test.zig +++ b/src/layout/store_test.zig @@ -91,7 +91,7 @@ fn expectTypeAndMonotypeResolversAgree( const type_layout_idx = try type_layout_resolver.resolve(0, type_var, <.type_scope, null); var mono_store = try mir.Monotype.Store.init(allocator); - defer mono_store.deinit(allocator); + defer mono_store.deinit(); var scratches = try mir.Monotype.Store.Scratches.init(allocator); defer scratches.deinit(); @@ -99,8 +99,6 @@ fn expectTypeAndMonotypeResolversAgree( var specializations = std.AutoHashMap(types.Var, mir.Monotype.Idx).init(allocator); defer specializations.deinit(); - var nominal_cycle_breakers = std.AutoHashMap(types.Var, mir.Monotype.Idx).init(allocator); - defer nominal_cycle_breakers.deinit(); const mono_idx = try mono_store.fromTypeVar( allocator, @@ -108,7 +106,6 @@ fn expectTypeAndMonotypeResolversAgree( type_var, lt.module_env.idents, &specializations, - &nominal_cycle_breakers, &scratches, ); diff --git a/src/lir/MirToLir.zig b/src/lir/MirToLir.zig index cdb9667e1e7..02e2058c8af 100644 --- a/src/lir/MirToLir.zig +++ b/src/lir/MirToLir.zig @@ -294,35 +294,28 @@ fn registerSpecializedMonotypeLayout( ) Allocator.Error!void { if (mono_idx.isNone()) return; - const monotype = self.mir_store.monotype_store.getMonotype(mono_idx); + const resolved = self.mir_store.monotype_store.resolve(mono_idx); // .func monotypes always resolve to their canonical closure layout. // They must not be overridden with a data layout from a containing // composite type, because lowerLambda/lowerHosted rely on // layoutFromMonotype returning a callable (closure) layout for the // lambda's fn_layout field. - if (monotype == .func) return; + if (resolved.kind == .func) return; - const mono_key = @intFromEnum(mono_idx); + const mono_key = @as(u32, @bitCast(mono_idx)); try self.saveMonotypeOverrideIfNeeded(saved, mono_key); - if (monotype == .func) { - // Function monotypes keep their callable layout. Specialization may refine the - // runtime value layout of a function value (e.g. empty captures -> zst), but that - // must not poison later `layoutFromMonotype(.func)` queries used for call/lambda nodes. - return; - } - if (self.specialized_monotype_layouts.get(mono_key)) |existing| { if (existing == layout_idx) return; if (builtin.mode == .Debug) { const existing_layout = self.layout_store.getLayout(existing); const new_layout = self.layout_store.getLayout(layout_idx); std.debug.panic( - "MirToLir specialized monotype layout mismatch: mono_idx={d} mono={any} existing={d}/{s} new={d}/{s}", + "MirToLir specialized monotype layout mismatch: mono_idx={d} kind={s} existing={d}/{s} new={d}/{s}", .{ mono_key, - monotype, + @tagName(resolved.kind), @intFromEnum(existing), @tagName(existing_layout.tag), @intFromEnum(layout_idx), @@ -339,30 +332,34 @@ fn registerSpecializedMonotypeLayout( self.specialized_closure_value_layout_cache.clearRetainingCapacity(); const layout_val = self.layout_store.getLayout(layout_idx); - switch (monotype) { - .recursive_placeholder, .unit, .prim => {}, + switch (resolved.kind) { + .rec => unreachable, // already resolved + .builtin => {}, .func => {}, - .box => |b| { + .box => { if (layout_val.tag == .box) { - try self.registerSpecializedMonotypeLayout(b.inner, layout_val.data.box, saved); + try self.registerSpecializedMonotypeLayout(self.mir_store.monotype_store.boxInner(resolved), layout_val.data.box, saved); } }, - .list => |l| switch (layout_val.tag) { - .list => try self.registerSpecializedMonotypeLayout(l.elem, layout_val.data.list, saved), - .list_of_zst => try self.registerSpecializedMonotypeLayout( - l.elem, - try self.zeroSizedSpecializationLayoutFromMonotype(l.elem), - saved, - ), + .list => switch (layout_val.tag) { + .list => try self.registerSpecializedMonotypeLayout(self.mir_store.monotype_store.listElem(resolved), layout_val.data.list, saved), + .list_of_zst => { + const elem = self.mir_store.monotype_store.listElem(resolved); + try self.registerSpecializedMonotypeLayout( + elem, + try self.zeroSizedSpecializationLayoutFromMonotype(elem), + saved, + ); + }, else => {}, }, - .tuple => |t| { - const elems = self.mir_store.monotype_store.getIdxSpan(t.elems); + .tuple => { + const elems = self.mir_store.monotype_store.tupleElems(resolved); if (elems.len == 0) return; if (builtin.mode == .Debug and layout_val.tag != .struct_) { std.debug.panic( - "MirToLir invariant violated: non-empty tuple monotype must specialize to struct_ layout, got mono_idx={d} mono={any} layout_idx={d} tag={s}", - .{ mono_key, monotype, @intFromEnum(layout_idx), @tagName(layout_val.tag) }, + "MirToLir invariant violated: non-empty tuple monotype must specialize to struct_ layout, got mono_idx={d} kind={s} layout_idx={d} tag={s}", + .{ mono_key, @tagName(resolved.kind), @intFromEnum(layout_idx), @tagName(layout_val.tag) }, ); } if (layout_val.tag != .struct_) return; @@ -378,13 +375,13 @@ fn registerSpecializedMonotypeLayout( } } }, - .record => |r| { - const fields = self.mir_store.monotype_store.getFields(r.fields); + .record => { + const fields = self.mir_store.monotype_store.recordFields(resolved); if (fields.len == 0) return; if (builtin.mode == .Debug and layout_val.tag != .struct_) { std.debug.panic( - "MirToLir invariant violated: non-empty record monotype must specialize to struct_ layout, got mono_idx={d} mono={any} layout_idx={d} tag={s}", - .{ mono_key, monotype, @intFromEnum(layout_idx), @tagName(layout_val.tag) }, + "MirToLir invariant violated: non-empty record monotype must specialize to struct_ layout, got mono_idx={d} kind={s} layout_idx={d} tag={s}", + .{ mono_key, @tagName(resolved.kind), @intFromEnum(layout_idx), @tagName(layout_val.tag) }, ); } if (layout_val.tag != .struct_) return; @@ -395,18 +392,18 @@ fn registerSpecializedMonotypeLayout( for (0..layout_fields.len) |li| { const layout_field = layout_fields.get(li); if (layout_field.index != semantic_index) continue; - try self.registerSpecializedMonotypeLayout(field.type_idx, layout_field.layout, saved); + try self.registerSpecializedMonotypeLayout(field.ty, layout_field.layout, saved); break; } } }, - .tag_union => |tu| { - const tags = self.mir_store.monotype_store.getTags(tu.tags); + .tag_union => { + const tags = self.mir_store.monotype_store.tagUnionTags(resolved); if (tags.len == 0) return; if (builtin.mode == .Debug and layout_val.tag != .tag_union) { std.debug.panic( - "MirToLir invariant violated: non-empty tag union monotype must specialize to tag_union layout, got mono_idx={d} mono={any} layout_idx={d} tag={s}", - .{ mono_key, monotype, @intFromEnum(layout_idx), @tagName(layout_val.tag) }, + "MirToLir invariant violated: non-empty tag union monotype must specialize to tag_union layout, got mono_idx={d} kind={s} layout_idx={d} tag={s}", + .{ mono_key, @tagName(resolved.kind), @intFromEnum(layout_idx), @tagName(layout_val.tag) }, ); } if (layout_val.tag != .tag_union) return; @@ -416,7 +413,7 @@ fn registerSpecializedMonotypeLayout( for (tags, 0..) |tag, i| { if (i >= union_layouts.len) break; const payload_layout_idx = union_layouts.get(i).payload_layout; - const payloads = self.mir_store.monotype_store.getIdxSpan(tag.payloads); + const payloads = self.mir_store.monotype_store.getIdxListItems(tag.payloads); if (payloads.len == 0) continue; const payload_layout_val = self.layout_store.getLayout(payload_layout_idx); if (payload_layout_val.tag != .struct_) continue; @@ -572,15 +569,14 @@ fn runtimeListLayoutFromExprs( } fn tagPayloadMonotypes(self: *Self, union_mono_idx: Monotype.Idx, tag_name: Ident.Idx) []const Monotype.Idx { - const union_mono = self.mir_store.monotype_store.getMonotype(union_mono_idx); - const tags = switch (union_mono) { - .tag_union => |tu| self.mir_store.monotype_store.getTags(tu.tags), - else => unreachable, - }; + const resolved = self.mir_store.monotype_store.resolve(union_mono_idx); + std.debug.assert(resolved.kind == .tag_union); + const tags = self.mir_store.monotype_store.tagUnionTags(resolved); for (tags) |tag| { - if (self.identsTextEqual(tag.name, tag_name)) { - return self.mir_store.monotype_store.getIdxSpan(tag.payloads); + const mono_tag_text = self.mir_store.monotype_store.getNameText(tag.name); + if (self.identMatchesText(tag_name, mono_tag_text)) { + return self.mir_store.monotype_store.getIdxListItems(tag.payloads); } } @@ -616,14 +612,13 @@ fn runtimeTagLayoutFromExpr( tag_data: anytype, union_mono_idx: Monotype.Idx, ) Allocator.Error!layout.Idx { - if (self.specialized_monotype_layouts.get(@intFromEnum(union_mono_idx))) |layout_idx| { + if (self.specialized_monotype_layouts.get(@as(u32, @bitCast(union_mono_idx)))) |layout_idx| { return layout_idx; } - const tags = switch (self.mir_store.monotype_store.getMonotype(union_mono_idx)) { - .tag_union => |tu| self.mir_store.monotype_store.getTags(tu.tags), - else => unreachable, - }; + const resolved = self.mir_store.monotype_store.resolve(union_mono_idx); + std.debug.assert(resolved.kind == .tag_union); + const tags = self.mir_store.monotype_store.tagUnionTags(resolved); const mir_args = self.tagPayloadExprs(union_mono_idx, tag_data.name, tag_data.args); if (tags.len == 0) return .zst; @@ -634,7 +629,8 @@ fn runtimeTagLayoutFromExpr( var found_active = false; for (tags) |tag| { - if (self.identsTextEqual(tag.name, tag_data.name)) { + const mono_tag_text = self.mir_store.monotype_store.getNameText(tag.name); + if (self.identMatchesText(tag_data.name, mono_tag_text)) { found_active = true; if (mir_args.len == 0) { try self.scratch_layout_idxs.append(self.allocator, zst_idx); @@ -644,7 +640,7 @@ fn runtimeTagLayoutFromExpr( continue; } - const payloads = self.mir_store.monotype_store.getIdxSpan(tag.payloads); + const payloads = self.mir_store.monotype_store.getIdxListItems(tag.payloads); if (payloads.len == 0) { try self.scratch_layout_idxs.append(self.allocator, zst_idx); } else { @@ -655,7 +651,7 @@ fn runtimeTagLayoutFromExpr( if (builtin.mode == .Debug and !found_active) { std.debug.panic( "MirToLir invariant violated: active tag ident idx {d} missing from tag union mono_idx={d}", - .{ tag_data.name.idx, @intFromEnum(union_mono_idx) }, + .{ tag_data.name.idx, @as(u32, @bitCast(union_mono_idx)) }, ); } @@ -685,8 +681,8 @@ fn runtimeLayoutFromPattern(self: *Self, mir_pat_id: MIR.PatternId) Allocator.Er return switch (pat) { .bind => |sym| blk: { - const mono = self.mir_store.monotype_store.getMonotype(mono_idx); - if (mono == .func) { + const resolved = self.mir_store.monotype_store.resolve(mono_idx); + if (resolved.kind == .func) { if (self.lambda_set_store.getSymbolLambdaSet(sym)) |ls_idx| { break :blk try self.closureValueLayoutFromLambdaSet(ls_idx); } @@ -702,7 +698,7 @@ fn runtimeLayoutFromPattern(self: *Self, mir_pat_id: MIR.PatternId) Allocator.Er .runtime_error, => self.layoutFromMonotype(mono_idx), .as_pattern => |ap| self.runtimeLayoutFromPattern(ap.pattern), - .struct_destructure => |sd| switch (self.mir_store.monotype_store.getMonotype(mono_idx)) { + .struct_destructure => |sd| switch (self.mir_store.monotype_store.resolve(mono_idx).kind) { .record => self.runtimeRecordLayoutFromPattern(mono_idx, self.mir_store.getPatternSpan(sd.fields)), .tuple => self.runtimeTupleLayoutFromPatternSpan(self.mir_store.getPatternSpan(sd.fields)), else => self.layoutFromMonotype(mono_idx), @@ -732,13 +728,10 @@ fn runtimeRecordLayoutFromPattern( mono_idx: Monotype.Idx, mir_patterns: []const MIR.PatternId, ) Allocator.Error!layout.Idx { - const mono = self.mir_store.monotype_store.getMonotype(mono_idx); - const record = switch (mono) { - .record => |r| r, - else => return self.layoutFromMonotype(mono_idx), - }; + const resolved = self.mir_store.monotype_store.resolve(mono_idx); + if (resolved.kind != .record) return self.layoutFromMonotype(mono_idx); - const all_fields = self.mir_store.monotype_store.getFields(record.fields); + const all_fields = self.mir_store.monotype_store.recordFields(resolved); if (all_fields.len == 0) return try self.layout_store.getEmptyRecordLayout(); const save_layouts = self.scratch_layouts.items.len; @@ -747,7 +740,7 @@ fn runtimeRecordLayoutFromPattern( const field_layout_idx = if (field_idx < mir_patterns.len) try self.runtimeLayoutFromPattern(mir_patterns[field_idx]) else - try self.layoutFromMonotype(field.type_idx); + try self.layoutFromMonotype(field.ty); try self.scratch_layouts.append(self.allocator, self.layout_store.getLayout(field_layout_idx)); } @@ -953,8 +946,8 @@ fn runtimeLayoutForStructField(self: *Self, expr_id: MIR.ExprId, field_idx: u32) .lookup => |symbol| blk: { if (self.symbol_layouts.get(symbol.raw())) |struct_layout| { const struct_layout_val = self.layout_store.getLayout(struct_layout); - const struct_mono = self.mir_store.monotype_store.getMonotype(self.mir_store.typeOf(expr_id)); - switch (struct_mono) { + const struct_resolved = self.mir_store.monotype_store.resolve(self.mir_store.typeOf(expr_id)); + switch (struct_resolved.kind) { .record, .tuple => {}, else => {}, } @@ -977,9 +970,9 @@ fn runtimeLayoutForStructField(self: *Self, expr_id: MIR.ExprId, field_idx: u32) /// captures payloads (or a tag union over payloads), not by callable code pointers. fn runtimeValueLayoutFromMirExpr(self: *Self, mir_expr_id: MIR.ExprId) Allocator.Error!layout.Idx { const mono_idx = self.mir_store.typeOf(mir_expr_id); - const mono = self.mir_store.monotype_store.getMonotype(mono_idx); + const resolved = self.mir_store.monotype_store.resolve(mono_idx); const expr = self.mir_store.getExpr(mir_expr_id); - if (mono == .func) { + if (resolved.kind == .func) { switch (expr) { .block => |block| return self.runtimeLayoutForBlockFinal(block), .lookup => |sym| { @@ -1047,12 +1040,12 @@ fn runtimeValueLayoutFromMirExpr(self: *Self, mir_expr_id: MIR.ExprId) Allocator return self.layoutFromMonotype(mono_idx); } return self.runtimeListLayoutFromExprs( - mono.list.elem, + self.mir_store.monotype_store.listElem(resolved), self.mir_store.getExprSpan(list_data.elems), ); }, .tag => |tag_data| { - if (mono == .tag_union) { + if (resolved.kind == .tag_union) { if (!(try self.monotypeContainsFunctionValue(mono_idx))) { return self.layoutFromMonotype(mono_idx); } @@ -1062,7 +1055,7 @@ fn runtimeValueLayoutFromMirExpr(self: *Self, mir_expr_id: MIR.ExprId) Allocator }, .run_low_level => |ll| { if (ll.op == .list_get_unsafe) { - if (self.specialized_monotype_layouts.get(@intFromEnum(mono_idx))) |layout_idx| { + if (self.specialized_monotype_layouts.get(@as(u32, @bitCast(mono_idx)))) |layout_idx| { return layout_idx; } const args = self.mir_store.getExprSpan(ll.args); @@ -1072,7 +1065,7 @@ fn runtimeValueLayoutFromMirExpr(self: *Self, mir_expr_id: MIR.ExprId) Allocator return self.runtimeListElemLayoutFromMirExpr(args[0]); } }, - .struct_ => |struct_| switch (mono) { + .struct_ => |struct_| switch (resolved.kind) { .tuple => { if (!(try self.monotypeContainsFunctionValue(mono_idx))) { return self.layoutFromMonotype(mono_idx); @@ -1087,6 +1080,7 @@ fn runtimeValueLayoutFromMirExpr(self: *Self, mir_expr_id: MIR.ExprId) Allocator }, else => {}, }, + .lookup => |sym| { if (self.symbol_layouts.get(sym.raw())) |layout_idx| { return try self.runtimeLayoutForBindingSymbol(sym, mono_idx, layout_idx); @@ -1372,11 +1366,9 @@ fn runtimeLayoutForLambdaBodyWithParamLayouts( const params = self.mir_store.getPatternSpan(resolved.params); if (params.len != param_layouts.len) return null; const fn_mono_idx = self.mir_store.typeOf(callee_expr_id); - const fn_mono = self.mir_store.monotype_store.getMonotype(fn_mono_idx); - const func_args = switch (fn_mono) { - .func => |f| self.mir_store.monotype_store.getIdxSpan(f.args), - else => return null, - }; + const fn_resolved = self.mir_store.monotype_store.resolve(fn_mono_idx); + if (fn_resolved.kind != .func) return null; + const func_args = self.mir_store.monotype_store.funcArgs(fn_resolved); if (func_args.len != param_layouts.len) return null; var saved_layouts = std.ArrayList(struct { @@ -1558,7 +1550,7 @@ fn runtimeLayoutFromSpecializedDirectCall( fn runtimeListElemLayoutFromMirExpr(self: *Self, list_mir_expr_id: MIR.ExprId) Allocator.Error!layout.Idx { const list_mono_idx = self.mir_store.typeOf(list_mir_expr_id); - const list_mono = self.mir_store.monotype_store.getMonotype(list_mono_idx); + const list_resolved = self.mir_store.monotype_store.resolve(list_mono_idx); switch (self.mir_store.getExpr(list_mir_expr_id)) { .list => |list_data| { @@ -1590,17 +1582,16 @@ fn runtimeListElemLayoutFromMirExpr(self: *Self, list_mir_expr_id: MIR.ExprId) A return switch (list_layout.tag) { .list => list_layout.data.list, - .list_of_zst => switch (list_mono) { - .list => |l| try self.zeroSizedSpecializationLayoutFromMonotype(l.elem), - else => { - if (builtin.mode == .Debug) { - std.debug.panic( - "MirToLir invariant violated: expected list monotype for list_get_unsafe source, got {s}", - .{@tagName(list_mono)}, - ); - } - unreachable; - }, + .list_of_zst => if (list_resolved.kind == .list) + try self.zeroSizedSpecializationLayoutFromMonotype(self.mir_store.monotype_store.listElem(list_resolved)) + else { + if (builtin.mode == .Debug) { + std.debug.panic( + "MirToLir invariant violated: expected list monotype for list_get_unsafe source, got {s}", + .{@tagName(list_resolved.kind)}, + ); + } + unreachable; }, else => { if (builtin.mode == .Debug) { @@ -1734,6 +1725,7 @@ fn identTextIfOwnedBy(env: anytype, ident: Ident.Idx) ?[]const u8 { return text; } +/// Resolve an Ident.Idx to its text string, searching all module envs. fn identMatchesText(self: *const Self, ident: Ident.Idx, expected: []const u8) bool { if (identTextIfOwnedBy(self.layout_store.currentEnv(), ident)) |text| { if (std.mem.eql(u8, text, expected)) return true; @@ -1748,52 +1740,34 @@ fn identMatchesText(self: *const Self, ident: Ident.Idx, expected: []const u8) b return false; } -fn identsTextEqual(self: *const Self, lhs: Ident.Idx, rhs: Ident.Idx) bool { - if (lhs.eql(rhs)) return true; - - if (identTextIfOwnedBy(self.layout_store.currentEnv(), lhs)) |lhs_text| { - if (self.identMatchesText(rhs, lhs_text)) return true; - } - - for (self.layout_store.moduleEnvs()) |env| { - if (identTextIfOwnedBy(env, lhs)) |lhs_text| { - if (self.identMatchesText(rhs, lhs_text)) return true; - } - } - - return false; -} - /// Given a tag name and the monotype of the containing tag union, /// return the discriminant (sorted index of the tag name). fn tagDiscriminant(self: *const Self, tag_name: Ident.Idx, union_mono_idx: Monotype.Idx) u16 { - const monotype = self.mir_store.monotype_store.getMonotype(union_mono_idx); - switch (monotype) { - .tag_union => |tu| { - const tags = self.mir_store.monotype_store.getTags(tu.tags); - - for (tags, 0..) |tag, i| { - if (self.identsTextEqual(tag.name, tag_name)) { - return @intCast(i); - } - } - if (builtin.mode == .Debug) { - std.debug.panic( - "MirToLir invariant violated: tag ident idx {d} not found in tag union mono_idx={d}", - .{ tag_name.idx, @intFromEnum(union_mono_idx) }, - ); - } - unreachable; - }, - .prim, .unit, .record, .tuple, .list, .box, .func, .recursive_placeholder => { - if (builtin.mode == .Debug) { - std.debug.panic( - "tagDiscriminant expected tag_union; got {s} for tag ident idx {d} mono_idx={d}", - .{ @tagName(std.meta.activeTag(monotype)), tag_name.idx, @intFromEnum(union_mono_idx) }, - ); + const resolved = self.mir_store.monotype_store.resolve(union_mono_idx); + if (resolved.kind == .tag_union) { + const tags = self.mir_store.monotype_store.tagUnionTags(resolved); + + for (tags, 0..) |tag, i| { + const mono_tag_text = self.mir_store.monotype_store.getNameText(tag.name); + if (self.identMatchesText(tag_name, mono_tag_text)) { + return @intCast(i); } - unreachable; - }, + } + if (builtin.mode == .Debug) { + std.debug.panic( + "MirToLir invariant violated: tag ident idx {d} not found in tag union mono_idx={d}", + .{ tag_name.idx, @as(u32, @bitCast(union_mono_idx)) }, + ); + } + unreachable; + } else { + if (builtin.mode == .Debug) { + std.debug.panic( + "tagDiscriminant expected tag_union; got {s} for tag ident idx {d} mono_idx={d}", + .{ @tagName(resolved.kind), tag_name.idx, @as(u32, @bitCast(union_mono_idx)) }, + ); + } + unreachable; } } @@ -2158,19 +2132,32 @@ fn lowerExpr(self: *Self, mir_expr_id: MIR.ExprId) Allocator.Error!LirExprId { break :blk self.lir_store.addExpr(.{ .str_literal = lir_str_idx }, region); }, .list => |l| self.lowerList(l, mir_expr_id, region), - .struct_ => |s| switch (self.mir_store.monotype_store.getMonotype(mono_idx)) { - .record => self.lowerRecord(s.fields, mono_idx, mir_expr_id, region), - .tuple => self.lowerTuple(s.fields, mono_idx, mir_expr_id, region), - .unit => self.lowerRecord(s.fields, mono_idx, mir_expr_id, region), - else => { - if (builtin.mode == .Debug) { - std.debug.panic( - "MirToLir invariant violated: MIR struct_ has unexpected monotype {s} at expr {d}", - .{ @tagName(self.mir_store.monotype_store.getMonotype(mono_idx)), @intFromEnum(mir_expr_id) }, - ); - } - unreachable; - }, + .struct_ => |s| blk: { + const struct_resolved = self.mir_store.monotype_store.resolve(mono_idx); + break :blk switch (struct_resolved.kind) { + .record => self.lowerRecord(s.fields, mono_idx, mir_expr_id, region), + .tuple => self.lowerTuple(s.fields, mono_idx, mir_expr_id, region), + .builtin => if (struct_resolved.isUnit()) + self.lowerRecord(s.fields, mono_idx, mir_expr_id, region) + else { + if (builtin.mode == .Debug) { + std.debug.panic( + "MirToLir invariant violated: MIR struct_ has unexpected monotype kind {s} at expr {d}", + .{ @tagName(struct_resolved.kind), @intFromEnum(mir_expr_id) }, + ); + } + unreachable; + }, + else => { + if (builtin.mode == .Debug) { + std.debug.panic( + "MirToLir invariant violated: MIR struct_ has unexpected monotype kind {s} at expr {d}", + .{ @tagName(struct_resolved.kind), @intFromEnum(mir_expr_id) }, + ); + } + unreachable; + }, + }; }, .tag => |t| self.lowerTag(t, mono_idx, mir_expr_id, region), .lookup => |sym| self.lowerLookup(sym, mono_idx, mir_expr_id, region), @@ -2179,7 +2166,7 @@ fn lowerExpr(self: *Self, mir_expr_id: MIR.ExprId) Allocator.Error!LirExprId { .call => |c| self.lowerCall(c, mir_expr_id, region), .block => |b| self.lowerBlock(b, mir_expr_id, region), .borrow_scope => |b| self.lowerBorrowScope(b, mir_expr_id, region), - .struct_access => |sa| switch (self.mir_store.monotype_store.getMonotype(self.mir_store.typeOf(sa.struct_))) { + .struct_access => |sa| switch (self.mir_store.monotype_store.resolve(self.mir_store.typeOf(sa.struct_)).kind) { .record => self.lowerRecordAccess(sa.struct_, sa.field_idx, mir_expr_id, region), .tuple => self.lowerTupleAccess(sa.struct_, sa.field_idx, mir_expr_id, region), else => unreachable, @@ -2187,9 +2174,11 @@ fn lowerExpr(self: *Self, mir_expr_id: MIR.ExprId) Allocator.Error!LirExprId { .str_escape_and_quote => |s| blk: { if (builtin.mode == .Debug) { const arg_mono = self.mir_store.typeOf(s); - const arg_type = self.mir_store.monotype_store.getMonotype(arg_mono); - const ret_type = self.mir_store.monotype_store.getMonotype(mono_idx); - if (!(arg_type == .prim and arg_type.prim == .str and ret_type == .prim and ret_type.prim == .str)) { + const arg_resolved = self.mir_store.monotype_store.resolve(arg_mono); + const ret_resolved = self.mir_store.monotype_store.resolve(mono_idx); + const arg_is_str = if (arg_resolved.builtinPrim()) |p| p == .str else false; + const ret_is_str = if (ret_resolved.builtinPrim()) |p| p == .str else false; + if (!(arg_is_str and ret_is_str)) { std.debug.panic("MIR invariant violated: str_escape_and_quote must be Str -> Str", .{}); } } @@ -2561,11 +2550,12 @@ fn lowerLookup(self: *Self, sym: Symbol, mono_idx: Monotype.Idx, _: MIR.ExprId, } } + const resolved = self.mir_store.monotype_store.resolve(mono_idx); const layout_idx = blk: { if (self.symbol_layouts.get(sym.raw())) |binding_layout| { break :blk try self.runtimeLayoutForBindingSymbol(sym, mono_idx, binding_layout); } - if (self.mir_store.monotype_store.getMonotype(mono_idx) == .func) { + if (resolved.kind == .func) { if (self.lambda_set_store.getSymbolLambdaSet(sym)) |ls_idx| { break :blk try self.closureValueLayoutFromLambdaSet(ls_idx); } @@ -2573,7 +2563,7 @@ fn lowerLookup(self: *Self, sym: Symbol, mono_idx: Monotype.Idx, _: MIR.ExprId, if (self.mir_store.getSymbolDef(sym)) |mir_def_id| { break :blk try self.runtimeValueLayoutFromMirExpr(mir_def_id); } - if (self.mir_store.monotype_store.getMonotype(mono_idx) == .func) { + if (resolved.kind == .func) { break :blk try self.layoutFromMonotype(mono_idx); } break :blk try self.layoutFromMonotype(mono_idx); @@ -2680,11 +2670,9 @@ fn lowerMatch(self: *Self, match_data: anytype, mir_expr_id: MIR.ExprId, region: } fn lowerLambda(self: *Self, lam: anytype, mono_idx: Monotype.Idx, _: MIR.ExprId, region: Region) Allocator.Error!LirExprId { - const monotype = self.mir_store.monotype_store.getMonotype(mono_idx); - const func_args = switch (monotype) { - .func => |f| self.mir_store.monotype_store.getIdxSpan(f.args), - .prim, .unit, .record, .tuple, .tag_union, .list, .box, .recursive_placeholder => unreachable, - }; + const lambda_resolved = self.mir_store.monotype_store.resolve(mono_idx); + std.debug.assert(lambda_resolved.kind == .func); + const func_args = self.mir_store.monotype_store.funcArgs(lambda_resolved); const save_layouts = self.scratch_layout_idxs.items.len; defer self.scratch_layout_idxs.shrinkRetainingCapacity(save_layouts); for (func_args) |arg_mono_idx| { @@ -2710,7 +2698,8 @@ fn lowerLambdaWithParamLayouts( }; const fn_layout = try self.layoutFromMonotype(mono_idx); - const monotype = self.mir_store.monotype_store.getMonotype(mono_idx); + const lwp_resolved = self.mir_store.monotype_store.resolve(mono_idx); + std.debug.assert(lwp_resolved.kind == .func); const mir_params = self.mir_store.getPatternSpan(lam.params); if (builtin.mode == .Debug and mir_params.len != param_layouts.len) { std.debug.panic( @@ -2718,10 +2707,7 @@ fn lowerLambdaWithParamLayouts( .{ mir_params.len, param_layouts.len }, ); } - const func_args = switch (monotype) { - .func => |f| self.mir_store.monotype_store.getIdxSpan(f.args), - .prim, .unit, .record, .tuple, .tag_union, .list, .box, .recursive_placeholder => unreachable, - }; + const func_args = self.mir_store.monotype_store.funcArgs(lwp_resolved); if (builtin.mode == .Debug and func_args.len != param_layouts.len) { std.debug.panic( "MirToLir invariant violated: function monotype arg/layout mismatch ({d} args, {d} layouts)", @@ -2802,13 +2788,10 @@ fn lowerLambdaWithParamLayouts( } } } - const ret_layout = switch (monotype) { - .func => |f| blk: { - const inferred = try self.runtimeValueLayoutFromMirExpr(lam.body); - try self.registerSpecializedMonotypeLayout(f.ret, inferred, &saved_monotype_layouts); - break :blk inferred; - }, - .prim, .unit, .record, .tuple, .tag_union, .list, .box, .recursive_placeholder => unreachable, // Lambda expressions always have .func monotype + const ret_layout = blk: { + const inferred = try self.runtimeValueLayoutFromMirExpr(lam.body); + try self.registerSpecializedMonotypeLayout(self.mir_store.monotype_store.funcRet(lwp_resolved), inferred, &saved_monotype_layouts); + break :blk inferred; }; var lir_body = try self.lowerExpr(lam.body); @@ -2943,24 +2926,24 @@ fn annotationOnlyIntrinsicForFunc( self: *Self, func_mono_idx: Monotype.Idx, ) Allocator.Error!?AnnotationOnlyIntrinsic { - const func_mono = self.mir_store.monotype_store.getMonotype(func_mono_idx); - if (func_mono != .func) return null; + const func_resolved = self.mir_store.monotype_store.resolve(func_mono_idx); + if (func_resolved.kind != .func) return null; - const fn_args = self.mir_store.monotype_store.getIdxSpan(func_mono.func.args); + const fn_args = self.mir_store.monotype_store.funcArgs(func_resolved); if (fn_args.len != 1) return null; const arg_mono = fn_args[0]; - const ret_mono = func_mono.func.ret; + const ret_mono = self.mir_store.monotype_store.funcRet(func_resolved); - const arg_ty = self.mir_store.monotype_store.getMonotype(arg_mono); - const ret_ty = self.mir_store.monotype_store.getMonotype(ret_mono); + const arg_resolved = self.mir_store.monotype_store.resolve(arg_mono); + const ret_resolved = self.mir_store.monotype_store.resolve(ret_mono); - if (ret_ty == .box) { - if (try self.monotypesStructurallyEqual(arg_mono, ret_ty.box.inner)) { + if (ret_resolved.kind == .box) { + if (arg_mono.eql(self.mir_store.monotype_store.boxInner(ret_resolved))) { return .{ .op = .box_box, .result_mono = ret_mono }; } } - if (arg_ty == .box) { - if (try self.monotypesStructurallyEqual(arg_ty.box.inner, ret_mono)) { + if (arg_resolved.kind == .box) { + if (self.mir_store.monotype_store.boxInner(arg_resolved).eql(ret_mono)) { return .{ .op = .box_unbox, .result_mono = ret_mono }; } } @@ -2995,87 +2978,6 @@ fn lowerAnnotationOnlyIntrinsicCall( return try acc.finish(ll_expr, ret_layout, region); } -fn monotypesStructurallyEqual(self: *Self, lhs: Monotype.Idx, rhs: Monotype.Idx) Allocator.Error!bool { - if (lhs == rhs) return true; - var seen = std.AutoHashMap(u64, void).init(self.allocator); - defer seen.deinit(); - return try self.monotypesStructurallyEqualRec(lhs, rhs, &seen); -} - -fn monotypesStructurallyEqualRec( - self: *Self, - lhs: Monotype.Idx, - rhs: Monotype.Idx, - seen: *std.AutoHashMap(u64, void), -) Allocator.Error!bool { - if (lhs == rhs) return true; - - const lhs_u32: u32 = @intFromEnum(lhs); - const rhs_u32: u32 = @intFromEnum(rhs); - const key: u64 = (@as(u64, lhs_u32) << 32) | @as(u64, rhs_u32); - if (seen.contains(key)) return true; - try seen.put(key, {}); - - const lhs_mono = self.mir_store.monotype_store.getMonotype(lhs); - const rhs_mono = self.mir_store.monotype_store.getMonotype(rhs); - if (std.meta.activeTag(lhs_mono) != std.meta.activeTag(rhs_mono)) return false; - - return switch (lhs_mono) { - .recursive_placeholder => unreachable, - .unit => true, - .prim => |p| p == rhs_mono.prim, - .list => |l| try self.monotypesStructurallyEqualRec(l.elem, rhs_mono.list.elem, seen), - .box => |b| try self.monotypesStructurallyEqualRec(b.inner, rhs_mono.box.inner, seen), - .tuple => |t| blk: { - const lhs_elems = self.mir_store.monotype_store.getIdxSpan(t.elems); - const rhs_elems = self.mir_store.monotype_store.getIdxSpan(rhs_mono.tuple.elems); - if (lhs_elems.len != rhs_elems.len) break :blk false; - for (lhs_elems, rhs_elems) |lhs_elem, rhs_elem| { - if (!try self.monotypesStructurallyEqualRec(lhs_elem, rhs_elem, seen)) break :blk false; - } - break :blk true; - }, - .func => |f| blk: { - const rf = rhs_mono.func; - if (f.effectful != rf.effectful) break :blk false; - const lhs_args = self.mir_store.monotype_store.getIdxSpan(f.args); - const rhs_args = self.mir_store.monotype_store.getIdxSpan(rf.args); - if (lhs_args.len != rhs_args.len) break :blk false; - for (lhs_args, rhs_args) |lhs_arg, rhs_arg| { - if (!try self.monotypesStructurallyEqualRec(lhs_arg, rhs_arg, seen)) break :blk false; - } - break :blk try self.monotypesStructurallyEqualRec(f.ret, rf.ret, seen); - }, - .record => |r| blk: { - const lhs_fields = self.mir_store.monotype_store.getFields(r.fields); - const rhs_fields = self.mir_store.monotype_store.getFields(rhs_mono.record.fields); - if (lhs_fields.len != rhs_fields.len) break :blk false; - for (lhs_fields, rhs_fields) |lhs_field, rhs_field| { - if (!lhs_field.name.eql(rhs_field.name)) break :blk false; - if (!try self.monotypesStructurallyEqualRec(lhs_field.type_idx, rhs_field.type_idx, seen)) { - break :blk false; - } - } - break :blk true; - }, - .tag_union => |tu| blk: { - const lhs_tags = self.mir_store.monotype_store.getTags(tu.tags); - const rhs_tags = self.mir_store.monotype_store.getTags(rhs_mono.tag_union.tags); - if (lhs_tags.len != rhs_tags.len) break :blk false; - for (lhs_tags, rhs_tags) |lhs_tag, rhs_tag| { - if (!lhs_tag.name.eql(rhs_tag.name)) break :blk false; - const lhs_payloads = self.mir_store.monotype_store.getIdxSpan(lhs_tag.payloads); - const rhs_payloads = self.mir_store.monotype_store.getIdxSpan(rhs_tag.payloads); - if (lhs_payloads.len != rhs_payloads.len) break :blk false; - for (lhs_payloads, rhs_payloads) |lhs_payload, rhs_payload| { - if (!try self.monotypesStructurallyEqualRec(lhs_payload, rhs_payload, seen)) break :blk false; - } - } - break :blk true; - }, - }; -} - /// Follow lookups/blocks/closure origins to recover the lambda parameter span. fn resolveToLambdaParams(self: *Self, expr_id: MIR.ExprId) ?MIR.PatternSpan { const expr = self.mir_store.getExpr(expr_id); @@ -3120,7 +3022,7 @@ fn adaptFunctionArgToParamLambdaSet( ) Allocator.Error!LirExprId { const param_symbol = self.functionParamSymbol(param_pat_id) orelse return arg_lir_expr; const param_mono = self.mir_store.patternTypeOf(param_pat_id); - if (self.mir_store.monotype_store.getMonotype(param_mono) != .func) return arg_lir_expr; + if (self.mir_store.monotype_store.resolve(param_mono).kind != .func) return arg_lir_expr; const param_ls_idx = self.lambda_set_store.getSymbolLambdaSet(param_symbol) orelse return arg_lir_expr; var param_members = try self.snapshotLambdaSetMembers(param_ls_idx); @@ -3745,12 +3647,12 @@ fn adaptValueLayout( ) Allocator.Error!LirExprId { if (source_layout == target_layout) return value_expr; - const monotype = self.mir_store.monotype_store.getMonotype(mono_idx); - return switch (monotype) { - .record => |record| self.adaptRecordValueLayout(value_expr, record, source_layout, target_layout, region), - .tuple => |tuple| self.adaptTupleValueLayout(value_expr, tuple, source_layout, target_layout, region), + const adapt_resolved = self.mir_store.monotype_store.resolve(mono_idx); + return switch (adapt_resolved.kind) { + .record => self.adaptRecordValueLayout(value_expr, adapt_resolved, source_layout, target_layout, region), + .tuple => self.adaptTupleValueLayout(value_expr, adapt_resolved, source_layout, target_layout, region), .func => self.adaptLayoutByStructure(value_expr, source_layout, target_layout, region), - .prim, .unit, .tag_union, .list, .box, .recursive_placeholder => value_expr, + .builtin, .tag_union, .list, .box, .rec => value_expr, }; } @@ -3938,12 +3840,12 @@ fn adaptConcreteClosureMemberPayload( fn adaptRecordValueLayout( self: *Self, value_expr: LirExprId, - record: anytype, + record_id: Monotype.TypeId, source_layout: layout.Idx, target_layout: layout.Idx, region: Region, ) Allocator.Error!LirExprId { - const fields = self.mir_store.monotype_store.getFields(record.fields); + const fields = self.mir_store.monotype_store.recordFields(record_id); if (fields.len == 0) return value_expr; const source_layout_val = self.layout_store.getLayout(source_layout); const target_layout_val = self.layout_store.getLayout(target_layout); @@ -3975,7 +3877,7 @@ fn adaptRecordValueLayout( } }, region); const adapted_field = try self.adaptValueLayout( field_expr, - fields[semantic_index].type_idx, + fields[semantic_index].ty, source_field.field_layout, target_field.layout, region, @@ -3995,12 +3897,12 @@ fn adaptRecordValueLayout( fn adaptTupleValueLayout( self: *Self, value_expr: LirExprId, - tuple: anytype, + tuple_id: Monotype.TypeId, source_layout: layout.Idx, target_layout: layout.Idx, region: Region, ) Allocator.Error!LirExprId { - const elems = self.mir_store.monotype_store.getIdxSpan(tuple.elems); + const elems = self.mir_store.monotype_store.tupleElems(tuple_id); if (elems.len == 0) return value_expr; const source_layout_val = self.layout_store.getLayout(source_layout); const target_layout_val = self.layout_store.getLayout(target_layout); @@ -4166,16 +4068,11 @@ fn lowerHosted(self: *Self, h: anytype, mono_idx: Monotype.Idx, region: Region) // as a hosted_call expression using h.index (the host function's dispatch // index). The string name is only needed for linking, handled separately. const fn_layout = try self.layoutFromMonotype(mono_idx); - const monotype = self.mir_store.monotype_store.getMonotype(mono_idx); - const ret_layout = switch (monotype) { - .func => |f| try self.layoutFromMonotype(f.ret), - .prim, .unit, .record, .tuple, .tag_union, .list, .box, .recursive_placeholder => unreachable, // Hosted expressions always have .func monotype - }; + const hosted_resolved = self.mir_store.monotype_store.resolve(mono_idx); + std.debug.assert(hosted_resolved.kind == .func); // Hosted expressions always have .func monotype + const ret_layout = try self.layoutFromMonotype(self.mir_store.monotype_store.funcRet(hosted_resolved)); - const func_args = switch (monotype) { - .func => |f| self.mir_store.monotype_store.getIdxSpan(f.args), - .prim, .unit, .record, .tuple, .tag_union, .list, .box, .recursive_placeholder => unreachable, - }; + const func_args = self.mir_store.monotype_store.funcArgs(hosted_resolved); const mir_params = self.mir_store.getPatternSpan(h.params); const save_rest_bindings = self.scratch_deferred_list_rest_bindings.items.len; defer self.scratch_deferred_list_rest_bindings.shrinkRetainingCapacity(save_rest_bindings); @@ -4353,9 +4250,9 @@ fn runtimeLayoutForBindingSymbol( fallback_layout: layout.Idx, ) Allocator.Error!layout.Idx { const existing_layout = self.symbol_layouts.get(sym.raw()); - const mono = self.mir_store.monotype_store.getMonotype(mono_idx); + const binding_resolved = self.mir_store.monotype_store.resolve(mono_idx); - if (mono != .func and !(try self.monotypeContainsFunctionValue(mono_idx))) { + if (binding_resolved.kind != .func and !(try self.monotypeContainsFunctionValue(mono_idx))) { return self.layoutFromMonotype(mono_idx); } @@ -4365,7 +4262,7 @@ fn runtimeLayoutForBindingSymbol( else false; - if (mono == .func and !has_callable_def and existing_layout == null) { + if (binding_resolved.kind == .func and !has_callable_def and existing_layout == null) { const generic_fn_layout = try self.monotype_layout_resolver.resolve(mono_idx, null); if (fallback_layout == generic_fn_layout) { if (self.lambda_set_store.getSymbolLambdaSet(sym)) |ls_idx| { @@ -4388,39 +4285,40 @@ fn monotypeContainsFunctionValueInner( mono_idx: Monotype.Idx, visited: *std.AutoHashMap(u32, void), ) Allocator.Error!bool { - const mono_key = @intFromEnum(mono_idx); + const mono_key = @as(u32, @bitCast(mono_idx)); if (visited.contains(mono_key)) return false; try visited.put(mono_key, {}); - return switch (self.mir_store.monotype_store.getMonotype(mono_idx)) { + const contains_resolved = self.mir_store.monotype_store.resolve(mono_idx); + return switch (contains_resolved.kind) { .func => true, - .record => |record| blk: { - const fields = self.mir_store.monotype_store.getFields(record.fields); + .record => blk: { + const fields = self.mir_store.monotype_store.recordFields(contains_resolved); for (fields) |field| { - if (try self.monotypeContainsFunctionValueInner(field.type_idx, visited)) break :blk true; + if (try self.monotypeContainsFunctionValueInner(field.ty, visited)) break :blk true; } break :blk false; }, - .tuple => |tuple| blk: { - const elems = self.mir_store.monotype_store.getIdxSpan(tuple.elems); + .tuple => blk: { + const elems = self.mir_store.monotype_store.tupleElems(contains_resolved); for (elems) |elem_mono| { if (try self.monotypeContainsFunctionValueInner(elem_mono, visited)) break :blk true; } break :blk false; }, - .tag_union => |tu| blk: { - const tags = self.mir_store.monotype_store.getTags(tu.tags); + .tag_union => blk: { + const tags = self.mir_store.monotype_store.tagUnionTags(contains_resolved); for (tags) |tag| { - const payloads = self.mir_store.monotype_store.getIdxSpan(tag.payloads); + const payloads = self.mir_store.monotype_store.getIdxListItems(tag.payloads); for (payloads) |payload_mono| { if (try self.monotypeContainsFunctionValueInner(payload_mono, visited)) break :blk true; } } break :blk false; }, - .list => |list| self.monotypeContainsFunctionValueInner(list.elem, visited), - .box => |box| self.monotypeContainsFunctionValueInner(box.inner, visited), - .prim, .unit, .recursive_placeholder => false, + .list => self.monotypeContainsFunctionValueInner(self.mir_store.monotype_store.listElem(contains_resolved), visited), + .box => self.monotypeContainsFunctionValueInner(self.mir_store.monotype_store.boxInner(contains_resolved), visited), + .builtin, .rec => false, }; } @@ -4555,9 +4453,10 @@ fn registerBindingPatternSymbols( .struct_destructure => |sd| { const mir_patterns = self.mir_store.getPatternSpan(sd.fields); if (mir_patterns.len == 0) return; - switch (self.mir_store.monotype_store.getMonotype(mono_idx)) { - .record => |record_mono| { - const all_fields = self.mir_store.monotype_store.getFields(record_mono.fields); + const resolved = self.mir_store.monotype_store.resolve(mono_idx); + switch (resolved.kind) { + .record => { + const all_fields = self.mir_store.monotype_store.recordFields(resolved); const record_layout_val = self.layout_store.getLayout(runtime_layout); if (builtin.mode == .Debug and all_fields.len != 0 and record_layout_val.tag != .struct_) { std.debug.panic( @@ -4597,10 +4496,9 @@ fn registerBindingPatternSymbols( }, .list_destructure => |ld| { const list_layout = try self.layoutFromMonotype(mono_idx); - const elem_mono = switch (self.mir_store.monotype_store.getMonotype(mono_idx)) { - .list => |list_mono| list_mono.elem, - else => unreachable, - }; + const ld_resolved = self.mir_store.monotype_store.resolve(mono_idx); + std.debug.assert(ld_resolved.kind == .list); + const elem_mono = self.mir_store.monotype_store.listElem(ld_resolved); const elem_layout = try self.layoutFromMonotype(elem_mono); const all_patterns = self.mir_store.getPatternSpan(ld.patterns); @@ -4740,9 +4638,10 @@ fn lowerPatternInternal( break :blk self.lowerWildcardBindingPattern(struct_layout, ownership_mode, region); } - switch (self.mir_store.monotype_store.getMonotype(mono_idx)) { - .record => |record_mono| { - const all_fields = self.mir_store.monotype_store.getFields(record_mono.fields); + const resolved = self.mir_store.monotype_store.resolve(mono_idx); + switch (resolved.kind) { + .record => { + const all_fields = self.mir_store.monotype_store.recordFields(resolved); if (all_fields.len == 0) { break :blk self.lowerWildcardBindingPattern(struct_layout, ownership_mode, region); @@ -4825,11 +4724,9 @@ fn lowerPatternInternal( }, .list_destructure => |ld| blk: { const list_layout = try self.layoutFromMonotype(mono_idx); - const list_monotype = self.mir_store.monotype_store.getMonotype(mono_idx); - const elem_layout = switch (list_monotype) { - .list => |l| try self.layoutFromMonotype(l.elem), - .prim, .unit, .record, .tuple, .tag_union, .box, .func, .recursive_placeholder => unreachable, - }; + const lp_resolved = self.mir_store.monotype_store.resolve(mono_idx); + std.debug.assert(lp_resolved.kind == .list); + const elem_layout = try self.layoutFromMonotype(self.mir_store.monotype_store.listElem(lp_resolved)); const all_patterns = self.mir_store.getPatternSpan(ld.patterns); const has_rest = !ld.rest_index.isNone(); const needs_owned_rest_discard = @@ -5227,16 +5124,11 @@ test "ANF: list of calls Let-binds each call to a symbol" { // With ANF, each call should be Let-bound to a fresh symbol, // and the list elements should be lookups to those symbols. - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const list_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .list = .{ .elem = i64_mono } }); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const list_mono = try env.mir_store.monotype_store.internList(i64_mono); // func_args_mono: (I64) -> I64 - const func_arg_span = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); - const func_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .func = .{ - .args = func_arg_span, - .ret = i64_mono, - .effectful = false, - } }); + const func_mono = try env.mir_store.monotype_store.internFunc(&.{i64_mono}, i64_mono, false); const ident_f = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; const sym_f = try testMirSymbol(&env.mir_store, allocator, ident_f); @@ -5292,7 +5184,7 @@ test "MIR int literal lowers to LIR i64_literal" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); const int_expr = try env.mir_store.addExpr(allocator, .{ .int = .{ .value = .{ .bytes = @bitCast(@as(i128, 42)), .kind = .i128 }, @@ -5315,11 +5207,11 @@ test "MIR zero-arg tag lowers to LIR zero_arg_tag" { defer testDeinit(&env); // Create a single-tag union monotype: [MyTag] - const tag_name = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const tag_span = try env.mir_store.monotype_store.addTags(allocator, &.{ - .{ .name = tag_name, .payloads = Monotype.Span.empty() }, + const tag_name = try env.module_env.common.idents.insert(allocator, Ident.for_text("MyTag")); + const my_tag_name = try env.mir_store.monotype_store.name_pool.intern("MyTag"); + const union_mono = try env.mir_store.monotype_store.internTagUnion(&.{ + .{ .name = my_tag_name, .payloads = .empty }, }); - const union_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tag_union = .{ .tags = tag_span } }); const tag_expr = try env.mir_store.addExpr(allocator, .{ .tag = .{ .name = tag_name, @@ -5341,8 +5233,8 @@ test "MIR empty list lowers to LIR empty_list" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const list_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .list = .{ .elem = i64_mono } }); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const list_mono = try env.mir_store.monotype_store.internList(i64_mono); const list_expr = try env.mir_store.addExpr(allocator, .{ .list = .{ .elems = MIR.ExprSpan.empty(), @@ -5362,7 +5254,7 @@ test "MIR lookup lowers to LIR lookup" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); const ident_x = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; const sym_x = try testMirSymbol(&env.mir_store, allocator, ident_x); @@ -5384,7 +5276,7 @@ test "MIR block lowers to LIR block" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Create a simple block: { x = 42; x } const ident_x = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; @@ -5425,7 +5317,7 @@ test "MIR match with pattern alternatives lowers to multiple LIR match-branches" try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Condition: integer literal 1 const cond = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -5493,20 +5385,18 @@ test "MIR multi-tag union produces proper tag_union layout" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Create a 2-tag union: [Foo I64, Bar] // Tags are sorted alphabetically: Bar < Foo, so Bar=0, Foo=1 - const tag_bar = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const tag_foo = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 2 }; - - const foo_payloads = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); + const bar_name = try env.mir_store.monotype_store.name_pool.intern("Bar"); + const foo_name = try env.mir_store.monotype_store.name_pool.intern("Foo"); + const foo_payloads = try env.mir_store.monotype_store.internIdxList(&.{i64_mono}); - const tag_span = try env.mir_store.monotype_store.addTags(allocator, &.{ - .{ .name = tag_bar, .payloads = Monotype.Span.empty() }, - .{ .name = tag_foo, .payloads = foo_payloads }, + const union_mono = try env.mir_store.monotype_store.internTagUnion(&.{ + .{ .name = bar_name, .payloads = .empty }, + .{ .name = foo_name, .payloads = foo_payloads }, }); - const union_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tag_union = .{ .tags = tag_span } }); var translator = Self.init(allocator, &env.mir_store, &env.lir_store, &env.layout_store, &env.lambda_set_store, env.module_env.idents.true_tag); defer translator.deinit(); @@ -5537,20 +5427,21 @@ test "MIR multi-tag union tags get correct discriminants" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Create a 2-tag union: [Bar, Foo I64] - // Sorted alphabetically by ident idx: Bar (idx=1) < Foo (idx=2) - const tag_bar = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const tag_foo = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 2 }; + // Sorted alphabetically: Bar < Foo + const tag_bar = try env.module_env.common.idents.insert(allocator, Ident.for_text("Bar")); + const tag_foo = try env.module_env.common.idents.insert(allocator, Ident.for_text("Foo")); - const foo_payloads = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); + const bar_name2 = try env.mir_store.monotype_store.name_pool.intern("Bar"); + const foo_name2 = try env.mir_store.monotype_store.name_pool.intern("Foo"); + const foo_payloads2 = try env.mir_store.monotype_store.internIdxList(&.{i64_mono}); - const tag_span = try env.mir_store.monotype_store.addTags(allocator, &.{ - .{ .name = tag_bar, .payloads = Monotype.Span.empty() }, - .{ .name = tag_foo, .payloads = foo_payloads }, + const union_mono = try env.mir_store.monotype_store.internTagUnion(&.{ + .{ .name = bar_name2, .payloads = .empty }, + .{ .name = foo_name2, .payloads = foo_payloads2 }, }); - const union_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tag_union = .{ .tags = tag_span } }); // Lower a Bar tag (zero-arg, discriminant should be 0) const bar_expr = try env.mir_store.addExpr(allocator, .{ .tag = .{ @@ -5590,13 +5481,8 @@ test "MIR function monotype lowers to closure layout" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const arg_span = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); - const fn_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .func = .{ - .args = arg_span, - .ret = i64_mono, - .effectful = false, - } }); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const fn_mono = try env.mir_store.monotype_store.internFunc(&.{i64_mono}, i64_mono, false); var translator = Self.init(allocator, &env.mir_store, &env.lir_store, &env.layout_store, &env.lambda_set_store, env.module_env.idents.true_tag); defer translator.deinit(); @@ -5611,20 +5497,17 @@ test "MIR record access finds correct field index for non-first field" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - - // Create field name idents: a (idx=1), b (idx=2), c (idx=3) - const field_a = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const field_b = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 2 }; - const field_c = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 3 }; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Create record monotype: { a: I64, b: I64, c: I64 } - const record_fields = try env.mir_store.monotype_store.addFields(allocator, &.{ - .{ .name = field_a, .type_idx = i64_mono }, - .{ .name = field_b, .type_idx = i64_mono }, - .{ .name = field_c, .type_idx = i64_mono }, + const name_a = try env.mir_store.monotype_store.name_pool.intern("a"); + const name_b = try env.mir_store.monotype_store.name_pool.intern("b"); + const name_c = try env.mir_store.monotype_store.name_pool.intern("c"); + const record_mono = try env.mir_store.monotype_store.internRecord(&.{ + .{ .name = name_a, .ty = i64_mono }, + .{ .name = name_b, .ty = i64_mono }, + .{ .name = name_c, .ty = i64_mono }, }); - const record_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .record = .{ .fields = record_fields } }); // Create a record literal: { a: 1, b: 2, c: 3 } const int_1 = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -5667,13 +5550,11 @@ test "MIR single-field record lowers as struct_ and preserves field access" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const field_only = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - - const record_fields = try env.mir_store.monotype_store.addFields(allocator, &.{ - .{ .name = field_only, .type_idx = i64_mono }, + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const name_only = try env.mir_store.monotype_store.name_pool.intern("only"); + const record_mono = try env.mir_store.monotype_store.internRecord(&.{ + .{ .name = name_only, .ty = i64_mono }, }); - const record_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .record = .{ .fields = record_fields } }); const int_1 = try env.mir_store.addExpr(allocator, .{ .int = .{ .value = .{ .bytes = @bitCast(@as(i128, 1)), .kind = .i128 }, @@ -5712,12 +5593,11 @@ test "MIR tuple access preserves element index" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(allocator, env.module_env.idents); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(); // Create tuple monotype: (I64, Bool, I64) - const tuple_elems = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{ i64_mono, bool_mono, i64_mono }); - const tuple_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tuple = .{ .elems = tuple_elems } }); + const tuple_mono = try env.mir_store.monotype_store.internTuple(&.{ i64_mono, bool_mono, i64_mono }); // Create a tuple literal: (1, true, 2) const int_1 = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -5760,9 +5640,8 @@ test "MIR single-element tuple lowers as struct_ and preserves field access" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const tuple_elems = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); - const tuple_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tuple = .{ .elems = tuple_elems } }); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const tuple_mono = try env.mir_store.monotype_store.internTuple(&.{i64_mono}); const int_1 = try env.mir_store.addExpr(allocator, .{ .int = .{ .value = .{ .bytes = @bitCast(@as(i128, 1)), .kind = .i128 }, @@ -5801,7 +5680,7 @@ test "MIR lookup propagates symbol def to LIR store" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Register a symbol def in MIR: x = 42 const ident_x = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; @@ -5844,15 +5723,15 @@ test "MIR single-tag union with one payload emits tag layout" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Create a single-tag union monotype: [Ok I64] - const tag_ok = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const ok_payloads = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); - const tag_span = try env.mir_store.monotype_store.addTags(allocator, &.{ - .{ .name = tag_ok, .payloads = ok_payloads }, + const tag_ok = try env.module_env.common.idents.insert(allocator, Ident.for_text("Ok")); + const ok_name = try env.mir_store.monotype_store.name_pool.intern("Ok"); + const ok_payloads = try env.mir_store.monotype_store.internIdxList(&.{i64_mono}); + const union_mono = try env.mir_store.monotype_store.internTagUnion(&.{ + .{ .name = ok_name, .payloads = ok_payloads }, }); - const union_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tag_union = .{ .tags = tag_span } }); // Create expression: Ok 42 const int_42 = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -5894,12 +5773,11 @@ test "MIR single-tag union with zero args emits zero_arg_tag" { defer testDeinit(&env); // Create a single zero-arg tag union: [Unit] - const tag_unit = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const empty_payloads = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{}); - const tag_span = try env.mir_store.monotype_store.addTags(allocator, &.{ - .{ .name = tag_unit, .payloads = empty_payloads }, + const tag_unit = try env.module_env.common.idents.insert(allocator, Ident.for_text("Unit")); + const unit_tag_name = try env.mir_store.monotype_store.name_pool.intern("Unit"); + const union_mono = try env.mir_store.monotype_store.internTagUnion(&.{ + .{ .name = unit_tag_name, .payloads = .empty }, }); - const union_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tag_union = .{ .tags = tag_span } }); // Create expression: Unit const empty_args = try env.mir_store.addExprSpan(allocator, &.{}); @@ -5930,16 +5808,16 @@ test "MIR single-tag union with multiple payloads emits tag layout" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(allocator, env.module_env.idents); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(); // Create a single-tag union with multiple payloads: [Pair I64 Bool] - const tag_pair = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const pair_payloads = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{ i64_mono, bool_mono }); - const tag_span = try env.mir_store.monotype_store.addTags(allocator, &.{ - .{ .name = tag_pair, .payloads = pair_payloads }, + const tag_pair = try env.module_env.common.idents.insert(allocator, Ident.for_text("Pair")); + const pair_tag_name = try env.mir_store.monotype_store.name_pool.intern("Pair"); + const pair_payloads = try env.mir_store.monotype_store.internIdxList(&.{ i64_mono, bool_mono }); + const union_mono = try env.mir_store.monotype_store.internTagUnion(&.{ + .{ .name = pair_tag_name, .payloads = pair_payloads }, }); - const union_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tag_union = .{ .tags = tag_span } }); // Create expression: Pair 42 true const int_42 = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -5978,18 +5856,18 @@ test "MIR single-tag union pattern with one arg preserves tag pattern" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Create a single-tag union monotype: [Ok I64] - const tag_ok = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const ok_payloads = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); - const tag_span = try env.mir_store.monotype_store.addTags(allocator, &.{ - .{ .name = tag_ok, .payloads = ok_payloads }, + const tag_ok = try env.module_env.common.idents.insert(allocator, Ident.for_text("Ok")); + const ok_name2 = try env.mir_store.monotype_store.name_pool.intern("Ok"); + const ok_payloads2 = try env.mir_store.monotype_store.internIdxList(&.{i64_mono}); + const union_mono = try env.mir_store.monotype_store.internTagUnion(&.{ + .{ .name = ok_name2, .payloads = ok_payloads2 }, }); - const union_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .tag_union = .{ .tags = tag_span } }); // Create pattern: Ok x (bind the payload) - const ident_x = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 2 }; + const ident_x = try env.module_env.common.idents.insert(allocator, Ident.for_text("x")); const sym_x = try testMirSymbol(&env.mir_store, allocator, ident_x); const bind_pat = try env.mir_store.addPattern(allocator, .{ .bind = sym_x }, i64_mono); const pat_args = try env.mir_store.addPatternSpan(allocator, &.{bind_pat}); @@ -6015,15 +5893,10 @@ test "MIR hosted lambda lowers to LIR lambda wrapping hosted_call" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Create function type: (I64, I64) -> I64 - const func_arg_span = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{ i64_mono, i64_mono }); - const func_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .func = .{ - .args = func_arg_span, - .ret = i64_mono, - .effectful = false, - } }); + const func_mono = try env.mir_store.monotype_store.internFunc(&.{ i64_mono, i64_mono }, i64_mono, false); // Create parameter patterns (two binds) const ident_a = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; @@ -6080,14 +5953,9 @@ test "lambdaSetForExpr unwraps dbg_expr wrapper" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); - const func_arg_span = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); - const func_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .func = .{ - .args = func_arg_span, - .ret = i64_mono, - .effectful = false, - } }); + const func_mono = try env.mir_store.monotype_store.internFunc(&.{i64_mono}, i64_mono, false); const ident_arg = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; const sym_arg = try testMirSymbol(&env.mir_store, allocator, ident_arg); @@ -6126,14 +5994,9 @@ test "MIR function lookup uses symbol lambda set before wrapper def layout" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); - const func_arg_span = try env.mir_store.monotype_store.addIdxSpan(allocator, &.{i64_mono}); - const func_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .func = .{ - .args = func_arg_span, - .ret = i64_mono, - .effectful = false, - } }); + const func_mono = try env.mir_store.monotype_store.internFunc(&.{i64_mono}, i64_mono, false); const ident_arg = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; const sym_arg = try testMirSymbol(&env.mir_store, allocator, ident_arg); @@ -6179,7 +6042,7 @@ test "MIR block with decl_var and mutate_var lowers to LIR decl and mutate" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Build MIR: { var s = 1; s = 2; s } const ident_s = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = true }, .idx = 1 }; @@ -6234,8 +6097,8 @@ test "MIR for_loop lowers to LIR for_loop" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const list_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .list = .{ .elem = i64_mono } }); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const list_mono = try env.mir_store.monotype_store.internList(i64_mono); const unit_mono = env.mir_store.monotype_store.unit_idx; // Build MIR: for elem in list { body } @@ -6274,8 +6137,8 @@ test "MIR while_loop lowers to LIR while_loop" { try testInitLayoutStore(&env); defer testDeinit(&env); - const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(allocator, env.module_env.idents); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); const unit_mono = env.mir_store.monotype_store.unit_idx; // Build MIR: while cond { body } @@ -6310,7 +6173,7 @@ test "MIR dbg_expr lowers to LIR dbg" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Build MIR: dbg(42) const inner_expr = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -6336,7 +6199,7 @@ test "MIR expect lowers to LIR expect" { try testInitLayoutStore(&env); defer testDeinit(&env); - const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(allocator, env.module_env.idents); + const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(); const unit_mono = env.mir_store.monotype_store.unit_idx; // Build MIR: expect (true) @@ -6385,7 +6248,7 @@ test "MIR return_expr lowers to LIR early_return" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Build MIR: return 42 const inner_expr = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -6433,7 +6296,7 @@ test "MIR num_plus low-level lowers to low-level" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Build MIR: run_low_level(.num_plus, [10, 20]) const arg0 = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -6466,8 +6329,8 @@ test "borrowed low-level temp arg lowers through explicit block binding" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const list_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .list = .{ .elem = i64_mono } }); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const list_mono = try env.mir_store.monotype_store.internList(i64_mono); const elem0 = try env.mir_store.addExpr(allocator, .{ .int = .{ .value = .{ .bytes = @bitCast(@as(i128, 3)), .kind = .i128 }, @@ -6511,8 +6374,8 @@ test "borrowed low-level large string literal lowers through explicit block bind try testInitLayoutStore(&env); defer testDeinit(&env); - const str_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.str)]; - const u64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.u64)]; + const str_mono = env.mir_store.monotype_store.primIdx(.str); + const u64_mono = env.mir_store.monotype_store.primIdx(.u64); const large_text = "This string is deliberately longer than RocStr small-string storage"; @@ -6548,8 +6411,8 @@ test "borrow-only low-level lookup arg stays as plain low_level" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - const list_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .list = .{ .elem = i64_mono } }); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); + const list_mono = try env.mir_store.monotype_store.internList(i64_mono); const ident_list = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 2 }; const sym_list = try testMirSymbol(&env.mir_store, allocator, ident_list); @@ -6585,7 +6448,7 @@ test "MIR large unsigned int (U64 max) lowers to LIR i128_literal" { try testInitLayoutStore(&env); defer testDeinit(&env); - const u64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.u64)]; + const u64_mono = env.mir_store.monotype_store.primIdx(.u64); // U64 max value = 18446744073709551615, which exceeds maxInt(i64) // so it should lower to i128_literal, not i64_literal @@ -6609,21 +6472,18 @@ test "record access uses layout field order not monotype alphabetical order" { try testInitLayoutStore(&env); defer testDeinit(&env); - const u8_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.u8)]; - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; - - // Field names: age (idx=1), name (idx=2), score (idx=3) - const field_age = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const field_name = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 2 }; - const field_score = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 3 }; + const u8_mono = env.mir_store.monotype_store.primIdx(.u8); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); // Monotype fields are alphabetical: { age: U8, name: I64, score: I64 } - const record_fields = try env.mir_store.monotype_store.addFields(allocator, &.{ - .{ .name = field_age, .type_idx = u8_mono }, - .{ .name = field_name, .type_idx = i64_mono }, - .{ .name = field_score, .type_idx = i64_mono }, + const name_age = try env.mir_store.monotype_store.name_pool.intern("age"); + const name_name = try env.mir_store.monotype_store.name_pool.intern("name"); + const name_score = try env.mir_store.monotype_store.name_pool.intern("score"); + const record_mono = try env.mir_store.monotype_store.internRecord(&.{ + .{ .name = name_age, .ty = u8_mono }, + .{ .name = name_name, .ty = i64_mono }, + .{ .name = name_score, .ty = i64_mono }, }); - const record_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .record = .{ .fields = record_fields } }); // Create a record literal const int_1 = try env.mir_store.addExpr(allocator, .{ .int = .{ @@ -6667,20 +6527,20 @@ test "record destructure wildcard gets actual field layout not zst" { try testInitLayoutStore(&env); defer testDeinit(&env); - const u8_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.u8)]; - const i64_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i64)]; + const u8_mono = env.mir_store.monotype_store.primIdx(.u8); + const i64_mono = env.mir_store.monotype_store.primIdx(.i64); const field_a = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 1 }; - const field_b = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 2 }; - const field_c = Ident.Idx{ .attributes = .{ .effectful = false, .ignored = false, .reassignable = false }, .idx = 3 }; // Record: { a: U8, b: I64, c: I64 } - const record_fields = try env.mir_store.monotype_store.addFields(allocator, &.{ - .{ .name = field_a, .type_idx = u8_mono }, - .{ .name = field_b, .type_idx = i64_mono }, - .{ .name = field_c, .type_idx = i64_mono }, + const name_a2 = try env.mir_store.monotype_store.name_pool.intern("a"); + const name_b2 = try env.mir_store.monotype_store.name_pool.intern("b"); + const name_c2 = try env.mir_store.monotype_store.name_pool.intern("c"); + const record_mono = try env.mir_store.monotype_store.internRecord(&.{ + .{ .name = name_a2, .ty = u8_mono }, + .{ .name = name_b2, .ty = i64_mono }, + .{ .name = name_c2, .ty = i64_mono }, }); - const record_mono = try env.mir_store.monotype_store.addMonotype(allocator, .{ .record = .{ .fields = record_fields } }); // Create a full-arity canonical destructure pattern that only binds field "a" (U8). // In canonical MIR order this is [a, b, c], but in layout order it becomes [b, c, a]. @@ -6726,7 +6586,7 @@ test "MIR small i128 value emits i128_literal not i64_literal" { try testInitLayoutStore(&env); defer testDeinit(&env); - const i128_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.i128)]; + const i128_mono = env.mir_store.monotype_store.primIdx(.i128); // Value 42 fits in i64, but monotype is i128. // Must emit i128_literal so LIR carries the correct 128-bit literal layout. @@ -6755,7 +6615,7 @@ test "LIR Bool match: True pattern gets discriminant 1, False body gets discrimi try testInitLayoutStore(&env); defer testDeinit(&env); - const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(allocator, env.module_env.idents); + const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(); const true_tag = env.module_env.idents.true_tag; const false_tag = env.module_env.idents.false_tag; @@ -6845,7 +6705,7 @@ test "LIR Bool match: False scrutinee gets discriminant 0" { try testInitLayoutStore(&env); defer testDeinit(&env); - const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(allocator, env.module_env.idents); + const bool_mono = try env.mir_store.monotype_store.addBoolTagUnion(); const true_tag = env.module_env.idents.true_tag; const false_tag = env.module_env.idents.false_tag; @@ -6919,7 +6779,7 @@ test "MIR small u128 value emits i128_literal not i64_literal" { try testInitLayoutStore(&env); defer testDeinit(&env); - const u128_mono = env.mir_store.monotype_store.prim_idxs[@intFromEnum(Monotype.Prim.u128)]; + const u128_mono = env.mir_store.monotype_store.primIdx(.u128); // Value 7 fits in i64, but monotype is u128. // Must emit i128_literal so LIR carries the correct 128-bit literal layout. diff --git a/src/mir/LambdaSet.zig b/src/mir/LambdaSet.zig index 5728eb6c803..32b5e37d505 100644 --- a/src/mir/LambdaSet.zig +++ b/src/mir/LambdaSet.zig @@ -591,11 +591,11 @@ fn structFieldArityForExpr(mir_store: *const MIR.Store, expr_id: MIR.ExprId) ?u3 } fn structFieldArityForMonotype(mir_store: *const MIR.Store, mono_idx: anytype) ?u32 { - const mono = mir_store.monotype_store.getMonotype(mono_idx); - return switch (mono) { - .record => |record| @intCast(mir_store.monotype_store.getFields(record.fields).len), - .tuple => |tuple| @intCast(mir_store.monotype_store.getIdxSpan(tuple.elems).len), - .box => |box| structFieldArityForMonotype(mir_store, box.inner), + const resolved = mir_store.monotype_store.resolve(mono_idx); + return switch (resolved.kind) { + .record => @intCast(mir_store.monotype_store.recordFields(resolved).len), + .tuple => @intCast(mir_store.monotype_store.tupleElems(resolved).len), + .box => structFieldArityForMonotype(mir_store, mir_store.monotype_store.boxInner(resolved)), else => null, }; } diff --git a/src/mir/Lower.zig b/src/mir/Lower.zig index bcb47284667..f59dff6095c 100644 --- a/src/mir/Lower.zig +++ b/src/mir/Lower.zig @@ -104,11 +104,6 @@ symbol_monotypes: std.AutoHashMap(u64, Monotype.Idx), /// Written by `bindTypeVarMonotypes`, read by `fromTypeVar`. type_var_seen: std.AutoHashMap(types.Var, Monotype.Idx), -/// Cycle breakers for recursive nominal types (e.g. Tree := [Leaf, Node(Tree)]). -/// Used only by `fromNominalType` during monotype construction; separate from -/// specialization bindings so monotype construction never pollutes them. -nominal_cycle_breakers: std.AutoHashMap(types.Var, Monotype.Idx), - /// Cache for already-lowered symbol definitions (avoids re-lowering). /// Key is @bitCast(MIR.Symbol) → u64. lowered_symbols: std.AutoHashMap(u64, MIR.ExprId), @@ -194,7 +189,6 @@ pub fn init( .pattern_symbols = std.AutoHashMap(u128, MIR.Symbol).init(allocator), .symbol_monotypes = std.AutoHashMap(u64, Monotype.Idx).init(allocator), .type_var_seen = std.AutoHashMap(types.Var, Monotype.Idx).init(allocator), - .nominal_cycle_breakers = std.AutoHashMap(types.Var, Monotype.Idx).init(allocator), .lowered_symbols = std.AutoHashMap(u64, MIR.ExprId).init(allocator), .poly_specializations = std.AutoHashMap(u128, MIR.Symbol).init(allocator), .symbol_metadata = std.AutoHashMap(u64, SymbolMetadata).init(allocator), @@ -225,7 +219,6 @@ pub fn deinit(self: *Self) void { self.pattern_symbols.deinit(); self.symbol_monotypes.deinit(); self.type_var_seen.deinit(); - self.nominal_cycle_breakers.deinit(); self.lowered_symbols.deinit(); self.poly_specializations.deinit(); self.symbol_metadata.deinit(); @@ -310,69 +303,48 @@ fn identTextIfOwnedBy(env: *const ModuleEnv, ident: Ident.Idx) ?[]const u8 { return text; } -fn identTextForCompare(self: *const Self, ident: Ident.Idx) ?[]const u8 { - if (identTextIfOwnedBy(self.all_module_envs[self.current_module_idx], ident)) |text| return text; - return null; -} - -fn identsStructurallyEqual(self: *const Self, lhs: Ident.Idx, rhs: Ident.Idx) bool { - if (lhs.eql(rhs)) return true; - const lhs_text = self.identTextForCompare(lhs) orelse return false; - const rhs_text = self.identTextForCompare(rhs) orelse return false; - return std.mem.eql(u8, lhs_text, rhs_text); -} - fn identLastSegment(text: []const u8) []const u8 { const dot = std.mem.lastIndexOfScalar(u8, text, '.') orelse return text; return text[dot + 1 ..]; } -fn identsTagNameEquivalent(self: *const Self, lhs: Ident.Idx, rhs: Ident.Idx) bool { - if (self.identsStructurallyEqual(lhs, rhs)) return true; - - const lhs_text = self.identTextForCompare(lhs) orelse return false; - const rhs_text = self.identTextForCompare(rhs) orelse return false; - return std.mem.eql(u8, identLastSegment(lhs_text), identLastSegment(rhs_text)); -} - -fn remapIdentBetweenModules( - self: *Self, - ident: Ident.Idx, - from_module_idx: u32, - to_module_idx: u32, -) Allocator.Error!Ident.Idx { - if (from_module_idx == to_module_idx) return ident; - - const from_env = self.all_module_envs[from_module_idx]; - const to_env = self.all_module_envs[to_module_idx]; - - if (identTextIfOwnedBy(from_env, ident)) |ident_text| { - if (to_env.common.findIdent(ident_text)) |mapped| return mapped; - - // If the source monotype uses the source module's self type name, - // map that self-name directly to the target module's self-name. - if (std.mem.eql(u8, ident_text, from_env.module_name)) { - if (to_env.common.findIdent(to_env.module_name)) |target_self| return target_self; - if (to_env.common.getIdentStore().lookup(Ident.for_text(to_env.module_name))) |target_self| return target_self; - return ident; - } - - // Structural names (record fields, tag names) can be introduced by the - // caller module and legitimately be absent in the target module's ident store. - // Keep using the source ident in that case and rely on structural text comparisons. - if (to_env.common.getIdentStore().lookup(Ident.for_text(ident_text))) |mapped| return mapped; - return ident; +/// Convert a StructuralNameId (from the monotype store) to an Ident.Idx +/// in the current module's ident store. Used when constructing MIR patterns +/// that require Ident.Idx names from monotype tag/field names. +fn identFromStructuralName(self: *const Self, name_id: Monotype.StructuralNameId) Ident.Idx { + const text = self.store.monotype_store.getNameText(name_id); + // Search current module first (most common case). + const current_env = self.all_module_envs[self.current_module_idx]; + if (findIdentInEnv(current_env, text)) |ident| return ident; + // Fall back to all other modules for cross-module tags. + for (self.all_module_envs, 0..) |env, i| { + if (i == self.current_module_idx) continue; + if (findIdentInEnv(env, text)) |ident| return ident; } + return Ident.Idx.NONE; +} - if (std.debug.runtime_safety) { - std.debug.panic( - "remapIdentBetweenModules: source ident {d} not owned by source module {d}", - .{ ident.idx, from_module_idx }, - ); - } - unreachable; +fn findIdentInEnv(env: *const ModuleEnv, text: []const u8) ?Ident.Idx { + if (env.common.findIdent(text)) |ident| return ident; + if (env.getIdentStoreConst().lookup(Ident.for_text(text))) |ident| return ident; + return null; } +/// Canonicalize a cross-module TypeId by deeply resolving `.rec` indirections. +/// +/// With content-based interning and a shared `TypeInterner`/`StructuralNamePool`, +/// names and indices are already canonical across modules — no actual remapping of +/// those occurs. However, recursive types created via `reserveRecursive` + +/// `finalizeRecursive` may leave `.rec` placeholder TypeIds in their children +/// (e.g. `List(rec(0))` instead of `List(tag_union(1))`). When a different module +/// constructs the same type freshly via `intern*`, it produces the resolved form +/// (e.g. `List(tag_union(1))`), causing TypeId mismatches on direct comparison. +/// +/// This function walks the type tree, resolves all `.rec` children via the +/// monotype store's `resolve()`, and re-interns to produce a canonical TypeId +/// where all children are direct (non-`.rec`) references. This is only needed +/// cross-module — within a single module, `.rec` placeholders are used +/// consistently, so TypeId comparisons already work. fn remapMonotypeBetweenModules( self: *Self, monotype: Monotype.Idx, @@ -381,7 +353,7 @@ fn remapMonotypeBetweenModules( ) Allocator.Error!Monotype.Idx { if (monotype.isNone() or from_module_idx == to_module_idx) return monotype; - var remapped = std.AutoHashMap(Monotype.Idx, Monotype.Idx).init(self.allocator); + var remapped = std.AutoHashMap(u32, Monotype.Idx).init(self.allocator); defer remapped.deinit(); return self.remapMonotypeBetweenModulesRec( @@ -392,222 +364,97 @@ fn remapMonotypeBetweenModules( ); } +/// Recursive worker for `remapMonotypeBetweenModules`. The `remapped` map +/// tracks already-processed TypeIds to handle cycles in recursive types. fn remapMonotypeBetweenModulesRec( self: *Self, monotype: Monotype.Idx, from_module_idx: u32, to_module_idx: u32, - remapped: *std.AutoHashMap(Monotype.Idx, Monotype.Idx), + remapped: *std.AutoHashMap(u32, Monotype.Idx), ) Allocator.Error!Monotype.Idx { if (monotype.isNone() or from_module_idx == to_module_idx) return monotype; - if (remapped.get(monotype)) |existing| return existing; - - const mono = self.store.monotype_store.getMonotype(monotype); - switch (mono) { - .unit => return self.store.monotype_store.unit_idx, - .prim => |prim| return self.store.monotype_store.primIdx(prim), - .recursive_placeholder => { - if (builtin.mode == .Debug) { - std.debug.panic("remapMonotypeBetweenModules: unexpected recursive_placeholder", .{}); - } - unreachable; - }, - .list, .box, .tuple, .func, .record, .tag_union => {}, + const mono_key: u32 = @bitCast(monotype); + if (remapped.get(mono_key)) |existing| return existing; + + const resolved = self.store.monotype_store.resolve(monotype); + switch (resolved.kind) { + .builtin => return resolved, // unit and prims are canonical + .rec => unreachable, + else => {}, } - const placeholder = try self.store.monotype_store.addMonotype(self.allocator, .recursive_placeholder); - try remapped.put(monotype, placeholder); + const rec_id = try self.store.monotype_store.reserveRecursive(); + try remapped.put(mono_key, rec_id); - const mapped_mono: Monotype.Monotype = switch (mono) { - .list => |list_mono| .{ .list = .{ - .elem = try self.remapMonotypeBetweenModulesRec( - list_mono.elem, - from_module_idx, - to_module_idx, - remapped, - ), - } }, - .box => |box_mono| .{ .box = .{ - .inner = try self.remapMonotypeBetweenModulesRec( - box_mono.inner, - from_module_idx, - to_module_idx, - remapped, - ), - } }, - .tuple => |tuple_mono| blk: { + const canonical: Monotype.Idx = switch (resolved.kind) { + .list => try self.store.monotype_store.internList( + try self.remapMonotypeBetweenModulesRec(self.store.monotype_store.listElem(resolved), from_module_idx, to_module_idx, remapped), + ), + .box => try self.store.monotype_store.internBox( + try self.remapMonotypeBetweenModulesRec(self.store.monotype_store.boxInner(resolved), from_module_idx, to_module_idx, remapped), + ), + .tuple => blk: { const idx_top = self.mono_scratches.idxs.top(); defer self.mono_scratches.idxs.clearFrom(idx_top); - - const elem_span = tuple_mono.elems; - var elem_i: u32 = 0; - while (elem_i < @as(u32, elem_span.len)) : (elem_i += 1) { - const elem_pos_u64 = @as(u64, elem_span.start) + elem_i; - if (builtin.mode == .Debug and elem_pos_u64 >= self.store.monotype_store.extra_idx.items.len) { - std.debug.panic( - "remapMonotypeBetweenModulesRec: tuple elem span out of bounds (start={d}, len={d}, i={d}, extra_len={d})", - .{ elem_span.start, elem_span.len, elem_i, self.store.monotype_store.extra_idx.items.len }, - ); - } - const elem_pos: usize = @intCast(elem_pos_u64); - const elem_mono: Monotype.Idx = @enumFromInt(self.store.monotype_store.extra_idx.items[elem_pos]); - try self.mono_scratches.idxs.append(try self.remapMonotypeBetweenModulesRec( - elem_mono, - from_module_idx, - to_module_idx, - remapped, - )); + for (self.store.monotype_store.tupleElems(resolved)) |elem| { + try self.mono_scratches.idxs.append(try self.remapMonotypeBetweenModulesRec(elem, from_module_idx, to_module_idx, remapped)); } - - const mapped_elems = try self.store.monotype_store.addIdxSpan( - self.allocator, - self.mono_scratches.idxs.sliceFromStart(idx_top), - ); - break :blk .{ .tuple = .{ .elems = mapped_elems } }; + break :blk try self.store.monotype_store.internTuple(self.mono_scratches.idxs.sliceFromStart(idx_top)); }, - .func => |func_mono| blk: { + .func => blk: { const idx_top = self.mono_scratches.idxs.top(); defer self.mono_scratches.idxs.clearFrom(idx_top); - - const arg_span = func_mono.args; - var arg_i: u32 = 0; - while (arg_i < @as(u32, arg_span.len)) : (arg_i += 1) { - const arg_pos_u64 = @as(u64, arg_span.start) + arg_i; - if (builtin.mode == .Debug and arg_pos_u64 >= self.store.monotype_store.extra_idx.items.len) { - std.debug.panic( - "remapMonotypeBetweenModulesRec: func arg span out of bounds (start={d}, len={d}, i={d}, extra_len={d})", - .{ arg_span.start, arg_span.len, arg_i, self.store.monotype_store.extra_idx.items.len }, - ); - } - const arg_pos: usize = @intCast(arg_pos_u64); - const arg_mono: Monotype.Idx = @enumFromInt(self.store.monotype_store.extra_idx.items[arg_pos]); - try self.mono_scratches.idxs.append(try self.remapMonotypeBetweenModulesRec( - arg_mono, - from_module_idx, - to_module_idx, - remapped, - )); + for (self.store.monotype_store.funcArgs(resolved)) |arg| { + try self.mono_scratches.idxs.append(try self.remapMonotypeBetweenModulesRec(arg, from_module_idx, to_module_idx, remapped)); } - const mapped_args = try self.store.monotype_store.addIdxSpan( - self.allocator, + const mapped_ret = try self.remapMonotypeBetweenModulesRec(self.store.monotype_store.funcRet(resolved), from_module_idx, to_module_idx, remapped); + break :blk try self.store.monotype_store.internFunc( self.mono_scratches.idxs.sliceFromStart(idx_top), + mapped_ret, + self.store.monotype_store.funcEffectful(resolved), ); - - const mapped_ret = try self.remapMonotypeBetweenModulesRec( - func_mono.ret, - from_module_idx, - to_module_idx, - remapped, - ); - - break :blk .{ .func = .{ - .args = mapped_args, - .ret = mapped_ret, - .effectful = func_mono.effectful, - } }; }, - .record => |record_mono| blk: { + .record => blk: { const fields_top = self.mono_scratches.fields.top(); defer self.mono_scratches.fields.clearFrom(fields_top); - - const field_span = record_mono.fields; - var field_i: u32 = 0; - while (field_i < @as(u32, field_span.len)) : (field_i += 1) { - const field_pos_u64 = @as(u64, field_span.start) + field_i; - if (builtin.mode == .Debug and field_pos_u64 >= self.store.monotype_store.fields.items.len) { - std.debug.panic( - "remapMonotypeBetweenModulesRec: record field span out of bounds (start={d}, len={d}, i={d}, fields_len={d})", - .{ field_span.start, field_span.len, field_i, self.store.monotype_store.fields.items.len }, - ); - } - const field_pos: usize = @intCast(field_pos_u64); - const field = self.store.monotype_store.fields.items[field_pos]; + for (self.store.monotype_store.recordFields(resolved)) |field| { try self.mono_scratches.fields.append(.{ - .name = try self.remapIdentBetweenModules(field.name, from_module_idx, to_module_idx), - .type_idx = try self.remapMonotypeBetweenModulesRec( - field.type_idx, - from_module_idx, - to_module_idx, - remapped, - ), + .name = field.name, // StructuralNameId is already canonical + .ty = try self.remapMonotypeBetweenModulesRec(field.ty, from_module_idx, to_module_idx, remapped), }); } - - const mapped_fields = try self.store.monotype_store.addFields( - self.allocator, - self.mono_scratches.fields.sliceFromStart(fields_top), - ); - break :blk .{ .record = .{ .fields = mapped_fields } }; + break :blk try self.store.monotype_store.internRecord(self.mono_scratches.fields.sliceFromStart(fields_top)); }, - .tag_union => |tag_union_mono| blk: { + .tag_union => blk: { const tags_top = self.mono_scratches.tags.top(); defer self.mono_scratches.tags.clearFrom(tags_top); - - const tag_span = tag_union_mono.tags; - var tag_i: u32 = 0; - while (tag_i < @as(u32, tag_span.len)) : (tag_i += 1) { - const tag_pos_u64 = @as(u64, tag_span.start) + tag_i; - if (builtin.mode == .Debug and tag_pos_u64 >= self.store.monotype_store.tags.items.len) { - std.debug.panic( - "remapMonotypeBetweenModulesRec: tag span out of bounds (start={d}, len={d}, i={d}, tags_len={d})", - .{ tag_span.start, tag_span.len, tag_i, self.store.monotype_store.tags.items.len }, - ); - } - const tag_pos: usize = @intCast(tag_pos_u64); - const tag = self.store.monotype_store.tags.items[tag_pos]; - + for (self.store.monotype_store.tagUnionTags(resolved)) |tag| { const payload_top = self.mono_scratches.idxs.top(); defer self.mono_scratches.idxs.clearFrom(payload_top); - - const payload_span = tag.payloads; - var payload_i: u32 = 0; - while (payload_i < @as(u32, payload_span.len)) : (payload_i += 1) { - const payload_pos_u64 = @as(u64, payload_span.start) + payload_i; - if (builtin.mode == .Debug and payload_pos_u64 >= self.store.monotype_store.extra_idx.items.len) { - std.debug.panic( - "remapMonotypeBetweenModulesRec: tag payload span out of bounds (start={d}, len={d}, i={d}, extra_len={d}, tag={d})", - .{ - payload_span.start, - payload_span.len, - payload_i, - self.store.monotype_store.extra_idx.items.len, - tag.name.idx, - }, - ); - } - const payload_pos: usize = @intCast(payload_pos_u64); - const payload_mono: Monotype.Idx = @enumFromInt(self.store.monotype_store.extra_idx.items[payload_pos]); - try self.mono_scratches.idxs.append(try self.remapMonotypeBetweenModulesRec( - payload_mono, - from_module_idx, - to_module_idx, - remapped, - )); + for (self.store.monotype_store.getIdxListItems(tag.payloads)) |payload| { + try self.mono_scratches.idxs.append(try self.remapMonotypeBetweenModulesRec(payload, from_module_idx, to_module_idx, remapped)); } - - const mapped_payloads = try self.store.monotype_store.addIdxSpan( - self.allocator, - self.mono_scratches.idxs.sliceFromStart(payload_top), - ); + const mapped_payloads = try self.store.monotype_store.internIdxList(self.mono_scratches.idxs.sliceFromStart(payload_top)); try self.mono_scratches.tags.append(.{ - .name = try self.remapIdentBetweenModules(tag.name, from_module_idx, to_module_idx), + .name = tag.name, // StructuralNameId is already canonical .payloads = mapped_payloads, }); } - - const mapped_tags = try self.store.monotype_store.addTags( - self.allocator, - self.mono_scratches.tags.sliceFromStart(tags_top), - ); - break :blk .{ .tag_union = .{ .tags = mapped_tags } }; + break :blk try self.store.monotype_store.internTagUnion(self.mono_scratches.tags.sliceFromStart(tags_top)); }, - .unit, .prim, .recursive_placeholder => unreachable, + .builtin, .rec => unreachable, }; - self.store.monotype_store.monotypes.items[@intFromEnum(placeholder)] = mapped_mono; - return placeholder; + self.store.monotype_store.finalizeRecursive(rec_id, canonical); + try remapped.put(mono_key, canonical); + return canonical; } +/// Convenience wrapper: canonicalize a caller's monotype when it will be +/// compared against or used as a key in the context of a symbol from a +/// different module. Same-module calls skip canonicalization since `.rec` +/// placeholders are already consistent within a module. fn normalizeCallerMonotypeForSymbolModule( self: *Self, symbol_module_idx: u32, @@ -815,7 +662,7 @@ fn emitMirUnitExpr(self: *Self, region: Region) Allocator.Error!MIR.ExprId { } fn emitMirBoolLiteral(self: *Self, module_env: *const ModuleEnv, value: bool, region: Region) Allocator.Error!MIR.ExprId { - const bool_mono = try self.store.monotype_store.addBoolTagUnion(self.allocator, self.currentCommonIdents()); + const bool_mono = try self.store.monotype_store.addBoolTagUnion(); return try self.store.addExpr( self.allocator, .{ .tag = .{ @@ -1014,15 +861,11 @@ fn monotypeFromTypeVarInStoreWithBindings( var_: types.Var, bindings: *std.AutoHashMap(types.Var, Monotype.Idx), ) Allocator.Error!Monotype.Idx { - var local_cycles = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); - defer local_cycles.deinit(); - return self.monotypeFromTypeVarWithBindings( module_idx, store_types, var_, bindings, - &local_cycles, ); } @@ -1082,15 +925,7 @@ fn bindTypeVarMonotypesInStore( if (monotype.isNone()) return; const resolved = store_types.resolveVar(type_var); - if (bindings.get(resolved.var_)) |existing| { - if (!(try self.monotypesStructurallyEqual(existing, monotype))) { - typeBindingInvariant( - "bindTypeVarMonotypesInStore: conflicting monotype binding for type var root {d} (existing={d}, new={d})", - .{ @intFromEnum(resolved.var_), @intFromEnum(existing), @intFromEnum(monotype) }, - ); - } - return; - } + if (bindings.contains(resolved.var_)) return; switch (resolved.desc.content) { .flex, .rigid => try bindings.put(resolved.var_, monotype), @@ -1213,7 +1048,6 @@ fn monotypeFromTypeVarWithBindings( store_types: *const types.Store, var_: types.Var, bindings: *std.AutoHashMap(types.Var, Monotype.Idx), - cycles: *std.AutoHashMap(types.Var, Monotype.Idx), ) Allocator.Error!Monotype.Idx { const saved_ident_store = self.mono_scratches.ident_store; self.mono_scratches.ident_store = self.all_module_envs[module_idx].getIdentStoreConst(); @@ -1228,7 +1062,6 @@ fn monotypeFromTypeVarWithBindings( var_, ModuleEnv.CommonIdents.find(&self.all_module_envs[module_idx].common), bindings, - cycles, &self.mono_scratches, ); } @@ -1243,19 +1076,18 @@ fn bindFlatTypeMonotypesInStore( ) Allocator.Error!void { if (monotype.isNone()) return; - const mono = self.store.monotype_store.getMonotype(monotype); + const resolved = self.store.monotype_store.resolve(monotype); switch (flat_type) { .fn_pure, .fn_effectful, .fn_unbound => |func| { - const mfunc = switch (mono) { - .func => |mfunc| mfunc, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(fn): expected function monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .func) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(fn): expected function monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const type_args = store_types.sliceVars(func.args); - const mono_args = self.store.monotype_store.getIdxSpan(mfunc.args); + const mono_args = self.store.monotype_store.funcArgs(resolved); if (type_args.len != mono_args.len) { typeBindingInvariant( "bindFlatTypeMonotypesInStore(fn): arity mismatch (type={d}, monotype={d})", @@ -1265,20 +1097,19 @@ fn bindFlatTypeMonotypesInStore( for (type_args, 0..) |arg_var, i| { try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, arg_var, mono_args[i]); } - try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, func.ret, mfunc.ret); + try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, func.ret, self.store.monotype_store.funcRet(resolved)); }, .nominal_type => |nominal| { const ident = nominal.ident.ident_idx; const origin = nominal.origin_module; if (origin.eql(common_idents.builtin_module) and ident.eql(common_idents.list)) { - const mlist = switch (mono) { - .list => |mlist| mlist, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(nominal List): expected list monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .list) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(nominal List): expected list monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const type_args = store_types.sliceNominalArgs(nominal); if (type_args.len != 1) { typeBindingInvariant( @@ -1286,18 +1117,17 @@ fn bindFlatTypeMonotypesInStore( .{type_args.len}, ); } - try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, type_args[0], mlist.elem); + try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, type_args[0], self.store.monotype_store.listElem(resolved)); return; } if (origin.eql(common_idents.builtin_module) and ident.eql(common_idents.box)) { - const mbox = switch (mono) { - .box => |mbox| mbox, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(nominal Box): expected box monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .box) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(nominal Box): expected box monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const type_args = store_types.sliceNominalArgs(nominal); if (type_args.len != 1) { typeBindingInvariant( @@ -1305,17 +1135,16 @@ fn bindFlatTypeMonotypesInStore( .{type_args.len}, ); } - try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, type_args[0], mbox.inner); + try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, type_args[0], self.store.monotype_store.boxInner(resolved)); return; } if (origin.eql(common_idents.builtin_module) and builtinPrimForNominal(ident, common_idents) != null) { - switch (mono) { - .prim => {}, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(nominal prim): expected prim monotype, found '{s}'", - .{@tagName(mono)}, - ), + if (resolved.kind != .builtin or resolved.builtinPrim() == null) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(nominal prim): expected prim monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); } return; } @@ -1329,14 +1158,13 @@ fn bindFlatTypeMonotypesInStore( ); }, .record => |record| { - const mrec = switch (mono) { - .record => |mrec| mrec, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(record): expected record monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; - const mono_fields = self.store.monotype_store.getFields(mrec.fields); + if (resolved.kind != .record) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(record): expected record monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + const mono_fields = self.store.monotype_store.recordFields(resolved); var seen_field_indices: std.ArrayListUnmanaged(u32) = .empty; defer seen_field_indices.deinit(self.allocator); @@ -1348,7 +1176,7 @@ fn bindFlatTypeMonotypesInStore( for (field_names, field_vars) |field_name, field_var| { const field_idx = self.recordFieldIndexByName(field_name, mono_fields); try appendSeenIndex(self.allocator, &seen_field_indices, field_idx); - try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, field_var, mono_fields[field_idx].type_idx); + try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, field_var, mono_fields[field_idx].ty); } var ext_var = current_row.ext; @@ -1371,7 +1199,7 @@ fn bindFlatTypeMonotypesInStore( for (ext_names, ext_vars) |field_name, field_var| { const field_idx = self.recordFieldIndexByName(field_name, mono_fields); try appendSeenIndex(self.allocator, &seen_field_indices, field_idx); - try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, field_var, mono_fields[field_idx].type_idx); + try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, field_var, mono_fields[field_idx].ty); } break :rows; }, @@ -1391,32 +1219,30 @@ fn bindFlatTypeMonotypesInStore( } }, .record_unbound => |fields_range| { - const mrec = switch (mono) { - .record => |mrec| mrec, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(record_unbound): expected record monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; - const mono_fields = self.store.monotype_store.getFields(mrec.fields); + if (resolved.kind != .record) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(record_unbound): expected record monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + const mono_fields = self.store.monotype_store.recordFields(resolved); const fields_slice = store_types.getRecordFieldsSlice(fields_range); const field_names = fields_slice.items(.name); const field_vars = fields_slice.items(.var_); for (field_names, field_vars) |field_name, field_var| { const field_idx = self.recordFieldIndexByName(field_name, mono_fields); - try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, field_var, mono_fields[field_idx].type_idx); + try self.bindTypeVarMonotypesInStore(store_types, common_idents, bindings, field_var, mono_fields[field_idx].ty); } }, .tuple => |tuple| { - const mtup = switch (mono) { - .tuple => |mtup| mtup, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(tuple): expected tuple monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .tuple) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(tuple): expected tuple monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const elem_vars = store_types.sliceVars(tuple.elems); - const elem_monos = self.store.monotype_store.getIdxSpan(mtup.elems); + const elem_monos = self.store.monotype_store.tupleElems(resolved); if (elem_vars.len != elem_monos.len) { typeBindingInvariant( "bindFlatTypeMonotypesInStore(tuple): arity mismatch (type={d}, monotype={d})", @@ -1428,14 +1254,13 @@ fn bindFlatTypeMonotypesInStore( } }, .tag_union => |tag_union| { - const mtag = switch (mono) { - .tag_union => |mtag| mtag, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(tag_union): expected tag union monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; - const mono_tags = self.store.monotype_store.getTags(mtag.tags); + if (resolved.kind != .tag_union) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(tag_union): expected tag union monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + const mono_tags = self.store.monotype_store.tagUnionTags(resolved); var seen_tag_indices: std.ArrayListUnmanaged(u32) = .empty; defer seen_tag_indices.deinit(self.allocator); @@ -1479,19 +1304,21 @@ fn bindFlatTypeMonotypesInStore( } } }, - .empty_record => switch (mono) { - .unit => {}, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(empty_record): expected unit monotype, found '{s}'", - .{@tagName(mono)}, - ), + .empty_record => { + if (!resolved.isUnit() and resolved.kind != .record) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(empty_record): expected unit monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } }, - .empty_tag_union => switch (mono) { - .tag_union => {}, - else => typeBindingInvariant( - "bindFlatTypeMonotypesInStore(empty_tag_union): expected tag union monotype, found '{s}'", - .{@tagName(mono)}, - ), + .empty_tag_union => { + if (resolved.kind != .tag_union) { + typeBindingInvariant( + "bindFlatTypeMonotypesInStore(empty_tag_union): expected tag union monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } }, } } @@ -1533,16 +1360,15 @@ fn lowerStrInspektNominal( } const outer_mono = try self.monotypeFromTypeVarInEnv(type_env, type_var); - const box_mono = self.store.monotype_store.getMonotype(outer_mono); - const box_data = switch (box_mono) { - .box => |box| box, - else => typeBindingInvariant( - "lowerStrInspektNominal(Box): expected box monotype, found '{s}'", - .{@tagName(box_mono)}, - ), - }; + const box_resolved = self.store.monotype_store.resolve(outer_mono); + if (box_resolved.kind != .box) { + typeBindingInvariant( + "lowerStrInspektNominal(Box): expected box monotype, found kind={d}", + .{@intFromEnum(box_resolved.kind)}, + ); + } - const unbox_fn_mono = try self.buildFuncMonotype(&.{outer_mono}, box_data.inner, false); + const unbox_fn_mono = try self.buildFuncMonotype(&.{outer_mono}, self.store.monotype_store.boxInner(box_resolved), false); const unbox_target: ResolvedDispatchTarget = .{ .origin = common.builtin_module, .method_ident = common.builtin_box_unbox, @@ -1561,7 +1387,7 @@ fn lowerStrInspektNominal( const unboxed = try self.store.addExpr( self.allocator, .{ .call = .{ .func = func_expr, .args = unbox_args } }, - box_data.inner, + self.store.monotype_store.boxInner(box_resolved), region, ); @@ -1606,13 +1432,14 @@ fn lowerStrInspektNominal( return self.lowerStrInspektExpr(type_env, value_expr, type_env.types.getNominalBackingVar(nominal), region); } - const method_func = switch (self.store.monotype_store.getMonotype(method_func_mono)) { - .func => |func| func, - else => typeBindingInvariant( - "lowerStrInspektNominal: expected function monotype for to_inspect, found '{s}'", - .{@tagName(self.store.monotype_store.getMonotype(method_func_mono))}, - ), - }; + const method_func_resolved = self.store.monotype_store.resolve(method_func_mono); + if (method_func_resolved.kind != .func) { + typeBindingInvariant( + "lowerStrInspektNominal: expected function monotype for to_inspect, found kind={d}", + .{@intFromEnum(method_func_resolved.kind)}, + ); + } + const method_ret = self.store.monotype_store.funcRet(method_func_resolved); const lowered_method_symbol = try self.specializeMethod(method_info.symbol, method_func_mono); const func_expr = try self.store.addExpr( @@ -1625,13 +1452,13 @@ fn lowerStrInspektNominal( const call_expr = try self.store.addExpr( self.allocator, .{ .call = .{ .func = func_expr, .args = call_args } }, - method_func.ret, + method_ret, region, ); - const ret_mono = self.store.monotype_store.getMonotype(method_func.ret); - if (ret_mono == .prim and ret_mono.prim == .str) { - return call_expr; + const ret_resolved = self.store.monotype_store.resolve(method_ret); + if (ret_resolved.builtinPrim()) |p| { + if (p == .str) return call_expr; } return self.lowerStrInspektExpr(method_info.target_env, call_expr, resolved_func.func.ret, region); @@ -1942,7 +1769,7 @@ fn lowerStrInspektList( region: Region, ) Allocator.Error!MIR.ExprId { const str_mono = self.store.monotype_store.primIdx(.str); - const bool_mono = try self.store.monotype_store.addBoolTagUnion(self.allocator, self.currentCommonIdents()); + const bool_mono = try self.store.monotype_store.addBoolTagUnion(); const unit_mono = self.store.monotype_store.unit_idx; const elem_mono = try self.monotypeFromTypeVarInEnv(type_env, elem_var); const current_env = self.all_module_envs[self.current_module_idx]; @@ -2020,19 +1847,20 @@ fn lowerStrInspektExprByMonotype( mono_idx: Monotype.Idx, region: Region, ) Allocator.Error!MIR.ExprId { - const mono = self.store.monotype_store.getMonotype(mono_idx); - return switch (mono) { - .prim => |prim| switch (prim) { - .str => blk: { - break :blk try self.store.addExpr( - self.allocator, - .{ .str_escape_and_quote = value_expr }, - self.store.monotype_store.primIdx(.str), - region, - ); - }, - else => |p| blk: { - const ll = toStrLowLevelForPrim(p) orelse unreachable; + const resolved = self.store.monotype_store.resolve(mono_idx); + return switch (resolved.kind) { + .builtin => blk: { + if (resolved.isUnit()) break :blk self.emitMirStrLiteral("{}", region); + if (resolved.builtinPrim()) |prim| { + if (prim == .str) { + break :blk try self.store.addExpr( + self.allocator, + .{ .str_escape_and_quote = value_expr }, + self.store.monotype_store.primIdx(.str), + region, + ); + } + const ll = toStrLowLevelForPrim(prim) orelse unreachable; const args = try self.store.addExprSpan(self.allocator, &.{value_expr}); break :blk try self.store.addExpr( self.allocator, @@ -2040,20 +1868,20 @@ fn lowerStrInspektExprByMonotype( self.store.monotype_store.primIdx(.str), region, ); - }, + } + unreachable; }, - .record => |record| self.lowerStrInspektRecordByMonotype(module_env, value_expr, record, region), - .tuple => |tup| self.lowerStrInspektTupleByMonotype(module_env, value_expr, tup, region), - .tag_union => |tu| self.lowerStrInspektTagUnionByMonotype(module_env, value_expr, tu, mono_idx, region), - .list => |list_data| self.lowerStrInspektListByMonotype(module_env, value_expr, list_data, region), - .unit => self.emitMirStrLiteral("{}", region), - .box => |box_data| blk: { + .record => self.lowerStrInspektRecordByMonotype(module_env, value_expr, resolved, region), + .tuple => self.lowerStrInspektTupleByMonotype(module_env, value_expr, resolved, region), + .tag_union => self.lowerStrInspektTagUnionByMonotype(module_env, value_expr, resolved, mono_idx, region), + .list => self.lowerStrInspektListByMonotype(module_env, value_expr, resolved, region), + .box => blk: { const common = self.currentCommonIdents(); - const unbox_fn_mono = try self.buildFuncMonotype(&.{mono_idx}, box_data.inner, false); + const box_inner = self.store.monotype_store.boxInner(resolved); + const unbox_fn_mono = try self.buildFuncMonotype(&.{mono_idx}, box_inner, false); const unbox_target: ResolvedDispatchTarget = .{ .origin = common.builtin_module, .method_ident = common.builtin_box_unbox, - // resolvedDispatchTargetToSymbol only uses origin/method_ident. .fn_var = undefined, }; @@ -2069,19 +1897,19 @@ fn lowerStrInspektExprByMonotype( const unboxed = try self.store.addExpr( self.allocator, .{ .call = .{ .func = func_expr, .args = unbox_args } }, - box_data.inner, + box_inner, region, ); - const inner_str = try self.lowerStrInspektExprByMonotype(module_env, unboxed, box_data.inner, region); + const inner_str = try self.lowerStrInspektExprByMonotype(module_env, unboxed, box_inner, region); const open = try self.emitMirStrLiteral("Box(", region); const close = try self.emitMirStrLiteral(")", region); break :blk self.foldMirStrConcat(&.{ open, inner_str, close }, region); }, .func => self.emitMirStrLiteral("", region), - .recursive_placeholder => { + .rec => { if (std.debug.runtime_safety) { - std.debug.panic("recursive_placeholder survived monotype construction", .{}); + std.debug.panic("recursive type survived monotype construction", .{}); } unreachable; }, @@ -2090,32 +1918,31 @@ fn lowerStrInspektExprByMonotype( fn lowerStrInspektRecordByMonotype( self: *Self, - module_env: *const ModuleEnv, + _: *const ModuleEnv, value_expr: MIR.ExprId, record: anytype, region: Region, ) Allocator.Error!MIR.ExprId { - const field_span = record.fields; - const fields = self.store.monotype_store.getFields(field_span); + const fields = self.store.monotype_store.recordFields(record); if (fields.len == 0) return self.emitMirStrLiteral("{}", region); const save_exprs = self.scratch_expr_ids.top(); defer self.scratch_expr_ids.clearFrom(save_exprs); try self.scratch_expr_ids.append(try self.emitMirStrLiteral("{ ", region)); - for (0..field_span.len) |i| { - const current_fields = self.store.monotype_store.getFields(field_span); + for (0..fields.len) |i| { + const current_fields = self.store.monotype_store.recordFields(record); const field = current_fields[i]; if (i > 0) { try self.scratch_expr_ids.append(try self.emitMirStrLiteral(", ", region)); } - const field_name = module_env.getIdent(field.name); + const field_name = self.store.monotype_store.getNameText(field.name); const label = try std.fmt.allocPrint(self.allocator, "{s}: ", .{field_name}); defer self.allocator.free(label); try self.scratch_expr_ids.append(try self.emitMirStrLiteral(label, region)); - const field_expr = try self.emitMirStructAccess(value_expr, @intCast(i), field.type_idx, region); - try self.scratch_expr_ids.append(try self.lowerStrInspektExprByMonotype(module_env, field_expr, field.type_idx, region)); + const field_expr = try self.emitMirStructAccess(value_expr, @intCast(i), field.ty, region); + try self.scratch_expr_ids.append(try self.lowerStrInspektExprByMonotype(self.all_module_envs[self.current_module_idx], field_expr, field.ty, region)); } try self.scratch_expr_ids.append(try self.emitMirStrLiteral(" }", region)); return self.foldMirStrConcat(self.scratch_expr_ids.sliceFromStart(save_exprs), region); @@ -2128,16 +1955,15 @@ fn lowerStrInspektTupleByMonotype( tup: anytype, region: Region, ) Allocator.Error!MIR.ExprId { - const elem_span = tup.elems; - const elems = self.store.monotype_store.getIdxSpan(elem_span); + const elems = self.store.monotype_store.tupleElems(tup); if (elems.len == 0) return self.emitMirStrLiteral("()", region); const save_exprs = self.scratch_expr_ids.top(); defer self.scratch_expr_ids.clearFrom(save_exprs); try self.scratch_expr_ids.append(try self.emitMirStrLiteral("(", region)); - for (0..elem_span.len) |i| { - const current_elems = self.store.monotype_store.getIdxSpan(elem_span); + for (0..elems.len) |i| { + const current_elems = self.store.monotype_store.tupleElems(tup); const elem_mono = current_elems[i]; if (i > 0) { try self.scratch_expr_ids.append(try self.emitMirStrLiteral(", ", region)); @@ -2158,35 +1984,34 @@ fn lowerStrInspektTagUnionByMonotype( mono_idx: Monotype.Idx, region: Region, ) Allocator.Error!MIR.ExprId { - const tag_span = tu.tags; - const tags = self.store.monotype_store.getTags(tag_span); + const tags = self.store.monotype_store.tagUnionTags(tu); if (tags.len == 0) return self.emitMirStrLiteral("", region); const save_branches = self.scratch_branches.top(); defer self.scratch_branches.clearFrom(save_branches); - for (0..tag_span.len) |tag_i| { - const current_tags = self.store.monotype_store.getTags(tag_span); + for (0..tags.len) |tag_i| { + const current_tags = self.store.monotype_store.tagUnionTags(tu); const tag = current_tags[tag_i]; - const payload_span = tag.payloads; - const payloads = self.store.monotype_store.getIdxSpan(payload_span); + const payloads = self.store.monotype_store.getIdxListItems(tag.payloads); const save_payload_patterns = self.scratch_pattern_ids.top(); defer self.scratch_pattern_ids.clearFrom(save_payload_patterns); const save_payload_symbols = self.scratch_captures.top(); defer self.scratch_captures.clearFrom(save_payload_symbols); - for (self.store.monotype_store.getIdxSpan(payload_span)) |payload_mono| { + for (payloads) |payload_mono| { const bind = try self.makeSyntheticBind(payload_mono, false); try self.scratch_pattern_ids.append(bind.pattern); try self.scratch_captures.append(.{ .symbol = bind.symbol }); } const payload_pattern_span = try self.store.addPatternSpan(self.allocator, self.scratch_pattern_ids.sliceFromStart(save_payload_patterns)); - const tag_args = try self.wrapMultiPayloadTagPatterns(tag.name, mono_idx, payload_pattern_span); + const tag_ident = self.identFromStructuralName(tag.name); + const tag_args = try self.wrapMultiPayloadTagPatterns(tag_ident, mono_idx, payload_pattern_span); const tag_pattern = try self.store.addPattern( self.allocator, - .{ .tag = .{ .name = tag.name, .args = tag_args } }, + .{ .tag = .{ .name = tag_ident, .args = tag_args } }, mono_idx, ); const branch_patterns = try self.store.addBranchPatterns(self.allocator, &.{MIR.BranchPattern{ @@ -2194,19 +2019,20 @@ fn lowerStrInspektTagUnionByMonotype( .degenerate = false, }}); + const tag_name_text = self.store.monotype_store.getNameText(tag.name); const body = if (payloads.len == 0) blk: { - break :blk try self.emitMirStrLiteral(module_env.getIdent(tag.name), region); + break :blk try self.emitMirStrLiteral(tag_name_text, region); } else blk: { const save_parts = self.scratch_expr_ids.top(); defer self.scratch_expr_ids.clearFrom(save_parts); - const tag_open = try std.fmt.allocPrint(self.allocator, "{s}(", .{module_env.getIdent(tag.name)}); + const tag_open = try std.fmt.allocPrint(self.allocator, "{s}(", .{tag_name_text}); defer self.allocator.free(tag_open); try self.scratch_expr_ids.append(try self.emitMirStrLiteral(tag_open, region)); const payload_symbols = self.scratch_captures.sliceFromStart(save_payload_symbols); - for (0..payload_span.len) |i| { - const payload_mono = self.store.monotype_store.getIdxSpan(payload_span)[i]; + for (0..payloads.len) |i| { + const payload_mono = self.store.monotype_store.getIdxListItems(tag.payloads)[i]; const payload_capture = payload_symbols[i]; if (i > 0) { try self.scratch_expr_ids.append(try self.emitMirStrLiteral(", ", region)); @@ -2245,12 +2071,12 @@ fn lowerStrInspektListByMonotype( region: Region, ) Allocator.Error!MIR.ExprId { const str_mono = self.store.monotype_store.primIdx(.str); - const bool_mono = try self.store.monotype_store.addBoolTagUnion(self.allocator, self.currentCommonIdents()); + const bool_mono = try self.store.monotype_store.addBoolTagUnion(); const unit_mono = self.store.monotype_store.unit_idx; const acc_bind = try self.makeSyntheticBind(str_mono, true); const first_bind = try self.makeSyntheticBind(bool_mono, true); - const elem_bind = try self.makeSyntheticBind(list_data.elem, false); + const elem_bind = try self.makeSyntheticBind(self.store.monotype_store.listElem(list_data), false); const open_bracket = try self.emitMirStrLiteral("[", region); const close_bracket = try self.emitMirStrLiteral("]", region); @@ -2265,8 +2091,8 @@ fn lowerStrInspektListByMonotype( // acc = acc ++ prefix ++ inspect(elem) const first_lookup = try self.emitMirLookup(first_bind.symbol, bool_mono, region); const prefix = try self.createBoolMatch(module_env, first_lookup, empty, comma, str_mono, region); - const elem_lookup = try self.emitMirLookup(elem_bind.symbol, list_data.elem, region); - const elem_inspected = try self.lowerStrInspektExprByMonotype(module_env, elem_lookup, list_data.elem, region); + const elem_lookup = try self.emitMirLookup(elem_bind.symbol, self.store.monotype_store.listElem(list_data), region); + const elem_inspected = try self.lowerStrInspektExprByMonotype(module_env, elem_lookup, self.store.monotype_store.listElem(list_data), region); const prefixed_elem = try self.emitMirStrConcat(prefix, elem_inspected, region); const acc_lookup = try self.emitMirLookup(acc_bind.symbol, str_mono, region); const new_acc = try self.emitMirStrConcat(acc_lookup, prefixed_elem, region); @@ -2356,9 +2182,9 @@ pub fn lowerExpr(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!MIR.ExprId .e_typed_int => |ti| try self.store.addExpr(self.allocator, .{ .int = .{ .value = ti.value } }, monotype, region), .e_typed_frac => |tf| { const roc_dec = builtins.dec.RocDec{ .num = tf.value.toI128() }; - const mono = self.store.monotype_store.getMonotype(monotype); - return switch (mono) { - .prim => |p| switch (p) { + const resolved_mono = self.store.monotype_store.resolve(monotype); + if (resolved_mono.builtinPrim()) |p| { + return switch (p) { .f64 => try self.store.addExpr(self.allocator, .{ .frac_f64 = roc_dec.toF64() }, monotype, region), .f32 => try self.store.addExpr(self.allocator, .{ .frac_f32 = @floatCast(roc_dec.toF64()) }, monotype, region), .dec => try self.store.addExpr(self.allocator, .{ .dec = roc_dec }, monotype, region), @@ -2372,18 +2198,17 @@ pub fn lowerExpr(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!MIR.ExprId } unreachable; }, - }, - else => { - if (std.debug.runtime_safety) { - const type_name = module_env.getIdent(tf.type_name); - std.debug.panic( - "lowerExpr(e_typed_frac): non-prim monotype for type '{s}' (checker/lowering invariant broken)", - .{type_name}, - ); - } - unreachable; - }, - }; + }; + } else { + if (std.debug.runtime_safety) { + const type_name = module_env.getIdent(tf.type_name); + std.debug.panic( + "lowerExpr(e_typed_frac): non-prim monotype for type '{s}' (checker/lowering invariant broken)", + .{type_name}, + ); + } + unreachable; + } }, // --- Strings --- @@ -2483,10 +2308,10 @@ pub fn lowerExpr(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!MIR.ExprId // Symbol is already lowered. Check if this is a polymorphic // specialization: the same function called with a different type. const cached_monotype = self.store.typeOf(cached_expr); - const monotype_matches_cached = try self.monotypesStructurallyEqual(cached_monotype, monotype); + const monotype_matches_cached = cached_monotype == monotype; if (!monotype.isNone() and !monotype_matches_cached) { // Check for an existing specialization with this monotype. - if (try self.lookupPolySpecialization(symbol_key, monotype)) |spec_symbol| { + if (self.lookupPolySpecialization(symbol_key, monotype)) |spec_symbol| { return try self.store.addExpr(self.allocator, .{ .lookup = spec_symbol }, monotype, region); } @@ -2517,13 +2342,9 @@ pub fn lowerExpr(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!MIR.ExprId const saved_type_var_seen = self.type_var_seen; self.type_var_seen = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); - const saved_nominal_cycle_breakers = self.nominal_cycle_breakers; - self.nominal_cycle_breakers = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); defer { self.type_var_seen.deinit(); self.type_var_seen = saved_type_var_seen; - self.nominal_cycle_breakers.deinit(); - self.nominal_cycle_breakers = saved_nominal_cycle_breakers; } try self.bindTypeVarMonotypes(ModuleEnv.varFrom(cir_def_expr), monotype); const saved_pattern_scope = self.current_pattern_scope; @@ -2548,7 +2369,7 @@ pub fn lowerExpr(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!MIR.ExprId const lookup_var = ModuleEnv.varFrom(lookup.pattern_idx); if (self.symbol_monotypes.get(symbol.raw())) |bound_monotype| { - const same_bound_mono = try self.monotypesStructurallyEqual(bound_monotype, monotype); + const same_bound_mono = bound_monotype == monotype; if (same_bound_mono) { return try self.store.addExpr(self.allocator, .{ .lookup = symbol }, bound_monotype, region); } @@ -2558,15 +2379,14 @@ pub fn lowerExpr(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!MIR.ExprId self.types_store, lookup_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); - if (try self.monotypesStructurallyEqual(pattern_monotype, monotype)) { + if (pattern_monotype == monotype) { return try self.store.addExpr(self.allocator, .{ .lookup = symbol }, pattern_monotype, region); } if (self.lowered_symbols.get(symbol_key)) |cached_expr| { const cached_monotype = self.store.typeOf(cached_expr); - const monotype_matches_cached = try self.monotypesStructurallyEqual(cached_monotype, monotype); + const monotype_matches_cached = cached_monotype == monotype; if (monotype_matches_cached) { return try self.store.addExpr(self.allocator, .{ .lookup = symbol }, cached_monotype, region); } @@ -2608,9 +2428,9 @@ pub fn lowerExpr(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!MIR.ExprId // Symbol already lowered. Check for polymorphic specialization. if (self.lowered_symbols.get(symbol_key)) |cached_expr| { const cached_monotype = self.store.typeOf(cached_expr); - const monotype_matches_cached = try self.monotypesStructurallyEqual(cached_monotype, normalized_lookup_monotype); + const monotype_matches_cached = cached_monotype == normalized_lookup_monotype; if (!normalized_lookup_monotype.isNone() and !monotype_matches_cached) { - if (try self.lookupPolySpecialization(symbol_key, normalized_lookup_monotype)) |spec_symbol| { + if (self.lookupPolySpecialization(symbol_key, normalized_lookup_monotype)) |spec_symbol| { return try self.store.addExpr(self.allocator, .{ .lookup = spec_symbol }, monotype, region); } @@ -2631,7 +2451,7 @@ pub fn lowerExpr(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!MIR.ExprId if (self.lowered_symbols.get(symbol_key)) |cached_expr| { const cached_monotype = self.store.typeOf(cached_expr); - if (try self.monotypesStructurallyEqual(cached_monotype, monotype)) { + if (cached_monotype == monotype) { return try self.store.addExpr(self.allocator, .{ .lookup = symbol }, cached_monotype, region); } } @@ -2993,7 +2813,7 @@ fn hoistAnonymousFunctionExpr(self: *Self, expr: MIR.ExprId) Allocator.Error!MIR /// stable symbol identity before lambda-set inference runs. Named lookups and /// lifted closures already have that identity; anonymous lambda expressions do not. fn stabilizeEscapingFunctionExpr(self: *Self, expr: MIR.ExprId) Allocator.Error!MIR.ExprId { - if (self.store.monotype_store.getMonotype(self.store.typeOf(expr)) != .func) return expr; + if (self.store.monotype_store.resolve(self.store.typeOf(expr)).kind != .func) return expr; if (self.store.getExprClosureMember(expr) != null) return expr; return switch (self.store.getExpr(expr)) { @@ -3034,142 +2854,11 @@ fn isLambdaExpr(mir_store: *const MIR.Store, expr_id: MIR.ExprId) bool { /// Compute the composite cache key for polymorphic specializations. fn polySpecKey(symbol_key: u64, monotype: Monotype.Idx) u128 { - return (@as(u128, symbol_key) << 32) | @as(u128, @intFromEnum(monotype)); -} - -fn lookupPolySpecialization(self: *Self, symbol_key: u64, caller_monotype: Monotype.Idx) Allocator.Error!?MIR.Symbol { - const exact_key = polySpecKey(symbol_key, caller_monotype); - if (self.poly_specializations.get(exact_key)) |exact| return exact; - - // Monotype IDs are not globally canonicalized. Reuse an existing specialization - // when its cached caller monotype is structurally equal to this call site. - var it = self.poly_specializations.iterator(); - while (it.next()) |entry| { - const cache_key = entry.key_ptr.*; - const cached_symbol_key: u64 = @intCast(cache_key >> 32); - if (cached_symbol_key != symbol_key) continue; - - const cached_mono_raw: u32 = @truncate(cache_key); - const cached_mono: Monotype.Idx = @enumFromInt(cached_mono_raw); - if (try self.monotypesStructurallyEqual(cached_mono, caller_monotype)) { - return entry.value_ptr.*; - } - } - - return null; -} - -fn monotypesStructurallyEqual(self: *Self, lhs: Monotype.Idx, rhs: Monotype.Idx) Allocator.Error!bool { - if (lhs == rhs) return true; - - var seen = std.AutoHashMap(u64, void).init(self.allocator); - defer seen.deinit(); - - return try self.monotypesStructurallyEqualRec(lhs, rhs, &seen); + return (@as(u128, symbol_key) << 32) | @as(u128, @as(u32, @bitCast(monotype))); } -fn monotypesStructurallyEqualRec( - self: *Self, - lhs: Monotype.Idx, - rhs: Monotype.Idx, - seen: *std.AutoHashMap(u64, void), -) Allocator.Error!bool { - if (lhs == rhs) return true; - - const lhs_u32: u32 = @intFromEnum(lhs); - const rhs_u32: u32 = @intFromEnum(rhs); - const key: u64 = (@as(u64, lhs_u32) << 32) | @as(u64, rhs_u32); - - if (seen.contains(key)) return true; - try seen.put(key, {}); - - const lhs_mono = self.store.monotype_store.getMonotype(lhs); - const rhs_mono = self.store.monotype_store.getMonotype(rhs); - if (std.meta.activeTag(lhs_mono) != std.meta.activeTag(rhs_mono)) return false; - - return switch (lhs_mono) { - .recursive_placeholder => { - if (std.debug.runtime_safety) { - std.debug.panic("recursive_placeholder survived monotype construction", .{}); - } - unreachable; - }, - .unit => true, - .prim => |lhs_prim| lhs_prim == rhs_mono.prim, - .list => |lhs_list| try self.monotypesStructurallyEqualRec(lhs_list.elem, rhs_mono.list.elem, seen), - .box => |lhs_box| try self.monotypesStructurallyEqualRec(lhs_box.inner, rhs_mono.box.inner, seen), - .tuple => |lhs_tuple| blk: { - const lhs_elem_span = lhs_tuple.elems; - const rhs_elem_span = rhs_mono.tuple.elems; - if (lhs_elem_span.len != rhs_elem_span.len) break :blk false; - for (0..lhs_elem_span.len) |i| { - const lhs_elems = self.store.monotype_store.getIdxSpan(lhs_elem_span); - const rhs_elems = self.store.monotype_store.getIdxSpan(rhs_elem_span); - const lhs_elem = lhs_elems[i]; - const rhs_elem = rhs_elems[i]; - if (!try self.monotypesStructurallyEqualRec(lhs_elem, rhs_elem, seen)) { - break :blk false; - } - } - break :blk true; - }, - .func => |lhs_func| blk: { - const rhs_func = rhs_mono.func; - if (lhs_func.effectful != rhs_func.effectful) break :blk false; - if (lhs_func.args.len != rhs_func.args.len) break :blk false; - for (0..lhs_func.args.len) |i| { - const lhs_args = self.store.monotype_store.getIdxSpan(lhs_func.args); - const rhs_args = self.store.monotype_store.getIdxSpan(rhs_func.args); - const lhs_arg = lhs_args[i]; - const rhs_arg = rhs_args[i]; - if (!try self.monotypesStructurallyEqualRec(lhs_arg, rhs_arg, seen)) { - break :blk false; - } - } - break :blk try self.monotypesStructurallyEqualRec(lhs_func.ret, rhs_func.ret, seen); - }, - .record => |lhs_record| blk: { - const lhs_field_span = lhs_record.fields; - const rhs_field_span = rhs_mono.record.fields; - if (lhs_field_span.len != rhs_field_span.len) break :blk false; - for (0..lhs_field_span.len) |i| { - const lhs_fields = self.store.monotype_store.getFields(lhs_field_span); - const rhs_fields = self.store.monotype_store.getFields(rhs_field_span); - const lhs_field = lhs_fields[i]; - const rhs_field = rhs_fields[i]; - if (!self.identsStructurallyEqual(lhs_field.name, rhs_field.name)) break :blk false; - if (!try self.monotypesStructurallyEqualRec(lhs_field.type_idx, rhs_field.type_idx, seen)) { - break :blk false; - } - } - break :blk true; - }, - .tag_union => |lhs_union| blk: { - const lhs_tag_span = lhs_union.tags; - const rhs_tag_span = rhs_mono.tag_union.tags; - if (lhs_tag_span.len != rhs_tag_span.len) break :blk false; - for (0..lhs_tag_span.len) |tag_i| { - const lhs_tags = self.store.monotype_store.getTags(lhs_tag_span); - const rhs_tags = self.store.monotype_store.getTags(rhs_tag_span); - const lhs_tag = lhs_tags[tag_i]; - const rhs_tag = rhs_tags[tag_i]; - - if (!self.identsTagNameEquivalent(lhs_tag.name, rhs_tag.name)) break :blk false; - - if (lhs_tag.payloads.len != rhs_tag.payloads.len) break :blk false; - for (0..lhs_tag.payloads.len) |payload_i| { - const lhs_payloads = self.store.monotype_store.getIdxSpan(lhs_tag.payloads); - const rhs_payloads = self.store.monotype_store.getIdxSpan(rhs_tag.payloads); - const lhs_payload = lhs_payloads[payload_i]; - const rhs_payload = rhs_payloads[payload_i]; - if (!try self.monotypesStructurallyEqualRec(lhs_payload, rhs_payload, seen)) { - break :blk false; - } - } - } - break :blk true; - }, - }; +fn lookupPolySpecialization(self: *Self, symbol_key: u64, caller_monotype: Monotype.Idx) ?MIR.Symbol { + return self.poly_specializations.get(polySpecKey(symbol_key, caller_monotype)); } /// Get the monotype for a CIR expression (via its type var). @@ -3180,7 +2869,6 @@ fn resolveMonotype(self: *Self, expr_idx: CIR.Expr.Idx) Allocator.Error!Monotype self.types_store, type_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); } @@ -3190,12 +2878,7 @@ fn currentCommonIdents(self: *const Self) ModuleEnv.CommonIdents { /// Build a function monotype from argument types, return type, and effectfulness. fn buildFuncMonotype(self: *Self, arg_monotypes: []const Monotype.Idx, ret: Monotype.Idx, effectful: bool) Allocator.Error!Monotype.Idx { - const args_span = try self.store.monotype_store.addIdxSpan(self.allocator, arg_monotypes); - return try self.store.monotype_store.addMonotype(self.allocator, .{ .func = .{ - .args = args_span, - .ret = ret, - .effectful = effectful, - } }); + return try self.store.monotype_store.internFunc(arg_monotypes, ret, effectful); } /// Lower a CIR Expr.Span to an MIR ExprSpan. @@ -3239,7 +2922,6 @@ fn lowerPattern(self: *Self, module_env: *const ModuleEnv, pattern_idx: CIR.Patt self.types_store, type_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); const lowered = switch (pattern) { @@ -3311,19 +2993,20 @@ fn lowerPattern(self: *Self, module_env: *const ModuleEnv, pattern_idx: CIR.Patt const cir_destructs = module_env.store.sliceRecordDestructs(record_pat.destructs); const pats_top = self.scratch_pattern_ids.top(); defer self.scratch_pattern_ids.clearFrom(pats_top); - const mono_field_span = switch (self.store.monotype_store.getMonotype(monotype)) { - .record => |record_mono| record_mono.fields, - .unit => Monotype.FieldSpan.empty(), - else => typeBindingInvariant( - "lowerPattern(record_destructure): expected record monotype, found '{s}'", - .{@tagName(self.store.monotype_store.getMonotype(monotype))}, - ), - }; - const mono_fields_for_defaults = self.store.monotype_store.getFields(mono_field_span); + const mono_resolved = self.store.monotype_store.resolve(monotype); + const mono_fields_for_defaults: []const Monotype.FieldKey = if (mono_resolved.kind == .record) + self.store.monotype_store.recordFields(mono_resolved) + else if (mono_resolved.isUnit()) + &.{} + else + typeBindingInvariant( + "lowerPattern(record_destructure): expected record monotype, found kind={d}", + .{@intFromEnum(mono_resolved.kind)}, + ); for (mono_fields_for_defaults) |mono_field| { try self.scratch_pattern_ids.append( - try self.store.addPattern(self.allocator, .wildcard, mono_field.type_idx), + try self.store.addPattern(self.allocator, .wildcard, mono_field.ty), ); } @@ -3331,7 +3014,7 @@ fn lowerPattern(self: *Self, module_env: *const ModuleEnv, pattern_idx: CIR.Patt const destruct = module_env.store.getRecordDestruct(destruct_idx); const pat_idx = destruct.kind.toPatternIdx(); const mir_pat = try self.lowerPattern(module_env, pat_idx); - const mono_fields = self.store.monotype_store.getFields(mono_field_span); + const mono_fields = self.store.monotype_store.recordFields(mono_resolved); const field_idx = self.recordFieldIndexByName(destruct.label, mono_fields); self.scratch_pattern_ids.items.items[@intCast(pats_top + field_idx)] = mir_pat; } @@ -3476,7 +3159,6 @@ fn lowerClosure(self: *Self, module_env: *const ModuleEnv, closure: CIR.Expr.Clo self.types_store, cap_type_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); try self.mono_scratches.idxs.append(cap_monotype); @@ -3497,10 +3179,7 @@ fn lowerClosure(self: *Self, module_env: *const ModuleEnv, closure: CIR.Expr.Clo try capture_lookup_exprs_snapshot.appendSlice(self.allocator, capture_lookup_exprs); // --- Step 2: Create captures tuple monotype --- - const captures_tuple_elems = try self.store.monotype_store.addIdxSpan(self.allocator, capture_monotypes_snapshot.items); - const captures_tuple_monotype = try self.store.monotype_store.addMonotype(self.allocator, .{ .tuple = .{ - .elems = captures_tuple_elems, - } }); + const captures_tuple_monotype = try self.store.monotype_store.internTuple(capture_monotypes_snapshot.items); // --- Step 3: Create the lifted function symbol --- // Use closure's tag_name as the basis if available, else synthesize @@ -3582,9 +3261,8 @@ fn lowerClosure(self: *Self, module_env: *const ModuleEnv, closure: CIR.Expr.Clo const all_params = try self.store.addPatternSpan(self.allocator, self.scratch_pattern_ids.sliceFromStart(pat_top)); // Build the lifted function's monotype: func(orig_args..., captures_tuple) -> ret - const orig_monotype = self.store.monotype_store.getMonotype(monotype); - const orig_func = orig_monotype.func; - const orig_arg_monos = self.store.monotype_store.getIdxSpan(orig_func.args); + const resolved_mono = self.store.monotype_store.resolve(monotype); + const orig_arg_monos = self.store.monotype_store.funcArgs(resolved_mono); const arg_top = self.mono_scratches.idxs.top(); defer self.mono_scratches.idxs.clearFrom(arg_top); for (orig_arg_monos) |am| { @@ -3593,8 +3271,8 @@ fn lowerClosure(self: *Self, module_env: *const ModuleEnv, closure: CIR.Expr.Clo try self.mono_scratches.idxs.append(captures_tuple_monotype); const lifted_func_monotype = try self.buildFuncMonotype( self.mono_scratches.idxs.sliceFromStart(arg_top), - orig_func.ret, - orig_func.effectful, + self.store.monotype_store.funcRet(resolved_mono), + self.store.monotype_store.funcEffectful(resolved_mono), ); // Create the lifted lambda expression @@ -3663,13 +3341,9 @@ fn lowerDeferredBlockLambda( // contaminated by earlier entries from this block/module. const saved_type_var_seen = self.type_var_seen; self.type_var_seen = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); - const saved_nominal_cycle_breakers = self.nominal_cycle_breakers; - self.nominal_cycle_breakers = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); defer { self.type_var_seen.deinit(); self.type_var_seen = saved_type_var_seen; - self.nominal_cycle_breakers.deinit(); - self.nominal_cycle_breakers = saved_nominal_cycle_breakers; } if (!caller_monotype.isNone()) { @@ -3759,11 +3433,12 @@ fn lowerCallWithLoweredFunc( ) Allocator.Error!MIR.ExprId { const lowered_func = try self.stabilizeEscapingFunctionExpr(lowered_func_input); const func_mono = self.store.typeOf(lowered_func); - const call_result_monotype = switch (self.store.monotype_store.getMonotype(func_mono)) { - .func => |f| f.ret, - else => monotype, - }; - if (self.store.monotype_store.getMonotype(func_mono) == .func and self.store.getExpr(lowered_func) != .lookup) { + const func_mono_resolved = self.store.monotype_store.resolve(func_mono); + const call_result_monotype = if (func_mono_resolved.kind == .func) + self.store.monotype_store.funcRet(func_mono_resolved) + else + monotype; + if (func_mono_resolved.kind == .func and self.store.getExpr(lowered_func) != .lookup) { const func_bind = try self.makeSyntheticBind(func_mono, false); try self.registerBoundSymbolDefIfNeeded(func_bind.pattern, lowered_func); const func_lookup = try self.emitMirLookup(func_bind.symbol, func_mono, region); @@ -4065,7 +3740,7 @@ fn lowerBlock(self: *Self, module_env: *const ModuleEnv, block: anytype, monotyp const lowered_monotype = self.store.typeOf(lowered); const pattern_monotype = self.store.patternTypeOf(slot.pattern); - const stmt_pattern = if (try self.monotypesStructurallyEqual(pattern_monotype, lowered_monotype)) + const stmt_pattern = if (pattern_monotype == lowered_monotype) slot.pattern else try self.store.addPattern(self.allocator, .{ .bind = slot.symbol }, lowered_monotype); @@ -4095,7 +3770,7 @@ fn lowerBinop(self: *Self, expr_idx: CIR.Expr.Idx, binop: CIR.Expr.Binop, monoty .@"and" => { const cond = try self.lowerExpr(binop.lhs); const body_true = try self.lowerExpr(binop.rhs); - const bool_monotype = try self.store.monotype_store.addBoolTagUnion(self.allocator, self.currentCommonIdents()); + const bool_monotype = try self.store.monotype_store.addBoolTagUnion(); const false_expr = try self.store.addExpr(self.allocator, .{ .tag = .{ .name = module_env.idents.false_tag, .args = MIR.ExprSpan.empty(), @@ -4107,7 +3782,7 @@ fn lowerBinop(self: *Self, expr_idx: CIR.Expr.Idx, binop: CIR.Expr.Binop, monoty .@"or" => { const cond = try self.lowerExpr(binop.lhs); const body_else = try self.lowerExpr(binop.rhs); - const bool_monotype = try self.store.monotype_store.addBoolTagUnion(self.allocator, self.currentCommonIdents()); + const bool_monotype = try self.store.monotype_store.addBoolTagUnion(); const true_expr = try self.store.addExpr(self.allocator, .{ .tag = .{ .name = module_env.idents.true_tag, .args = MIR.ExprSpan.empty(), @@ -4128,8 +3803,8 @@ fn lowerBinop(self: *Self, expr_idx: CIR.Expr.Idx, binop: CIR.Expr.Binop, monoty // Equality on structural types is decomposed field-by-field in MIR // rather than dispatched to a nominal method. if (binop.op == .eq or binop.op == .ne) { - const lhs_mono = self.store.monotype_store.getMonotype(lhs_monotype); - switch (lhs_mono) { + const lhs_resolved = self.store.monotype_store.resolve(lhs_monotype); + switch (lhs_resolved.kind) { // Records, tuples, and lists are always structural. .record, .tuple, .list => { const result = try self.lowerStructuralEquality(lhs, rhs, lhs_monotype, monotype, region); @@ -4149,21 +3824,22 @@ fn lowerBinop(self: *Self, expr_idx: CIR.Expr.Idx, binop: CIR.Expr.Binop, monoty } // Nominal tag union — fall through to method call dispatch below. }, - // Unit is always equal. - .unit => return try self.emitMirBoolLiteral(module_env, binop.op == .eq, region), - // Primitives: emit low-level eq op directly. - .prim => |p| { - const op: CIR.Expr.LowLevel = switch (p) { - .str => .str_is_eq, - else => .num_is_eq, - }; - const args = try self.store.addExprSpan(self.allocator, &.{ lhs, rhs }); - const result = try self.store.addExpr(self.allocator, .{ .run_low_level = .{ - .op = op, - .args = args, - } }, monotype, region); - if (binop.op == .ne) return try self.negBool(module_env, result, monotype, region); - return result; + // Builtins: unit is always equal, prims use low-level eq. + .builtin => { + if (lhs_resolved.isUnit()) return try self.emitMirBoolLiteral(module_env, binop.op == .eq, region); + if (lhs_resolved.builtinPrim()) |p| { + const op: CIR.Expr.LowLevel = switch (p) { + .str => .str_is_eq, + else => .num_is_eq, + }; + const args = try self.store.addExprSpan(self.allocator, &.{ lhs, rhs }); + const result = try self.store.addExpr(self.allocator, .{ .run_low_level = .{ + .op = op, + .args = args, + } }, monotype, region); + if (binop.op == .ne) return try self.negBool(module_env, result, monotype, region); + return result; + } }, else => {}, } @@ -4250,7 +3926,7 @@ fn createBoolMatch( monotype: Monotype.Idx, region: Region, ) Allocator.Error!MIR.ExprId { - const bool_monotype = try self.store.monotype_store.addBoolTagUnion(self.allocator, self.currentCommonIdents()); + const bool_monotype = try self.store.monotype_store.addBoolTagUnion(); const true_pattern = try self.store.addPattern(self.allocator, .{ .tag = .{ .name = module_env.idents.true_tag, @@ -4274,7 +3950,7 @@ fn createBoolMatch( /// Negate a Bool: `match expr { True => False, _ => True }` fn negBool(self: *Self, module_env: *const ModuleEnv, expr: MIR.ExprId, monotype: Monotype.Idx, region: Region) Allocator.Error!MIR.ExprId { - const bool_monotype = try self.store.monotype_store.addBoolTagUnion(self.allocator, self.currentCommonIdents()); + const bool_monotype = try self.store.monotype_store.addBoolTagUnion(); const false_expr = try self.store.addExpr(self.allocator, .{ .tag = .{ .name = module_env.idents.false_tag, .args = MIR.ExprSpan.empty(), @@ -4303,9 +3979,14 @@ fn lowerStructuralEquality( region: Region, ) Allocator.Error!MIR.ExprId { const module_env = self.all_module_envs[self.current_module_idx]; - const mono = self.store.monotype_store.getMonotype(operand_monotype); - return switch (mono) { - .unit => try self.emitMirBoolLiteral(module_env, true, region), + const resolved = self.store.monotype_store.resolve(operand_monotype); + return switch (resolved.kind) { + .builtin => if (resolved.isUnit()) try self.emitMirBoolLiteral(module_env, true, region) else { + if (std.debug.runtime_safety) { + std.debug.panic("lowerStructuralEquality: unexpected builtin monotype", .{}); + } + unreachable; + }, .record, .tuple, .tag_union, .list => blk: { // Structural equality may inspect the same operand many times; bind // each side once so downstream helpers only work with stable lookups. @@ -4314,11 +3995,11 @@ fn lowerStructuralEquality( const lhs_lookup = try self.emitMirLookup(lhs_bind.symbol, operand_monotype, region); const rhs_lookup = try self.emitMirLookup(rhs_bind.symbol, operand_monotype, region); - const inner = switch (mono) { - .record => |rec| try self.lowerRecordEquality(module_env, lhs_lookup, rhs_lookup, rec, ret_monotype, region), - .tuple => |tup| try self.lowerTupleEquality(module_env, lhs_lookup, rhs_lookup, tup, ret_monotype, region), - .tag_union => |tu| try self.lowerTagUnionEquality(module_env, lhs_lookup, rhs_lookup, tu, operand_monotype, ret_monotype, region), - .list => |lst| try self.lowerListEquality(module_env, lhs_lookup, rhs_lookup, lst, ret_monotype, region), + const inner = switch (resolved.kind) { + .record => try self.lowerRecordEquality(module_env, lhs_lookup, rhs_lookup, resolved, ret_monotype, region), + .tuple => try self.lowerTupleEquality(module_env, lhs_lookup, rhs_lookup, resolved, ret_monotype, region), + .tag_union => try self.lowerTagUnionEquality(module_env, lhs_lookup, rhs_lookup, resolved, operand_monotype, ret_monotype, region), + .list => try self.lowerListEquality(module_env, lhs_lookup, rhs_lookup, resolved, ret_monotype, region), else => unreachable, }; @@ -4334,7 +4015,7 @@ fn lowerStructuralEquality( }, else => { if (std.debug.runtime_safety) { - std.debug.panic("lowerStructuralEquality: unexpected monotype {s}", .{@tagName(mono)}); + std.debug.panic("lowerStructuralEquality: unexpected monotype kind={d}", .{@intFromEnum(resolved.kind)}); } unreachable; }, @@ -4353,28 +4034,34 @@ fn lowerFieldEquality( ret_monotype: Monotype.Idx, region: Region, ) Allocator.Error!MIR.ExprId { - const mono = self.store.monotype_store.getMonotype(field_monotype); - switch (mono) { - .prim => |p| { - const op: CIR.Expr.LowLevel = switch (p) { - .str => .str_is_eq, - else => .num_is_eq, - }; - const args = try self.store.addExprSpan(self.allocator, &.{ lhs, rhs }); - return try self.store.addExpr(self.allocator, .{ .run_low_level = .{ - .op = op, - .args = args, - } }, ret_monotype, region); + const resolved = self.store.monotype_store.resolve(field_monotype); + switch (resolved.kind) { + .builtin => { + if (resolved.isUnit()) { + return try self.emitMirBoolLiteral(module_env, true, region); + } + if (resolved.builtinPrim()) |p| { + const op: CIR.Expr.LowLevel = switch (p) { + .str => .str_is_eq, + else => .num_is_eq, + }; + const args = try self.store.addExprSpan(self.allocator, &.{ lhs, rhs }); + return try self.store.addExpr(self.allocator, .{ .run_low_level = .{ + .op = op, + .args = args, + } }, ret_monotype, region); + } + if (std.debug.runtime_safety) { + std.debug.panic("lowerFieldEquality: unexpected builtin monotype", .{}); + } + unreachable; }, .record, .tuple, .tag_union, .list => { return try self.lowerStructuralEquality(lhs, rhs, field_monotype, ret_monotype, region); }, - .unit => { - return try self.emitMirBoolLiteral(module_env, true, region); - }, else => { if (std.debug.runtime_safety) { - std.debug.panic("lowerFieldEquality: unexpected field monotype {s}", .{@tagName(mono)}); + std.debug.panic("lowerFieldEquality: unexpected field monotype kind={d}", .{@intFromEnum(resolved.kind)}); } unreachable; }, @@ -4396,25 +4083,24 @@ fn lowerRecordEquality( self.debugAssertLookupExpr(lhs, "lowerRecordEquality(lhs)"); self.debugAssertLookupExpr(rhs, "lowerRecordEquality(rhs)"); - const field_span = rec.fields; - const fields = self.store.monotype_store.getFields(field_span); + const fields = self.store.monotype_store.recordFields(rec); // Empty record: always equal if (fields.len == 0) return try self.emitMirBoolLiteral(module_env, true, region); // Build field comparisons from last to first (innermost result first). var result: MIR.ExprId = undefined; - var i: usize = field_span.len; + var i: usize = fields.len; while (i > 0) { i -= 1; - const field = self.store.monotype_store.getFields(field_span)[i]; + const field = self.store.monotype_store.recordFields(rec)[i]; - const lhs_field = try self.emitMirStructAccess(lhs, @intCast(i), field.type_idx, region); - const rhs_field = try self.emitMirStructAccess(rhs, @intCast(i), field.type_idx, region); + const lhs_field = try self.emitMirStructAccess(lhs, @intCast(i), field.ty, region); + const rhs_field = try self.emitMirStructAccess(rhs, @intCast(i), field.ty, region); - const field_eq = try self.lowerFieldEquality(module_env, lhs_field, rhs_field, field.type_idx, ret_monotype, region); + const field_eq = try self.lowerFieldEquality(module_env, lhs_field, rhs_field, field.ty, ret_monotype, region); - if (i == field_span.len - 1) { + if (i == fields.len - 1) { result = field_eq; } else { // Short-circuit AND: match field_eq { True => , _ => False } @@ -4439,25 +4125,24 @@ fn lowerTupleEquality( self.debugAssertLookupExpr(lhs, "lowerTupleEquality(lhs)"); self.debugAssertLookupExpr(rhs, "lowerTupleEquality(rhs)"); - const elem_span = tup.elems; - const elems = self.store.monotype_store.getIdxSpan(elem_span); + const elems = self.store.monotype_store.tupleElems(tup); // Empty tuple: always equal if (elems.len == 0) return try self.emitMirBoolLiteral(module_env, true, region); // Build element comparisons from last to first. var result: MIR.ExprId = undefined; - var i: usize = elem_span.len; + var i: usize = elems.len; while (i > 0) { i -= 1; - const elem_mono = self.store.monotype_store.getIdxSpan(elem_span)[i]; + const elem_mono = self.store.monotype_store.tupleElems(tup)[i]; const lhs_elem = try self.emitMirStructAccess(lhs, @intCast(i), elem_mono, region); const rhs_elem = try self.emitMirStructAccess(rhs, @intCast(i), elem_mono, region); const elem_eq = try self.lowerFieldEquality(module_env, lhs_elem, rhs_elem, elem_mono, ret_monotype, region); - if (i == elem_span.len - 1) { + if (i == elems.len - 1) { result = elem_eq; } else { const false_expr = try self.emitMirBoolLiteral(module_env, false, region); @@ -4484,8 +4169,7 @@ fn lowerTagUnionEquality( self.debugAssertLookupExpr(lhs, "lowerTagUnionEquality(lhs)"); self.debugAssertLookupExpr(rhs, "lowerTagUnionEquality(rhs)"); - const tag_span = tu.tags; - const tags = self.store.monotype_store.getTags(tag_span); + const tags = self.store.monotype_store.tagUnionTags(tu); // Empty tag union: vacuously true if (tags.len == 0) return try self.emitMirBoolLiteral(module_env, true, region); @@ -4493,17 +4177,17 @@ fn lowerTagUnionEquality( const save_branches = self.scratch_branches.top(); defer self.scratch_branches.clearFrom(save_branches); - for (0..tag_span.len) |tag_i| { - const current_tags = self.store.monotype_store.getTags(tag_span); + for (0..tags.len) |tag_i| { + const current_tags = self.store.monotype_store.tagUnionTags(tu); const tag = current_tags[tag_i]; - const payload_span = tag.payloads; + const payloads = self.store.monotype_store.getIdxListItems(tag.payloads); // --- LHS pattern: Tag(lhs_p0, lhs_p1, ...) --- const save_pats = self.scratch_pattern_ids.top(); const save_caps = self.scratch_captures.top(); defer self.scratch_captures.clearFrom(save_caps); - for (self.store.monotype_store.getIdxSpan(payload_span)) |payload_mono| { + for (payloads) |payload_mono| { const bind = try self.makeSyntheticBind(payload_mono, false); try self.scratch_pattern_ids.append(bind.pattern); try self.scratch_captures.append(.{ .symbol = bind.symbol }); @@ -4513,10 +4197,11 @@ fn lowerTagUnionEquality( self.scratch_pattern_ids.sliceFromStart(save_pats), ); self.scratch_pattern_ids.clearFrom(save_pats); - const lhs_tag_args = try self.wrapMultiPayloadTagPatterns(tag.name, tu_monotype, lhs_payload_patterns); + const tag_ident = self.identFromStructuralName(tag.name); + const lhs_tag_args = try self.wrapMultiPayloadTagPatterns(tag_ident, tu_monotype, lhs_payload_patterns); const lhs_tag_pattern = try self.store.addPattern(self.allocator, .{ .tag = .{ - .name = tag.name, + .name = tag_ident, .args = lhs_tag_args, } }, tu_monotype); const lhs_bp = try self.store.addBranchPatterns(self.allocator, &.{.{ @@ -4525,7 +4210,7 @@ fn lowerTagUnionEquality( }}); // --- RHS pattern: Tag(rhs_p0, rhs_p1, ...) --- - for (self.store.monotype_store.getIdxSpan(payload_span)) |payload_mono| { + for (payloads) |payload_mono| { const bind = try self.makeSyntheticBind(payload_mono, false); try self.scratch_pattern_ids.append(bind.pattern); try self.scratch_captures.append(.{ .symbol = bind.symbol }); @@ -4535,10 +4220,10 @@ fn lowerTagUnionEquality( self.scratch_pattern_ids.sliceFromStart(save_pats), ); self.scratch_pattern_ids.clearFrom(save_pats); - const rhs_tag_args = try self.wrapMultiPayloadTagPatterns(tag.name, tu_monotype, rhs_payload_patterns); + const rhs_tag_args = try self.wrapMultiPayloadTagPatterns(tag_ident, tu_monotype, rhs_payload_patterns); const rhs_tag_pattern = try self.store.addPattern(self.allocator, .{ .tag = .{ - .name = tag.name, + .name = tag_ident, .args = rhs_tag_args, } }, tu_monotype); const rhs_bp = try self.store.addBranchPatterns(self.allocator, &.{.{ @@ -4548,23 +4233,23 @@ fn lowerTagUnionEquality( // --- Build payload comparison --- const all_caps = self.scratch_captures.sliceFromStart(save_caps); - const payload_eq = if (payload_span.len == 0) + const payload_eq = if (payloads.len == 0) try self.emitMirBoolLiteral(module_env, true, region) else blk: { - const lhs_caps = all_caps[0..payload_span.len]; - const rhs_caps = all_caps[payload_span.len..][0..payload_span.len]; + const lhs_caps = all_caps[0..payloads.len]; + const rhs_caps = all_caps[payloads.len..][0..payloads.len]; // Chain payload comparisons with short-circuit AND (last to first) var payload_result: MIR.ExprId = undefined; - var j: usize = payload_span.len; + var j: usize = payloads.len; while (j > 0) { j -= 1; - const payload_mono = self.store.monotype_store.getIdxSpan(payload_span)[j]; + const payload_mono = self.store.monotype_store.getIdxListItems(tag.payloads)[j]; const lhs_lookup = try self.emitMirLookup(lhs_caps[j].symbol, payload_mono, region); const rhs_lookup = try self.emitMirLookup(rhs_caps[j].symbol, payload_mono, region); const field_eq = try self.lowerFieldEquality(module_env, lhs_lookup, rhs_lookup, payload_mono, ret_monotype, region); - if (j == payload_span.len - 1) { + if (j == payloads.len - 1) { payload_result = field_eq; } else { const false_expr = try self.emitMirBoolLiteral(module_env, false, region); @@ -4638,7 +4323,7 @@ fn lowerListEquality( self.debugAssertLookupExpr(rhs, "lowerListEquality(rhs)"); const u64_mono = self.store.monotype_store.primIdx(.u64); - const bool_mono = try self.store.monotype_store.addBoolTagUnion(self.allocator, self.currentCommonIdents()); + const bool_mono = try self.store.monotype_store.addBoolTagUnion(); const unit_mono = self.store.monotype_store.unit_idx; // len = list_len(lhs) @@ -4694,17 +4379,17 @@ fn lowerListEquality( const lhs_elem = try self.store.addExpr(self.allocator, .{ .run_low_level = .{ .op = .list_get_unsafe, .args = lhs_get_args, - } }, lst.elem, region); + } }, self.store.monotype_store.listElem(lst), region); const i_lookup_rhs = try self.emitMirLookup(i_bind.symbol, u64_mono, region); const rhs_get_args = try self.store.addExprSpan(self.allocator, &.{ rhs, i_lookup_rhs }); const rhs_elem = try self.store.addExpr(self.allocator, .{ .run_low_level = .{ .op = .list_get_unsafe, .args = rhs_get_args, - } }, lst.elem, region); + } }, self.store.monotype_store.listElem(lst), region); // Compare elements - const elem_eq = try self.lowerFieldEquality(module_env, lhs_elem, rhs_elem, lst.elem, ret_monotype, region); + const elem_eq = try self.lowerFieldEquality(module_env, lhs_elem, rhs_elem, self.store.monotype_store.listElem(lst), ret_monotype, region); // i = num_plus(i, 1) const i_lookup_inc = try self.emitMirLookup(i_bind.symbol, u64_mono, region); @@ -4783,10 +4468,11 @@ fn lowerDotAccess(self: *Self, module_env: *const ModuleEnv, expr_idx: CIR.Expr. const receiver = try self.lowerExpr(da.receiver); const rcv_mono_idx = try self.resolveMonotype(da.receiver); if (rcv_mono_idx.isNone()) break :structural_eq; - const rcv_mono = self.store.monotype_store.getMonotype(rcv_mono_idx); - switch (rcv_mono) { - // Records, tuples, lists, and unit are always structural. - .record, .tuple, .list, .unit => {}, + const rcv_resolved = self.store.monotype_store.resolve(rcv_mono_idx); + switch (rcv_resolved.kind) { + // Records, tuples, lists are always structural. Unit (builtin) too. + .record, .tuple, .list => {}, + .builtin => if (!rcv_resolved.isUnit()) break :structural_eq, // Tag unions may be nominal or anonymous structural. .tag_union => if (self.lookupResolvedDispatchTarget(expr_idx) != null) break :structural_eq, else => break :structural_eq, @@ -4829,21 +4515,9 @@ fn lowerDotAccess(self: *Self, module_env: *const ModuleEnv, expr_idx: CIR.Expr. } } self.type_var_seen = call_type_var_seen; - const saved_nominal_cycle_breakers = self.nominal_cycle_breakers; - var call_nominal_cycle_breakers = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); - errdefer call_nominal_cycle_breakers.deinit(); - { - var it = saved_nominal_cycle_breakers.iterator(); - while (it.next()) |entry| { - try call_nominal_cycle_breakers.put(entry.key_ptr.*, entry.value_ptr.*); - } - } - self.nominal_cycle_breakers = call_nominal_cycle_breakers; defer { self.type_var_seen.deinit(); self.type_var_seen = saved_type_var_seen; - self.nominal_cycle_breakers.deinit(); - self.nominal_cycle_breakers = saved_nominal_cycle_breakers; } var method_effectful = false; @@ -4890,20 +4564,16 @@ fn lowerDotAccess(self: *Self, module_env: *const ModuleEnv, expr_idx: CIR.Expr. self.types_store, resolved_target.fn_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); if (!dispatch_func_monotype.isNone()) { - const dispatch_mono = self.store.monotype_store.getMonotype(dispatch_func_monotype); - const dispatch_func = switch (dispatch_mono) { - .func => |f| f, - else => { - typeBindingInvariant( - "lowerDotAccess: dispatch fn_var monotype is not function (method='{s}', monotype='{s}')", - .{ module_env.getIdent(da.field_name), @tagName(dispatch_mono) }, - ); - }, - }; - const dispatch_args = self.store.monotype_store.getIdxSpan(dispatch_func.args); + const dispatch_resolved = self.store.monotype_store.resolve(dispatch_func_monotype); + if (dispatch_resolved.kind != .func) { + typeBindingInvariant( + "lowerDotAccess: dispatch fn_var monotype is not function (method='{s}', kind={d})", + .{ module_env.getIdent(da.field_name), @intFromEnum(dispatch_resolved.kind) }, + ); + } + const dispatch_args = self.store.monotype_store.funcArgs(dispatch_resolved); if (dispatch_args.len > 0) { expected_param_monos = dispatch_args; } @@ -4938,14 +4608,13 @@ fn lowerDotAccess(self: *Self, module_env: *const ModuleEnv, expr_idx: CIR.Expr. self.types_store, resolved_target.fn_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); if (!refined.isNone()) { - const refined_mono = self.store.monotype_store.getMonotype(refined); - if (refined_mono != .func) { + const resolved_refined = self.store.monotype_store.resolve(refined); + if (resolved_refined.kind != .func) { typeBindingInvariant( - "lowerDotAccess: refined dispatch fn_var monotype is not function (method='{s}', monotype='{s}')", - .{ module_env.getIdent(da.field_name), @tagName(refined_mono) }, + "lowerDotAccess: refined dispatch fn_var monotype is not function (method='{s}', kind={d})", + .{ module_env.getIdent(da.field_name), @intFromEnum(resolved_refined.kind) }, ); } break :blk refined; @@ -4962,10 +4631,11 @@ fn lowerDotAccess(self: *Self, module_env: *const ModuleEnv, expr_idx: CIR.Expr. method_effectful, ); }; - const call_result_monotype = switch (self.store.monotype_store.getMonotype(method_func_monotype)) { - .func => |f| f.ret, - else => monotype, - }; + const mfm_resolved = self.store.monotype_store.resolve(method_func_monotype); + const call_result_monotype = if (mfm_resolved.kind == .func) + self.store.monotype_store.funcRet(mfm_resolved) + else + monotype; // Ensure the method body is lowered so codegen can find it. const lowered_method_symbol = try self.specializeMethod(method_symbol, method_func_monotype); @@ -4981,16 +4651,16 @@ fn lowerDotAccess(self: *Self, module_env: *const ModuleEnv, expr_idx: CIR.Expr. // Field access const receiver = try self.lowerExpr(da.receiver); const receiver_monotype = self.store.typeOf(receiver); - const receiver_record = switch (self.store.monotype_store.getMonotype(receiver_monotype)) { - .record => |record| record, - else => typeBindingInvariant( - "lowerDotAccess: field access receiver is not a record monotype (field='{s}', monotype='{s}')", - .{ module_env.getIdent(da.field_name), @tagName(self.store.monotype_store.getMonotype(receiver_monotype)) }, - ), - }; + const receiver_resolved = self.store.monotype_store.resolve(receiver_monotype); + if (receiver_resolved.kind != .record) { + typeBindingInvariant( + "lowerDotAccess: field access receiver is not a record monotype (field='{s}', kind={d})", + .{ module_env.getIdent(da.field_name), @intFromEnum(receiver_resolved.kind) }, + ); + } const field_idx = self.recordFieldIndexByName( da.field_name, - self.store.monotype_store.getFields(receiver_record.fields), + self.store.monotype_store.recordFields(receiver_resolved), ); return try self.emitMirStructAccess(receiver, field_idx, monotype, region); } @@ -4999,14 +4669,10 @@ fn lowerDotAccess(self: *Self, module_env: *const ModuleEnv, expr_idx: CIR.Expr. /// Lower a CIR record expression. fn lowerRecord(self: *Self, module_env: *const ModuleEnv, record: anytype, monotype: Monotype.Idx, region: Region) Allocator.Error!MIR.ExprId { const cir_field_indices = module_env.store.sliceRecordFields(record.fields); - const mono_record = switch (self.store.monotype_store.getMonotype(monotype)) { - .record => |mono_record| mono_record, - else => typeBindingInvariant( - "lowerRecord: expected record monotype, found '{s}'", - .{@tagName(self.store.monotype_store.getMonotype(monotype))}, - ), - }; - const mono_field_span = mono_record.fields; + const record_resolved = self.store.monotype_store.resolve(monotype); + if (record_resolved.kind != .record) { + typeBindingInvariant("lowerRecord: expected record monotype", .{}); + } const ProvidedField = struct { name: Ident.Idx, @@ -5034,11 +4700,12 @@ fn lowerRecord(self: *Self, module_env: *const ModuleEnv, record: anytype, monot const exprs_top = self.scratch_expr_ids.top(); defer self.scratch_expr_ids.clearFrom(exprs_top); - const canonical_field_exprs = try self.allocator.alloc(?MIR.ExprId, mono_field_span.len); + const record_mono_fields = self.store.monotype_store.recordFields(record_resolved); + const canonical_field_exprs = try self.allocator.alloc(?MIR.ExprId, record_mono_fields.len); defer self.allocator.free(canonical_field_exprs); @memset(canonical_field_exprs, null); - const mono_fields = self.store.monotype_store.getFields(mono_field_span); + const mono_fields = record_mono_fields; for (provided_fields.items) |provided| { const field_idx = self.recordFieldIndexByName(provided.name, mono_fields); canonical_field_exprs[@intCast(field_idx)] = provided.expr; @@ -5064,12 +4731,12 @@ fn lowerRecord(self: *Self, module_env: *const ModuleEnv, record: anytype, monot // Record update: include all fields in the resulting record. // Updated fields use explicit expressions; missing fields become accesses on the // base record expression from `..record`. - const updated_mono_fields = self.store.monotype_store.getFields(mono_field_span); + const updated_mono_fields = self.store.monotype_store.recordFields(record_resolved); for (updated_mono_fields, 0..) |mono_field, field_idx| { const field_expr = canonical_field_exprs[field_idx] orelse try self.emitMirStructAccess( extension_binding.?.lookup, @intCast(field_idx), - mono_field.type_idx, + mono_field.ty, region, ); @@ -5280,13 +4947,9 @@ fn resolveDispatchTargetForExpr( return resolved; } -fn monotypeDispatchCompatible( - self: *Self, - expected: Monotype.Idx, - actual: Monotype.Idx, -) Allocator.Error!bool { +fn monotypeDispatchCompatible(expected: Monotype.Idx, actual: Monotype.Idx) bool { if (expected.isNone() or actual.isNone()) return true; - return try self.monotypesStructurallyEqual(expected, actual); + return expected == actual; } fn resolveDispatchTargetForDotCall( @@ -5327,7 +4990,6 @@ fn resolveDispatchTargetForDotCall( self.types_store, constraint.fn_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); if (fn_mono.isNone()) { if (!constraint.resolved_target.isNone()) { @@ -5338,13 +5000,13 @@ fn resolveDispatchTargetForDotCall( continue; } - const mono = self.store.monotype_store.getMonotype(fn_mono); - if (mono != .func) continue; - const fn_args = self.store.monotype_store.getIdxSpan(mono.func.args); + const fn_resolved = self.store.monotype_store.resolve(fn_mono); + if (fn_resolved.kind != .func) continue; + const fn_args = self.store.monotype_store.funcArgs(fn_resolved); const compatible = if (fn_args.len == 0) true else - try self.monotypeDispatchCompatible(fn_args[0], receiver_monotype); + monotypeDispatchCompatible(fn_args[0], receiver_monotype); if (!compatible) continue; if (!constraint.resolved_target.isNone()) { @@ -5378,7 +5040,6 @@ fn resolveUnresolvedTypeVarDispatchTarget( self.types_store, constraint.fn_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); if (desired_func_monotype.isNone()) { if (std.debug.runtime_safety) { @@ -5420,7 +5081,7 @@ fn resolveUnresolvedTypeVarDispatchTarget( self.current_module_idx, ); } - if (!try self.monotypesStructurallyEqual(candidate_mono, desired_func_monotype)) continue; + if (candidate_mono != desired_func_monotype) continue; const candidate_origin_name = candidate_env.getIdent(candidate_env.qualified_module_ident); const mapped_origin = module_env.common.findIdent(candidate_origin_name) orelse module_env.qualified_module_ident; @@ -5476,8 +5137,6 @@ fn monotypeFromTypeVarInStore( ) Allocator.Error!Monotype.Idx { var local_seen = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); defer local_seen.deinit(); - var local_cycles = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); - defer local_cycles.deinit(); const saved_ident_store = self.mono_scratches.ident_store; const saved_module_env = self.mono_scratches.module_env; @@ -5493,7 +5152,6 @@ fn monotypeFromTypeVarInStore( store_types, var_, &local_seen, - &local_cycles, ); } @@ -5629,7 +5287,7 @@ fn specializeMethod(self: *Self, symbol: MIR.Symbol, caller_func_monotype: ?Mono ); const spec_key = polySpecKey(symbol_key, normalized_caller_monotype); - if (try self.lookupPolySpecialization(symbol_key, normalized_caller_monotype)) |spec_symbol| { + if (self.lookupPolySpecialization(symbol_key, normalized_caller_monotype)) |spec_symbol| { return spec_symbol; } @@ -5641,7 +5299,7 @@ fn specializeMethod(self: *Self, symbol: MIR.Symbol, caller_func_monotype: ?Mono const same_specialization = if (active_monotype.isNone()) false else - try self.monotypesStructurallyEqual(active_monotype, normalized_caller_monotype); + active_monotype == normalized_caller_monotype; if (!same_specialization) { const def_expr = self.findDefExprByMetadata(symbol_meta) orelse { @@ -5663,7 +5321,7 @@ fn specializeMethod(self: *Self, symbol: MIR.Symbol, caller_func_monotype: ?Mono if (self.lowered_symbols.get(symbol_key)) |cached_expr| { const cached_monotype = self.store.typeOf(cached_expr); - if (!try self.monotypesStructurallyEqual(cached_monotype, normalized_caller_monotype)) { + if (cached_monotype != normalized_caller_monotype) { const def_expr = self.findDefExprByMetadata(symbol_meta) orelse { if (std.debug.runtime_safety) { std.debug.panic( @@ -5758,7 +5416,6 @@ fn lowerExternalDefWithType(self: *Self, symbol: MIR.Symbol, cir_expr_idx: CIR.E self.types_store, target_type_var, &self.type_var_seen, - &self.nominal_cycle_breakers, ); if (!derived.isNone()) break :blk derived; @@ -5784,7 +5441,6 @@ fn lowerExternalDefWithType(self: *Self, symbol: MIR.Symbol, cir_expr_idx: CIR.E const saved_pattern_scope = self.current_pattern_scope; const saved_types_store = self.types_store; const saved_type_var_seen = self.type_var_seen; - const saved_nominal_cycle_breakers = self.nominal_cycle_breakers; const saved_ident_store = self.mono_scratches.ident_store; const saved_module_env = self.mono_scratches.module_env; self.current_pattern_scope = symbol_key; @@ -5799,7 +5455,6 @@ fn lowerExternalDefWithType(self: *Self, symbol: MIR.Symbol, cir_expr_idx: CIR.E // Reusing a shared cache across polymorphic specializations can pin flex // and rigid vars to an earlier specialization. self.type_var_seen = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); - self.nominal_cycle_breakers = std.AutoHashMap(types.Var, Monotype.Idx).init(self.allocator); try self.seedTypeScopeBindingsInStore( self.current_module_idx, @@ -5824,8 +5479,6 @@ fn lowerExternalDefWithType(self: *Self, symbol: MIR.Symbol, cir_expr_idx: CIR.E defer { self.type_var_seen.deinit(); self.type_var_seen = saved_type_var_seen; - self.nominal_cycle_breakers.deinit(); - self.nominal_cycle_breakers = saved_nominal_cycle_breakers; self.current_pattern_scope = saved_pattern_scope; if (switching_module) { self.types_store = saved_types_store; @@ -5876,45 +5529,49 @@ fn bindRecordFieldByName( self: *Self, field_name: Ident.Idx, field_var: types.Var, - mono_fields: []const Monotype.Field, + mono_fields: []const Monotype.FieldKey, ) Allocator.Error!void { const field_idx = self.recordFieldIndexByName(field_name, mono_fields); - try self.bindTypeVarMonotypes(field_var, mono_fields[@intCast(field_idx)].type_idx); + try self.bindTypeVarMonotypes(field_var, mono_fields[@intCast(field_idx)].ty); } fn recordFieldIndexByName( self: *Self, field_name: Ident.Idx, - mono_fields: []const Monotype.Field, + mono_fields: []const Monotype.FieldKey, ) u32 { + const field_name_text = self.all_module_envs[self.current_module_idx].getIdent(field_name); for (mono_fields, 0..) |mono_field, field_idx| { - if (self.identsStructurallyEqual(mono_field.name, field_name)) { + if (std.mem.eql(u8, self.store.monotype_store.getNameText(mono_field.name), field_name_text)) { return @intCast(field_idx); } } - const module_env = self.all_module_envs[self.current_module_idx]; typeBindingInvariant( "record field '{s}' missing from monotype", - .{module_env.getIdent(field_name)}, + .{field_name_text}, ); } fn tagIndexByName( self: *Self, tag_name: Ident.Idx, - mono_tags: []const Monotype.Tag, + mono_tags: []const Monotype.TagKey, ) u32 { + const tag_name_text = self.all_module_envs[self.current_module_idx].getIdent(tag_name); + const tag_last_segment = identLastSegment(tag_name_text); for (mono_tags, 0..) |mono_tag, tag_idx| { - if (self.identsTagNameEquivalent(mono_tag.name, tag_name)) { + const mono_tag_text = self.store.monotype_store.getNameText(mono_tag.name); + if (std.mem.eql(u8, mono_tag_text, tag_name_text) or + std.mem.eql(u8, identLastSegment(mono_tag_text), tag_last_segment)) + { return @intCast(tag_idx); } } - const module_env = self.all_module_envs[self.current_module_idx]; typeBindingInvariant( "tag '{s}' missing from monotype", - .{module_env.getIdent(tag_name)}, + .{tag_name_text}, ); } @@ -5936,10 +5593,10 @@ fn appendSeenIndex( fn remainingRecordTailMonotype( self: *Self, - mono_fields: []const Monotype.Field, + mono_fields: []const Monotype.FieldKey, seen_indices: []const u32, ) Allocator.Error!Monotype.Idx { - var remaining_fields: std.ArrayListUnmanaged(Monotype.Field) = .empty; + var remaining_fields: std.ArrayListUnmanaged(Monotype.FieldKey) = .empty; defer remaining_fields.deinit(self.allocator); for (mono_fields, 0..) |field, field_idx| { @@ -5951,16 +5608,15 @@ fn remainingRecordTailMonotype( return self.store.monotype_store.unit_idx; } - const field_span = try self.store.monotype_store.addFields(self.allocator, remaining_fields.items); - return try self.store.monotype_store.addMonotype(self.allocator, .{ .record = .{ .fields = field_span } }); + return try self.store.monotype_store.internRecord(remaining_fields.items); } fn remainingTagUnionTailMonotype( self: *Self, - mono_tags: []const Monotype.Tag, + mono_tags: []const Monotype.TagKey, seen_indices: []const u32, ) Allocator.Error!Monotype.Idx { - var remaining_tags: std.ArrayListUnmanaged(Monotype.Tag) = .empty; + var remaining_tags: std.ArrayListUnmanaged(Monotype.TagKey) = .empty; defer remaining_tags.deinit(self.allocator); for (mono_tags, 0..) |tag, tag_idx| { @@ -5968,8 +5624,7 @@ fn remainingTagUnionTailMonotype( try remaining_tags.append(self.allocator, tag); } - const tag_span = try self.store.monotype_store.addTags(self.allocator, remaining_tags.items); - return try self.store.monotype_store.addMonotype(self.allocator, .{ .tag_union = .{ .tags = tag_span } }); + return try self.store.monotype_store.internTagUnion(remaining_tags.items); } fn bindRecordRowTailInStore( @@ -5978,7 +5633,7 @@ fn bindRecordRowTailInStore( common_idents: ModuleEnv.CommonIdents, bindings: *std.AutoHashMap(types.Var, Monotype.Idx), ext_var: types.Var, - mono_fields: []const Monotype.Field, + mono_fields: []const Monotype.FieldKey, seen_indices: []const u32, ) Allocator.Error!void { const tail_mono = try self.remainingRecordTailMonotype(mono_fields, seen_indices); @@ -5988,7 +5643,7 @@ fn bindRecordRowTailInStore( fn bindRecordRowTail( self: *Self, ext_var: types.Var, - mono_fields: []const Monotype.Field, + mono_fields: []const Monotype.FieldKey, seen_indices: []const u32, ) Allocator.Error!void { const tail_mono = try self.remainingRecordTailMonotype(mono_fields, seen_indices); @@ -6001,7 +5656,7 @@ fn bindTagUnionRowTailInStore( common_idents: ModuleEnv.CommonIdents, bindings: *std.AutoHashMap(types.Var, Monotype.Idx), ext_var: types.Var, - mono_tags: []const Monotype.Tag, + mono_tags: []const Monotype.TagKey, seen_indices: []const u32, ) Allocator.Error!void { const tail_mono = try self.remainingTagUnionTailMonotype(mono_tags, seen_indices); @@ -6011,7 +5666,7 @@ fn bindTagUnionRowTailInStore( fn bindTagUnionRowTail( self: *Self, ext_var: types.Var, - mono_tags: []const Monotype.Tag, + mono_tags: []const Monotype.TagKey, seen_indices: []const u32, ) Allocator.Error!void { const tail_mono = try self.remainingTagUnionTailMonotype(mono_tags, seen_indices); @@ -6019,8 +5674,7 @@ fn bindTagUnionRowTail( } fn tupleMonotypeForFields(self: *Self, field_monotypes: []const Monotype.Idx) Allocator.Error!Monotype.Idx { - const elems = try self.store.monotype_store.addIdxSpan(self.allocator, field_monotypes); - return try self.store.monotype_store.addMonotype(self.allocator, .{ .tuple = .{ .elems = elems } }); + return try self.store.monotype_store.internTuple(field_monotypes); } fn tagPayloadMonotypesByName( @@ -6028,25 +5682,29 @@ fn tagPayloadMonotypesByName( union_monotype: Monotype.Idx, tag_name: Ident.Idx, ) []const Monotype.Idx { - const mono = self.store.monotype_store.getMonotype(union_monotype); - const tags = switch (mono) { - .tag_union => |tu| self.store.monotype_store.getTags(tu.tags), - else => typeBindingInvariant( - "tag payload lookup expected tag_union monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + const resolved = self.store.monotype_store.resolve(union_monotype); + if (resolved.kind != .tag_union) { + typeBindingInvariant( + "tag payload lookup expected tag_union monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + const tags = self.store.monotype_store.tagUnionTags(resolved); + const tag_name_text = self.all_module_envs[self.current_module_idx].getIdent(tag_name); + const tag_last_segment = identLastSegment(tag_name_text); for (tags) |tag| { - if (self.identsTagNameEquivalent(tag.name, tag_name)) { - return self.store.monotype_store.getIdxSpan(tag.payloads); + const mono_tag_text = self.store.monotype_store.getNameText(tag.name); + if (std.mem.eql(u8, mono_tag_text, tag_name_text) or + std.mem.eql(u8, identLastSegment(mono_tag_text), tag_last_segment)) + { + return self.store.monotype_store.getIdxListItems(tag.payloads); } } - const module_env = self.all_module_envs[self.current_module_idx]; typeBindingInvariant( "tag '{s}' missing from monotype", - .{module_env.getIdent(tag_name)}, + .{tag_name_text}, ); } @@ -6103,30 +5761,31 @@ fn bindTagPayloadsByName( self: *Self, tag_name: Ident.Idx, payload_vars: []const types.Var, - mono_tags: []const Monotype.Tag, + mono_tags: []const Monotype.TagKey, ) Allocator.Error!void { + const tag_name_text = self.all_module_envs[self.current_module_idx].getIdent(tag_name); + const tag_last_segment = identLastSegment(tag_name_text); for (mono_tags) |mono_tag| { - if (!self.identsTagNameEquivalent(mono_tag.name, tag_name)) continue; + const mono_tag_text = self.store.monotype_store.getNameText(mono_tag.name); + if (!std.mem.eql(u8, mono_tag_text, tag_name_text) and + !std.mem.eql(u8, identLastSegment(mono_tag_text), tag_last_segment)) continue; - const mono_payload_span = mono_tag.payloads; - if (payload_vars.len != mono_payload_span.len) { - const module_env = self.all_module_envs[self.current_module_idx]; + const mono_payloads = self.store.monotype_store.getIdxListItems(mono_tag.payloads); + if (payload_vars.len != mono_payloads.len) { typeBindingInvariant( "bindFlatTypeMonotypes(tag_union): payload arity mismatch for tag '{s}'", - .{module_env.getIdent(tag_name)}, + .{tag_name_text}, ); } for (payload_vars, 0..) |payload_var, i| { - const mono_payload = self.store.monotype_store.getIdxSpan(mono_payload_span)[i]; - try self.bindTypeVarMonotypes(payload_var, mono_payload); + try self.bindTypeVarMonotypes(payload_var, mono_payloads[i]); } return; } - const module_env = self.all_module_envs[self.current_module_idx]; typeBindingInvariant( "bindFlatTypeMonotypes(tag_union): tag '{s}' missing from monotype", - .{module_env.getIdent(tag_name)}, + .{tag_name_text}, ); } @@ -6136,15 +5795,7 @@ fn bindTypeVarMonotypes(self: *Self, type_var: types.Var, monotype: Monotype.Idx const resolved = self.types_store.resolveVar(type_var); - if (self.type_var_seen.get(resolved.var_)) |existing| { - if (!(try self.monotypesStructurallyEqual(existing, monotype))) { - typeBindingInvariant( - "bindTypeVarMonotypes: conflicting monotype binding for type var root {d} (existing={d}, new={d})", - .{ @intFromEnum(resolved.var_), @intFromEnum(existing), @intFromEnum(monotype) }, - ); - } - return; - } + if (self.type_var_seen.contains(resolved.var_)) return; switch (resolved.desc.content) { .flex, .rigid => { @@ -6166,31 +5817,28 @@ fn bindTypeVarMonotypes(self: *Self, type_var: types.Var, monotype: Monotype.Idx fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monotype.Idx) Allocator.Error!void { if (monotype.isNone()) return; - const mono = self.store.monotype_store.getMonotype(monotype); - const module_env = self.all_module_envs[self.current_module_idx]; + const resolved = self.store.monotype_store.resolve(monotype); switch (flat_type) { .fn_pure, .fn_effectful, .fn_unbound => |func| { - const mfunc = switch (mono) { - .func => |mfunc| mfunc, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(fn): expected function monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .func) { + typeBindingInvariant( + "bindFlatTypeMonotypes(fn): expected function monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const type_args = self.types_store.sliceVars(func.args); - const mono_arg_span = mfunc.args; - if (type_args.len != mono_arg_span.len) { + const mono_args = self.store.monotype_store.funcArgs(resolved); + if (type_args.len != mono_args.len) { typeBindingInvariant( "bindFlatTypeMonotypes(fn): arity mismatch (type={d}, monotype={d})", - .{ type_args.len, mono_arg_span.len }, + .{ type_args.len, mono_args.len }, ); } for (type_args, 0..) |ta, i| { - const ma = self.store.monotype_store.getIdxSpan(mono_arg_span)[i]; - try self.bindTypeVarMonotypes(ta, ma); + try self.bindTypeVarMonotypes(ta, mono_args[i]); } - try self.bindTypeVarMonotypes(func.ret, mfunc.ret); + try self.bindTypeVarMonotypes(func.ret, self.store.monotype_store.funcRet(resolved)); }, .nominal_type => |nominal| { const common = self.currentCommonIdents(); @@ -6198,13 +5846,12 @@ fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monot const origin = nominal.origin_module; if (origin.eql(common.builtin_module) and ident.eql(common.list)) { - const mlist = switch (mono) { - .list => |mlist| mlist, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(nominal List): expected list monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .list) { + typeBindingInvariant( + "bindFlatTypeMonotypes(nominal List): expected list monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const type_args = self.types_store.sliceNominalArgs(nominal); if (type_args.len != 1) { typeBindingInvariant( @@ -6212,17 +5859,16 @@ fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monot .{type_args.len}, ); } - try self.bindTypeVarMonotypes(type_args[0], mlist.elem); + try self.bindTypeVarMonotypes(type_args[0], self.store.monotype_store.listElem(resolved)); return; } if (origin.eql(common.builtin_module) and ident.eql(common.box)) { - const mbox = switch (mono) { - .box => |mbox| mbox, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(nominal Box): expected box monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .box) { + typeBindingInvariant( + "bindFlatTypeMonotypes(nominal Box): expected box monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const type_args = self.types_store.sliceNominalArgs(nominal); if (type_args.len != 1) { typeBindingInvariant( @@ -6230,17 +5876,16 @@ fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monot .{type_args.len}, ); } - try self.bindTypeVarMonotypes(type_args[0], mbox.inner); + try self.bindTypeVarMonotypes(type_args[0], self.store.monotype_store.boxInner(resolved)); return; } if (origin.eql(common.builtin_module) and builtinPrimForNominal(ident, common) != null) { - switch (mono) { - .prim => {}, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(nominal prim): expected prim monotype, found '{s}'", - .{@tagName(mono)}, - ), + if (resolved.kind != .builtin or resolved.builtinPrim() == null) { + typeBindingInvariant( + "bindFlatTypeMonotypes(nominal prim): expected prim monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); } return; } @@ -6250,15 +5895,13 @@ fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monot try self.bindTypeVarMonotypes(backing_var, monotype); }, .record => |record| { - const mrec = switch (mono) { - .record => |mrec| mrec, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(record): expected record monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; - const mono_field_span = mrec.fields; - const mono_fields = self.store.monotype_store.getFields(mono_field_span); + if (resolved.kind != .record) { + typeBindingInvariant( + "bindFlatTypeMonotypes(record): expected record monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + const mono_fields = self.store.monotype_store.recordFields(resolved); var seen_field_indices: std.ArrayListUnmanaged(u32) = .empty; defer seen_field_indices.deinit(self.allocator); @@ -6271,7 +5914,7 @@ fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monot for (field_names, field_vars) |field_name, field_var| { const field_idx = self.recordFieldIndexByName(field_name, mono_fields); try appendSeenIndex(self.allocator, &seen_field_indices, field_idx); - try self.bindTypeVarMonotypes(field_var, mono_fields[field_idx].type_idx); + try self.bindTypeVarMonotypes(field_var, mono_fields[field_idx].ty); } var ext_var = current_row.ext; @@ -6294,7 +5937,7 @@ fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monot for (ext_field_names, ext_field_vars) |field_name, field_var| { const field_idx = self.recordFieldIndexByName(field_name, mono_fields); try appendSeenIndex(self.allocator, &seen_field_indices, field_idx); - try self.bindTypeVarMonotypes(field_var, mono_fields[field_idx].type_idx); + try self.bindTypeVarMonotypes(field_var, mono_fields[field_idx].ty); } break :rows; }, @@ -6323,65 +5966,61 @@ fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monot if (!seenIndex(seen_field_indices.items, @intCast(field_idx))) { typeBindingInvariant( "bindFlatTypeMonotypes(record): monotype field '{s}' missing from type row", - .{module_env.getIdent(mono_field.name)}, + .{self.store.monotype_store.getNameText(mono_field.name)}, ); } } }, .record_unbound => |fields_range| { - const mrec = switch (mono) { - .record => |mrec| mrec, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(record_unbound): expected record monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .record) { + typeBindingInvariant( + "bindFlatTypeMonotypes(record_unbound): expected record monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const fields_slice = self.types_store.getRecordFieldsSlice(fields_range); const field_names = fields_slice.items(.name); const field_vars = fields_slice.items(.var_); - const mono_field_span = mrec.fields; + const mono_fields = self.store.monotype_store.recordFields(resolved); - if (field_names.len != mono_field_span.len) { + if (field_names.len != mono_fields.len) { typeBindingInvariant( "bindFlatTypeMonotypes(record_unbound): field count mismatch (type={d}, monotype={d})", - .{ field_names.len, mono_field_span.len }, + .{ field_names.len, mono_fields.len }, ); } for (field_names, field_vars) |field_name, field_var| { - try self.bindRecordFieldByName(field_name, field_var, self.store.monotype_store.getFields(mono_field_span)); + try self.bindRecordFieldByName(field_name, field_var, mono_fields); } }, .tuple => |tuple| { - const mtuple = switch (mono) { - .tuple => |mtuple| mtuple, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(tuple): expected tuple monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .tuple) { + typeBindingInvariant( + "bindFlatTypeMonotypes(tuple): expected tuple monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } const elem_vars = self.types_store.sliceVars(tuple.elems); - const mono_elem_span = mtuple.elems; - if (elem_vars.len != mono_elem_span.len) { + const mono_elems = self.store.monotype_store.tupleElems(resolved); + if (elem_vars.len != mono_elems.len) { typeBindingInvariant( "bindFlatTypeMonotypes(tuple): arity mismatch (type={d}, monotype={d})", - .{ elem_vars.len, mono_elem_span.len }, + .{ elem_vars.len, mono_elems.len }, ); } for (elem_vars, 0..) |ev, i| { - const me = self.store.monotype_store.getIdxSpan(mono_elem_span)[i]; - try self.bindTypeVarMonotypes(ev, me); + try self.bindTypeVarMonotypes(ev, mono_elems[i]); } }, .tag_union => |tag_union_row| { - const mono_tag_span = switch (mono) { - .tag_union => |mtu| mtu.tags, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(tag_union): expected tag_union monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; - const mono_tags = self.store.monotype_store.getTags(mono_tag_span); + if (resolved.kind != .tag_union) { + typeBindingInvariant( + "bindFlatTypeMonotypes(tag_union): expected tag_union monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + const mono_tags = self.store.monotype_store.tagUnionTags(resolved); var seen_tag_indices: std.ArrayListUnmanaged(u32) = .empty; defer seen_tag_indices.deinit(self.allocator); @@ -6436,37 +6075,36 @@ fn bindFlatTypeMonotypes(self: *Self, flat_type: types.FlatType, monotype: Monot if (!seenIndex(seen_tag_indices.items, @intCast(tag_idx))) { typeBindingInvariant( "bindFlatTypeMonotypes(tag_union): monotype tag '{s}' missing from type row", - .{module_env.getIdent(mono_tag.name)}, + .{self.store.monotype_store.getNameText(mono_tag.name)}, ); } } }, .empty_record => { - switch (mono) { - .unit => {}, - .record => |mrec| { - const fields = self.store.monotype_store.getFields(mrec.fields); - if (fields.len != 0) { - typeBindingInvariant( - "bindFlatTypeMonotypes(empty_record): expected zero record fields, found {d}", - .{fields.len}, - ); - } - }, - else => typeBindingInvariant( - "bindFlatTypeMonotypes(empty_record): expected unit/empty-record monotype, found '{s}'", - .{@tagName(mono)}, - ), + if (!resolved.isUnit() and resolved.kind != .record) { + typeBindingInvariant( + "bindFlatTypeMonotypes(empty_record): expected unit/empty-record monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + if (resolved.kind == .record) { + const fields = self.store.monotype_store.recordFields(resolved); + if (fields.len != 0) { + typeBindingInvariant( + "bindFlatTypeMonotypes(empty_record): expected zero record fields, found {d}", + .{fields.len}, + ); + } } }, .empty_tag_union => { - const mono_tags = switch (mono) { - .tag_union => |mtu| self.store.monotype_store.getTags(mtu.tags), - else => typeBindingInvariant( - "bindFlatTypeMonotypes(empty_tag_union): expected empty tag union monotype, found '{s}'", - .{@tagName(mono)}, - ), - }; + if (resolved.kind != .tag_union) { + typeBindingInvariant( + "bindFlatTypeMonotypes(empty_tag_union): expected empty tag union monotype, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + const mono_tags = self.store.monotype_store.tagUnionTags(resolved); if (mono_tags.len != 0) { typeBindingInvariant( "bindFlatTypeMonotypes(empty_tag_union): expected zero tags, found {d}", diff --git a/src/mir/MIR.zig b/src/mir/MIR.zig index 4e2b984447c..40ee85e8b87 100644 --- a/src/mir/MIR.zig +++ b/src/mir/MIR.zig @@ -604,7 +604,7 @@ pub const Store = struct { self.borrow_bindings.deinit(allocator); self.captures.deinit(allocator); self.capture_bindings.deinit(allocator); - self.monotype_store.deinit(allocator); + self.monotype_store.deinit(); self.closure_members.deinit(allocator); self.expr_closure_members.deinit(allocator); self.fn_closure_members.deinit(allocator); diff --git a/src/mir/Monotype.zig b/src/mir/Monotype.zig index 118c902ea24..fa9dbd0ec29 100644 --- a/src/mir/Monotype.zig +++ b/src/mir/Monotype.zig @@ -1,11 +1,8 @@ //! Monomorphic type system for MIR. //! -//! Monotypes are fully concrete types with no type variables, no extensions, -//! no aliases, and no nominal/opaque/structural distinction. Records, tag unions, -//! and tuples are just records, tag unions, and tuples — the distinctions that -//! existed for static dispatch and module boundary enforcement are no longer needed. -//! -//! Each MIR expression has exactly one Monotype via a 1:1 Expr.Idx → Monotype.Idx mapping. +//! Types are fully concrete — no type variables, extensions, aliases, or +//! nominal/opaque/structural distinction. The TypeInterner provides canonical +//! type identity: structurally equal types always share the same TypeId. const std = @import("std"); const base = @import("base"); @@ -18,93 +15,91 @@ const ModuleEnv = can.ModuleEnv; const CommonIdents = can.ModuleEnv.CommonIdents; const StaticDispatchConstraint = types.StaticDispatchConstraint; -/// Check if a constraint range contains a numeric constraint (from_numeral, -/// desugared_binop, or desugared_unaryop). These imply the type variable is -/// numeric and should default to Dec rather than unit. -fn hasNumeralConstraint(types_store: *const types.Store, constraints: StaticDispatchConstraint.SafeList.Range) bool { - if (constraints.isEmpty()) return false; - for (types_store.sliceStaticDispatchConstraints(constraints)) |constraint| { - switch (constraint.origin) { - .from_numeral, .desugared_binop, .desugared_unaryop => return true, - .method_call, .where_clause => {}, - } +// ═══════════════════════════════════════════════════════════════ +// Type Handles +// ═══════════════════════════════════════════════════════════════ + +/// Canonical type handle — equality IS semantic type equality. +/// 32-bit packed: 29-bit per-kind index + 3-bit kind tag. +pub const TypeId = packed struct(u32) { + index: u29, + kind: Kind, + + pub const Kind = enum(u3) { + builtin, // unit + all Prim variants (encoded in index) + list, // payload: TypeId (elem) + box, // payload: TypeId (inner) + tuple, // payload: IdxListId + record, // payload: FieldListId + tag_union, // payload: TagListId + func, // payload: FuncPayload + rec, // recursive type indirection + }; + + pub const none: TypeId = .{ .index = std.math.maxInt(u29), .kind = .builtin }; + + pub fn isNone(self: TypeId) bool { + return @as(u32, @bitCast(self)) == @as(u32, @bitCast(none)); } - return false; -} -/// Index into the Store's monotypes array. -/// Since MIR has a 1:1 expr-to-type mapping, an Expr.Idx can be directly -/// reinterpreted as a Monotype.Idx. -pub const Idx = enum(u32) { - _, + pub fn eql(a: TypeId, b: TypeId) bool { + return @as(u32, @bitCast(a)) == @as(u32, @bitCast(b)); + } - pub const none: Idx = @enumFromInt(std.math.maxInt(u32)); + /// True if this TypeId represents the unit type (builtin index 0). + pub fn isUnit(self: TypeId) bool { + return self.kind == .builtin and self.index == 0; + } - pub fn isNone(self: Idx) bool { - return self == none; + /// If this TypeId is a primitive, return which one. + pub fn builtinPrim(self: TypeId) ?Prim { + if (self.kind != .builtin or self.index == 0) return null; + const prim_count = @typeInfo(Prim).@"enum".fields.len; + if (self.index > prim_count) return null; + return @enumFromInt(self.index - 1); } }; -/// Span of Monotype.Idx values stored in the extra_data array. -pub const Span = extern struct { - start: u32, - len: u16, +/// Backward-compat alias so existing `Monotype.Idx` references compile. +pub const Idx = TypeId; - pub fn empty() Span { - return .{ .start = 0, .len = 0 }; +/// Canonical list of TypeIds (function args, tuple elems, tag payloads). +pub const IdxListId = enum(u32) { + empty = 0, + _, + + pub fn isEmpty(self: IdxListId) bool { + return self == .empty; } +}; - pub fn isEmpty(self: Span) bool { - return self.len == 0; +/// Canonical list of record fields. +pub const FieldListId = enum(u32) { + empty = 0, + _, + + pub fn isEmpty(self: FieldListId) bool { + return self == .empty; } }; -/// A monomorphic type — fully concrete, no type variables, no extensions. -pub const Monotype = union(enum) { - /// Function type: args -> ret - func: struct { - args: Span, - ret: Idx, - effectful: bool, - }, - - /// Closed tag union (tags sorted by name) - tag_union: struct { - tags: TagSpan, - }, - - /// Closed record (fields sorted by name) - record: struct { - fields: FieldSpan, - }, - - /// Tuple - tuple: struct { - elems: Span, - }, - - /// List with element type - list: struct { - elem: Idx, - }, - - /// Primitive type - prim: Prim, - - /// Box (heap-allocated wrapper) - box: struct { - inner: Idx, - }, - - /// Unit / empty record - unit: void, - - /// Temporary placeholder used during recursive type construction. - /// Must be overwritten before construction completes; surviving - /// placeholders indicate a bug in fromFlatType/fromFuncType/fromNominalType. - recursive_placeholder: void, +/// Canonical list of tag-union tags. +pub const TagListId = enum(u32) { + empty = 0, + _, + + pub fn isEmpty(self: TagListId) bool { + return self == .empty; + } }; +/// Canonical structural name (record field names, tag names). +pub const StructuralNameId = enum(u32) { _ }; + +// ═══════════════════════════════════════════════════════════════ +// Payload / Key Types +// ═══════════════════════════════════════════════════════════════ + /// Primitive type kinds. pub const Prim = enum { str, @@ -123,107 +118,229 @@ pub const Prim = enum { dec, }; -/// A tag in a tag union. -pub const Tag = struct { - name: Ident.Idx, - /// Span of Monotype.Idx for payload types - payloads: Span, +/// A record field key: canonical name + canonical type. +pub const FieldKey = struct { + name: StructuralNameId, + ty: TypeId, - pub fn sortByNameAsc(ident_store: *const Ident.Store, a: Tag, b: Tag) bool { - return orderByName(ident_store, a, b) == .lt; + pub fn sortByNameAsc(pool: *const StructuralNamePool, a: FieldKey, b: FieldKey) bool { + return std.mem.order(u8, pool.getText(a.name), pool.getText(b.name)) == .lt; } +}; + +/// A tag-union tag key: canonical name + canonical payload list. +pub const TagKey = struct { + name: StructuralNameId, + payloads: IdxListId, - fn orderByName(ident_store: *const Ident.Store, a: Tag, b: Tag) std.math.Order { - const a_text = ident_store.getText(a.name); - const b_text = ident_store.getText(b.name); - return std.mem.order(u8, a_text, b_text); + pub fn sortByNameAsc(pool: *const StructuralNamePool, a: TagKey, b: TagKey) bool { + return std.mem.order(u8, pool.getText(a.name), pool.getText(b.name)) == .lt; } }; -/// Span of Tags stored in the tags array. -pub const TagSpan = extern struct { - start: u32, - len: u16, +/// Function type payload: canonical arg list + return type + effectfulness. +pub const FuncPayload = struct { + args: IdxListId, + ret: TypeId, + effectful: bool, +}; - pub fn empty() TagSpan { - return .{ .start = 0, .len = 0 }; - } +// ═══════════════════════════════════════════════════════════════ +// Structural Name Pool +// ═══════════════════════════════════════════════════════════════ + +/// Compilation-wide pool for canonical structural field/tag names. +pub const StructuralNamePool = struct { + alloc: Allocator, + map: std.StringHashMapUnmanaged(StructuralNameId), + texts: std.ArrayListUnmanaged([]const u8), - pub fn isEmpty(self: TagSpan) bool { - return self.len == 0; + fn init(allocator: Allocator) StructuralNamePool { + return .{ .alloc = allocator, .map = .{}, .texts = .empty }; } -}; -/// A field in a record. -pub const Field = struct { - name: Ident.Idx, - type_idx: Idx, + fn deinit(self: *StructuralNamePool) void { + for (self.texts.items) |text| self.alloc.free(text); + self.texts.deinit(self.alloc); + self.map.deinit(self.alloc); + } - pub fn sortByNameAsc(ident_store: *const Ident.Store, a: Field, b: Field) bool { - return orderByName(ident_store, a, b) == .lt; + pub fn intern(self: *StructuralNamePool, text: []const u8) !StructuralNameId { + const gop = try self.map.getOrPut(self.alloc, text); + if (gop.found_existing) return gop.value_ptr.*; + const owned = try self.alloc.dupe(u8, text); + const id: StructuralNameId = @enumFromInt(self.texts.items.len); + try self.texts.append(self.alloc, owned); + gop.key_ptr.* = owned; + gop.value_ptr.* = id; + return id; } - fn orderByName(ident_store: *const Ident.Store, a: Field, b: Field) std.math.Order { - const a_text = ident_store.getText(a.name); - const b_text = ident_store.getText(b.name); - return std.mem.order(u8, a_text, b_text); + pub fn getText(self: *const StructuralNamePool, id: StructuralNameId) []const u8 { + return self.texts.items[@intFromEnum(id)]; } }; -/// Span of Fields stored in the fields array. -pub const FieldSpan = extern struct { - start: u32, - len: u16, +/// Key identifying a nominal type instance by its origin, type name, and type arguments. +pub const NominalInstanceKey = struct { + origin_name_id: StructuralNameId, + type_name_id: StructuralNameId, + args: IdxListId, +}; + +const NominalCacheEntry = union(enum) { + /// Lowering is in progress. rec_id is allocated lazily on recursive hit. + in_progress: TypeId, // TypeId.none until recursion detected + /// Fully lowered canonical type. + resolved: TypeId, +}; + +// ═══════════════════════════════════════════════════════════════ +// Internal helpers +// ═══════════════════════════════════════════════════════════════ - pub fn empty() FieldSpan { - return .{ .start = 0, .len = 0 }; +fn hasNumeralConstraint(types_store: *const types.Store, constraints: StaticDispatchConstraint.SafeList.Range) bool { + if (constraints.isEmpty()) return false; + for (types_store.sliceStaticDispatchConstraints(constraints)) |constraint| { + switch (constraint.origin) { + .from_numeral, .desugared_binop, .desugared_unaryop => return true, + .method_call, .where_clause => {}, + } } + return false; +} - pub fn isEmpty(self: FieldSpan) bool { - return self.len == 0; +/// Hash a slice of TypeIds for list interning. +fn hashTypeIds(items: []const TypeId) u64 { + var h = std.hash.Wyhash.init(0); + for (items) |item| std.hash.autoHash(&h, @as(u32, @bitCast(item))); + std.hash.autoHash(&h, items.len); + return h.final(); +} + +/// Hash a slice of FieldKeys for field-list interning. +fn hashFieldKeys(items: []const FieldKey) u64 { + var h = std.hash.Wyhash.init(1); + for (items) |item| { + std.hash.autoHash(&h, @intFromEnum(item.name)); + std.hash.autoHash(&h, @as(u32, @bitCast(item.ty))); } -}; + std.hash.autoHash(&h, items.len); + return h.final(); +} + +/// Hash a slice of TagKeys for tag-list interning. +fn hashTagKeys(items: []const TagKey) u64 { + var h = std.hash.Wyhash.init(2); + for (items) |item| { + std.hash.autoHash(&h, @intFromEnum(item.name)); + std.hash.autoHash(&h, @intFromEnum(item.payloads)); + } + std.hash.autoHash(&h, items.len); + return h.final(); +} + +/// Compare two TypeId slices for equality. +fn eqlTypeIds(a: []const TypeId, b: []const TypeId) bool { + if (a.len != b.len) return false; + return std.mem.eql(u8, std.mem.sliceAsBytes(a), std.mem.sliceAsBytes(b)); +} + +/// Compare two FieldKey slices for equality. +fn eqlFieldKeys(a: []const FieldKey, b: []const FieldKey) bool { + if (a.len != b.len) return false; + return std.mem.eql(u8, std.mem.sliceAsBytes(a), std.mem.sliceAsBytes(b)); +} + +/// Compare two TagKey slices for equality. +fn eqlTagKeys(a: []const TagKey, b: []const TagKey) bool { + if (a.len != b.len) return false; + return std.mem.eql(u8, std.mem.sliceAsBytes(a), std.mem.sliceAsBytes(b)); +} const NamedSpecialization = struct { name_text: []const u8, - type_idx: Idx, + type_idx: TypeId, }; -/// Flat storage for monomorphic types. -pub const Store = struct { - monotypes: std.ArrayListUnmanaged(Monotype), - /// Monotype.Idx values for spans (func args, tuple elems) - extra_idx: std.ArrayListUnmanaged(u32), - tags: std.ArrayListUnmanaged(Tag), - fields: std.ArrayListUnmanaged(Field), - - /// Pre-interned index for the unit monotype. - unit_idx: Idx, - /// Pre-interned indices for each primitive monotype, indexed by `@intFromEnum(Prim)`. - prim_idxs: [prim_count]Idx, - /// Cached ordinary tag-union monotype for nominal Bool. - bool_tag_union_idx: Idx, +// ═══════════════════════════════════════════════════════════════ +// TypeInterner +// ═══════════════════════════════════════════════════════════════ + +/// Compilation-wide canonical type interner. Every structurally equal +/// monotype receives exactly one TypeId. +pub const TypeInterner = struct { + allocator: Allocator, + + // Per-kind payload arenas (indexed by TypeId.index for that kind) + list_payloads: std.ArrayListUnmanaged(TypeId), // elem type + box_payloads: std.ArrayListUnmanaged(TypeId), // inner type + tuple_payloads: std.ArrayListUnmanaged(IdxListId), + record_payloads: std.ArrayListUnmanaged(FieldListId), + union_payloads: std.ArrayListUnmanaged(TagListId), + func_payloads: std.ArrayListUnmanaged(FuncPayload), + rec_targets: std.ArrayListUnmanaged(TypeId), // recursive indirection + + // Canonical list storage + idx_list_metas: std.ArrayListUnmanaged(IdxListMeta), + idx_list_items: std.ArrayListUnmanaged(TypeId), + field_list_metas: std.ArrayListUnmanaged(FieldListMeta), + field_list_items: std.ArrayListUnmanaged(FieldKey), + tag_list_metas: std.ArrayListUnmanaged(TagListMeta), + tag_list_items: std.ArrayListUnmanaged(TagKey), + + // Per-kind intern maps (canonical key → TypeId) + list_map: std.AutoHashMapUnmanaged(u32, TypeId), // @bitCast(elem TypeId) + box_map: std.AutoHashMapUnmanaged(u32, TypeId), // @bitCast(inner TypeId) + tuple_map: std.AutoHashMapUnmanaged(IdxListId, TypeId), + record_map: std.AutoHashMapUnmanaged(FieldListId, TypeId), + union_map: std.AutoHashMapUnmanaged(TagListId, TypeId), + func_map: std.AutoHashMapUnmanaged(u96, TypeId), // packed func key + + // Fingerprint → chain-head maps for list interning + idx_list_fp_map: std.AutoHashMapUnmanaged(u64, IdxListId), + field_list_fp_map: std.AutoHashMapUnmanaged(u64, FieldListId), + tag_list_fp_map: std.AutoHashMapUnmanaged(u64, TagListId), + + // Structural name pool + name_pool: StructuralNamePool, + + // Nominal instance cache + nominal_cache: std.AutoHashMapUnmanaged(NominalInstanceKey, NominalCacheEntry), + + // Pre-interned builtins + unit_idx: TypeId, + prim_idxs: [prim_count]TypeId, + bool_tag_union_idx: TypeId, const prim_count = @typeInfo(Prim).@"enum".fields.len; + // --- Internal list metadata --- + + const IdxListMeta = struct { start: u32, len: u32, fingerprint: u64, next: IdxListId }; + const FieldListMeta = struct { start: u32, len: u32, fingerprint: u64, next: FieldListId }; + const TagListMeta = struct { start: u32, len: u32, fingerprint: u64, next: TagListId }; + + // --- Scratches (reusable temporary buffers) --- + pub const Scratches = struct { - fields: base.Scratch(Field), - tags: base.Scratch(Tag), - idxs: base.Scratch(Idx), + fields: base.Scratch(FieldKey), + tags: base.Scratch(TagKey), + idxs: base.Scratch(TypeId), named_specializations: base.Scratch(NamedSpecialization), - /// Ident store for sorting tag names alphabetically. + /// Ident store for resolving tag/field name text. /// Updated when switching modules during cross-module lowering. ident_store: ?*const Ident.Store = null, - /// Module env owning the current `types_store` / `ident_store`. + /// Module env owning the current types_store / ident_store. module_env: ?*const ModuleEnv = null, - /// Shared module env slice used to resolve nominal definitions by origin module. + /// Shared module env slice for resolving nominal definitions. all_module_envs: ?[]const *ModuleEnv = null, pub fn init(allocator: Allocator) Allocator.Error!Scratches { return .{ - .fields = try base.Scratch(Field).init(allocator), - .tags = try base.Scratch(Tag).init(allocator), - .idxs = try base.Scratch(Idx).init(allocator), + .fields = try base.Scratch(FieldKey).init(allocator), + .tags = try base.Scratch(TagKey).init(allocator), + .idxs = try base.Scratch(TypeId).init(allocator), .named_specializations = try base.Scratch(NamedSpecialization).init(allocator), }; } @@ -236,141 +353,365 @@ pub const Store = struct { } }; - /// Look up the pre-interned index for a primitive type. - pub fn primIdx(self: *const Store, prim: Prim) Idx { + pub fn init(allocator: Allocator) Allocator.Error!TypeInterner { + var self = TypeInterner{ + .allocator = allocator, + .list_payloads = .empty, + .box_payloads = .empty, + .tuple_payloads = .empty, + .record_payloads = .empty, + .union_payloads = .empty, + .func_payloads = .empty, + .rec_targets = .empty, + .idx_list_metas = .empty, + .idx_list_items = .empty, + .field_list_metas = .empty, + .field_list_items = .empty, + .tag_list_metas = .empty, + .tag_list_items = .empty, + .list_map = .{}, + .box_map = .{}, + .tuple_map = .{}, + .record_map = .{}, + .union_map = .{}, + .func_map = .{}, + .idx_list_fp_map = .{}, + .field_list_fp_map = .{}, + .tag_list_fp_map = .{}, + .name_pool = StructuralNamePool.init(allocator), + .nominal_cache = .{}, + .unit_idx = undefined, + .prim_idxs = undefined, + .bool_tag_union_idx = TypeId.none, + }; + + // Pre-intern unit (builtin index 0) and prims (builtin indices 1..14). + self.unit_idx = TypeId{ .kind = .builtin, .index = 0 }; + for (0..prim_count) |i| { + self.prim_idxs[i] = TypeId{ .kind = .builtin, .index = @intCast(i + 1) }; + } + + return self; + } + + pub fn deinit(self: *TypeInterner) void { + self.list_payloads.deinit(self.allocator); + self.box_payloads.deinit(self.allocator); + self.tuple_payloads.deinit(self.allocator); + self.record_payloads.deinit(self.allocator); + self.union_payloads.deinit(self.allocator); + self.func_payloads.deinit(self.allocator); + self.rec_targets.deinit(self.allocator); + self.idx_list_metas.deinit(self.allocator); + self.idx_list_items.deinit(self.allocator); + self.field_list_metas.deinit(self.allocator); + self.field_list_items.deinit(self.allocator); + self.tag_list_metas.deinit(self.allocator); + self.tag_list_items.deinit(self.allocator); + self.list_map.deinit(self.allocator); + self.box_map.deinit(self.allocator); + self.tuple_map.deinit(self.allocator); + self.record_map.deinit(self.allocator); + self.union_map.deinit(self.allocator); + self.func_map.deinit(self.allocator); + self.idx_list_fp_map.deinit(self.allocator); + self.field_list_fp_map.deinit(self.allocator); + self.tag_list_fp_map.deinit(self.allocator); + self.name_pool.deinit(); + self.nominal_cache.deinit(self.allocator); + } + + /// Look up the pre-interned TypeId for a primitive type. + pub fn primIdx(self: *const TypeInterner, prim: Prim) TypeId { return self.prim_idxs[@intFromEnum(prim)]; } - pub fn addBoolTagUnion(self: *Store, allocator: Allocator, common_idents: CommonIdents) !Idx { + /// Get or create the Bool tag union ([False, True]). + pub fn addBoolTagUnion(self: *TypeInterner) !TypeId { if (!self.bool_tag_union_idx.isNone()) return self.bool_tag_union_idx; + const false_name = try self.name_pool.intern("False"); + const true_name = try self.name_pool.intern("True"); + const result = try self.internTagUnion(&.{ + .{ .name = false_name, .payloads = .empty }, + .{ .name = true_name, .payloads = .empty }, + }); + self.bool_tag_union_idx = result; + return result; + } - const false_payloads = Span.empty(); - const true_payloads = Span.empty(); - const tags = try self.addTags(allocator, &.{ - .{ .name = common_idents.false_tag, .payloads = false_payloads }, - .{ .name = common_idents.true_tag, .payloads = true_payloads }, + pub fn internList(self: *TypeInterner, elem: TypeId) !TypeId { + const key: u32 = @bitCast(elem); + if (self.list_map.get(key)) |existing| return existing; + const index: u29 = @intCast(self.list_payloads.items.len); + try self.list_payloads.append(self.allocator, elem); + const id = TypeId{ .kind = .list, .index = index }; + try self.list_map.put(self.allocator, key, id); + return id; + } + + pub fn internBox(self: *TypeInterner, inner: TypeId) !TypeId { + const key: u32 = @bitCast(inner); + if (self.box_map.get(key)) |existing| return existing; + const index: u29 = @intCast(self.box_payloads.items.len); + try self.box_payloads.append(self.allocator, inner); + const id = TypeId{ .kind = .box, .index = index }; + try self.box_map.put(self.allocator, key, id); + return id; + } + + pub fn internTuple(self: *TypeInterner, elems: []const TypeId) !TypeId { + const elem_list = try self.internIdxList(elems); + if (self.tuple_map.get(elem_list)) |existing| return existing; + const index: u29 = @intCast(self.tuple_payloads.items.len); + try self.tuple_payloads.append(self.allocator, elem_list); + const id = TypeId{ .kind = .tuple, .index = index }; + try self.tuple_map.put(self.allocator, elem_list, id); + return id; + } + + pub fn internFunc(self: *TypeInterner, args: []const TypeId, ret: TypeId, effectful: bool) !TypeId { + const arg_list = try self.internIdxList(args); + const key = packFuncKey(arg_list, ret, effectful); + if (self.func_map.get(key)) |existing| return existing; + const index: u29 = @intCast(self.func_payloads.items.len); + try self.func_payloads.append(self.allocator, .{ + .args = arg_list, + .ret = ret, + .effectful = effectful, }); - const idx = try self.addMonotype(allocator, .{ .tag_union = .{ .tags = tags } }); - self.bool_tag_union_idx = idx; - return idx; + const id = TypeId{ .kind = .func, .index = index }; + try self.func_map.put(self.allocator, key, id); + return id; + } + + pub fn internRecord(self: *TypeInterner, fields: []const FieldKey) !TypeId { + const field_list = try self.internFieldList(fields); + if (self.record_map.get(field_list)) |existing| return existing; + const index: u29 = @intCast(self.record_payloads.items.len); + try self.record_payloads.append(self.allocator, field_list); + const id = TypeId{ .kind = .record, .index = index }; + try self.record_map.put(self.allocator, field_list, id); + return id; } - /// Pre-populate the store with the 16 fixed monotypes (unit + 15 primitives). - pub fn init(allocator: Allocator) Allocator.Error!Store { - var monotypes: std.ArrayListUnmanaged(Monotype) = .empty; - try monotypes.ensureTotalCapacity(allocator, 1 + prim_count); + pub fn internTagUnion(self: *TypeInterner, tag_keys: []const TagKey) !TypeId { + const tag_list = try self.internTagList(tag_keys); + if (self.union_map.get(tag_list)) |existing| return existing; + const index: u29 = @intCast(self.union_payloads.items.len); + try self.union_payloads.append(self.allocator, tag_list); + const id = TypeId{ .kind = .tag_union, .index = index }; + try self.union_map.put(self.allocator, tag_list, id); + return id; + } - // Unit slot - const unit_idx: Idx = @enumFromInt(monotypes.items.len); - monotypes.appendAssumeCapacity(.unit); + fn packFuncKey(args: IdxListId, ret: TypeId, effectful: bool) u96 { + return @as(u96, @intFromEnum(args)) | + (@as(u96, @as(u32, @bitCast(ret))) << 32) | + (@as(u96, @intFromBool(effectful)) << 64); + } - // Primitive slots in enum order - var prim_idxs: [prim_count]Idx = undefined; - for (0..prim_count) |i| { - prim_idxs[i] = @enumFromInt(monotypes.items.len); - monotypes.appendAssumeCapacity(.{ .prim = @enumFromInt(i) }); + pub fn internIdxList(self: *TypeInterner, items: []const TypeId) !IdxListId { + if (items.len == 0) return .empty; + const fp = hashTypeIds(items); + + const gop = try self.idx_list_fp_map.getOrPut(self.allocator, fp); + + if (gop.found_existing) { + var cur = gop.value_ptr.*; + while (cur != .empty) { + const meta = self.idx_list_metas.items[@intFromEnum(cur) - 1]; + if (meta.len == items.len and + eqlTypeIds(self.idx_list_items.items[meta.start..][0..meta.len], items)) + return cur; + cur = meta.next; + } } - return .{ - .monotypes = monotypes, - .extra_idx = .empty, - .tags = .empty, - .fields = .empty, - .unit_idx = unit_idx, - .prim_idxs = prim_idxs, - .bool_tag_union_idx = .none, - }; + const old_head: IdxListId = if (gop.found_existing) gop.value_ptr.* else .empty; + const start: u32 = @intCast(self.idx_list_items.items.len); + try self.idx_list_items.appendSlice(self.allocator, items); + try self.idx_list_metas.append(self.allocator, .{ + .start = start, + .len = @intCast(items.len), + .fingerprint = fp, + .next = old_head, + }); + const new_id: IdxListId = @enumFromInt(@as(u32, @intCast(self.idx_list_metas.items.len))); + gop.value_ptr.* = new_id; + return new_id; } - pub fn deinit(self: *Store, allocator: Allocator) void { - self.monotypes.deinit(allocator); - self.extra_idx.deinit(allocator); - self.tags.deinit(allocator); - self.fields.deinit(allocator); + pub fn internFieldList(self: *TypeInterner, items: []const FieldKey) !FieldListId { + if (items.len == 0) return .empty; + const fp = hashFieldKeys(items); + + const gop = try self.field_list_fp_map.getOrPut(self.allocator, fp); + + if (gop.found_existing) { + var cur = gop.value_ptr.*; + while (cur != .empty) { + const meta = self.field_list_metas.items[@intFromEnum(cur) - 1]; + if (meta.len == items.len and + eqlFieldKeys(self.field_list_items.items[meta.start..][0..meta.len], items)) + return cur; + cur = meta.next; + } + } + + const old_head: FieldListId = if (gop.found_existing) gop.value_ptr.* else .empty; + const start: u32 = @intCast(self.field_list_items.items.len); + try self.field_list_items.appendSlice(self.allocator, items); + try self.field_list_metas.append(self.allocator, .{ + .start = start, + .len = @intCast(items.len), + .fingerprint = fp, + .next = old_head, + }); + const new_id: FieldListId = @enumFromInt(@as(u32, @intCast(self.field_list_metas.items.len))); + gop.value_ptr.* = new_id; + return new_id; } - pub fn addMonotype(self: *Store, allocator: Allocator, mono: Monotype) !Idx { - const idx: u32 = @intCast(self.monotypes.items.len); - try self.monotypes.append(allocator, mono); - return @enumFromInt(idx); + pub fn internTagList(self: *TypeInterner, items: []const TagKey) !TagListId { + if (items.len == 0) return .empty; + const fp = hashTagKeys(items); + + const gop = try self.tag_list_fp_map.getOrPut(self.allocator, fp); + + if (gop.found_existing) { + var cur = gop.value_ptr.*; + while (cur != .empty) { + const meta = self.tag_list_metas.items[@intFromEnum(cur) - 1]; + if (meta.len == items.len and + eqlTagKeys(self.tag_list_items.items[meta.start..][0..meta.len], items)) + return cur; + cur = meta.next; + } + } + + const old_head: TagListId = if (gop.found_existing) gop.value_ptr.* else .empty; + const start: u32 = @intCast(self.tag_list_items.items.len); + try self.tag_list_items.appendSlice(self.allocator, items); + try self.tag_list_metas.append(self.allocator, .{ + .start = start, + .len = @intCast(items.len), + .fingerprint = fp, + .next = old_head, + }); + const new_id: TagListId = @enumFromInt(@as(u32, @intCast(self.tag_list_metas.items.len))); + gop.value_ptr.* = new_id; + return new_id; } - pub fn getMonotype(self: *const Store, idx: Idx) Monotype { - return self.monotypes.items[@intFromEnum(idx)]; + pub fn getIdxListItems(self: *const TypeInterner, id: IdxListId) []const TypeId { + if (id == .empty) return &.{}; + const meta = self.idx_list_metas.items[@intFromEnum(id) - 1]; + return self.idx_list_items.items[meta.start..][0..meta.len]; } - /// Add a span of Monotype.Idx values to extra_idx and return a Span. - pub fn addIdxSpan(self: *Store, allocator: Allocator, ids: []const Idx) !Span { - if (ids.len == 0) return Span.empty(); - const start: u32 = @intCast(self.extra_idx.items.len); - for (ids) |id| { - try self.extra_idx.append(allocator, @intFromEnum(id)); - } - return .{ .start = start, .len = @intCast(ids.len) }; + pub fn getFieldListItems(self: *const TypeInterner, id: FieldListId) []const FieldKey { + if (id == .empty) return &.{}; + const meta = self.field_list_metas.items[@intFromEnum(id) - 1]; + return self.field_list_items.items[meta.start..][0..meta.len]; + } + + pub fn getTagListItems(self: *const TypeInterner, id: TagListId) []const TagKey { + if (id == .empty) return &.{}; + const meta = self.tag_list_metas.items[@intFromEnum(id) - 1]; + return self.tag_list_items.items[meta.start..][0..meta.len]; + } + + pub fn listElem(self: *const TypeInterner, id: TypeId) TypeId { + std.debug.assert(id.kind == .list); + return self.list_payloads.items[id.index]; + } + + pub fn boxInner(self: *const TypeInterner, id: TypeId) TypeId { + std.debug.assert(id.kind == .box); + return self.box_payloads.items[id.index]; + } + + pub fn tupleElems(self: *const TypeInterner, id: TypeId) []const TypeId { + std.debug.assert(id.kind == .tuple); + return self.getIdxListItems(self.tuple_payloads.items[id.index]); + } + + pub fn getFuncPayload(self: *const TypeInterner, id: TypeId) FuncPayload { + std.debug.assert(id.kind == .func); + return self.func_payloads.items[id.index]; + } + + pub fn funcArgs(self: *const TypeInterner, id: TypeId) []const TypeId { + return self.getIdxListItems(self.getFuncPayload(id).args); + } + + pub fn funcRet(self: *const TypeInterner, id: TypeId) TypeId { + return self.getFuncPayload(id).ret; + } + + pub fn funcEffectful(self: *const TypeInterner, id: TypeId) bool { + return self.getFuncPayload(id).effectful; + } + + pub fn recordFields(self: *const TypeInterner, id: TypeId) []const FieldKey { + std.debug.assert(id.kind == .record); + return self.getFieldListItems(self.record_payloads.items[id.index]); } - /// Get a slice of Monotype.Idx from a Span. - pub fn getIdxSpan(self: *const Store, span: Span) []const Idx { - if (span.len == 0) return &.{}; - const raw = self.extra_idx.items[span.start..][0..span.len]; - return @ptrCast(raw); + pub fn tagUnionTags(self: *const TypeInterner, id: TypeId) []const TagKey { + std.debug.assert(id.kind == .tag_union); + return self.getTagListItems(self.union_payloads.items[id.index]); } - /// Add tags to the tags array and return a TagSpan. - pub fn addTags(self: *Store, allocator: Allocator, tag_slice: []const Tag) !TagSpan { - if (tag_slice.len == 0) return TagSpan.empty(); - const start: u32 = @intCast(self.tags.items.len); - try self.tags.appendSlice(allocator, tag_slice); - return .{ .start = start, .len = @intCast(tag_slice.len) }; + /// Resolve a TypeId through any `.rec` indirection. + pub fn resolve(self: *const TypeInterner, id: TypeId) TypeId { + if (id.kind != .rec) return id; + const target = self.rec_targets.items[id.index]; + // At most one level of indirection + std.debug.assert(target.kind != .rec); + return target; } - /// Get a slice of Tags from a TagSpan. - pub fn getTags(self: *const Store, span: TagSpan) []const Tag { - if (span.len == 0) return &.{}; - return self.tags.items[span.start..][0..span.len]; + /// Reserve a recursive type slot. Returns a `.rec` TypeId that can be + /// stored in cycle-breaker maps. Must be finalized later. + pub fn reserveRecursive(self: *TypeInterner) !TypeId { + const index: u29 = @intCast(self.rec_targets.items.len); + try self.rec_targets.append(self.allocator, TypeId.none); + return TypeId{ .kind = .rec, .index = index }; } - /// Add fields to the fields array and return a FieldSpan. - pub fn addFields(self: *Store, allocator: Allocator, field_slice: []const Field) !FieldSpan { - if (field_slice.len == 0) return FieldSpan.empty(); - const start: u32 = @intCast(self.fields.items.len); - try self.fields.appendSlice(allocator, field_slice); - return .{ .start = start, .len = @intCast(field_slice.len) }; + /// Finalize a reserved recursive slot with the actual canonical TypeId. + pub fn finalizeRecursive(self: *TypeInterner, rec_id: TypeId, actual: TypeId) void { + std.debug.assert(rec_id.kind == .rec); + self.rec_targets.items[rec_id.index] = actual; } - /// Get a slice of Fields from a FieldSpan. - pub fn getFields(self: *const Store, span: FieldSpan) []const Field { - if (span.len == 0) return &.{}; - return self.fields.items[span.start..][0..span.len]; + /// Intern a structural name from an Ident.Idx (resolves text via ident_store). + pub fn internNameFromIdent(self: *TypeInterner, ident_store: *const Ident.Store, ident: Ident.Idx) !StructuralNameId { + return try self.name_pool.intern(ident_store.getText(ident)); } - /// Convert a CIR type variable to a Monotype, recursively resolving all - /// type structure. - /// - /// `specializations` is a read-only map of type vars already bound to - /// concrete monotypes by `bindTypeVarMonotypes` (polymorphic specialization). - /// - /// Cycle detection for recursive nominal types (the only legitimate source - /// of cycles after type checking) is handled internally by `fromNominalType` - /// using `nominal_cycle_breakers`. + /// Get the text for a structural name. + pub fn getNameText(self: *const TypeInterner, id: StructuralNameId) []const u8 { + return self.name_pool.getText(id); + } + + /// Convert a CIR type variable to a canonical TypeId, recursively + /// resolving all type structure. pub fn fromTypeVar( - self: *Store, + self: *TypeInterner, allocator: Allocator, types_store: *const types.Store, type_var: types.Var, common_idents: CommonIdents, - specializations: *const std.AutoHashMap(types.Var, Idx), - nominal_cycle_breakers: *std.AutoHashMap(types.Var, Idx), + specializations: *const std.AutoHashMap(types.Var, TypeId), scratches: *Scratches, - ) Allocator.Error!Idx { + ) Allocator.Error!TypeId { + _ = allocator; // TypeInterner uses self.allocator const resolved = types_store.resolveVar(type_var); - // Check specialization bindings first (from bindTypeVarMonotypes). if (specializations.get(resolved.var_)) |cached| return cached; - // Check nominal cycle breakers (for recursive nominal types like Tree := [Leaf, Node(Tree)]). - if (nominal_cycle_breakers.get(resolved.var_)) |cached| return cached; - return switch (resolved.desc.content) { .flex => |flex| { if (flex.name) |name| { @@ -387,46 +728,34 @@ pub const Store = struct { return self.unit_idx; }, .alias => |alias| { - // Aliases are transparent — follow the backing var const backing_var = types_store.getAliasBackingVar(alias); - return try self.fromTypeVar(allocator, types_store, backing_var, common_idents, specializations, nominal_cycle_breakers, scratches); + return try self.fromTypeVar(self.allocator, types_store, backing_var, common_idents, specializations, scratches); }, .structure => |flat_type| { - return try self.fromFlatType(allocator, types_store, resolved.var_, flat_type, common_idents, specializations, nominal_cycle_breakers, scratches); + return try self.fromFlatType(types_store, flat_type, common_idents, specializations, scratches); }, - // Error types can appear nested inside function types (as argument - // or return types) when --allow-errors is used. The guard in - // lowerExpr only catches top-level error types, so we need to handle - // them here too. Return unit as a safe placeholder so lowering can - // continue and the runtime-error path is hit at runtime. .err => return self.unit_idx, }; } fn fromFlatType( - self: *Store, - allocator: Allocator, + self: *TypeInterner, types_store: *const types.Store, - var_: types.Var, flat_type: types.FlatType, common_idents: CommonIdents, - specializations: *const std.AutoHashMap(types.Var, Idx), - nominal_cycle_breakers: *std.AutoHashMap(types.Var, Idx), + specializations: *const std.AutoHashMap(types.Var, TypeId), scratches: *Scratches, - ) Allocator.Error!Idx { + ) Allocator.Error!TypeId { return switch (flat_type) { .nominal_type => |nominal| { - return try self.fromNominalType(allocator, types_store, var_, nominal, common_idents, specializations, nominal_cycle_breakers, scratches); + return try self.fromNominalType(types_store, nominal, common_idents, specializations, scratches); }, .empty_record => self.unit_idx, - .empty_tag_union => try self.addMonotype(allocator, .{ .tag_union = .{ .tags = TagSpan.empty() } }), + .empty_tag_union => try self.internTagUnion(&.{}), .record => |record| { const scratch_top = scratches.fields.top(); defer scratches.fields.clearFrom(scratch_top); - // Follow the record extension chain to collect ALL fields. - // Roc's type system represents records as linked rows: - // { a: A | ext } where ext -> { b: B | ext2 } where ext2 -> {} var current_row = record; rows: while (true) { const fields_slice = types_store.getRecordFieldsSlice(current_row.fields); @@ -436,15 +765,18 @@ pub const Store = struct { for (names, vars) |name, field_var| { var seen_name = false; for (scratches.fields.sliceFromStart(scratch_top)) |existing| { - if (existing.name.eql(name)) { + const existing_text = self.name_pool.getText(existing.name); + const name_text = scratches.ident_store.?.getText(name); + if (std.mem.eql(u8, existing_text, name_text)) { seen_name = true; break; } } if (seen_name) continue; - const field_type = try self.fromTypeVar(allocator, types_store, field_var, common_idents, specializations, nominal_cycle_breakers, scratches); - try scratches.fields.append(.{ .name = name, .type_idx = field_type }); + const field_type = try self.fromTypeVar(self.allocator, types_store, field_var, common_idents, specializations, scratches); + const sname = try self.internNameFromIdent(scratches.ident_store.?, name); + try scratches.fields.append(.{ .name = sname, .ty = field_type }); } var ext_var = current_row.ext; @@ -461,22 +793,24 @@ pub const Store = struct { continue :rows; }, .record_unbound => |fields_range| { - // Final open-row segment: append known fields. const ext_fields = types_store.getRecordFieldsSlice(fields_range); const ext_names = ext_fields.items(.name); const ext_vars = ext_fields.items(.var_); for (ext_names, ext_vars) |name, field_var| { var seen_name = false; for (scratches.fields.sliceFromStart(scratch_top)) |existing| { - if (existing.name.eql(name)) { + const existing_text = self.name_pool.getText(existing.name); + const name_text = scratches.ident_store.?.getText(name); + if (std.mem.eql(u8, existing_text, name_text)) { seen_name = true; break; } } if (seen_name) continue; - const field_type = try self.fromTypeVar(allocator, types_store, field_var, common_idents, specializations, nominal_cycle_breakers, scratches); - try scratches.fields.append(.{ .name = name, .type_idx = field_type }); + const field_type = try self.fromTypeVar(self.allocator, types_store, field_var, common_idents, specializations, scratches); + const sname = try self.internNameFromIdent(scratches.ident_store.?, name); + try scratches.fields.append(.{ .name = sname, .ty = field_type }); } break :rows; }, @@ -495,20 +829,17 @@ pub const Store = struct { if (findNamedRowExtensionMonotype(scratches, ext_var, types_store)) |specialized| { try self.appendSpecializedRecordFields(specialized, scratch_top, scratches); } - break :rows; // Open record — treat as closed with collected fields + break :rows; }, .rigid => { if (findNamedRowExtensionMonotype(scratches, ext_var, types_store)) |specialized| { try self.appendSpecializedRecordFields(specialized, scratch_top, scratches); } - break :rows; // Rigid record — treat as closed with collected fields + break :rows; }, .err => { if (std.debug.runtime_safety) { - std.debug.panic( - "Monotype.fromTypeVar(record): error row extension tail", - .{}, - ); + std.debug.panic("Monotype.fromTypeVar(record): error row extension tail", .{}); } unreachable; }, @@ -517,15 +848,10 @@ pub const Store = struct { } const collected_fields = scratches.fields.sliceFromStart(scratch_top); - // Each row segment is sorted, but concatenation may not be. - if (scratches.ident_store) |ident_store| { - std.mem.sort(Field, collected_fields, ident_store, Field.sortByNameAsc); - } - const field_span = try self.addFields(allocator, collected_fields); - return try self.addMonotype(allocator, .{ .record = .{ .fields = field_span } }); + std.mem.sort(FieldKey, collected_fields, &self.name_pool, FieldKey.sortByNameAsc); + return try self.internRecord(collected_fields); }, .record_unbound => |fields_range| { - // Extensible record — treat like a closed record with the known fields const fields_slice = types_store.getRecordFieldsSlice(fields_range); const names = fields_slice.items(.name); const vars = fields_slice.items(.var_); @@ -534,12 +860,14 @@ pub const Store = struct { defer scratches.fields.clearFrom(scratch_top); for (names, vars) |name, field_var| { - const field_type = try self.fromTypeVar(allocator, types_store, field_var, common_idents, specializations, nominal_cycle_breakers, scratches); - try scratches.fields.append(.{ .name = name, .type_idx = field_type }); + const field_type = try self.fromTypeVar(self.allocator, types_store, field_var, common_idents, specializations, scratches); + const sname = try self.internNameFromIdent(scratches.ident_store.?, name); + try scratches.fields.append(.{ .name = sname, .ty = field_type }); } - const field_span = try self.addFields(allocator, scratches.fields.sliceFromStart(scratch_top)); - return try self.addMonotype(allocator, .{ .record = .{ .fields = field_span } }); + const collected = scratches.fields.sliceFromStart(scratch_top); + std.mem.sort(FieldKey, collected, &self.name_pool, FieldKey.sortByNameAsc); + return try self.internRecord(collected); }, .tuple => |tuple| { const elem_vars = types_store.sliceVars(tuple.elems); @@ -547,20 +875,16 @@ pub const Store = struct { defer scratches.idxs.clearFrom(scratch_top); for (elem_vars) |elem_var| { - const elem_type = try self.fromTypeVar(allocator, types_store, elem_var, common_idents, specializations, nominal_cycle_breakers, scratches); + const elem_type = try self.fromTypeVar(self.allocator, types_store, elem_var, common_idents, specializations, scratches); try scratches.idxs.append(elem_type); } - const elem_span = try self.addIdxSpan(allocator, scratches.idxs.sliceFromStart(scratch_top)); - return try self.addMonotype(allocator, .{ .tuple = .{ .elems = elem_span } }); + return try self.internTuple(scratches.idxs.sliceFromStart(scratch_top)); }, .tag_union => |tag_union_row| { const tags_top = scratches.tags.top(); defer scratches.tags.clearFrom(tags_top); - // Follow the tag union extension chain to collect ALL tags. - // Roc's type system represents tag unions as linked rows: - // [Ok a | ext] where ext -> [Err b | ext2] where ext2 -> empty_tag_union var current_row = tag_union_row; rows: while (true) { const tags_slice = types_store.getTagsSlice(current_row.tags); @@ -573,15 +897,15 @@ pub const Store = struct { defer scratches.idxs.clearFrom(idxs_top); for (arg_vars) |arg_var| { - const payload_type = try self.fromTypeVar(allocator, types_store, arg_var, common_idents, specializations, nominal_cycle_breakers, scratches); + const payload_type = try self.fromTypeVar(self.allocator, types_store, arg_var, common_idents, specializations, scratches); try scratches.idxs.append(payload_type); } - const payloads_span = try self.addIdxSpan(allocator, scratches.idxs.sliceFromStart(idxs_top)); - try scratches.tags.append(.{ .name = name, .payloads = payloads_span }); + const payloads_list = try self.internIdxList(scratches.idxs.sliceFromStart(idxs_top)); + const sname = try self.internNameFromIdent(scratches.ident_store.?, name); + try scratches.tags.append(.{ .name = sname, .payloads = payloads_list }); } - // Follow extension variable to find more tags var ext_var = current_row.ext; while (true) { const ext_resolved = types_store.resolveVar(ext_var); @@ -610,20 +934,17 @@ pub const Store = struct { if (findNamedRowExtensionMonotype(scratches, ext_var, types_store)) |specialized| { try self.appendSpecializedTagUnionTags(specialized, scratches); } - break :rows; // Open tag union — treat as closed with collected tags + break :rows; }, .rigid => { if (findNamedRowExtensionMonotype(scratches, ext_var, types_store)) |specialized| { try self.appendSpecializedTagUnionTags(specialized, scratches); } - break :rows; // Rigid tag union — treat as closed with collected tags + break :rows; }, .err => { if (std.debug.runtime_safety) { - std.debug.panic( - "Monotype.fromTypeVar(tag_union): error row extension tail", - .{}, - ); + std.debug.panic("Monotype.fromTypeVar(tag_union): error row extension tail", .{}); } unreachable; }, @@ -632,93 +953,109 @@ pub const Store = struct { } const collected_tags = scratches.tags.sliceFromStart(tags_top); - // Sort tags alphabetically to match discriminant assignment order. - // Each ext-chain row is pre-sorted, but the concatenation may not be. - if (scratches.ident_store) |ident_store| { - std.mem.sort(Tag, collected_tags, ident_store, Tag.sortByNameAsc); - } - const tag_span = try self.addTags(allocator, collected_tags); - return try self.addMonotype(allocator, .{ .tag_union = .{ .tags = tag_span } }); + std.mem.sort(TagKey, collected_tags, &self.name_pool, TagKey.sortByNameAsc); + return try self.internTagUnion(collected_tags); }, - .fn_pure => |func| try self.fromFuncType(allocator, types_store, func, false, common_idents, specializations, nominal_cycle_breakers, scratches), - .fn_effectful => |func| try self.fromFuncType(allocator, types_store, func, true, common_idents, specializations, nominal_cycle_breakers, scratches), - .fn_unbound => |func| try self.fromFuncType(allocator, types_store, func, false, common_idents, specializations, nominal_cycle_breakers, scratches), + .fn_pure => |func| try self.fromFuncType(types_store, func, false, common_idents, specializations, scratches), + .fn_effectful => |func| try self.fromFuncType(types_store, func, true, common_idents, specializations, scratches), + .fn_unbound => |func| try self.fromFuncType(types_store, func, false, common_idents, specializations, scratches), }; } fn fromFuncType( - self: *Store, - allocator: Allocator, + self: *TypeInterner, types_store: *const types.Store, func: types.Func, effectful: bool, common_idents: CommonIdents, - specializations: *const std.AutoHashMap(types.Var, Idx), - nominal_cycle_breakers: *std.AutoHashMap(types.Var, Idx), + specializations: *const std.AutoHashMap(types.Var, TypeId), scratches: *Scratches, - ) Allocator.Error!Idx { + ) Allocator.Error!TypeId { const arg_vars = types_store.sliceVars(func.args); const scratch_top = scratches.idxs.top(); defer scratches.idxs.clearFrom(scratch_top); for (arg_vars) |arg_var| { - const arg_type = try self.fromTypeVar(allocator, types_store, arg_var, common_idents, specializations, nominal_cycle_breakers, scratches); + const arg_type = try self.fromTypeVar(self.allocator, types_store, arg_var, common_idents, specializations, scratches); try scratches.idxs.append(arg_type); } - const args_span = try self.addIdxSpan(allocator, scratches.idxs.sliceFromStart(scratch_top)); - const ret = try self.fromTypeVar(allocator, types_store, func.ret, common_idents, specializations, nominal_cycle_breakers, scratches); + const ret = try self.fromTypeVar(self.allocator, types_store, func.ret, common_idents, specializations, scratches); + return try self.internFunc(scratches.idxs.sliceFromStart(scratch_top), ret, effectful); + } - return try self.addMonotype(allocator, .{ .func = .{ - .args = args_span, - .ret = ret, - .effectful = effectful, - } }); + fn buildNominalCacheKey( + self: *TypeInterner, + types_store: *const types.Store, + nominal: types.NominalType, + common_idents: CommonIdents, + specializations: *const std.AutoHashMap(types.Var, TypeId), + scratches: *Scratches, + ) Allocator.Error!NominalInstanceKey { + const ident_store = if (scratches.module_env) |env| env.getIdentStoreConst() else scratches.ident_store.?; + const origin_text = ident_store.getText(nominal.origin_module); + const type_text = ident_store.getText(nominal.ident.ident_idx); + const origin_name_id = try self.name_pool.intern(origin_text); + const type_name_id = try self.name_pool.intern(type_text); + + const actual_args = types_store.sliceNominalArgs(nominal); + const idx_top = scratches.idxs.top(); + defer scratches.idxs.clearFrom(idx_top); + for (actual_args) |arg_var| { + const arg_mono = try self.fromTypeVar( + self.allocator, + types_store, + arg_var, + common_idents, + specializations, + scratches, + ); + try scratches.idxs.append(arg_mono); + } + const args = try self.internIdxList(scratches.idxs.sliceFromStart(idx_top)); + + return .{ + .origin_name_id = origin_name_id, + .type_name_id = type_name_id, + .args = args, + }; } fn fromNominalType( - self: *Store, - allocator: Allocator, + self: *TypeInterner, types_store: *const types.Store, - nominal_var: types.Var, nominal: types.NominalType, common_idents: CommonIdents, - specializations: *const std.AutoHashMap(types.Var, Idx), - nominal_cycle_breakers: *std.AutoHashMap(types.Var, Idx), + specializations: *const std.AutoHashMap(types.Var, TypeId), scratches: *Scratches, - ) Allocator.Error!Idx { + ) Allocator.Error!TypeId { const ident = nominal.ident.ident_idx; const origin = nominal.origin_module; if (origin.eql(common_idents.builtin_module)) { - // Bool/Str: unqualified idents from source declarations if (ident.eql(common_idents.str)) return self.primIdx(.str); - if (ident.eql(common_idents.bool)) return try self.addBoolTagUnion(allocator, common_idents); + if (ident.eql(common_idents.bool)) return try self.addBoolTagUnion(); } if (origin.eql(common_idents.builtin_module)) { - - // List: unqualified ident from mkListContent if (ident.eql(common_idents.list)) { const type_args = types_store.sliceNominalArgs(nominal); if (type_args.len > 0) { - const elem_type = try self.fromTypeVar(allocator, types_store, type_args[0], common_idents, specializations, nominal_cycle_breakers, scratches); - return try self.addMonotype(allocator, .{ .list = .{ .elem = elem_type } }); + const elem_type = try self.fromTypeVar(self.allocator, types_store, type_args[0], common_idents, specializations, scratches); + return try self.internList(elem_type); } return self.unit_idx; } - // Box: unqualified ident from mkBoxContent if (ident.eql(common_idents.box)) { const type_args = types_store.sliceNominalArgs(nominal); if (type_args.len > 0) { - const inner_type = try self.fromTypeVar(allocator, types_store, type_args[0], common_idents, specializations, nominal_cycle_breakers, scratches); - return try self.addMonotype(allocator, .{ .box = .{ .inner = inner_type } }); + const inner_type = try self.fromTypeVar(self.allocator, types_store, type_args[0], common_idents, specializations, scratches); + return try self.internBox(inner_type); } return self.unit_idx; } - // Numeric types: qualified idents from mkNumberTypeContent (e.g. "Builtin.Num.I64") if (ident.eql(common_idents.i64_type)) return self.primIdx(.i64); if (ident.eql(common_idents.u8_type)) return self.primIdx(.u8); if (ident.eql(common_idents.i8_type)) return self.primIdx(.i8); @@ -734,49 +1071,76 @@ pub const Store = struct { if (ident.eql(common_idents.dec_type)) return self.primIdx(.dec); } - // For all other nominal types, strip the wrapper and follow the backing var. - // In MIR there is no nominal/opaque/structural distinction. - // - // Recursive nominal types (e.g. Tree := [Leaf, Node(Tree)]) are the - // only legitimate source of type cycles — the type checker has already - // converted infinite/anonymous recursive types to errors. We use - // `nominal_cycle_breakers` (separate from the specialization map) to - // break these cycles: register a placeholder before recursing, then - // overwrite it in-place with the real value. - if (nominal_cycle_breakers.get(nominal_var)) |cached| return cached; - if (findEquivalentNominalCycleBreaker(types_store, nominal, nominal_cycle_breakers)) |cached| { - return cached; + // Non-builtin nominal: use nominal instance cache for dedup. + // 1. Build canonical cache key (lowers args as side effect). + const cache_key = try self.buildNominalCacheKey( + types_store, + nominal, + common_idents, + specializations, + scratches, + ); + + // 2. Check nominal instance cache. + if (self.nominal_cache.get(cache_key)) |entry| { + switch (entry) { + .resolved => |type_id| return type_id, + .in_progress => |rec_id| { + // Recursive hit: allocate .rec lazily if first time. + if (rec_id.isNone()) { + const new_rec = try self.reserveRecursive(); + // Must re-fetch after potential map growth from reserveRecursive. + self.nominal_cache.getPtr(cache_key).?.* = .{ .in_progress = new_rec }; + return new_rec; + } + return rec_id; + }, + } } - const placeholder_idx = try self.addMonotype(allocator, .recursive_placeholder); - try nominal_cycle_breakers.put(nominal_var, placeholder_idx); + // 3. Cache miss: mark in-progress before recursing. + try self.nominal_cache.put(self.allocator, cache_key, .{ .in_progress = TypeId.none }); + // 4. Push nominal arg specializations (args already lowered by buildNominalCacheKey). const named_specializations_top = try self.pushNominalArgSpecializations( - allocator, types_store, nominal, common_idents, specializations, - nominal_cycle_breakers, scratches, ); defer scratches.named_specializations.clearFrom(named_specializations_top); + // 5. Lower the backing var. const backing_var = types_store.getNominalBackingVar(nominal); - const backing_idx = try self.fromTypeVar(allocator, types_store, backing_var, common_idents, specializations, nominal_cycle_breakers, scratches); - - // Copy the resolved backing type's value into our placeholder slot. - // This value-copy is safe because every field inside a Monotype is an - // index (Idx, Span, Ident.Idx) — never a pointer. The monotype store - // is append-only, so all indices remain valid after the copy. - self.monotypes.items[@intFromEnum(placeholder_idx)] = self.monotypes.items[@intFromEnum(backing_idx)]; - if (std.debug.runtime_safety) { - std.debug.assert(self.monotypes.items[@intFromEnum(placeholder_idx)] != .recursive_placeholder); + const backing_idx = try self.fromTypeVar( + self.allocator, + types_store, + backing_var, + common_idents, + specializations, + scratches, + ); + std.debug.assert(!backing_idx.isNone()); + + // 6. Finalize: check if recursion occurred. + // Must re-fetch pointer because recursive lowering may have grown the map. + const entry_ptr = self.nominal_cache.getPtr(cache_key).?; + const in_progress_rec = entry_ptr.in_progress; + + if (!in_progress_rec.isNone()) { + // Recursive: finalize the .rec slot, cache the .rec TypeId. + self.finalizeRecursive(in_progress_rec, backing_idx); + entry_ptr.* = .{ .resolved = in_progress_rec }; + return in_progress_rec; + } else { + // Non-recursive: cache the backing type directly. + entry_ptr.* = .{ .resolved = backing_idx }; + return backing_idx; } - return placeholder_idx; } - fn lookupNamedSpecialization(scratches: *const Scratches, name: Ident.Idx) ?Idx { + fn lookupNamedSpecialization(scratches: *const Scratches, name: Ident.Idx) ?TypeId { const ident_store = scratches.ident_store orelse return null; const name_text = ident_store.getText(name); const items = scratches.named_specializations.items.items; @@ -791,7 +1155,7 @@ pub const Store = struct { return null; } - fn findNamedRowExtensionMonotype(scratches: *const Scratches, ext_var: types.Var, types_store: *const types.Store) ?Idx { + fn findNamedRowExtensionMonotype(scratches: *const Scratches, ext_var: types.Var, types_store: *const types.Store) ?TypeId { const resolved = types_store.resolveVar(ext_var); return switch (resolved.desc.content) { .flex => |flex| if (flex.name) |name| lookupNamedSpecialization(scratches, name) else null, @@ -801,71 +1165,61 @@ pub const Store = struct { } fn appendSpecializedRecordFields( - self: *Store, - specialized: Idx, + self: *TypeInterner, + specialized: TypeId, scratch_top: u32, scratches: *Scratches, ) Allocator.Error!void { - const mono = self.getMonotype(specialized); - switch (mono) { - .record => |record| { - for (self.getFields(record.fields)) |field| { - var seen_name = false; - for (scratches.fields.sliceFromStart(scratch_top)) |existing| { - if (existing.name.eql(field.name)) { - seen_name = true; - break; - } - } - if (seen_name) continue; - try scratches.fields.append(field); - } - }, - .unit => {}, - else => { - if (std.debug.runtime_safety) { - std.debug.panic( - "Monotype.fromTypeVar(record): nominal row specialization must be record or unit, found '{s}'", - .{@tagName(mono)}, - ); + const resolved = self.resolve(specialized); + if (resolved.isUnit()) return; + if (resolved.kind != .record) { + if (std.debug.runtime_safety) { + std.debug.panic( + "Monotype.fromTypeVar(record): nominal row specialization must be record or unit, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + unreachable; + } + for (self.recordFields(resolved)) |field| { + var seen_name = false; + for (scratches.fields.sliceFromStart(scratch_top)) |existing| { + if (@intFromEnum(existing.name) == @intFromEnum(field.name)) { + seen_name = true; + break; } - unreachable; - }, + } + if (seen_name) continue; + try scratches.fields.append(field); } } fn appendSpecializedTagUnionTags( - self: *Store, - specialized: Idx, + self: *TypeInterner, + specialized: TypeId, scratches: *Scratches, ) Allocator.Error!void { - const mono = self.getMonotype(specialized); - switch (mono) { - .tag_union => |tag_union| { - for (self.getTags(tag_union.tags)) |tag| { - try scratches.tags.append(tag); - } - }, - else => { - if (std.debug.runtime_safety) { - std.debug.panic( - "Monotype.fromTypeVar(tag_union): nominal row specialization must be tag_union, found '{s}'", - .{@tagName(mono)}, - ); - } - unreachable; - }, + const resolved = self.resolve(specialized); + if (resolved.kind != .tag_union) { + if (std.debug.runtime_safety) { + std.debug.panic( + "Monotype.fromTypeVar(tag_union): nominal row specialization must be tag_union, found kind={d}", + .{@intFromEnum(resolved.kind)}, + ); + } + unreachable; + } + for (self.tagUnionTags(resolved)) |tag| { + try scratches.tags.append(tag); } } fn pushNominalArgSpecializations( - self: *Store, - allocator: Allocator, + self: *TypeInterner, types_store: *const types.Store, nominal: types.NominalType, common_idents: CommonIdents, - specializations: *const std.AutoHashMap(types.Var, Idx), - nominal_cycle_breakers: *std.AutoHashMap(types.Var, Idx), + specializations: *const std.AutoHashMap(types.Var, TypeId), scratches: *Scratches, ) Allocator.Error!u32 { const top = scratches.named_specializations.top(); @@ -891,12 +1245,11 @@ pub const Store = struct { for (formal_args, actual_args) |formal_arg, actual_arg| { const formal_name_text = resolvedTypeVarNameText(&definition_env.types, definition_env, formal_arg) orelse continue; const actual_mono = try self.fromTypeVar( - allocator, + self.allocator, types_store, actual_arg, common_idents, specializations, - nominal_cycle_breakers, scratches, ); try scratches.named_specializations.append(.{ @@ -954,38 +1307,6 @@ pub const Store = struct { } }; -fn findEquivalentNominalCycleBreaker( - types_store: *const types.Store, - nominal: types.NominalType, - nominal_cycle_breakers: *const std.AutoHashMap(types.Var, Idx), -) ?Idx { - var iter = nominal_cycle_breakers.iterator(); - while (iter.next()) |entry| { - const resolved = types_store.resolveVar(entry.key_ptr.*); - if (resolved.desc.content != .structure) continue; - const flat = resolved.desc.content.structure; - if (flat != .nominal_type) continue; - const other_nominal = flat.nominal_type; - - if (!nominal.origin_module.eql(other_nominal.origin_module)) continue; - if (!nominal.ident.ident_idx.eql(other_nominal.ident.ident_idx)) continue; - - const lhs_args = types_store.sliceNominalArgs(nominal); - const rhs_args = types_store.sliceNominalArgs(other_nominal); - if (lhs_args.len != rhs_args.len) continue; - - var args_match = true; - for (lhs_args, rhs_args) |lhs_arg, rhs_arg| { - const lhs_resolved = types_store.resolveVar(lhs_arg); - const rhs_resolved = types_store.resolveVar(rhs_arg); - if (lhs_resolved.var_ != rhs_resolved.var_) { - args_match = false; - break; - } - } - - if (args_match) return entry.value_ptr.*; - } - - return null; -} +/// Backward-compat alias used in 17+ call sites (MIR.zig, Lower.zig, MirToLir.zig, etc.). +/// May be removed in a future rename pass. +pub const Store = TypeInterner; diff --git a/src/mir/test/lower_test.zig b/src/mir/test/lower_test.zig index 9a92adb601c..c84ef79137e 100644 --- a/src/mir/test/lower_test.zig +++ b/src/mir/test/lower_test.zig @@ -218,111 +218,102 @@ test "MIR Store: multiple expressions round trip" { test "Monotype Store: primitive types" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); - try testing.expectEqual(Monotype.Prim.str, store.getMonotype(store.primIdx(.str)).prim); - try testing.expectEqual(Monotype.Prim.i64, store.getMonotype(store.primIdx(.i64)).prim); + try testing.expectEqual(Monotype.Prim.str, store.primIdx(.str).builtinPrim().?); + try testing.expectEqual(Monotype.Prim.i64, store.primIdx(.i64).builtinPrim().?); } test "Monotype Store: unit type" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); - try testing.expect(store.getMonotype(store.unit_idx) == .unit); + try testing.expect(store.unit_idx.isUnit()); } test "Monotype Store: list type" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); const elem = store.primIdx(.str); - const list = try store.addMonotype(test_allocator, .{ .list = .{ .elem = elem } }); + const list = try store.internList(elem); - const retrieved = store.getMonotype(list); - try testing.expectEqual(elem, retrieved.list.elem); + try testing.expect(list.kind == .list); + try testing.expect(store.listElem(list).eql(elem)); } test "Monotype Store: func type" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); const arg1 = store.primIdx(.i64); const arg2 = store.primIdx(.str); const ret = store.primIdx(.i64); - const args_span = try store.addIdxSpan(test_allocator, &.{ arg1, arg2 }); - const func = try store.addMonotype(test_allocator, .{ .func = .{ - .args = args_span, - .ret = ret, - .effectful = false, - } }); + const func = try store.internFunc(&.{ arg1, arg2 }, ret, false); - const retrieved = store.getMonotype(func); - try testing.expectEqual(ret, retrieved.func.ret); - try testing.expectEqual(false, retrieved.func.effectful); + try testing.expect(func.kind == .func); + try testing.expect(store.funcRet(func).eql(ret)); + try testing.expect(!store.funcEffectful(func)); } test "Monotype Store: record type" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); const field1_type = store.primIdx(.i64); const field2_type = store.primIdx(.str); - const field_span = try store.addFields(test_allocator, &.{ - .{ .name = Ident.Idx.NONE, .type_idx = field1_type }, - .{ .name = Ident.Idx.NONE, .type_idx = field2_type }, + const name1 = try store.name_pool.intern("field1"); + const name2 = try store.name_pool.intern("field2"); + const record = try store.internRecord(&.{ + .{ .name = name1, .ty = field1_type }, + .{ .name = name2, .ty = field2_type }, }); - const record = try store.addMonotype(test_allocator, .{ .record = .{ .fields = field_span } }); - const retrieved = store.getMonotype(record); - try testing.expect(retrieved == .record); + try testing.expect(record.kind == .record); } test "Monotype Store: tag union type" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); const payload_type = store.primIdx(.str); - const payload_span = try store.addIdxSpan(test_allocator, &.{payload_type}); - - const tag_span = try store.addTags(test_allocator, &.{ - .{ .name = Ident.Idx.NONE, .payloads = payload_span }, + const tag_name = try store.name_pool.intern("MyTag"); + const payload_list = try store.internIdxList(&.{payload_type}); + const tag_union = try store.internTagUnion(&.{ + .{ .name = tag_name, .payloads = payload_list }, }); - const tag_union = try store.addMonotype(test_allocator, .{ .tag_union = .{ .tags = tag_span } }); - const retrieved = store.getMonotype(tag_union); - try testing.expect(retrieved == .tag_union); + try testing.expect(tag_union.kind == .tag_union); } test "Monotype Store: box type" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); const inner = store.primIdx(.i64); - const boxed = try store.addMonotype(test_allocator, .{ .box = .{ .inner = inner } }); + const boxed = try store.internBox(inner); - const retrieved = store.getMonotype(boxed); - try testing.expectEqual(inner, retrieved.box.inner); + try testing.expect(boxed.kind == .box); + try testing.expect(store.boxInner(boxed).eql(inner)); } test "Monotype Store: tuple type" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); const elem1 = store.primIdx(.i64); const elem2 = store.primIdx(.str); - const elems_span = try store.addIdxSpan(test_allocator, &.{ elem1, elem2 }); - const tuple = try store.addMonotype(test_allocator, .{ .tuple = .{ .elems = elems_span } }); + const tuple = try store.internTuple(&.{ elem1, elem2 }); - const retrieved = store.getMonotype(tuple); - try testing.expect(retrieved == .tuple); + try testing.expect(tuple.kind == .tuple); } test "Monotype Store: all primitive types" { var store = try Monotype.Store.init(test_allocator); - defer store.deinit(test_allocator); + defer store.deinit(); const prims = [_]Monotype.Prim{ .str, @@ -343,7 +334,7 @@ test "Monotype Store: all primitive types" { for (prims) |p| { const idx = store.primIdx(p); - try testing.expectEqual(p, store.getMonotype(idx).prim); + try testing.expectEqual(p, idx.builtinPrim().?); } } @@ -1039,9 +1030,8 @@ test "lambda set: higher-order closure captures monotype stays numeric tuple" { const closure_member = env.mir_store.getClosureMember(members[0].closure_member); const capture_bindings = env.mir_store.getCaptureBindings(closure_member.capture_bindings); try testing.expectEqual(@as(usize, 1), capture_bindings.len); - const elem_mono = env.mir_store.monotype_store.getMonotype(capture_bindings[0].monotype); - try testing.expect(elem_mono == .prim); - try testing.expectEqual(Monotype.Prim.dec, elem_mono.prim); + const elem_mono = env.mir_store.monotype_store.resolve(capture_bindings[0].monotype); + try testing.expectEqual(Monotype.Prim.dec, elem_mono.builtinPrim().?); } test "lambda set: imported List.any receives predicate lambda set" { @@ -1080,21 +1070,18 @@ test "lambda set: imported List.any receives predicate lambda set" { const param_ids = env.mir_store.getPatternSpan(params); try testing.expectEqual(@as(usize, 2), param_ids.len); - const list_param_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.patternTypeOf(param_ids[0])); - try testing.expect(list_param_mono == .list); - const list_elem_mono = env.mir_store.monotype_store.getMonotype(list_param_mono.list.elem); - try testing.expect(list_elem_mono == .prim); - try testing.expectEqual(Monotype.Prim.i64, list_elem_mono.prim); + const list_param_id = env.mir_store.monotype_store.resolve(env.mir_store.patternTypeOf(param_ids[0])); + try testing.expect(list_param_id.kind == .list); + const list_elem_id = env.mir_store.monotype_store.listElem(list_param_id); + try testing.expectEqual(Monotype.Prim.i64, list_elem_id.builtinPrim().?); - const predicate_param_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.patternTypeOf(param_ids[1])); - try testing.expect(predicate_param_mono == .func); - const predicate_arg_monos = env.mir_store.monotype_store.getIdxSpan(predicate_param_mono.func.args); + const predicate_param_id = env.mir_store.monotype_store.resolve(env.mir_store.patternTypeOf(param_ids[1])); + try testing.expect(predicate_param_id.kind == .func); + const predicate_arg_monos = env.mir_store.monotype_store.funcArgs(predicate_param_id); try testing.expectEqual(@as(usize, 1), predicate_arg_monos.len); - const predicate_arg_mono = env.mir_store.monotype_store.getMonotype(predicate_arg_monos[0]); - try testing.expect(predicate_arg_mono == .prim); - try testing.expectEqual(Monotype.Prim.i64, predicate_arg_mono.prim); - const predicate_ret_mono = env.mir_store.monotype_store.getMonotype(predicate_param_mono.func.ret); - try testing.expect(predicate_ret_mono == .tag_union); + try testing.expectEqual(Monotype.Prim.i64, predicate_arg_monos[0].builtinPrim().?); + const predicate_ret_id = env.mir_store.monotype_store.funcRet(predicate_param_id); + try testing.expect(predicate_ret_id.kind == .tag_union); const predicate_pat = env.mir_store.getPattern(param_ids[1]); try testing.expect(predicate_pat == .bind); @@ -1128,9 +1115,8 @@ test "lambda set: imported List.any receives predicate lambda set" { } } - const loop_item_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.patternTypeOf(loop_item_pat.?)); - try testing.expect(loop_item_mono == .prim); - try testing.expectEqual(Monotype.Prim.i64, loop_item_mono.prim); + const loop_item_id = env.mir_store.monotype_store.resolve(env.mir_store.patternTypeOf(loop_item_pat.?)); + try testing.expectEqual(Monotype.Prim.i64, loop_item_id.builtinPrim().?); const all_module_envs = [_]*ModuleEnv{ @constCast(env.builtin_module.env), @@ -1185,9 +1171,8 @@ test "fromTypeVar: int with suffix resolves to prim i64" { var env = try MirTestEnv.initExpr("42.I64"); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .prim); - try testing.expectEqual(Monotype.Prim.i64, monotype.prim); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expectEqual(Monotype.Prim.i64, mono_id.builtinPrim().?); } test "fromTypeVar: string resolves to valid monotype" { @@ -1215,17 +1200,17 @@ test "fromTypeVar: list resolves to list monotype" { // This catches the cross-module ident mismatch bug where // fromNominalType fails to recognize List because the Builtin // module's Ident.Idx for "List" differs from the current module's. - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .list); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .list); } test "fromTypeVar: record resolves to record with fields" { var env = try MirTestEnv.initExpr("{ x: 1, y: 2 }"); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .record); - try testing.expectEqual(@as(u16, 2), monotype.record.fields.len); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .record); + try testing.expectEqual(@as(usize, 2), env.mir_store.monotype_store.recordFields(mono_id).len); } test "fromTypeVar: lambda resolves to func type" { @@ -1235,17 +1220,17 @@ test "fromTypeVar: lambda resolves to func type" { ); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .func); - try testing.expectEqual(@as(u16, 1), monotype.func.args.len); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .func); + try testing.expectEqual(@as(usize, 1), env.mir_store.monotype_store.funcArgs(mono_id).len); } test "fromTypeVar: tag resolves to tag_union" { var env = try MirTestEnv.initExpr("Ok(42)"); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "fromTypeVar: tuple resolves to tuple type" { @@ -1254,9 +1239,9 @@ test "fromTypeVar: tuple resolves to tuple type" { ); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tuple); - try testing.expectEqual(@as(u16, 2), monotype.tuple.elems.len); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tuple); + try testing.expectEqual(@as(usize, 2), env.mir_store.monotype_store.tupleElems(mono_id).len); } // --- Gap #25: Recursive types in fromTypeVar --- @@ -1272,8 +1257,8 @@ test "fromTypeVar: recursive linked list type completes without hanging" { const expr = try env.lowerNamedDef("x"); const result = env.mir_store.getExpr(expr); try testing.expect(result == .tag); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "fromTypeVar: recursive binary tree type completes without hanging" { @@ -1287,8 +1272,8 @@ test "fromTypeVar: recursive binary tree type completes without hanging" { const expr = try env.lowerNamedDef("x"); const result = env.mir_store.getExpr(expr); try testing.expect(result == .tag); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "fromTypeVar: polymorphic opaque with function field propagates nominal args into backing type" { @@ -1304,27 +1289,25 @@ test "fromTypeVar: polymorphic opaque with function field propagates nominal arg defer env.deinit(); const expr = try env.lowerNamedDef("w"); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .record); + try testing.expect(mono_id.kind == .record); - const fields = env.mir_store.monotype_store.getFields(monotype.record.fields); + const fields = env.mir_store.monotype_store.recordFields(mono_id); try testing.expectEqual(@as(usize, 1), fields.len); - const field_type = env.mir_store.monotype_store.getMonotype(fields[0].type_idx); - try testing.expect(field_type == .func); + const field_type = env.mir_store.monotype_store.resolve(fields[0].ty); + try testing.expect(field_type.kind == .func); - const ret_type = env.mir_store.monotype_store.getMonotype(field_type.func.ret); - try testing.expect(ret_type == .tag_union); + const ret_type = env.mir_store.monotype_store.resolve(env.mir_store.monotype_store.funcRet(field_type)); + try testing.expect(ret_type.kind == .tag_union); - const tags = env.mir_store.monotype_store.getTags(ret_type.tag_union.tags); + const tags = env.mir_store.monotype_store.tagUnionTags(ret_type); try testing.expectEqual(@as(usize, 1), tags.len); - try testing.expectEqual(@as(usize, 1), tags[0].payloads.len); - const payloads = env.mir_store.monotype_store.getIdxSpan(tags[0].payloads); - const payload_type = env.mir_store.monotype_store.getMonotype(payloads[0]); - try testing.expect(payload_type == .prim); - try testing.expectEqual(Monotype.Prim.str, payload_type.prim); + const payloads = env.mir_store.monotype_store.getIdxListItems(tags[0].payloads); + try testing.expectEqual(@as(usize, 1), payloads.len); + try testing.expectEqual(Monotype.Prim.str, payloads[0].builtinPrim().?); } test "lambda set: opaque function field call through param gets field lambda set" { @@ -1776,10 +1759,10 @@ test "lowerExternalDef: mutually recursive defs get monotypes patched (not left try testing.expect(odd_type != env.mir_store.monotype_store.unit_idx); // Both should resolve to func types (U64 -> Bool) - const even_mono = env.mir_store.monotype_store.getMonotype(even_type); - const odd_mono = env.mir_store.monotype_store.getMonotype(odd_type); - try testing.expect(even_mono == .func); - try testing.expect(odd_mono == .func); + const even_resolved = env.mir_store.monotype_store.resolve(even_type); + const odd_resolved = env.mir_store.monotype_store.resolve(odd_type); + try testing.expect(even_resolved.kind == .func); + try testing.expect(odd_resolved.kind == .func); } // --- Cross-module builtin call lowering --- @@ -2222,8 +2205,8 @@ test "Bool diagnostic MIR: Bool.True lowers to tag with tag_union" { const tag_name = env.module_env.getIdent(result.tag.name); try testing.expectEqualStrings("True", tag_name); // Monotype should be tag_union - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Bool diagnostic MIR: Bool.False lowers to tag with tag_union" { @@ -2235,8 +2218,8 @@ test "Bool diagnostic MIR: Bool.False lowers to tag with tag_union" { try testing.expectEqual(@as(u16, 0), result.tag.args.len); const tag_name = env.module_env.getIdent(result.tag.name); try testing.expectEqualStrings("False", tag_name); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Bool diagnostic MIR: Bool.not(True) lowers with tag_union type" { @@ -2246,8 +2229,8 @@ test "Bool diagnostic MIR: Bool.not(True) lowers with tag_union type" { const result = env.mir_store.getExpr(expr); // Bool.not may lower as a cross-module call or inlined match try testing.expect(result != .runtime_err_type); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Bool diagnostic MIR: Bool.not(False) lowers with tag_union type" { @@ -2256,8 +2239,8 @@ test "Bool diagnostic MIR: Bool.not(False) lowers with tag_union type" { const expr = try env.lowerFirstDef(); const result = env.mir_store.getExpr(expr); try testing.expect(result != .runtime_err_type); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Bool diagnostic MIR: !Bool.True lowers to match_expr with tag_union" { @@ -2269,8 +2252,8 @@ test "Bool diagnostic MIR: !Bool.True lowers to match_expr with tag_union" { const expr = try env.lowerFirstDef(); // !Bool.True desugars via negBool to a match expression try testing.expect(env.mir_store.getExpr(expr) == .match_expr); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Bool diagnostic MIR: !Bool.False lowers to match_expr with tag_union" { @@ -2281,8 +2264,8 @@ test "Bool diagnostic MIR: !Bool.False lowers to match_expr with tag_union" { defer env.deinit(); const expr = try env.lowerFirstDef(); try testing.expect(env.mir_store.getExpr(expr) == .match_expr); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Bool diagnostic MIR: Bool.True and Bool.False lowers to match_expr with tag_union" { @@ -2292,8 +2275,8 @@ test "Bool diagnostic MIR: Bool.True and Bool.False lowers to match_expr with ta const result = env.mir_store.getExpr(expr); // `and` short-circuit desugars to a match expression try testing.expect(result == .match_expr); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Bool diagnostic MIR: !Bool.True or !Bool.True lowers with tag_union" { @@ -2306,8 +2289,8 @@ test "Bool diagnostic MIR: !Bool.True or !Bool.True lowers with tag_union" { const result = env.mir_store.getExpr(expr); // `or` short-circuit desugars to a match expression try testing.expect(result == .match_expr); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Bool diagnostic MIR: lambda negation applied to Bool.True lowers with tag_union" { @@ -2317,8 +2300,8 @@ test "Bool diagnostic MIR: lambda negation applied to Bool.True lowers with tag_ const result = env.mir_store.getExpr(expr); // A lambda call should produce a .call expression try testing.expect(result != .runtime_err_type); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } // --- Bool.not structural tests --- @@ -2342,8 +2325,8 @@ test "Bool.not MIR: !Bool.True match has True pattern -> False body, wildcard -> try testing.expectEqualStrings("True", cond_tag_name); // Check condition monotype is tag_union - const cond_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(result.match_expr.cond)); - try testing.expect(cond_mono == .tag_union); + const cond_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(result.match_expr.cond)); + try testing.expect(cond_mono_id.kind == .tag_union); // Check there are 2 branches const branches = env.mir_store.getBranches(result.match_expr.branches); @@ -2357,8 +2340,8 @@ test "Bool.not MIR: !Bool.True match has True pattern -> False body, wildcard -> const pat0_name = env.module_env.getIdent(pat0.tag.name); try testing.expectEqualStrings("True", pat0_name); // Pattern monotype should be tag_union - const pat0_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.patternTypeOf(bp0[0].pattern)); - try testing.expect(pat0_mono == .tag_union); + const pat0_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.patternTypeOf(bp0[0].pattern)); + try testing.expect(pat0_mono_id.kind == .tag_union); // Body should be False tag const body0 = env.mir_store.getExpr(branches[0].body); try testing.expect(body0 == .tag); @@ -2392,8 +2375,8 @@ test "Bool.not MIR: !Bool.False match has True pattern -> False body, wildcard - try testing.expect(cond == .tag); const cond_tag_name = env.module_env.getIdent(cond.tag.name); try testing.expectEqualStrings("False", cond_tag_name); - const cond_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(result.match_expr.cond)); - try testing.expect(cond_mono == .tag_union); + const cond_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(result.match_expr.cond)); + try testing.expect(cond_mono_id.kind == .tag_union); // Branch structure should be identical: True => False, _ => True const branches = env.mir_store.getBranches(result.match_expr.branches); @@ -2423,16 +2406,16 @@ test "Bool.not MIR: Bool.not(True) is a call with tag_union return type" { // Bool.not(True) should be a call expression (cross-module method call) try testing.expect(result == .call); // Return type should be tag_union - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); // The argument should be a tag (True) with tag_union type const args = env.mir_store.getExprSpan(result.call.args); try testing.expectEqual(@as(usize, 1), args.len); const arg = env.mir_store.getExpr(args[0]); try testing.expect(arg == .tag); try testing.expectEqualStrings("True", env.module_env.getIdent(arg.tag.name)); - const arg_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(args[0])); - try testing.expect(arg_mono == .tag_union); + const arg_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(args[0])); + try testing.expect(arg_mono_id.kind == .tag_union); } test "Bool.not MIR: Bool.not(False) is a call with tag_union return type" { @@ -2441,15 +2424,15 @@ test "Bool.not MIR: Bool.not(False) is a call with tag_union return type" { const expr = try env.lowerFirstDef(); const result = env.mir_store.getExpr(expr); try testing.expect(result == .call); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); const args = env.mir_store.getExprSpan(result.call.args); try testing.expectEqual(@as(usize, 1), args.len); const arg = env.mir_store.getExpr(args[0]); try testing.expect(arg == .tag); try testing.expectEqualStrings("False", env.module_env.getIdent(arg.tag.name)); - const arg_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(args[0])); - try testing.expect(arg_mono == .tag_union); + const arg_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(args[0])); + try testing.expect(arg_mono_id.kind == .tag_union); } // --- Nominal Bool vs structural tag union MIR tests --- @@ -2464,8 +2447,8 @@ test "Nominal Bool MIR: annotated True lowers with tag_union" { ); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Nominal Bool MIR: annotated False lowers with tag_union" { @@ -2475,32 +2458,32 @@ test "Nominal Bool MIR: annotated False lowers with tag_union" { ); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Structural tag MIR: bare True lowers as tag_union" { var env = try MirTestEnv.initExpr("True"); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Structural tag MIR: bare False lowers as tag_union" { var env = try MirTestEnv.initExpr("False"); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Structural tag MIR: if True True else False lowers as tag_union" { var env = try MirTestEnv.initExpr("if True True else False"); defer env.deinit(); const expr = try env.lowerFirstDef(); - const monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(monotype == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } // --- Cross-module type resolution: method dispatch resolves concrete types --- @@ -2522,29 +2505,27 @@ test "cross-module type resolution: U32.to dispatches with concrete U32 function const func_expr = env.mir_store.getExpr(top.call.func); try testing.expect(func_expr == .lookup); - const func_monotype_idx = env.mir_store.typeOf(top.call.func); - const func_mono = env.mir_store.monotype_store.getMonotype(func_monotype_idx); - try testing.expect(func_mono == .func); + const func_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(top.call.func)); + try testing.expect(func_mono_id.kind == .func); // The function's return type must be List(U32), not List(unit) - const ret_mono = env.mir_store.monotype_store.getMonotype(func_mono.func.ret); - try testing.expect(ret_mono == .list); + const ret_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.monotype_store.funcRet(func_mono_id)); + try testing.expect(ret_mono_id.kind == .list); - const elem_mono = env.mir_store.monotype_store.getMonotype(ret_mono.list.elem); - try testing.expectEqual(Monotype.Monotype{ .prim = .u32 }, elem_mono); + const elem_mono_id = env.mir_store.monotype_store.listElem(ret_mono_id); + try testing.expectEqual(Monotype.Prim.u32, elem_mono_id.builtinPrim().?); // Verify the function DEFINITION body was also lowered with concrete types. const method_symbol = func_expr.lookup; const sym_key: u64 = @bitCast(method_symbol); const def_expr_id = env.mir_store.symbol_defs.get(sym_key) orelse return error.TestUnexpectedResult; - const def_mono_idx = env.mir_store.typeOf(def_expr_id); - const def_mono = env.mir_store.monotype_store.getMonotype(def_mono_idx); - try testing.expect(def_mono == .func); - const def_ret_mono = env.mir_store.monotype_store.getMonotype(def_mono.func.ret); - try testing.expect(def_ret_mono == .list); - const def_elem = env.mir_store.monotype_store.getMonotype(def_ret_mono.list.elem); - try testing.expectEqual(Monotype.Monotype{ .prim = .u32 }, def_elem); + const def_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(def_expr_id)); + try testing.expect(def_mono_id.kind == .func); + const def_ret_id = env.mir_store.monotype_store.resolve(env.mir_store.monotype_store.funcRet(def_mono_id)); + try testing.expect(def_ret_id.kind == .list); + const def_elem_id = env.mir_store.monotype_store.listElem(def_ret_id); + try testing.expectEqual(Monotype.Prim.u32, def_elem_id.builtinPrim().?); } // --- Polymorphic numeric specialization tests --- @@ -2573,15 +2554,14 @@ test "polymorphic lambda in block: sum called with U64 gets U64 monotype, not De try testing.expect(final_expr == .call); // The call's return type must be U64, not Dec - const call_monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(result.block.final_expr)); - try testing.expectEqual(Monotype.Prim.u64, call_monotype.prim); + const call_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(result.block.final_expr)); + try testing.expectEqual(Monotype.Prim.u64, call_mono_id.builtinPrim().?); // The call's arguments must be U64 const args = env.mir_store.getExprSpan(final_expr.call.args); for (args) |arg| { - const arg_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(arg)); - try testing.expect(arg_mono == .prim); - try testing.expectEqual(Monotype.Prim.u64, arg_mono.prim); + const arg_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(arg)); + try testing.expectEqual(Monotype.Prim.u64, arg_mono_id.builtinPrim().?); } // The lambda itself (first stmt's expr) should have params typed as U64 @@ -2589,12 +2569,11 @@ test "polymorphic lambda in block: sum called with U64 gets U64 monotype, not De try testing.expect(stmts.len >= 1); const decl_expr = env.mir_store.getExpr(stmts[0].decl_const.expr); try testing.expect(decl_expr == .lambda); - const lambda_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(stmts[0].decl_const.expr)); - try testing.expect(lambda_mono == .func); + const lambda_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(stmts[0].decl_const.expr)); + try testing.expect(lambda_mono_id.kind == .func); // Return type of the lambda must be U64 - const ret_mono = env.mir_store.monotype_store.getMonotype(lambda_mono.func.ret); - try testing.expect(ret_mono == .prim); - try testing.expectEqual(Monotype.Prim.u64, ret_mono.prim); + const ret_mono_id = env.mir_store.monotype_store.funcRet(lambda_mono_id); + try testing.expectEqual(Monotype.Prim.u64, ret_mono_id.builtinPrim().?); } test "polymorphic lambda in block: fn called via arrow syntax gets correct type" { @@ -2611,9 +2590,8 @@ test "polymorphic lambda in block: fn called via arrow syntax gets correct type" try testing.expect(result == .block); // The final expression's return type must be U64 - const call_monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(result.block.final_expr)); - try testing.expect(call_monotype == .prim); - try testing.expectEqual(Monotype.Prim.u64, call_monotype.prim); + const call_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(result.block.final_expr)); + try testing.expectEqual(Monotype.Prim.u64, call_mono_id.builtinPrim().?); } test "polymorphic lambda with literal in body: a + b + 0 called with U64" { @@ -2629,8 +2607,8 @@ test "polymorphic lambda with literal in body: a + b + 0 called with U64" { try testing.expect(result == .block); // The call's return type must be U64 - const call_monotype = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(result.block.final_expr)); - try testing.expectEqual(Monotype.Prim.u64, call_monotype.prim); + const call_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(result.block.final_expr)); + try testing.expectEqual(Monotype.Prim.u64, call_mono_id.builtinPrim().?); // Check the lambda body const stmts = env.mir_store.getStmts(result.block.stmts); @@ -2639,17 +2617,17 @@ test "polymorphic lambda with literal in body: a + b + 0 called with U64" { try testing.expect(decl_expr == .lambda); // Lambda return must be U64 - const lambda_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(stmts[0].decl_const.expr)); - try testing.expect(lambda_mono == .func); - const ret_mono = env.mir_store.monotype_store.getMonotype(lambda_mono.func.ret); - try testing.expectEqual(Monotype.Prim.u64, ret_mono.prim); + const lambda_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(stmts[0].decl_const.expr)); + try testing.expect(lambda_mono_id.kind == .func); + const ret_mono_id = env.mir_store.monotype_store.funcRet(lambda_mono_id); + try testing.expectEqual(Monotype.Prim.u64, ret_mono_id.builtinPrim().?); // Check that the lambda body's subexpressions are all U64, not Dec // The body is `a + b + 0` which desugars to `(a + b) + 0` // The body is either a call or run_low_level for the outer `+` const body = env.mir_store.getExpr(decl_expr.lambda.body); - const body_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(decl_expr.lambda.body)); - try testing.expectEqual(Monotype.Prim.u64, body_mono.prim); + const body_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(decl_expr.lambda.body)); + try testing.expectEqual(Monotype.Prim.u64, body_mono_id.builtinPrim().?); // The outer `+` has args: (a + b) and 0 // Check that the 0 literal has U64 monotype @@ -2658,8 +2636,8 @@ test "polymorphic lambda with literal in body: a + b + 0 called with U64" { if (ll_args.len == 2) { const zero_expr = env.mir_store.getExpr(ll_args[1]); try testing.expect(zero_expr == .int); - const zero_mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(ll_args[1])); - try testing.expectEqual(Monotype.Prim.u64, zero_mono.prim); + const zero_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(ll_args[1])); + try testing.expectEqual(Monotype.Prim.u64, zero_mono_id.builtinPrim().?); } } else if (body == .call) { const call_args = env.mir_store.getExprSpan(body.call.args); @@ -2687,8 +2665,8 @@ test "structural equality: record == produces match_expr (field-by-field)" { try testing.expectEqual(@as(usize, 2), bindings.len); try testing.expect(env.mir_store.getExpr(result.borrow_scope.body) == .match_expr); // Return type should be Bool - const mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(mono == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "structural equality: record != produces negated match_expr" { @@ -2701,8 +2679,8 @@ test "structural equality: record != produces negated match_expr" { const result = env.mir_store.getExpr(expr); // != wraps the == result in negBool (match True => False, _ => True) try testing.expect(result == .match_expr); - const mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(mono == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "structural equality: empty record == is True" { @@ -2730,8 +2708,8 @@ test "structural equality: tuple == produces match_expr" { const bindings = env.mir_store.getBorrowBindings(result.borrow_scope.bindings); try testing.expectEqual(@as(usize, 2), bindings.len); try testing.expect(env.mir_store.getExpr(result.borrow_scope.body) == .match_expr); - const mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(mono == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "structural equality: tag union == produces nested match_expr" { @@ -2748,8 +2726,8 @@ test "structural equality: tag union == produces nested match_expr" { const bindings = env.mir_store.getBorrowBindings(result.borrow_scope.bindings); try testing.expectEqual(@as(usize, 2), bindings.len); try testing.expect(env.mir_store.getExpr(result.borrow_scope.body) == .match_expr); - const mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(mono == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "structural equality: single-field record produces run_low_level (no match)" { @@ -2791,8 +2769,8 @@ test "structural equality: list == produces block with length check" { try testing.expect(result == .borrow_scope); const bindings = env.mir_store.getBorrowBindings(result.borrow_scope.bindings); try testing.expectEqual(@as(usize, 2), bindings.len); - const mono = env.mir_store.monotype_store.getMonotype(env.mir_store.typeOf(expr)); - try testing.expect(mono == .tag_union); + const mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(expr)); + try testing.expect(mono_id.kind == .tag_union); } test "Dec.abs lowers to num_abs with Dec monotype, not unit" { @@ -2803,15 +2781,49 @@ test "Dec.abs lowers to num_abs with Dec monotype, not unit" { // The result monotype must be Dec, not unit const mono_idx = env.mir_store.typeOf(expr); - const mono = env.mir_store.monotype_store.getMonotype(mono_idx); - try testing.expectEqual(Monotype.Monotype{ .prim = .dec }, mono); + const mono_id = env.mir_store.monotype_store.resolve(mono_idx); + try testing.expectEqual(Monotype.Prim.dec, mono_id.builtinPrim().?); // If it's a call, check the function's monotype too if (top == .call) { - const func_mono_idx = env.mir_store.typeOf(top.call.func); - const func_mono = env.mir_store.monotype_store.getMonotype(func_mono_idx); - try testing.expect(func_mono == .func); - const ret = env.mir_store.monotype_store.getMonotype(func_mono.func.ret); - try testing.expectEqual(Monotype.Monotype{ .prim = .dec }, ret); + const func_mono_id = env.mir_store.monotype_store.resolve(env.mir_store.typeOf(top.call.func)); + try testing.expect(func_mono_id.kind == .func); + const ret_id = env.mir_store.monotype_store.funcRet(func_mono_id); + try testing.expectEqual(Monotype.Prim.dec, ret_id.builtinPrim().?); } } + +// --- Nominal instance cache tests --- + +test "nominal cache: non-recursive nominal returns direct type (not .rec)" { + var env = try MirTestEnv.initModule("Test", + \\Color := [Red, Green, Blue] + \\ + \\x : Color + \\x = Color.Red + ); + defer env.deinit(); + const expr = try env.lowerNamedDef("x"); + const raw_type = env.mir_store.typeOf(expr); + // Non-recursive nominal should NOT produce a .rec indirection. + try testing.expect(raw_type.kind == .tag_union); +} + +test "nominal cache: identical nominal instantiations share TypeId" { + var env = try MirTestEnv.initModule("Test", + \\Pair(a) := { fst: a, snd: a } + \\ + \\x : Pair(U64) + \\x = { fst: 1, snd: 2 } + \\ + \\y : Pair(U64) + \\y = { fst: 3, snd: 4 } + ); + defer env.deinit(); + const expr_x = try env.lowerNamedDef("x"); + const expr_y = try env.lowerNamedDef("y"); + const type_x = env.mir_store.typeOf(expr_x); + const type_y = env.mir_store.typeOf(expr_y); + // Same nominal with same args should return the exact same TypeId. + try testing.expect(type_x.eql(type_y)); +} diff --git a/test/fx/cross_module_recursive_nominal.roc b/test/fx/cross_module_recursive_nominal.roc index 27c87b4dba8..0b1f43919ce 100644 --- a/test/fx/cross_module_recursive_nominal.roc +++ b/test/fx/cross_module_recursive_nominal.roc @@ -1,3 +1,13 @@ +## Regression test for cross-module recursive nominal type resolution. +## +## The Elem type is a recursive nominal defined in another module: +## Elem := [Div(List(Elem)), Text(Str)] +## +## Cross-module, the TypeId for Elem's children may contain `.rec` +## placeholder indirections (e.g. List(rec(0))) that differ from the +## canonical form (List(tag_union(N))) used by the importing module. +## The MIR lowerer's `remapMonotypeBetweenModules` resolves these so +## that polymorphic specialization and type comparisons work correctly. app [main!] { pf: platform "./platform/main.roc", elem: "./elem_pkg/main.roc",