Commit 9ff514b6a3

Andrew Kelley <andrew@ziglang.org>
2023-05-20 21:09:07
compiler: move error union types and error set types to InternPool
One change worth noting in this commit is that `module.global_error_set` is no longer kept strictly up-to-date. The previous code reserved integer error values when dealing with error set types, but this is no longer needed because the integer values are not needed for semantic analysis unless `@errorToInt` or `@intToError` are used and therefore may be assigned lazily.
1 parent 7bf91fc
src/arch/aarch64/CodeGen.zig
@@ -3065,8 +3065,8 @@ fn errUnionErr(
     maybe_inst: ?Air.Inst.Index,
 ) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const err_ty = error_union_ty.errorUnionSet();
-    const payload_ty = error_union_ty.errorUnionPayload();
+    const err_ty = error_union_ty.errorUnionSet(mod);
+    const payload_ty = error_union_ty.errorUnionPayload(mod);
     if (err_ty.errorSetIsEmpty(mod)) {
         return MCValue{ .immediate = 0 };
     }
@@ -3145,8 +3145,8 @@ fn errUnionPayload(
     maybe_inst: ?Air.Inst.Index,
 ) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const err_ty = error_union_ty.errorUnionSet();
-    const payload_ty = error_union_ty.errorUnionPayload();
+    const err_ty = error_union_ty.errorUnionSet(mod);
+    const payload_ty = error_union_ty.errorUnionPayload(mod);
     if (err_ty.errorSetIsEmpty(mod)) {
         return try error_union_bind.resolveToMcv(self);
     }
@@ -3305,8 +3305,8 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_ty = self.air.getRefType(ty_op.ty);
-        const error_ty = error_union_ty.errorUnionSet();
-        const payload_ty = error_union_ty.errorUnionPayload();
+        const error_ty = error_union_ty.errorUnionSet(mod);
+        const payload_ty = error_union_ty.errorUnionPayload(mod);
         const operand = try self.resolveInst(ty_op.operand);
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) break :result operand;
 
@@ -3329,8 +3329,8 @@ fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const mod = self.bin_file.options.module.?;
         const error_union_ty = self.air.getRefType(ty_op.ty);
-        const error_ty = error_union_ty.errorUnionSet();
-        const payload_ty = error_union_ty.errorUnionPayload();
+        const error_ty = error_union_ty.errorUnionSet(mod);
+        const payload_ty = error_union_ty.errorUnionPayload(mod);
         const operand = try self.resolveInst(ty_op.operand);
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) break :result operand;
 
@@ -4893,7 +4893,7 @@ fn isErr(
     error_union_ty: Type,
 ) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const error_type = error_union_ty.errorUnionSet();
+    const error_type = error_union_ty.errorUnionSet(mod);
 
     if (error_type.errorSetIsEmpty(mod)) {
         return MCValue{ .immediate = 0 }; // always false
src/arch/arm/CodeGen.zig
@@ -2042,8 +2042,8 @@ fn errUnionErr(
     maybe_inst: ?Air.Inst.Index,
 ) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const err_ty = error_union_ty.errorUnionSet();
-    const payload_ty = error_union_ty.errorUnionPayload();
+    const err_ty = error_union_ty.errorUnionSet(mod);
+    const payload_ty = error_union_ty.errorUnionPayload(mod);
     if (err_ty.errorSetIsEmpty(mod)) {
         return MCValue{ .immediate = 0 };
     }
@@ -2119,8 +2119,8 @@ fn errUnionPayload(
     maybe_inst: ?Air.Inst.Index,
 ) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const err_ty = error_union_ty.errorUnionSet();
-    const payload_ty = error_union_ty.errorUnionPayload();
+    const err_ty = error_union_ty.errorUnionSet(mod);
+    const payload_ty = error_union_ty.errorUnionPayload(mod);
     if (err_ty.errorSetIsEmpty(mod)) {
         return try error_union_bind.resolveToMcv(self);
     }
@@ -2232,8 +2232,8 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_ty = self.air.getRefType(ty_op.ty);
-        const error_ty = error_union_ty.errorUnionSet();
-        const payload_ty = error_union_ty.errorUnionPayload();
+        const error_ty = error_union_ty.errorUnionSet(mod);
+        const payload_ty = error_union_ty.errorUnionPayload(mod);
         const operand = try self.resolveInst(ty_op.operand);
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) break :result operand;
 
@@ -2256,8 +2256,8 @@ fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_ty = self.air.getRefType(ty_op.ty);
-        const error_ty = error_union_ty.errorUnionSet();
-        const payload_ty = error_union_ty.errorUnionPayload();
+        const error_ty = error_union_ty.errorUnionSet(mod);
+        const payload_ty = error_union_ty.errorUnionPayload(mod);
         const operand = try self.resolveInst(ty_op.operand);
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) break :result operand;
 
@@ -4871,7 +4871,7 @@ fn isErr(
     error_union_ty: Type,
 ) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const error_type = error_union_ty.errorUnionSet();
+    const error_type = error_union_ty.errorUnionSet(mod);
 
     if (error_type.errorSetIsEmpty(mod)) {
         return MCValue{ .immediate = 0 }; // always false
src/arch/sparc64/CodeGen.zig
@@ -2707,12 +2707,12 @@ fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void {
 }
 
 fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
+    const mod = self.bin_file.options.module.?;
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_ty = self.typeOf(ty_op.operand);
-        const payload_ty = error_union_ty.errorUnionPayload();
+        const payload_ty = error_union_ty.errorUnionPayload(mod);
         const mcv = try self.resolveInst(ty_op.operand);
-        const mod = self.bin_file.options.module.?;
         if (!payload_ty.hasRuntimeBits(mod)) break :result mcv;
 
         return self.fail("TODO implement unwrap error union error for non-empty payloads", .{});
@@ -2721,11 +2721,11 @@ fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
 }
 
 fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
+    const mod = self.bin_file.options.module.?;
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_ty = self.typeOf(ty_op.operand);
-        const payload_ty = error_union_ty.errorUnionPayload();
-        const mod = self.bin_file.options.module.?;
+        const payload_ty = error_union_ty.errorUnionPayload(mod);
         if (!payload_ty.hasRuntimeBits(mod)) break :result MCValue.none;
 
         return self.fail("TODO implement unwrap error union payload for non-empty payloads", .{});
@@ -2735,12 +2735,12 @@ fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
 
 /// E to E!T
 fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
+    const mod = self.bin_file.options.module.?;
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_ty = self.air.getRefType(ty_op.ty);
-        const payload_ty = error_union_ty.errorUnionPayload();
+        const payload_ty = error_union_ty.errorUnionPayload(mod);
         const mcv = try self.resolveInst(ty_op.operand);
-        const mod = self.bin_file.options.module.?;
         if (!payload_ty.hasRuntimeBits(mod)) break :result mcv;
 
         return self.fail("TODO implement wrap errunion error for non-empty payloads", .{});
@@ -3529,8 +3529,8 @@ fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
 /// Given an error union, returns the payload
 fn errUnionPayload(self: *Self, error_union_mcv: MCValue, error_union_ty: Type) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const err_ty = error_union_ty.errorUnionSet();
-    const payload_ty = error_union_ty.errorUnionPayload();
+    const err_ty = error_union_ty.errorUnionSet(mod);
+    const payload_ty = error_union_ty.errorUnionPayload(mod);
     if (err_ty.errorSetIsEmpty(mod)) {
         return error_union_mcv;
     }
@@ -4168,8 +4168,8 @@ fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue {
 
 fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const error_type = ty.errorUnionSet();
-    const payload_type = ty.errorUnionPayload();
+    const error_type = ty.errorUnionSet(mod);
+    const payload_type = ty.errorUnionPayload(mod);
 
     if (!error_type.hasRuntimeBits(mod)) {
         return MCValue{ .immediate = 0 }; // always false
src/arch/wasm/CodeGen.zig
@@ -1264,7 +1264,7 @@ fn genFunc(func: *CodeGen) InnerError!void {
     if (func_type.returns.len != 0 and func.air.instructions.len > 0) {
         const inst = @intCast(u32, func.air.instructions.len - 1);
         const last_inst_ty = func.typeOfIndex(inst);
-        if (!last_inst_ty.hasRuntimeBitsIgnoreComptime(mod) or last_inst_ty.isNoReturn()) {
+        if (!last_inst_ty.hasRuntimeBitsIgnoreComptime(mod) or last_inst_ty.isNoReturn(mod)) {
             try func.addTag(.@"unreachable");
         }
     }
@@ -1757,7 +1757,7 @@ fn isByRef(ty: Type, mod: *Module) bool {
         .Int => return ty.intInfo(mod).bits > 64,
         .Float => return ty.floatBits(target) > 64,
         .ErrorUnion => {
-            const pl_ty = ty.errorUnionPayload();
+            const pl_ty = ty.errorUnionPayload(mod);
             if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
                 return false;
             }
@@ -2256,7 +2256,7 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif
     const result_value = result_value: {
         if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod) and !ret_ty.isError(mod)) {
             break :result_value WValue{ .none = {} };
-        } else if (ret_ty.isNoReturn()) {
+        } else if (ret_ty.isNoReturn(mod)) {
             try func.addTag(.@"unreachable");
             break :result_value WValue{ .none = {} };
         } else if (first_param_sret) {
@@ -2346,7 +2346,7 @@ fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerE
     const abi_size = ty.abiSize(mod);
     switch (ty.zigTypeTag(mod)) {
         .ErrorUnion => {
-            const pl_ty = ty.errorUnionPayload();
+            const pl_ty = ty.errorUnionPayload(mod);
             if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
                 return func.store(lhs, rhs, Type.anyerror, 0);
             }
@@ -3111,8 +3111,8 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue {
             else => return WValue{ .imm32 = 0 },
         },
         .ErrorUnion => {
-            const error_type = ty.errorUnionSet();
-            const payload_type = ty.errorUnionPayload();
+            const error_type = ty.errorUnionSet(mod);
+            const payload_type = ty.errorUnionPayload(mod);
             if (!payload_type.hasRuntimeBitsIgnoreComptime(mod)) {
                 // We use the error type directly as the type.
                 const is_pl = val.errorUnionIsPayload();
@@ -3916,10 +3916,10 @@ fn airIsErr(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerErro
     const un_op = func.air.instructions.items(.data)[inst].un_op;
     const operand = try func.resolveInst(un_op);
     const err_union_ty = func.typeOf(un_op);
-    const pl_ty = err_union_ty.errorUnionPayload();
+    const pl_ty = err_union_ty.errorUnionPayload(mod);
 
     const result = result: {
-        if (err_union_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+        if (err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
             switch (opcode) {
                 .i32_ne => break :result WValue{ .imm32 = 0 },
                 .i32_eq => break :result WValue{ .imm32 = 1 },
@@ -3953,7 +3953,7 @@ fn airUnwrapErrUnionPayload(func: *CodeGen, inst: Air.Inst.Index, op_is_ptr: boo
     const operand = try func.resolveInst(ty_op.operand);
     const op_ty = func.typeOf(ty_op.operand);
     const err_ty = if (op_is_ptr) op_ty.childType(mod) else op_ty;
-    const payload_ty = err_ty.errorUnionPayload();
+    const payload_ty = err_ty.errorUnionPayload(mod);
 
     const result = result: {
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
@@ -3981,10 +3981,10 @@ fn airUnwrapErrUnionError(func: *CodeGen, inst: Air.Inst.Index, op_is_ptr: bool)
     const operand = try func.resolveInst(ty_op.operand);
     const op_ty = func.typeOf(ty_op.operand);
     const err_ty = if (op_is_ptr) op_ty.childType(mod) else op_ty;
-    const payload_ty = err_ty.errorUnionPayload();
+    const payload_ty = err_ty.errorUnionPayload(mod);
 
     const result = result: {
-        if (err_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+        if (err_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
             break :result WValue{ .imm32 = 0 };
         }
 
@@ -4031,7 +4031,7 @@ fn airWrapErrUnionErr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
 
     const operand = try func.resolveInst(ty_op.operand);
     const err_ty = func.air.getRefType(ty_op.ty);
-    const pl_ty = err_ty.errorUnionPayload();
+    const pl_ty = err_ty.errorUnionPayload(mod);
 
     const result = result: {
         if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
@@ -4044,7 +4044,7 @@ fn airWrapErrUnionErr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
 
         // write 'undefined' to the payload
         const payload_ptr = try func.buildPointerOffset(err_union, @intCast(u32, errUnionPayloadOffset(pl_ty, mod)), .new);
-        const len = @intCast(u32, err_ty.errorUnionPayload().abiSize(mod));
+        const len = @intCast(u32, err_ty.errorUnionPayload(mod).abiSize(mod));
         try func.memset(Type.u8, payload_ptr, .{ .imm32 = len }, .{ .imm32 = 0xaa });
 
         break :result err_union;
@@ -5362,7 +5362,7 @@ fn airErrUnionPayloadPtrSet(func: *CodeGen, inst: Air.Inst.Index) InnerError!voi
     const ty_op = func.air.instructions.items(.data)[inst].ty_op;
 
     const err_set_ty = func.typeOf(ty_op.operand).childType(mod);
-    const payload_ty = err_set_ty.errorUnionPayload();
+    const payload_ty = err_set_ty.errorUnionPayload(mod);
     const operand = try func.resolveInst(ty_op.operand);
 
     // set error-tag to '0' to annotate error union is non-error
@@ -6177,10 +6177,10 @@ fn lowerTry(
         return func.fail("TODO: lowerTry for pointers", .{});
     }
 
-    const pl_ty = err_union_ty.errorUnionPayload();
+    const pl_ty = err_union_ty.errorUnionPayload(mod);
     const pl_has_bits = pl_ty.hasRuntimeBitsIgnoreComptime(mod);
 
-    if (!err_union_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+    if (!err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
         // Block we can jump out of when error is not set
         try func.startBlock(.block, wasm.block_empty);
 
@@ -6742,7 +6742,7 @@ fn callIntrinsic(
 
     if (!return_type.hasRuntimeBitsIgnoreComptime(mod)) {
         return WValue.none;
-    } else if (return_type.isNoReturn()) {
+    } else if (return_type.isNoReturn(mod)) {
         try func.addTag(.@"unreachable");
         return WValue.none;
     } else if (want_sret_param) {
@@ -6941,20 +6941,21 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
 }
 
 fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
+    const mod = func.bin_file.base.options.module.?;
     const ty_op = func.air.instructions.items(.data)[inst].ty_op;
 
     const operand = try func.resolveInst(ty_op.operand);
     const error_set_ty = func.air.getRefType(ty_op.ty);
     const result = try func.allocLocal(Type.bool);
 
-    const names = error_set_ty.errorSetNames();
+    const names = error_set_ty.errorSetNames(mod);
     var values = try std.ArrayList(u32).initCapacity(func.gpa, names.len);
     defer values.deinit();
 
-    const mod = func.bin_file.base.options.module.?;
     var lowest: ?u32 = null;
     var highest: ?u32 = null;
-    for (names) |name| {
+    for (names) |name_ip| {
+        const name = mod.intern_pool.stringToSlice(name_ip);
         const err_int = mod.global_error_set.get(name).?;
         if (lowest) |*l| {
             if (err_int < l.*) {
src/arch/x86_64/CodeGen.zig
@@ -3612,8 +3612,8 @@ fn airUnwrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
     const mod = self.bin_file.options.module.?;
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const err_union_ty = self.typeOf(ty_op.operand);
-    const err_ty = err_union_ty.errorUnionSet();
-    const payload_ty = err_union_ty.errorUnionPayload();
+    const err_ty = err_union_ty.errorUnionSet(mod);
+    const payload_ty = err_union_ty.errorUnionPayload(mod);
     const operand = try self.resolveInst(ty_op.operand);
 
     const result: MCValue = result: {
@@ -3671,7 +3671,7 @@ fn genUnwrapErrorUnionPayloadMir(
     err_union: MCValue,
 ) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const payload_ty = err_union_ty.errorUnionPayload();
+    const payload_ty = err_union_ty.errorUnionPayload(mod);
 
     const result: MCValue = result: {
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) break :result .none;
@@ -3731,8 +3731,8 @@ fn airUnwrapErrUnionErrPtr(self: *Self, inst: Air.Inst.Index) !void {
     defer self.register_manager.unlockReg(dst_lock);
 
     const eu_ty = src_ty.childType(mod);
-    const pl_ty = eu_ty.errorUnionPayload();
-    const err_ty = eu_ty.errorUnionSet();
+    const pl_ty = eu_ty.errorUnionPayload(mod);
+    const err_ty = eu_ty.errorUnionSet(mod);
     const err_off = @intCast(i32, errUnionErrorOffset(pl_ty, mod));
     const err_abi_size = @intCast(u32, err_ty.abiSize(mod));
     try self.asmRegisterMemory(
@@ -3771,7 +3771,7 @@ fn airUnwrapErrUnionPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
     defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);
 
     const eu_ty = src_ty.childType(mod);
-    const pl_ty = eu_ty.errorUnionPayload();
+    const pl_ty = eu_ty.errorUnionPayload(mod);
     const pl_off = @intCast(i32, errUnionPayloadOffset(pl_ty, mod));
     const dst_abi_size = @intCast(u32, dst_ty.abiSize(mod));
     try self.asmRegisterMemory(
@@ -3797,8 +3797,8 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
         defer self.register_manager.unlockReg(src_lock);
 
         const eu_ty = src_ty.childType(mod);
-        const pl_ty = eu_ty.errorUnionPayload();
-        const err_ty = eu_ty.errorUnionSet();
+        const pl_ty = eu_ty.errorUnionPayload(mod);
+        const err_ty = eu_ty.errorUnionSet(mod);
         const err_off = @intCast(i32, errUnionErrorOffset(pl_ty, mod));
         const err_abi_size = @intCast(u32, err_ty.abiSize(mod));
         try self.asmMemoryImmediate(
@@ -3901,8 +3901,8 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
 
     const eu_ty = self.air.getRefType(ty_op.ty);
-    const pl_ty = eu_ty.errorUnionPayload();
-    const err_ty = eu_ty.errorUnionSet();
+    const pl_ty = eu_ty.errorUnionPayload(mod);
+    const err_ty = eu_ty.errorUnionSet(mod);
     const operand = try self.resolveInst(ty_op.operand);
 
     const result: MCValue = result: {
@@ -3924,8 +3924,8 @@ fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
 
     const eu_ty = self.air.getRefType(ty_op.ty);
-    const pl_ty = eu_ty.errorUnionPayload();
-    const err_ty = eu_ty.errorUnionSet();
+    const pl_ty = eu_ty.errorUnionPayload(mod);
+    const err_ty = eu_ty.errorUnionSet(mod);
 
     const result: MCValue = result: {
         if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) break :result try self.resolveInst(ty_op.operand);
@@ -8782,7 +8782,7 @@ fn isNullPtr(self: *Self, inst: Air.Inst.Index, ptr_ty: Type, ptr_mcv: MCValue)
 
 fn isErr(self: *Self, maybe_inst: ?Air.Inst.Index, ty: Type, operand: MCValue) !MCValue {
     const mod = self.bin_file.options.module.?;
-    const err_type = ty.errorUnionSet();
+    const err_type = ty.errorUnionSet(mod);
 
     if (err_type.errorSetIsEmpty(mod)) {
         return MCValue{ .immediate = 0 }; // always false
@@ -8793,7 +8793,7 @@ fn isErr(self: *Self, maybe_inst: ?Air.Inst.Index, ty: Type, operand: MCValue) !
         self.eflags_inst = inst;
     }
 
-    const err_off = errUnionErrorOffset(ty.errorUnionPayload(), mod);
+    const err_off = errUnionErrorOffset(ty.errorUnionPayload(mod), mod);
     switch (operand) {
         .register => |reg| {
             const eu_lock = self.register_manager.lockReg(reg);
src/codegen/c/type.zig
@@ -1680,14 +1680,14 @@ pub const CType = extern union {
                         .complete, .parameter, .global => try lookup.typeToIndex(ty, .forward),
                         .payload => unreachable,
                     }) |fwd_idx| {
-                        const payload_ty = ty.errorUnionPayload();
+                        const payload_ty = ty.errorUnionPayload(mod);
                         if (try lookup.typeToIndex(payload_ty, switch (kind) {
                             .forward, .forward_parameter => .forward,
                             .complete, .parameter => .complete,
                             .global => .global,
                             .payload => unreachable,
                         })) |payload_idx| {
-                            const error_ty = ty.errorUnionSet();
+                            const error_ty = ty.errorUnionSet(mod);
                             if (payload_idx == Tag.void.toIndex()) {
                                 try self.initType(error_ty, kind, lookup);
                             } else if (try lookup.typeToIndex(error_ty, kind)) |error_idx| {
src/codegen/c.zig
@@ -465,7 +465,7 @@ pub const Function = struct {
                     }),
                 },
                 .data = switch (key) {
-                    .tag_name => .{ .tag_name = try data.tag_name.copy(arena) },
+                    .tag_name => .{ .tag_name = data.tag_name },
                     .never_tail => .{ .never_tail = data.never_tail },
                     .never_inline => .{ .never_inline = data.never_inline },
                 },
@@ -862,8 +862,8 @@ pub const DeclGen = struct {
                     return writer.writeByte('}');
                 },
                 .ErrorUnion => {
-                    const payload_ty = ty.errorUnionPayload();
-                    const error_ty = ty.errorUnionSet();
+                    const payload_ty = ty.errorUnionPayload(mod);
+                    const error_ty = ty.errorUnionSet(mod);
 
                     if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
                         return dg.renderValue(writer, error_ty, val, location);
@@ -1252,8 +1252,8 @@ pub const DeclGen = struct {
                 }
             },
             .ErrorUnion => {
-                const payload_ty = ty.errorUnionPayload();
-                const error_ty = ty.errorUnionSet();
+                const payload_ty = ty.errorUnionPayload(mod);
+                const error_ty = ty.errorUnionSet(mod);
                 const error_val = if (val.errorUnionIsPayload()) try mod.intValue(Type.anyerror, 0) else val;
 
                 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
@@ -4252,6 +4252,7 @@ fn airDbgVar(f: *Function, inst: Air.Inst.Index) !CValue {
 }
 
 fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue {
+    const mod = f.object.dg.module;
     const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
     const extra = f.air.extraData(Air.Block, ty_pl.payload);
     const body = f.air.extra[extra.end..][0..extra.data.body_len];
@@ -4284,7 +4285,7 @@ fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue {
     try f.object.indent_writer.insertNewline();
 
     // noreturn blocks have no `br` instructions reaching them, so we don't want a label
-    if (!f.typeOfIndex(inst).isNoReturn()) {
+    if (!f.typeOfIndex(inst).isNoReturn(mod)) {
         // label must be followed by an expression, include an empty one.
         try writer.print("zig_block_{d}:;\n", .{block_id});
     }
@@ -4322,10 +4323,10 @@ fn lowerTry(
     const inst_ty = f.typeOfIndex(inst);
     const liveness_condbr = f.liveness.getCondBr(inst);
     const writer = f.object.writer();
-    const payload_ty = err_union_ty.errorUnionPayload();
+    const payload_ty = err_union_ty.errorUnionPayload(mod);
     const payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(mod);
 
-    if (!err_union_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+    if (!err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
         try writer.writeAll("if (");
         if (!payload_has_bits) {
             if (is_ptr)
@@ -5500,8 +5501,8 @@ fn airUnwrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
 
     const operand_is_ptr = operand_ty.zigTypeTag(mod) == .Pointer;
     const error_union_ty = if (operand_is_ptr) operand_ty.childType(mod) else operand_ty;
-    const error_ty = error_union_ty.errorUnionSet();
-    const payload_ty = error_union_ty.errorUnionPayload();
+    const error_ty = error_union_ty.errorUnionSet(mod);
+    const payload_ty = error_union_ty.errorUnionPayload(mod);
     const local = try f.allocLocal(inst, inst_ty);
 
     if (!payload_ty.hasRuntimeBits(mod) and operand == .local and operand.local == local.new_local) {
@@ -5539,7 +5540,7 @@ fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValu
     const error_union_ty = if (is_ptr) operand_ty.childType(mod) else operand_ty;
 
     const writer = f.object.writer();
-    if (!error_union_ty.errorUnionPayload().hasRuntimeBits(mod)) {
+    if (!error_union_ty.errorUnionPayload(mod).hasRuntimeBits(mod)) {
         if (!is_ptr) return .none;
 
         const local = try f.allocLocal(inst, inst_ty);
@@ -5601,9 +5602,9 @@ fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
     const ty_op = f.air.instructions.items(.data)[inst].ty_op;
 
     const inst_ty = f.typeOfIndex(inst);
-    const payload_ty = inst_ty.errorUnionPayload();
+    const payload_ty = inst_ty.errorUnionPayload(mod);
     const repr_is_err = !payload_ty.hasRuntimeBitsIgnoreComptime(mod);
-    const err_ty = inst_ty.errorUnionSet();
+    const err_ty = inst_ty.errorUnionSet(mod);
     const err = try f.resolveInst(ty_op.operand);
     try reap(f, inst, &.{ty_op.operand});
 
@@ -5642,8 +5643,8 @@ fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
     const operand = try f.resolveInst(ty_op.operand);
     const error_union_ty = f.typeOf(ty_op.operand).childType(mod);
 
-    const error_ty = error_union_ty.errorUnionSet();
-    const payload_ty = error_union_ty.errorUnionPayload();
+    const error_ty = error_union_ty.errorUnionSet(mod);
+    const payload_ty = error_union_ty.errorUnionPayload(mod);
 
     // First, set the non-error value.
     if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
@@ -5691,10 +5692,10 @@ fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
     const ty_op = f.air.instructions.items(.data)[inst].ty_op;
 
     const inst_ty = f.typeOfIndex(inst);
-    const payload_ty = inst_ty.errorUnionPayload();
+    const payload_ty = inst_ty.errorUnionPayload(mod);
     const payload = try f.resolveInst(ty_op.operand);
     const repr_is_err = !payload_ty.hasRuntimeBitsIgnoreComptime(mod);
-    const err_ty = inst_ty.errorUnionSet();
+    const err_ty = inst_ty.errorUnionSet(mod);
     try reap(f, inst, &.{ty_op.operand});
 
     const writer = f.object.writer();
@@ -5729,8 +5730,8 @@ fn airIsErr(f: *Function, inst: Air.Inst.Index, is_ptr: bool, operator: []const
     const operand_ty = f.typeOf(un_op);
     const local = try f.allocLocal(inst, Type.bool);
     const err_union_ty = if (is_ptr) operand_ty.childType(mod) else operand_ty;
-    const payload_ty = err_union_ty.errorUnionPayload();
-    const error_ty = err_union_ty.errorUnionSet();
+    const payload_ty = err_union_ty.errorUnionPayload(mod);
+    const error_ty = err_union_ty.errorUnionSet(mod);
 
     try f.writeCValue(writer, local, .Other);
     try writer.writeAll(" = ");
src/codegen/llvm.zig
@@ -362,15 +362,11 @@ pub const Object = struct {
     decl_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *llvm.Value),
     /// Serves the same purpose as `decl_map` but only used for the `is_named_enum_value` instruction.
     named_enum_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *llvm.Value),
-    /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of
-    /// the compiler, but the Type/Value memory here is backed by `type_map_arena`.
-    /// TODO we need to remove entries from this map in response to incremental compilation
-    /// but I think the frontend won't tell us about types that get deleted because
-    /// hasRuntimeBits() is false for types.
+    /// Maps Zig types to LLVM types. The table memory is backed by the GPA of
+    /// the compiler.
+    /// TODO when InternPool garbage collection is implemented, this map needs
+    /// to be garbage collected as well.
     type_map: TypeMap,
-    /// The backing memory for `type_map`. Periodically garbage collected after flush().
-    /// The code for doing the periodical GC is not yet implemented.
-    type_map_arena: std.heap.ArenaAllocator,
     di_type_map: DITypeMap,
     /// The LLVM global table which holds the names corresponding to Zig errors.
     /// Note that the values are not added until flushModule, when all errors in
@@ -381,12 +377,7 @@ pub const Object = struct {
     /// name collision.
     extern_collisions: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, void),
 
-    pub const TypeMap = std.HashMapUnmanaged(
-        Type,
-        *llvm.Type,
-        Type.HashContext64,
-        std.hash_map.default_max_load_percentage,
-    );
+    pub const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, *llvm.Type);
 
     /// This is an ArrayHashMap as opposed to a HashMap because in `flushModule` we
     /// want to iterate over it while adding entries to it.
@@ -543,7 +534,6 @@ pub const Object = struct {
             .decl_map = .{},
             .named_enum_map = .{},
             .type_map = .{},
-            .type_map_arena = std.heap.ArenaAllocator.init(gpa),
             .di_type_map = .{},
             .error_name_table = null,
             .extern_collisions = .{},
@@ -563,7 +553,6 @@ pub const Object = struct {
         self.decl_map.deinit(gpa);
         self.named_enum_map.deinit(gpa);
         self.type_map.deinit(gpa);
-        self.type_map_arena.deinit();
         self.extern_collisions.deinit(gpa);
         self.* = undefined;
     }
@@ -1462,9 +1451,6 @@ pub const Object = struct {
             return o.lowerDebugTypeImpl(entry, resolve, di_type);
         }
         errdefer assert(o.di_type_map.orderedRemoveContext(ty, .{ .mod = o.module }));
-        // The Type memory is ephemeral; since we want to store a longer-lived
-        // reference, we need to copy it here.
-        gop.key_ptr.* = try ty.copy(o.type_map_arena.allocator());
         const entry: Object.DITypeMap.Entry = .{
             .key_ptr = gop.key_ptr,
             .value_ptr = gop.value_ptr,
@@ -1868,7 +1854,7 @@ pub const Object = struct {
                 return full_di_ty;
             },
             .ErrorUnion => {
-                const payload_ty = ty.errorUnionPayload();
+                const payload_ty = ty.errorUnionPayload(mod);
                 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
                     const err_set_di_ty = try o.lowerDebugType(Type.anyerror, .full);
                     // The recursive call to `lowerDebugType` means we can't use `gop` anymore.
@@ -2823,7 +2809,7 @@ pub const DeclGen = struct {
             .Opaque => {
                 if (t.ip_index == .anyopaque_type) return dg.context.intType(8);
 
-                const gop = try dg.object.type_map.getOrPutContext(gpa, t, .{ .mod = mod });
+                const gop = try dg.object.type_map.getOrPut(gpa, t.toIntern());
                 if (gop.found_existing) return gop.value_ptr.*;
 
                 const opaque_type = mod.intern_pool.indexToKey(t.ip_index).opaque_type;
@@ -2869,7 +2855,7 @@ pub const DeclGen = struct {
                 return dg.context.structType(&fields_buf, 3, .False);
             },
             .ErrorUnion => {
-                const payload_ty = t.errorUnionPayload();
+                const payload_ty = t.errorUnionPayload(mod);
                 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
                     return try dg.lowerType(Type.anyerror);
                 }
@@ -2913,13 +2899,9 @@ pub const DeclGen = struct {
             },
             .ErrorSet => return dg.context.intType(16),
             .Struct => {
-                const gop = try dg.object.type_map.getOrPutContext(gpa, t, .{ .mod = mod });
+                const gop = try dg.object.type_map.getOrPut(gpa, t.toIntern());
                 if (gop.found_existing) return gop.value_ptr.*;
 
-                // The Type memory is ephemeral; since we want to store a longer-lived
-                // reference, we need to copy it here.
-                gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator());
-
                 const struct_type = switch (mod.intern_pool.indexToKey(t.ip_index)) {
                     .anon_struct_type => |tuple| {
                         const llvm_struct_ty = dg.context.structCreateNamed("");
@@ -3041,13 +3023,9 @@ pub const DeclGen = struct {
                 return llvm_struct_ty;
             },
             .Union => {
-                const gop = try dg.object.type_map.getOrPutContext(gpa, t, .{ .mod = mod });
+                const gop = try dg.object.type_map.getOrPut(gpa, t.toIntern());
                 if (gop.found_existing) return gop.value_ptr.*;
 
-                // The Type memory is ephemeral; since we want to store a longer-lived
-                // reference, we need to copy it here.
-                gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator());
-
                 const layout = t.unionGetLayout(mod);
                 const union_obj = mod.typeToUnion(t).?;
 
@@ -3571,7 +3549,7 @@ pub const DeclGen = struct {
                 }
             },
             .ErrorUnion => {
-                const payload_type = tv.ty.errorUnionPayload();
+                const payload_type = tv.ty.errorUnionPayload(mod);
                 const is_pl = tv.val.errorUnionIsPayload();
 
                 if (!payload_type.hasRuntimeBitsIgnoreComptime(mod)) {
@@ -4130,7 +4108,7 @@ pub const DeclGen = struct {
                 const eu_payload_ptr = ptr_val.castTag(.eu_payload_ptr).?.data;
                 const parent_llvm_ptr = try dg.lowerParentPtr(eu_payload_ptr.container_ptr, true);
 
-                const payload_ty = eu_payload_ptr.container_ty.errorUnionPayload();
+                const payload_ty = eu_payload_ptr.container_ty.errorUnionPayload(mod);
                 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
                     // In this case, we represent pointer to error union the same as pointer
                     // to the payload.
@@ -5368,7 +5346,7 @@ pub const FuncGen = struct {
         const inst_ty = self.typeOfIndex(inst);
         const parent_bb = self.context.createBasicBlock("Block");
 
-        if (inst_ty.isNoReturn()) {
+        if (inst_ty.isNoReturn(mod)) {
             try self.genBody(body);
             return null;
         }
@@ -5490,11 +5468,11 @@ pub const FuncGen = struct {
         is_unused: bool,
     ) !?*llvm.Value {
         const mod = fg.dg.module;
-        const payload_ty = err_union_ty.errorUnionPayload();
+        const payload_ty = err_union_ty.errorUnionPayload(mod);
         const payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(mod);
         const err_union_llvm_ty = try fg.dg.lowerType(err_union_ty);
 
-        if (!err_union_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+        if (!err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
             const is_err = err: {
                 const err_set_ty = try fg.dg.lowerType(Type.anyerror);
                 const zero = err_set_ty.constNull();
@@ -5601,6 +5579,7 @@ pub const FuncGen = struct {
     }
 
     fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
+        const mod = self.dg.module;
         const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
         const loop = self.air.extraData(Air.Block, ty_pl.payload);
         const body = self.air.extra[loop.end..][0..loop.data.body_len];
@@ -5616,7 +5595,7 @@ pub const FuncGen = struct {
         // would have been emitted already. Also the main loop in genBody can
         // be while(true) instead of for(body), which will eliminate 1 branch on
         // a hot path.
-        if (body.len == 0 or !self.typeOfIndex(body[body.len - 1]).isNoReturn()) {
+        if (body.len == 0 or !self.typeOfIndex(body[body.len - 1]).isNoReturn(mod)) {
             _ = self.builder.buildBr(loop_block);
         }
         return null;
@@ -6674,11 +6653,11 @@ pub const FuncGen = struct {
         const operand = try self.resolveInst(un_op);
         const operand_ty = self.typeOf(un_op);
         const err_union_ty = if (operand_is_ptr) operand_ty.childType(mod) else operand_ty;
-        const payload_ty = err_union_ty.errorUnionPayload();
+        const payload_ty = err_union_ty.errorUnionPayload(mod);
         const err_set_ty = try self.dg.lowerType(Type.anyerror);
         const zero = err_set_ty.constNull();
 
-        if (err_union_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+        if (err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
             const llvm_i1 = self.context.intType(1);
             switch (op) {
                 .EQ => return llvm_i1.constInt(1, .False), // 0 == 0
@@ -6825,7 +6804,7 @@ pub const FuncGen = struct {
         const operand = try self.resolveInst(ty_op.operand);
         const operand_ty = self.typeOf(ty_op.operand);
         const err_union_ty = if (operand_is_ptr) operand_ty.childType(mod) else operand_ty;
-        if (err_union_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+        if (err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
             const err_llvm_ty = try self.dg.lowerType(Type.anyerror);
             if (operand_is_ptr) {
                 return operand;
@@ -6836,7 +6815,7 @@ pub const FuncGen = struct {
 
         const err_set_llvm_ty = try self.dg.lowerType(Type.anyerror);
 
-        const payload_ty = err_union_ty.errorUnionPayload();
+        const payload_ty = err_union_ty.errorUnionPayload(mod);
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
             if (!operand_is_ptr) return operand;
             return self.builder.buildLoad(err_set_llvm_ty, operand, "");
@@ -6859,7 +6838,7 @@ pub const FuncGen = struct {
         const operand = try self.resolveInst(ty_op.operand);
         const err_union_ty = self.typeOf(ty_op.operand).childType(mod);
 
-        const payload_ty = err_union_ty.errorUnionPayload();
+        const payload_ty = err_union_ty.errorUnionPayload(mod);
         const non_error_val = try self.dg.lowerValue(.{ .ty = Type.anyerror, .val = try mod.intValue(Type.err_int, 0) });
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
             _ = self.builder.buildStore(non_error_val, operand);
@@ -6968,7 +6947,7 @@ pub const FuncGen = struct {
         const mod = self.dg.module;
         const ty_op = self.air.instructions.items(.data)[inst].ty_op;
         const err_un_ty = self.typeOfIndex(inst);
-        const payload_ty = err_un_ty.errorUnionPayload();
+        const payload_ty = err_un_ty.errorUnionPayload(mod);
         const operand = try self.resolveInst(ty_op.operand);
         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
             return operand;
@@ -8787,13 +8766,14 @@ pub const FuncGen = struct {
         const operand = try self.resolveInst(ty_op.operand);
         const error_set_ty = self.air.getRefType(ty_op.ty);
 
-        const names = error_set_ty.errorSetNames();
+        const names = error_set_ty.errorSetNames(mod);
         const valid_block = self.context.appendBasicBlock(self.llvm_func, "Valid");
         const invalid_block = self.context.appendBasicBlock(self.llvm_func, "Invalid");
         const end_block = self.context.appendBasicBlock(self.llvm_func, "End");
         const switch_instr = self.builder.buildSwitch(operand, invalid_block, @intCast(c_uint, names.len));
 
-        for (names) |name| {
+        for (names) |name_ip| {
+            const name = mod.intern_pool.stringToSlice(name_ip);
             const err_int = mod.global_error_set.get(name).?;
             const this_tag_int_value = try self.dg.lowerValue(.{
                 .ty = Type.err_int,
@@ -11095,7 +11075,7 @@ fn isByRef(ty: Type, mod: *Module) bool {
             else => return ty.hasRuntimeBits(mod),
         },
         .ErrorUnion => {
-            const payload_ty = ty.errorUnionPayload();
+            const payload_ty = ty.errorUnionPayload(mod);
             if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
                 return false;
             }
src/codegen/spirv.zig
@@ -801,7 +801,7 @@ pub const DeclGen = struct {
                     },
                 },
                 .ErrorUnion => {
-                    const payload_ty = ty.errorUnionPayload();
+                    const payload_ty = ty.errorUnionPayload(mod);
                     const is_pl = val.errorUnionIsPayload();
                     const error_val = if (!is_pl) val else try mod.intValue(Type.anyerror, 0);
 
@@ -1365,7 +1365,7 @@ pub const DeclGen = struct {
             .Union => return try self.resolveUnionType(ty, null),
             .ErrorSet => return try self.intType(.unsigned, 16),
             .ErrorUnion => {
-                const payload_ty = ty.errorUnionPayload();
+                const payload_ty = ty.errorUnionPayload(mod);
                 const error_ty_ref = try self.resolveType(Type.anyerror, .indirect);
 
                 const eu_layout = self.errorUnionLayout(payload_ty);
@@ -2875,7 +2875,7 @@ pub const DeclGen = struct {
 
         const eu_layout = self.errorUnionLayout(payload_ty);
 
-        if (!err_union_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+        if (!err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
             const err_id = if (eu_layout.payload_has_bits)
                 try self.extractField(Type.anyerror, err_union_id, eu_layout.errorFieldIndex())
             else
@@ -2929,12 +2929,12 @@ pub const DeclGen = struct {
         const err_union_ty = self.typeOf(ty_op.operand);
         const err_ty_ref = try self.resolveType(Type.anyerror, .direct);
 
-        if (err_union_ty.errorUnionSet().errorSetIsEmpty(mod)) {
+        if (err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
             // No error possible, so just return undefined.
             return try self.spv.constUndef(err_ty_ref);
         }
 
-        const payload_ty = err_union_ty.errorUnionPayload();
+        const payload_ty = err_union_ty.errorUnionPayload(mod);
         const eu_layout = self.errorUnionLayout(payload_ty);
 
         if (!eu_layout.payload_has_bits) {
@@ -2948,9 +2948,10 @@ pub const DeclGen = struct {
     fn airWrapErrUnionErr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
         if (self.liveness.isUnused(inst)) return null;
 
+        const mod = self.module;
         const ty_op = self.air.instructions.items(.data)[inst].ty_op;
         const err_union_ty = self.typeOfIndex(inst);
-        const payload_ty = err_union_ty.errorUnionPayload();
+        const payload_ty = err_union_ty.errorUnionPayload(mod);
         const operand_id = try self.resolve(ty_op.operand);
         const eu_layout = self.errorUnionLayout(payload_ty);
 
src/link/Dwarf.zig
@@ -18,6 +18,7 @@ const LinkBlock = File.LinkBlock;
 const LinkFn = File.LinkFn;
 const LinkerLoad = @import("../codegen.zig").LinkerLoad;
 const Module = @import("../Module.zig");
+const InternPool = @import("../InternPool.zig");
 const StringTable = @import("strtab.zig").StringTable;
 const Type = @import("../type.zig").Type;
 const Value = @import("../value.zig").Value;
@@ -518,9 +519,9 @@ pub const DeclState = struct {
                 );
             },
             .ErrorUnion => {
-                const error_ty = ty.errorUnionSet();
-                const payload_ty = ty.errorUnionPayload();
-                const payload_align = if (payload_ty.isNoReturn()) 0 else payload_ty.abiAlignment(mod);
+                const error_ty = ty.errorUnionSet(mod);
+                const payload_ty = ty.errorUnionPayload(mod);
+                const payload_align = if (payload_ty.isNoReturn(mod)) 0 else payload_ty.abiAlignment(mod);
                 const error_align = Type.anyerror.abiAlignment(mod);
                 const abi_size = ty.abiSize(mod);
                 const payload_off = if (error_align >= payload_align) Type.anyerror.abiSize(mod) else 0;
@@ -534,7 +535,7 @@ pub const DeclState = struct {
                 const name = try ty.nameAllocArena(arena, mod);
                 try dbg_info_buffer.writer().print("{s}\x00", .{name});
 
-                if (!payload_ty.isNoReturn()) {
+                if (!payload_ty.isNoReturn(mod)) {
                     // DW.AT.member
                     try dbg_info_buffer.ensureUnusedCapacity(7);
                     dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
@@ -1266,10 +1267,11 @@ pub fn commitDeclState(
             const symbol = &decl_state.abbrev_table.items[sym_index];
             const ty = symbol.type;
             const deferred: bool = blk: {
-                if (ty.isAnyError()) break :blk true;
-                switch (ty.tag()) {
-                    .error_set_inferred => {
-                        if (!ty.castTag(.error_set_inferred).?.data.is_resolved) break :blk true;
+                if (ty.isAnyError(mod)) break :blk true;
+                switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+                    .inferred_error_set_type => |ies_index| {
+                        const ies = mod.inferredErrorSetPtr(ies_index);
+                        if (!ies.is_resolved) break :blk true;
                     },
                     else => {},
                 }
@@ -1290,10 +1292,11 @@ pub fn commitDeclState(
             const symbol = decl_state.abbrev_table.items[target];
             const ty = symbol.type;
             const deferred: bool = blk: {
-                if (ty.isAnyError()) break :blk true;
-                switch (ty.tag()) {
-                    .error_set_inferred => {
-                        if (!ty.castTag(.error_set_inferred).?.data.is_resolved) break :blk true;
+                if (ty.isAnyError(mod)) break :blk true;
+                switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+                    .inferred_error_set_type => |ies_index| {
+                        const ies = mod.inferredErrorSetPtr(ies_index);
+                        if (!ies.is_resolved) break :blk true;
                     },
                     else => {},
                 }
@@ -2529,18 +2532,22 @@ pub fn flushModule(self: *Dwarf, module: *Module) !void {
         defer arena_alloc.deinit();
         const arena = arena_alloc.allocator();
 
-        const error_set = try arena.create(Module.ErrorSet);
-        const error_ty = try Type.Tag.error_set.create(arena, error_set);
-        var names = Module.ErrorSet.NameMap{};
-        try names.ensureUnusedCapacity(arena, module.global_error_set.count());
-        var it = module.global_error_set.keyIterator();
-        while (it.next()) |key| {
-            names.putAssumeCapacityNoClobber(key.*, {});
+        // TODO: don't create a zig type for this, just make the dwarf info
+        // without touching the zig type system.
+        const names = try arena.alloc(InternPool.NullTerminatedString, module.global_error_set.count());
+        {
+            var it = module.global_error_set.keyIterator();
+            var i: usize = 0;
+            while (it.next()) |key| : (i += 1) {
+                names[i] = module.intern_pool.getString(key.*).unwrap().?;
+            }
         }
-        error_set.names = names;
 
+        std.mem.sort(InternPool.NullTerminatedString, names, {}, InternPool.NullTerminatedString.indexLessThan);
+
+        const error_ty = try module.intern(.{ .error_set_type = .{ .names = names } });
         var dbg_info_buffer = std.ArrayList(u8).init(arena);
-        try addDbgInfoErrorSet(arena, module, error_ty, self.target, &dbg_info_buffer);
+        try addDbgInfoErrorSet(arena, module, error_ty.toType(), self.target, &dbg_info_buffer);
 
         const di_atom_index = try self.createAtom(.di_atom);
         log.debug("updateDeclDebugInfoAllocation in flushModule", .{});
@@ -2684,8 +2691,9 @@ fn addDbgInfoErrorSet(
     // DW.AT.const_value, DW.FORM.data8
     mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), 0, target_endian);
 
-    const error_names = ty.errorSetNames();
-    for (error_names) |error_name| {
+    const error_names = ty.errorSetNames(mod);
+    for (error_names) |error_name_ip| {
+        const error_name = mod.intern_pool.stringToSlice(error_name_ip);
         const kv = mod.getErrorValue(error_name) catch unreachable;
         // DW.AT.enumerator
         try dbg_info_buffer.ensureUnusedCapacity(error_name.len + 2 + @sizeOf(u64));
src/Liveness/Verify.zig
@@ -453,7 +453,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
 
                 for (block_liveness.deaths) |death| try self.verifyDeath(inst, death);
 
-                if (block_ty.isNoReturn()) {
+                if (ip.isNoReturn(block_ty.toIntern())) {
                     assert(!self.blocks.contains(inst));
                 } else {
                     var live = self.blocks.fetchRemove(inst).?.value;
src/Air.zig
@@ -1411,7 +1411,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index, ip: InternPool) Type {
 
         .@"try" => {
             const err_union_ty = air.typeOf(datas[inst].pl_op.operand, ip);
-            return err_union_ty.errorUnionPayload();
+            return ip.indexToKey(err_union_ty.ip_index).error_union_type.payload_type.toType();
         },
 
         .work_item_id,
src/codegen.zig
@@ -139,7 +139,7 @@ pub fn generateLazySymbol(
         return generateLazyFunction(bin_file, src_loc, lazy_sym, code, debug_output);
     }
 
-    if (lazy_sym.ty.isAnyError()) {
+    if (lazy_sym.ty.isAnyError(mod)) {
         alignment.* = 4;
         const err_names = mod.error_name_list.items;
         mem.writeInt(u32, try code.addManyAsArray(4), @intCast(u32, err_names.len), endian);
@@ -670,8 +670,8 @@ pub fn generateSymbol(
             return Result.ok;
         },
         .ErrorUnion => {
-            const error_ty = typed_value.ty.errorUnionSet();
-            const payload_ty = typed_value.ty.errorUnionPayload();
+            const error_ty = typed_value.ty.errorUnionSet(mod);
+            const payload_ty = typed_value.ty.errorUnionPayload(mod);
             const is_payload = typed_value.val.errorUnionIsPayload();
 
             if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
@@ -894,7 +894,7 @@ fn lowerParentPtr(
         },
         .eu_payload_ptr => {
             const eu_payload_ptr = parent_ptr.castTag(.eu_payload_ptr).?.data;
-            const pl_ty = eu_payload_ptr.container_ty.errorUnionPayload();
+            const pl_ty = eu_payload_ptr.container_ty.errorUnionPayload(mod);
             return lowerParentPtr(
                 bin_file,
                 src_loc,
@@ -1249,8 +1249,8 @@ pub fn genTypedValue(
             }
         },
         .ErrorUnion => {
-            const error_type = typed_value.ty.errorUnionSet();
-            const payload_type = typed_value.ty.errorUnionPayload();
+            const error_type = typed_value.ty.errorUnionSet(mod);
+            const payload_type = typed_value.ty.errorUnionPayload(mod);
             const is_pl = typed_value.val.errorUnionIsPayload();
 
             if (!payload_type.hasRuntimeBitsIgnoreComptime(mod)) {
src/InternPool.zig
@@ -34,6 +34,14 @@ allocated_unions: std.SegmentedList(Module.Union, 0) = .{},
 /// When a Union object is freed from `allocated_unions`, it is pushed into this stack.
 unions_free_list: std.ArrayListUnmanaged(Module.Union.Index) = .{},
 
+/// InferredErrorSet objects are stored in this data structure because:
+/// * They contain pointers such as the errors map and the set of other inferred error sets.
+/// * They need to be mutated after creation.
+allocated_inferred_error_sets: std.SegmentedList(Module.Fn.InferredErrorSet, 0) = .{},
+/// When a Struct object is freed from `allocated_inferred_error_sets`, it is
+/// pushed into this stack.
+inferred_error_sets_free_list: std.ArrayListUnmanaged(Module.Fn.InferredErrorSet.Index) = .{},
+
 /// Some types such as enums, structs, and unions need to store mappings from field names
 /// to field index, or value to field index. In such cases, they will store the underlying
 /// field names and values directly, relying on one of these maps, stored separately,
@@ -113,6 +121,12 @@ pub const NullTerminatedString = enum(u32) {
             return std.hash.uint32(@enumToInt(a));
         }
     };
+
+    /// Compare based on integer value alone, ignoring the string contents.
+    pub fn indexLessThan(ctx: void, a: NullTerminatedString, b: NullTerminatedString) bool {
+        _ = ctx;
+        return @enumToInt(a) < @enumToInt(b);
+    }
 };
 
 /// An index into `string_bytes` which might be `none`.
@@ -135,10 +149,7 @@ pub const Key = union(enum) {
     /// `anyframe->T`. The payload is the child type, which may be `none` to indicate
     /// `anyframe`.
     anyframe_type: Index,
-    error_union_type: struct {
-        error_set_type: Index,
-        payload_type: Index,
-    },
+    error_union_type: ErrorUnionType,
     simple_type: SimpleType,
     /// This represents a struct that has been explicitly declared in source code,
     /// or was created with `@Type`. It is unique and based on a declaration.
@@ -152,6 +163,8 @@ pub const Key = union(enum) {
     opaque_type: OpaqueType,
     enum_type: EnumType,
     func_type: FuncType,
+    error_set_type: ErrorSetType,
+    inferred_error_set_type: Module.Fn.InferredErrorSet.Index,
 
     /// Typed `undefined`. This will never be `none`; untyped `undefined` is represented
     /// via `simple_value` and has a named `Index` tag for it.
@@ -183,6 +196,26 @@ pub const Key = union(enum) {
 
     pub const IntType = std.builtin.Type.Int;
 
+    pub const ErrorUnionType = struct {
+        error_set_type: Index,
+        payload_type: Index,
+    };
+
+    pub const ErrorSetType = struct {
+        /// Set of error names, sorted by null terminated string index.
+        names: []const NullTerminatedString,
+        /// This is ignored by `get` but will always be provided by `indexToKey`.
+        names_map: OptionalMapIndex = .none,
+
+        /// Look up field index based on field name.
+        pub fn nameIndex(self: ErrorSetType, ip: *const InternPool, name: NullTerminatedString) ?u32 {
+            const map = &ip.maps.items[@enumToInt(self.names_map.unwrap().?)];
+            const adapter: NullTerminatedString.Adapter = .{ .strings = self.names };
+            const field_index = map.getIndexAdapted(name, adapter) orelse return null;
+            return @intCast(u32, field_index);
+        }
+    };
+
     pub const PtrType = struct {
         elem_type: Index,
         sentinel: Index = .none,
@@ -507,6 +540,7 @@ pub const Key = union(enum) {
             .un,
             .undef,
             .enum_tag,
+            .inferred_error_set_type,
             => |info| std.hash.autoHash(hasher, info),
 
             .opaque_type => |opaque_type| std.hash.autoHash(hasher, opaque_type.decl),
@@ -535,7 +569,7 @@ pub const Key = union(enum) {
             .ptr => |ptr| {
                 std.hash.autoHash(hasher, ptr.ty);
                 // Int-to-ptr pointers are hashed separately than decl-referencing pointers.
-                // This is sound due to pointer province rules.
+                // This is sound due to pointer provenance rules.
                 switch (ptr.addr) {
                     .int => |int| std.hash.autoHash(hasher, int),
                     .decl => @panic("TODO"),
@@ -547,6 +581,10 @@ pub const Key = union(enum) {
                 for (aggregate.fields) |field| std.hash.autoHash(hasher, field);
             },
 
+            .error_set_type => |error_set_type| {
+                for (error_set_type.names) |elem| std.hash.autoHash(hasher, elem);
+            },
+
             .anon_struct_type => |anon_struct_type| {
                 for (anon_struct_type.types) |elem| std.hash.autoHash(hasher, elem);
                 for (anon_struct_type.values) |elem| std.hash.autoHash(hasher, elem);
@@ -726,6 +764,14 @@ pub const Key = union(enum) {
                     std.mem.eql(Index, a_info.values, b_info.values) and
                     std.mem.eql(NullTerminatedString, a_info.names, b_info.names);
             },
+            .error_set_type => |a_info| {
+                const b_info = b.error_set_type;
+                return std.mem.eql(NullTerminatedString, a_info.names, b_info.names);
+            },
+            .inferred_error_set_type => |a_info| {
+                const b_info = b.inferred_error_set_type;
+                return a_info == b_info;
+            },
 
             .func_type => |a_info| {
                 const b_info = b.func_type;
@@ -752,6 +798,8 @@ pub const Key = union(enum) {
             .opt_type,
             .anyframe_type,
             .error_union_type,
+            .error_set_type,
+            .inferred_error_set_type,
             .simple_type,
             .struct_type,
             .union_type,
@@ -1207,8 +1255,14 @@ pub const Tag = enum(u8) {
     /// If the child type is `none`, the type is `anyframe`.
     type_anyframe,
     /// An error union type.
-    /// data is payload to ErrorUnion.
+    /// data is payload to `Key.ErrorUnionType`.
     type_error_union,
+    /// An error set type.
+    /// data is payload to `ErrorSet`.
+    type_error_set,
+    /// The inferred error set type of a function.
+    /// data is `Module.Fn.InferredErrorSet.Index`.
+    type_inferred_error_set,
     /// An enum type with auto-numbered tag values.
     /// The enum is exhaustive.
     /// data is payload index to `EnumAuto`.
@@ -1355,6 +1409,12 @@ pub const Tag = enum(u8) {
     aggregate,
 };
 
+/// Trailing:
+/// 0. name: NullTerminatedString for each names_len
+pub const ErrorSet = struct {
+    names_len: u32,
+};
+
 /// Trailing:
 /// 0. param_type: Index for each params_len
 pub const TypeFunction = struct {
@@ -1539,11 +1599,6 @@ pub const Array = struct {
     }
 };
 
-pub const ErrorUnion = struct {
-    error_set_type: Index,
-    payload_type: Index,
-};
-
 /// Trailing:
 /// 0. field name: NullTerminatedString for each fields_len; declaration order
 /// 1. tag value: Index for each fields_len; declaration order
@@ -1719,6 +1774,9 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
     ip.unions_free_list.deinit(gpa);
     ip.allocated_unions.deinit(gpa);
 
+    ip.inferred_error_sets_free_list.deinit(gpa);
+    ip.allocated_inferred_error_sets.deinit(gpa);
+
     for (ip.maps.items) |*map| map.deinit(gpa);
     ip.maps.deinit(gpa);
 
@@ -1798,7 +1856,18 @@ pub fn indexToKey(ip: InternPool, index: Index) Key {
         .type_optional => .{ .opt_type = @intToEnum(Index, data) },
         .type_anyframe => .{ .anyframe_type = @intToEnum(Index, data) },
 
-        .type_error_union => @panic("TODO"),
+        .type_error_union => .{ .error_union_type = ip.extraData(Key.ErrorUnionType, data) },
+        .type_error_set => {
+            const error_set = ip.extraDataTrail(ErrorSet, data);
+            const names_len = error_set.data.names_len;
+            const names = ip.extra.items[error_set.end..][0..names_len];
+            return .{ .error_set_type = .{
+                .names = @ptrCast([]const NullTerminatedString, names),
+            } };
+        },
+        .type_inferred_error_set => .{
+            .inferred_error_set_type = @intToEnum(Module.Fn.InferredErrorSet.Index, data),
+        },
 
         .type_opaque => .{ .opaque_type = ip.extraData(Key.OpaqueType, data) },
         .type_struct => {
@@ -2179,11 +2248,29 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
         .error_union_type => |error_union_type| {
             ip.items.appendAssumeCapacity(.{
                 .tag = .type_error_union,
-                .data = try ip.addExtra(gpa, ErrorUnion{
-                    .error_set_type = error_union_type.error_set_type,
-                    .payload_type = error_union_type.payload_type,
+                .data = try ip.addExtra(gpa, error_union_type),
+            });
+        },
+        .error_set_type => |error_set_type| {
+            assert(error_set_type.names_map == .none);
+            assert(std.sort.isSorted(NullTerminatedString, error_set_type.names, {}, NullTerminatedString.indexLessThan));
+            const names_map = try ip.addMap(gpa);
+            try addStringsToMap(ip, gpa, names_map, error_set_type.names);
+            const names_len = @intCast(u32, error_set_type.names.len);
+            try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(ErrorSet).Struct.fields.len + names_len);
+            ip.items.appendAssumeCapacity(.{
+                .tag = .type_error_set,
+                .data = ip.addExtraAssumeCapacity(ErrorSet{
+                    .names_len = names_len,
                 }),
             });
+            ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, error_set_type.names));
+        },
+        .inferred_error_set_type => |ies_index| {
+            ip.items.appendAssumeCapacity(.{
+                .tag = .type_inferred_error_set,
+                .data = @enumToInt(ies_index),
+            });
         },
         .simple_type => |simple_type| {
             ip.items.appendAssumeCapacity(.{
@@ -3192,12 +3279,26 @@ pub fn indexToFuncType(ip: InternPool, val: Index) ?Key.FuncType {
     }
 }
 
+pub fn indexToInferredErrorSetType(ip: InternPool, val: Index) Module.Fn.InferredErrorSet.OptionalIndex {
+    assert(val != .none);
+    const tags = ip.items.items(.tag);
+    if (tags[@enumToInt(val)] != .type_inferred_error_set) return .none;
+    const datas = ip.items.items(.data);
+    return @intToEnum(Module.Fn.InferredErrorSet.Index, datas[@enumToInt(val)]).toOptional();
+}
+
 pub fn isOptionalType(ip: InternPool, ty: Index) bool {
     const tags = ip.items.items(.tag);
     if (ty == .none) return false;
     return tags[@enumToInt(ty)] == .type_optional;
 }
 
+pub fn isInferredErrorSetType(ip: InternPool, ty: Index) bool {
+    const tags = ip.items.items(.tag);
+    assert(ty != .none);
+    return tags[@enumToInt(ty)] == .type_inferred_error_set;
+}
+
 pub fn dump(ip: InternPool) void {
     dumpFallible(ip, std.heap.page_allocator) catch return;
 }
@@ -3258,7 +3359,12 @@ fn dumpFallible(ip: InternPool, arena: Allocator) anyerror!void {
             .type_slice => 0,
             .type_optional => 0,
             .type_anyframe => 0,
-            .type_error_union => @sizeOf(ErrorUnion),
+            .type_error_union => @sizeOf(Key.ErrorUnionType),
+            .type_error_set => b: {
+                const info = ip.extraData(ErrorSet, data);
+                break :b @sizeOf(ErrorSet) + (@sizeOf(u32) * info.names_len);
+            },
+            .type_inferred_error_set => @sizeOf(Module.Fn.InferredErrorSet),
             .type_enum_explicit, .type_enum_nonexhaustive => @sizeOf(EnumExplicit),
             .type_enum_auto => @sizeOf(EnumAuto),
             .type_opaque => @sizeOf(Key.OpaqueType),
@@ -3359,6 +3465,14 @@ pub fn unionPtr(ip: *InternPool, index: Module.Union.Index) *Module.Union {
     return ip.allocated_unions.at(@enumToInt(index));
 }
 
+pub fn inferredErrorSetPtr(ip: *InternPool, index: Module.Fn.InferredErrorSet.Index) *Module.Fn.InferredErrorSet {
+    return ip.allocated_inferred_error_sets.at(@enumToInt(index));
+}
+
+pub fn inferredErrorSetPtrConst(ip: InternPool, index: Module.Fn.InferredErrorSet.Index) *const Module.Fn.InferredErrorSet {
+    return ip.allocated_inferred_error_sets.at(@enumToInt(index));
+}
+
 pub fn createStruct(
     ip: *InternPool,
     gpa: Allocator,
@@ -3397,6 +3511,25 @@ pub fn destroyUnion(ip: *InternPool, gpa: Allocator, index: Module.Union.Index)
     };
 }
 
+pub fn createInferredErrorSet(
+    ip: *InternPool,
+    gpa: Allocator,
+    initialization: Module.Fn.InferredErrorSet,
+) Allocator.Error!Module.Fn.InferredErrorSet.Index {
+    if (ip.inferred_error_sets_free_list.popOrNull()) |index| return index;
+    const ptr = try ip.allocated_inferred_error_sets.addOne(gpa);
+    ptr.* = initialization;
+    return @intToEnum(Module.Fn.InferredErrorSet.Index, ip.allocated_inferred_error_sets.len - 1);
+}
+
+pub fn destroyInferredErrorSet(ip: *InternPool, gpa: Allocator, index: Module.Fn.InferredErrorSet.Index) void {
+    ip.inferredErrorSetPtr(index).* = undefined;
+    ip.inferred_error_sets_free_list.append(gpa, index) catch {
+        // In order to keep `destroyInferredErrorSet` a non-fallible function, we ignore memory
+        // allocation failures here, instead leaking the InferredErrorSet until garbage collection.
+    };
+}
+
 pub fn getOrPutString(
     ip: *InternPool,
     gpa: Allocator,
@@ -3459,3 +3592,14 @@ pub fn aggregateTypeLen(ip: InternPool, ty: Index) u64 {
         else => unreachable,
     };
 }
+
+pub fn isNoReturn(ip: InternPool, ty: InternPool.Index) bool {
+    return switch (ty) {
+        .noreturn_type => true,
+        else => switch (ip.indexToKey(ty)) {
+            .error_set_type => |error_set_type| error_set_type.names.len == 0,
+            .enum_type => |enum_type| enum_type.names.len == 0,
+            else => false,
+        },
+    };
+}
src/Liveness.zig
@@ -1416,7 +1416,7 @@ fn analyzeInstBlock(
 
             // If the block is noreturn, block deaths not only aren't useful, they're impossible to
             // find: there could be more stuff alive after the block than before it!
-            if (!a.air.getRefType(ty_pl.ty).isNoReturn()) {
+            if (!a.intern_pool.isNoReturn(a.air.getRefType(ty_pl.ty).ip_index)) {
                 // The block kills the difference in the live sets
                 const block_scope = data.block_scopes.get(inst).?;
                 const num_deaths = data.live_set.count() - block_scope.live_set.count();
src/Module.zig
@@ -960,38 +960,6 @@ pub const EmitH = struct {
     fwd_decl: ArrayListUnmanaged(u8) = .{},
 };
 
-/// Represents the data that an explicit error set syntax provides.
-pub const ErrorSet = struct {
-    /// The Decl that corresponds to the error set itself.
-    owner_decl: Decl.Index,
-    /// The string bytes are stored in the owner Decl arena.
-    /// These must be in sorted order. See sortNames.
-    names: NameMap,
-
-    pub const NameMap = std.StringArrayHashMapUnmanaged(void);
-
-    pub fn srcLoc(self: ErrorSet, mod: *Module) SrcLoc {
-        const owner_decl = mod.declPtr(self.owner_decl);
-        return .{
-            .file_scope = owner_decl.getFileScope(mod),
-            .parent_decl_node = owner_decl.src_node,
-            .lazy = LazySrcLoc.nodeOffset(0),
-        };
-    }
-
-    /// sort the NameMap. This should be called whenever the map is modified.
-    /// alloc should be the allocator used for the NameMap data.
-    pub fn sortNames(names: *NameMap) void {
-        const Context = struct {
-            keys: [][]const u8,
-            pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
-                return std.mem.lessThan(u8, ctx.keys[a_index], ctx.keys[b_index]);
-            }
-        };
-        names.sort(Context{ .keys = names.keys() });
-    }
-};
-
 pub const PropertyBoolean = enum { no, yes, unknown, wip };
 
 /// Represents the data that a struct declaration provides.
@@ -1530,13 +1498,6 @@ pub const Fn = struct {
     is_noinline: bool,
     calls_or_awaits_errorable_fn: bool = false,
 
-    /// Any inferred error sets that this function owns, both its own inferred error set and
-    /// inferred error sets of any inline/comptime functions called. Not to be confused
-    /// with inferred error sets of generic instantiations of this function, which are
-    /// *not* tracked here - they are tracked in the new `Fn` object created for the
-    /// instantiations.
-    inferred_error_sets: InferredErrorSetList = .{},
-
     pub const Analysis = enum {
         /// This function has not yet undergone analysis, because we have not
         /// seen a potential runtime call. It may be analyzed in future.
@@ -1568,10 +1529,10 @@ pub const Fn = struct {
         /// direct additions via `return error.Foo;`, and possibly also errors that
         /// are returned from any dependent functions. When the inferred error set is
         /// fully resolved, this map contains all the errors that the function might return.
-        errors: ErrorSet.NameMap = .{},
+        errors: NameMap = .{},
 
         /// Other inferred error sets which this inferred error set should include.
-        inferred_error_sets: std.AutoArrayHashMapUnmanaged(*InferredErrorSet, void) = .{},
+        inferred_error_sets: std.AutoArrayHashMapUnmanaged(InferredErrorSet.Index, void) = .{},
 
         /// Whether the function returned anyerror. This is true if either of
         /// the dependent functions returns anyerror.
@@ -1581,51 +1542,59 @@ pub const Fn = struct {
         /// can skip resolving any dependents of this inferred error set.
         is_resolved: bool = false,
 
-        pub fn addErrorSet(self: *InferredErrorSet, gpa: Allocator, err_set_ty: Type) !void {
+        pub const NameMap = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void);
+
+        pub const Index = enum(u32) {
+            _,
+
+            pub fn toOptional(i: Index) OptionalIndex {
+                return @intToEnum(OptionalIndex, @enumToInt(i));
+            }
+        };
+
+        pub const OptionalIndex = enum(u32) {
+            none = std.math.maxInt(u32),
+            _,
+
+            pub fn init(oi: ?Index) OptionalIndex {
+                return @intToEnum(OptionalIndex, @enumToInt(oi orelse return .none));
+            }
+
+            pub fn unwrap(oi: OptionalIndex) ?Index {
+                if (oi == .none) return null;
+                return @intToEnum(Index, @enumToInt(oi));
+            }
+        };
+
+        pub fn addErrorSet(
+            self: *InferredErrorSet,
+            err_set_ty: Type,
+            ip: *InternPool,
+            gpa: Allocator,
+        ) !void {
             switch (err_set_ty.ip_index) {
                 .anyerror_type => {
                     self.is_anyerror = true;
                 },
-                .none => switch (err_set_ty.tag()) {
-                    .error_set => {
-                        const names = err_set_ty.castTag(.error_set).?.data.names.keys();
-                        for (names) |name| {
+                else => switch (ip.indexToKey(err_set_ty.ip_index)) {
+                    .error_set_type => |error_set_type| {
+                        for (error_set_type.names) |name| {
                             try self.errors.put(gpa, name, {});
                         }
                     },
-                    .error_set_single => {
-                        const name = err_set_ty.castTag(.error_set_single).?.data;
-                        try self.errors.put(gpa, name, {});
-                    },
-                    .error_set_inferred => {
-                        const ies = err_set_ty.castTag(.error_set_inferred).?.data;
-                        try self.inferred_error_sets.put(gpa, ies, {});
-                    },
-                    .error_set_merged => {
-                        const names = err_set_ty.castTag(.error_set_merged).?.data.keys();
-                        for (names) |name| {
-                            try self.errors.put(gpa, name, {});
-                        }
+                    .inferred_error_set_type => |ies_index| {
+                        try self.inferred_error_sets.put(gpa, ies_index, {});
                     },
                     else => unreachable,
                 },
-                else => @panic("TODO"),
             }
         }
     };
 
-    pub const InferredErrorSetList = std.SinglyLinkedList(InferredErrorSet);
-    pub const InferredErrorSetListNode = InferredErrorSetList.Node;
-
+    /// TODO: remove this function
     pub fn deinit(func: *Fn, gpa: Allocator) void {
-        var it = func.inferred_error_sets.first;
-        while (it) |node| {
-            const next = node.next;
-            node.data.errors.deinit(gpa);
-            node.data.inferred_error_sets.deinit(gpa);
-            gpa.destroy(node);
-            it = next;
-        }
+        _ = func;
+        _ = gpa;
     }
 
     pub fn isAnytypeParam(func: Fn, mod: *Module, index: u32) bool {
@@ -3508,6 +3477,10 @@ pub fn structPtr(mod: *Module, index: Struct.Index) *Struct {
     return mod.intern_pool.structPtr(index);
 }
 
+pub fn inferredErrorSetPtr(mod: *Module, index: Fn.InferredErrorSet.Index) *Fn.InferredErrorSet {
+    return mod.intern_pool.inferredErrorSetPtr(index);
+}
+
 /// This one accepts an index from the InternPool and asserts that it is not
 /// the anonymous empty struct type.
 pub fn structPtrUnwrap(mod: *Module, index: Struct.OptionalIndex) ?*Struct {
@@ -4722,7 +4695,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
                 decl_tv.ty.fmt(mod),
             });
         }
-        const ty = try decl_tv.val.toType().copy(decl_arena_allocator);
+        const ty = decl_tv.val.toType();
         if (ty.getNamespace(mod) == null) {
             return sema.fail(&block_scope, ty_src, "type {} has no namespace", .{ty.fmt(mod)});
         }
@@ -4756,7 +4729,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
             }
             decl.clearValues(mod);
 
-            decl.ty = try decl_tv.ty.copy(decl_arena_allocator);
+            decl.ty = decl_tv.ty;
             decl.val = try decl_tv.val.copy(decl_arena_allocator);
             // linksection, align, and addrspace were already set by Sema
             decl.has_tv = true;
@@ -4823,7 +4796,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
         },
     }
 
-    decl.ty = try decl_tv.ty.copy(decl_arena_allocator);
+    decl.ty = decl_tv.ty;
     decl.val = try decl_tv.val.copy(decl_arena_allocator);
     decl.@"align" = blk: {
         const align_ref = decl.zirAlignRef(mod);
@@ -6599,7 +6572,7 @@ pub fn populateTestFunctions(
             // This copy accesses the old Decl Type/Value so it must be done before `clearValues`.
             const new_ty = try Type.ptr(arena, mod, .{
                 .size = .Slice,
-                .pointee_type = try tmp_test_fn_ty.copy(arena),
+                .pointee_type = tmp_test_fn_ty,
                 .mutable = false,
                 .@"addrspace" = .generic,
             });
@@ -6877,6 +6850,42 @@ pub fn anyframeType(mod: *Module, payload_ty: Type) Allocator.Error!Type {
     return (try intern(mod, .{ .anyframe_type = payload_ty.toIntern() })).toType();
 }
 
+pub fn errorUnionType(mod: *Module, error_set_ty: Type, payload_ty: Type) Allocator.Error!Type {
+    return (try intern(mod, .{ .error_union_type = .{
+        .error_set_type = error_set_ty.toIntern(),
+        .payload_type = payload_ty.toIntern(),
+    } })).toType();
+}
+
+pub fn singleErrorSetType(mod: *Module, name: []const u8) Allocator.Error!Type {
+    const gpa = mod.gpa;
+    const ip = &mod.intern_pool;
+    return singleErrorSetTypeNts(mod, try ip.getOrPutString(gpa, name));
+}
+
+pub fn singleErrorSetTypeNts(mod: *Module, name: InternPool.NullTerminatedString) Allocator.Error!Type {
+    const gpa = mod.gpa;
+    const ip = &mod.intern_pool;
+    const names = [1]InternPool.NullTerminatedString{name};
+    const i = try ip.get(gpa, .{ .error_set_type = .{ .names = &names } });
+    return i.toType();
+}
+
+/// Sorts `names` in place.
+pub fn errorSetFromUnsortedNames(
+    mod: *Module,
+    names: []InternPool.NullTerminatedString,
+) Allocator.Error!Type {
+    std.mem.sort(
+        InternPool.NullTerminatedString,
+        names,
+        {},
+        InternPool.NullTerminatedString.indexLessThan,
+    );
+    const new_ty = try mod.intern(.{ .error_set_type = .{ .names = names } });
+    return new_ty.toType();
+}
+
 /// Supports optionals in addition to pointers.
 pub fn ptrIntValue(mod: *Module, ty: Type, x: u64) Allocator.Error!Value {
     if (ty.isPtrLikeOptional(mod)) {
@@ -7240,6 +7249,16 @@ pub fn typeToFunc(mod: *Module, ty: Type) ?InternPool.Key.FuncType {
     return mod.intern_pool.indexToFuncType(ty.ip_index);
 }
 
+pub fn typeToInferredErrorSet(mod: *Module, ty: Type) ?*Fn.InferredErrorSet {
+    const index = typeToInferredErrorSetIndex(mod, ty).unwrap() orelse return null;
+    return mod.inferredErrorSetPtr(index);
+}
+
+pub fn typeToInferredErrorSetIndex(mod: *Module, ty: Type) Fn.InferredErrorSet.OptionalIndex {
+    if (ty.ip_index == .none) return .none;
+    return mod.intern_pool.indexToInferredErrorSetType(ty.ip_index);
+}
+
 pub fn fieldSrcLoc(mod: *Module, owner_decl_index: Decl.Index, query: FieldSrcQuery) SrcLoc {
     @setCold(true);
     const owner_decl = mod.declPtr(owner_decl_index);
src/print_air.zig
@@ -370,7 +370,6 @@ const Writer = struct {
             .none => switch (ty.tag()) {
                 .inferred_alloc_const => try s.writeAll("(inferred_alloc_const)"),
                 .inferred_alloc_mut => try s.writeAll("(inferred_alloc_mut)"),
-                else => try ty.print(s, w.module),
             },
             else => try ty.print(s, w.module),
         }
src/Sema.zig
@@ -825,12 +825,13 @@ pub fn analyzeBodyBreak(
     block: *Block,
     body: []const Zir.Inst.Index,
 ) CompileError!?BreakData {
+    const mod = sema.mod;
     const break_inst = sema.analyzeBodyInner(block, body) catch |err| switch (err) {
         error.ComptimeBreak => sema.comptime_break_inst,
         else => |e| return e,
     };
     if (block.instructions.items.len != 0 and
-        sema.typeOf(Air.indexToRef(block.instructions.items[block.instructions.items.len - 1])).isNoReturn())
+        sema.typeOf(Air.indexToRef(block.instructions.items[block.instructions.items.len - 1])).isNoReturn(mod))
         return null;
     const break_data = sema.code.instructions.items(.data)[break_inst].@"break";
     const extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data;
@@ -1701,7 +1702,7 @@ fn analyzeBodyInner(
                 break :blk Air.Inst.Ref.void_value;
             },
         };
-        if (sema.typeOf(air_inst).isNoReturn())
+        if (sema.typeOf(air_inst).isNoReturn(mod))
             break always_noreturn;
         map.putAssumeCapacity(inst, air_inst);
         i += 1;
@@ -1796,8 +1797,7 @@ fn analyzeAsType(
     const wanted_type = Type.type;
     const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src);
     const val = try sema.resolveConstValue(block, src, coerced_inst, "types must be comptime-known");
-    const ty = val.toType();
-    return ty.copy(sema.arena);
+    return val.toType();
 }
 
 pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) !void {
@@ -2004,7 +2004,7 @@ fn resolveMaybeUndefValAllowVariablesMaybeRuntime(
             if (val.isPtrToThreadLocal(sema.mod)) make_runtime.* = true;
             return val;
         },
-        .const_ty => return try air_datas[i].ty.toValue(sema.arena),
+        .const_ty => return air_datas[i].ty.toValue(),
         .interned => return air_datas[i].interned.toValue(),
         else => return null,
     }
@@ -2131,7 +2131,7 @@ fn failWithInvalidFieldAccess(sema: *Sema, block: *Block, src: LazySrcLoc, objec
         };
         return sema.failWithOwnedErrorMsg(msg);
     } else if (inner_ty.zigTypeTag(mod) == .ErrorUnion) err: {
-        const child_ty = inner_ty.errorUnionPayload();
+        const child_ty = inner_ty.errorUnionPayload(mod);
         if (!typeSupportsFieldAccess(mod, child_ty, field_name)) break :err;
         const msg = msg: {
             const msg = try sema.errMsg(block, src, "error union type '{}' does not support field access", .{object_ty.fmt(sema.mod)});
@@ -2473,7 +2473,7 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
                     var anon_decl = try block.startAnonDecl();
                     defer anon_decl.deinit();
                     iac.data.decl_index = try anon_decl.finish(
-                        try pointee_ty.copy(anon_decl.arena()),
+                        pointee_ty,
                         Value.undef,
                         iac.data.alignment,
                     );
@@ -3250,47 +3250,35 @@ fn zirErrorSetDecl(
     const tracy = trace(@src());
     defer tracy.end();
 
+    const mod = sema.mod;
     const gpa = sema.gpa;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
     const extra = sema.code.extraData(Zir.Inst.ErrorSetDecl, inst_data.payload_index);
 
-    var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
-    errdefer new_decl_arena.deinit();
-    const new_decl_arena_allocator = new_decl_arena.allocator();
-
-    const error_set = try new_decl_arena_allocator.create(Module.ErrorSet);
-    const error_set_ty = try Type.Tag.error_set.create(new_decl_arena_allocator, error_set);
-    const error_set_val = try Value.Tag.ty.create(new_decl_arena_allocator, error_set_ty);
-    const mod = sema.mod;
-    const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{
-        .ty = Type.type,
-        .val = error_set_val,
-    }, name_strategy, "error", inst);
-    const new_decl = mod.declPtr(new_decl_index);
-    new_decl.owns_tv = true;
-    errdefer mod.abortAnonDecl(new_decl_index);
-
-    var names = Module.ErrorSet.NameMap{};
-    try names.ensureUnusedCapacity(new_decl_arena_allocator, extra.data.fields_len);
+    var names: Module.Fn.InferredErrorSet.NameMap = .{};
+    try names.ensureUnusedCapacity(sema.arena, extra.data.fields_len);
 
     var extra_index = @intCast(u32, extra.end);
     const extra_index_end = extra_index + (extra.data.fields_len * 2);
     while (extra_index < extra_index_end) : (extra_index += 2) { // +2 to skip over doc_string
         const str_index = sema.code.extra[extra_index];
-        const kv = try mod.getErrorValue(sema.code.nullTerminatedString(str_index));
-        const result = names.getOrPutAssumeCapacity(kv.key);
+        const name = sema.code.nullTerminatedString(str_index);
+        const name_ip = try mod.intern_pool.getOrPutString(gpa, name);
+        const result = names.getOrPutAssumeCapacity(name_ip);
         assert(!result.found_existing); // verified in AstGen
     }
 
-    // names must be sorted.
-    Module.ErrorSet.sortNames(&names);
+    const error_set_ty = try mod.errorSetFromUnsortedNames(names.keys());
+
+    const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{
+        .ty = Type.type,
+        .val = error_set_ty.toValue(),
+    }, name_strategy, "error", inst);
+    const new_decl = mod.declPtr(new_decl_index);
+    new_decl.owns_tv = true;
+    errdefer mod.abortAnonDecl(new_decl_index);
 
-    error_set.* = .{
-        .owner_decl = new_decl_index,
-        .names = names,
-    };
-    try new_decl.finalizeNewArena(&new_decl_arena);
     return sema.analyzeDeclVal(block, src, new_decl_index);
 }
 
@@ -3407,7 +3395,7 @@ fn zirEnsureErrUnionPayloadVoid(sema: *Sema, block: *Block, inst: Zir.Inst.Index
     else
         operand_ty;
     if (err_union_ty.zigTypeTag(mod) != .ErrorUnion) return;
-    const payload_ty = err_union_ty.errorUnionPayload().zigTypeTag(mod);
+    const payload_ty = err_union_ty.errorUnionPayload(mod).zigTypeTag(mod);
     if (payload_ty != .Void and payload_ty != .NoReturn) {
         const msg = msg: {
             const msg = try sema.errMsg(block, src, "error union payload is ignored", .{});
@@ -3590,7 +3578,7 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
         var anon_decl = try block.startAnonDecl();
         defer anon_decl.deinit();
         return sema.analyzeDeclRef(try anon_decl.finish(
-            try elem_ty.copy(anon_decl.arena()),
+            elem_ty,
             try store_val.copy(anon_decl.arena()),
             ptr_info.@"align",
         ));
@@ -3722,7 +3710,6 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
     const var_is_mut = switch (sema.typeOf(ptr).tag()) {
         .inferred_alloc_const => false,
         .inferred_alloc_mut => true,
-        else => unreachable,
     };
     const target = sema.mod.getTarget();
 
@@ -3733,7 +3720,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
             try sema.mod.declareDeclDependency(sema.owner_decl_index, decl_index);
 
             const decl = sema.mod.declPtr(decl_index);
-            const final_elem_ty = try decl.ty.copy(sema.arena);
+            const final_elem_ty = decl.ty;
             const final_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{
                 .pointee_type = final_elem_ty,
                 .mutable = true,
@@ -3833,7 +3820,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
                     var anon_decl = try block.startAnonDecl();
                     defer anon_decl.deinit();
                     const new_decl_index = try anon_decl.finish(
-                        try final_elem_ty.copy(anon_decl.arena()),
+                        final_elem_ty,
                         try store_val.copy(anon_decl.arena()),
                         inferred_alloc.data.alignment,
                     );
@@ -5042,7 +5029,7 @@ fn storeToInferredAllocComptime(
         var anon_decl = try block.startAnonDecl();
         defer anon_decl.deinit();
         iac.data.decl_index = try anon_decl.finish(
-            try operand_ty.copy(anon_decl.arena()),
+            operand_ty,
             try operand_val.copy(anon_decl.arena()),
             iac.data.alignment,
         );
@@ -5286,6 +5273,7 @@ fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError
     const tracy = trace(@src());
     defer tracy.end();
 
+    const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
     const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
@@ -5335,7 +5323,7 @@ fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError
     try sema.analyzeBody(&loop_block, body);
 
     const loop_block_len = loop_block.instructions.items.len;
-    if (loop_block_len > 0 and sema.typeOf(Air.indexToRef(loop_block.instructions.items[loop_block_len - 1])).isNoReturn()) {
+    if (loop_block_len > 0 and sema.typeOf(Air.indexToRef(loop_block.instructions.items[loop_block_len - 1])).isNoReturn(mod)) {
         // If the loop ended with a noreturn terminator, then there is no way for it to loop,
         // so we can just use the block instead.
         try child_block.instructions.appendSlice(gpa, loop_block.instructions.items);
@@ -5588,7 +5576,7 @@ fn analyzeBlockBody(
 
     // Blocks must terminate with noreturn instruction.
     assert(child_block.instructions.items.len != 0);
-    assert(sema.typeOf(Air.indexToRef(child_block.instructions.items[child_block.instructions.items.len - 1])).isNoReturn());
+    assert(sema.typeOf(Air.indexToRef(child_block.instructions.items[child_block.instructions.items.len - 1])).isNoReturn(mod));
 
     if (merges.results.items.len == 0) {
         // No need for a block instruction. We can put the new instructions
@@ -5755,7 +5743,7 @@ fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
             var anon_decl = try block.startAnonDecl();
             defer anon_decl.deinit();
             break :blk try anon_decl.finish(
-                try operand.ty.copy(anon_decl.arena()),
+                operand.ty,
                 try operand.val.copy(anon_decl.arena()),
                 0,
             );
@@ -6434,7 +6422,7 @@ fn zirCall(
         };
 
         const return_ty = sema.typeOf(call_inst);
-        if (modifier != .always_tail and return_ty.isNoReturn())
+        if (modifier != .always_tail and return_ty.isNoReturn(mod))
             return call_inst; // call to "fn(...) noreturn", don't pop
 
         // If any input is an error-type, we might need to pop any trace it generated. Otherwise, we only
@@ -6957,17 +6945,11 @@ fn analyzeCall(
         // Create a fresh inferred error set type for inline/comptime calls.
         const fn_ret_ty = blk: {
             if (module_fn.hasInferredErrorSet(mod)) {
-                const node = try sema.gpa.create(Module.Fn.InferredErrorSetListNode);
-                node.data = .{ .func = module_fn };
-                if (parent_func) |some| {
-                    some.inferred_error_sets.prepend(node);
-                }
-
-                const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, &node.data);
-                break :blk try Type.Tag.error_union.create(sema.arena, .{
-                    .error_set = error_set_ty,
-                    .payload = bare_return_type,
+                const ies_index = try mod.intern_pool.createInferredErrorSet(gpa, .{
+                    .func = module_fn,
                 });
+                const error_set_ty = try mod.intern(.{ .inferred_error_set_type = ies_index });
+                break :blk try mod.errorUnionType(error_set_ty.toType(), bare_return_type);
             }
             break :blk bare_return_type;
         };
@@ -7843,21 +7825,21 @@ fn resolveGenericInstantiationType(
         // `GenericCallAdapter.eql` as well as function body analysis.
         // Whether it is anytype is communicated by `isAnytypeParam`.
         const arg = child_sema.inst_map.get(inst).?;
-        const copied_arg_ty = try child_sema.typeOf(arg).copy(new_decl_arena_allocator);
+        const arg_ty = child_sema.typeOf(arg);
 
-        if (try sema.typeRequiresComptime(copied_arg_ty)) {
+        if (try sema.typeRequiresComptime(arg_ty)) {
             is_comptime = true;
         }
 
         if (is_comptime) {
             const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(arg) catch unreachable).?;
             child_sema.comptime_args[arg_i] = .{
-                .ty = copied_arg_ty,
+                .ty = arg_ty,
                 .val = try arg_val.copy(new_decl_arena_allocator),
             };
         } else {
             child_sema.comptime_args[arg_i] = .{
-                .ty = copied_arg_ty,
+                .ty = arg_ty,
                 .val = Value.generic_poison,
             };
         }
@@ -7868,7 +7850,7 @@ fn resolveGenericInstantiationType(
     try wip_captures.finalize();
 
     // Populate the Decl ty/val with the function and its type.
-    new_decl.ty = try child_sema.typeOf(new_func_inst).copy(new_decl_arena_allocator);
+    new_decl.ty = child_sema.typeOf(new_func_inst);
     // If the call evaluated to a return type that requires comptime, never mind
     // our generic instantiation. Instead we need to perform a comptime call.
     const new_fn_info = mod.typeToFunc(new_decl.ty).?;
@@ -8068,7 +8050,7 @@ fn zirErrorUnionType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
         });
     }
     try sema.validateErrorUnionPayloadType(block, payload, rhs_src);
-    const err_union_ty = try Type.errorUnion(sema.arena, error_set, payload, sema.mod);
+    const err_union_ty = try mod.errorUnionType(error_set, payload);
     return sema.addType(err_union_ty);
 }
 
@@ -8087,16 +8069,13 @@ fn validateErrorUnionPayloadType(sema: *Sema, block: *Block, payload_ty: Type, p
 
 fn zirErrorValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     _ = block;
-    const tracy = trace(@src());
-    defer tracy.end();
-
+    const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
-
-    // Create an anonymous error set type with only this error value, and return the value.
-    const kv = try sema.mod.getErrorValue(inst_data.get(sema.code));
-    const result_type = try Type.Tag.error_set_single.create(sema.arena, kv.key);
+    const name = inst_data.get(sema.code);
+    // Create an error set type with only this error value, and return the value.
+    const kv = try sema.mod.getErrorValue(name);
     return sema.addConstant(
-        result_type,
+        try mod.singleErrorSetType(kv.key),
         try Value.Tag.@"error".create(sema.arena, .{
             .name = kv.key,
         }),
@@ -8139,11 +8118,14 @@ fn zirErrorToInt(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
 
     const op_ty = sema.typeOf(uncasted_operand);
     try sema.resolveInferredErrorSetTy(block, src, op_ty);
-    if (!op_ty.isAnyError()) {
-        const names = op_ty.errorSetNames();
+    if (!op_ty.isAnyError(mod)) {
+        const names = op_ty.errorSetNames(mod);
         switch (names.len) {
             0 => return sema.addConstant(Type.err_int, try mod.intValue(Type.err_int, 0)),
-            1 => return sema.addIntUnsigned(Type.err_int, sema.mod.global_error_set.get(names[0]).?),
+            1 => {
+                const name = mod.intern_pool.stringToSlice(names[0]);
+                return sema.addIntUnsigned(Type.err_int, mod.global_error_set.get(name).?);
+            },
             else => {},
         }
     }
@@ -8224,22 +8206,22 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
         return Air.Inst.Ref.anyerror_type;
     }
 
-    if (lhs_ty.castTag(.error_set_inferred)) |payload| {
-        try sema.resolveInferredErrorSet(block, src, payload.data);
+    if (mod.typeToInferredErrorSetIndex(lhs_ty).unwrap()) |ies_index| {
+        try sema.resolveInferredErrorSet(block, src, ies_index);
         // isAnyError might have changed from a false negative to a true positive after resolution.
-        if (lhs_ty.isAnyError()) {
+        if (lhs_ty.isAnyError(mod)) {
             return Air.Inst.Ref.anyerror_type;
         }
     }
-    if (rhs_ty.castTag(.error_set_inferred)) |payload| {
-        try sema.resolveInferredErrorSet(block, src, payload.data);
+    if (mod.typeToInferredErrorSetIndex(rhs_ty).unwrap()) |ies_index| {
+        try sema.resolveInferredErrorSet(block, src, ies_index);
         // isAnyError might have changed from a false negative to a true positive after resolution.
-        if (rhs_ty.isAnyError()) {
+        if (rhs_ty.isAnyError(mod)) {
             return Air.Inst.Ref.anyerror_type;
         }
     }
 
-    const err_set_ty = try lhs_ty.errorSetMerge(sema.arena, rhs_ty);
+    const err_set_ty = try sema.errorSetMerge(lhs_ty, rhs_ty);
     return sema.addType(err_set_ty);
 }
 
@@ -8484,7 +8466,7 @@ fn zirOptionalPayload(
             if (true) break :t operand_ty;
             const ptr_info = operand_ty.ptrInfo(mod);
             break :t try Type.ptr(sema.arena, sema.mod, .{
-                .pointee_type = try ptr_info.pointee_type.copy(sema.arena),
+                .pointee_type = ptr_info.pointee_type,
                 .@"align" = ptr_info.@"align",
                 .@"addrspace" = ptr_info.@"addrspace",
                 .mutable = ptr_info.mutable,
@@ -8547,7 +8529,7 @@ fn analyzeErrUnionPayload(
     safety_check: bool,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
-    const payload_ty = err_union_ty.errorUnionPayload();
+    const payload_ty = err_union_ty.errorUnionPayload(mod);
     if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| {
         if (val.getError()) |name| {
             return sema.fail(block, src, "caught unexpected error '{s}'", .{name});
@@ -8560,7 +8542,7 @@ fn analyzeErrUnionPayload(
 
     // If the error set has no fields then no safety check is needed.
     if (safety_check and block.wantSafety() and
-        !err_union_ty.errorUnionSet().errorSetIsEmpty(mod))
+        !err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod))
     {
         try sema.panicUnwrapError(block, operand, .unwrap_errunion_err, .is_non_err);
     }
@@ -8603,7 +8585,7 @@ fn analyzeErrUnionPayloadPtr(
     }
 
     const err_union_ty = operand_ty.childType(mod);
-    const payload_ty = err_union_ty.errorUnionPayload();
+    const payload_ty = err_union_ty.errorUnionPayload(mod);
     const operand_pointer_ty = try Type.ptr(sema.arena, sema.mod, .{
         .pointee_type = payload_ty,
         .mutable = !operand_ty.isConstPtr(mod),
@@ -8646,7 +8628,7 @@ fn analyzeErrUnionPayloadPtr(
 
     // If the error set has no fields then no safety check is needed.
     if (safety_check and block.wantSafety() and
-        !err_union_ty.errorUnionSet().errorSetIsEmpty(mod))
+        !err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod))
     {
         try sema.panicUnwrapError(block, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr);
     }
@@ -8678,7 +8660,7 @@ fn analyzeErrUnionCode(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air
         });
     }
 
-    const result_ty = operand_ty.errorUnionSet();
+    const result_ty = operand_ty.errorUnionSet(mod);
 
     if (try sema.resolveDefinedValue(block, src, operand)) |val| {
         assert(val.getError() != null);
@@ -8707,7 +8689,7 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
         });
     }
 
-    const result_ty = operand_ty.childType(mod).errorUnionSet();
+    const result_ty = operand_ty.childType(mod).errorUnionSet(mod);
 
     if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
         if (try sema.pointerDeref(block, src, pointer_val, operand_ty)) |val| {
@@ -8755,7 +8737,7 @@ fn zirFunc(
             extra_index += ret_ty_body.len;
 
             const ret_ty_val = try sema.resolveGenericBody(block, ret_ty_src, ret_ty_body, inst, Type.type, "return type must be comptime-known");
-            break :blk try ret_ty_val.toType().copy(sema.arena);
+            break :blk ret_ty_val.toType();
         },
     };
 
@@ -8927,6 +8909,7 @@ fn funcCommon(
     is_noinline: bool,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
+    const gpa = sema.gpa;
     const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
     const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = src_node_offset };
     const func_src = LazySrcLoc.nodeOffset(src_node_offset);
@@ -8955,16 +8938,12 @@ fn funcCommon(
             break :new_func new_func;
         }
         destroy_fn_on_error = true;
-        const new_func = try sema.gpa.create(Module.Fn);
+        const new_func = try gpa.create(Module.Fn);
         // Set this here so that the inferred return type can be printed correctly if it appears in an error.
         new_func.owner_decl = sema.owner_decl_index;
         break :new_func new_func;
     };
-    errdefer if (destroy_fn_on_error) sema.gpa.destroy(new_func);
-
-    var maybe_inferred_error_set_node: ?*Module.Fn.InferredErrorSetListNode = null;
-    errdefer if (maybe_inferred_error_set_node) |node| sema.gpa.destroy(node);
-    // Note: no need to errdefer since this will still be in its default state at the end of the function.
+    errdefer if (destroy_fn_on_error) gpa.destroy(new_func);
 
     const target = sema.mod.getTarget();
     const fn_ty: Type = fn_ty: {
@@ -9027,15 +9006,11 @@ fn funcCommon(
             bare_return_type
         else blk: {
             try sema.validateErrorUnionPayloadType(block, bare_return_type, ret_ty_src);
-            const node = try sema.gpa.create(Module.Fn.InferredErrorSetListNode);
-            node.data = .{ .func = new_func };
-            maybe_inferred_error_set_node = node;
-
-            const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, &node.data);
-            break :blk try Type.Tag.error_union.create(sema.arena, .{
-                .error_set = error_set_ty,
-                .payload = bare_return_type,
+            const ies_index = try mod.intern_pool.createInferredErrorSet(gpa, .{
+                .func = new_func,
             });
+            const error_set_ty = try mod.intern(.{ .inferred_error_set_type = ies_index });
+            break :blk try mod.errorUnionType(error_set_ty.toType(), bare_return_type);
         };
 
         if (!return_type.isValidReturnType(mod)) {
@@ -9044,7 +9019,7 @@ fn funcCommon(
                 const msg = try sema.errMsg(block, ret_ty_src, "{s}return type '{}' not allowed", .{
                     opaque_str, return_type.fmt(sema.mod),
                 });
-                errdefer msg.destroy(sema.gpa);
+                errdefer msg.destroy(gpa);
 
                 try sema.addDeclaredHereNote(msg, return_type);
                 break :msg msg;
@@ -9058,7 +9033,7 @@ fn funcCommon(
                 const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{
                     return_type.fmt(sema.mod), @tagName(cc_resolved),
                 });
-                errdefer msg.destroy(sema.gpa);
+                errdefer msg.destroy(gpa);
 
                 const src_decl = sema.mod.declPtr(block.src_decl);
                 try sema.explainWhyTypeIsNotExtern(msg, ret_ty_src.toSrcLoc(src_decl, mod), return_type, .ret_ty);
@@ -9182,8 +9157,8 @@ fn funcCommon(
     sema.owner_decl.@"addrspace" = address_space orelse .generic;
 
     if (is_extern) {
-        const new_extern_fn = try sema.gpa.create(Module.ExternFn);
-        errdefer sema.gpa.destroy(new_extern_fn);
+        const new_extern_fn = try gpa.create(Module.ExternFn);
+        errdefer gpa.destroy(new_extern_fn);
 
         new_extern_fn.* = Module.ExternFn{
             .owner_decl = sema.owner_decl_index,
@@ -9232,10 +9207,6 @@ fn funcCommon(
         .branch_quota = default_branch_quota,
         .is_noinline = is_noinline,
     };
-    if (maybe_inferred_error_set_node) |node| {
-        new_func.inferred_error_sets.prepend(node);
-    }
-    maybe_inferred_error_set_node = null;
     fn_payload.* = .{
         .base = .{ .tag = .function },
         .data = new_func,
@@ -10139,6 +10110,7 @@ fn zirSwitchCapture(
     defer tracy.end();
 
     const mod = sema.mod;
+    const gpa = sema.gpa;
     const zir_datas = sema.code.instructions.items(.data);
     const capture_info = zir_datas[inst].switch_capture;
     const switch_info = zir_datas[capture_info.switch_inst].pl_node;
@@ -10248,7 +10220,7 @@ fn zirSwitchCapture(
                         const capture_src = raw_capture_src.resolve(mod, sema.mod.declPtr(block.src_decl), switch_info.src_node, .first);
 
                         const msg = try sema.errMsg(block, capture_src, "capture group with incompatible types", .{});
-                        errdefer msg.destroy(sema.gpa);
+                        errdefer msg.destroy(gpa);
 
                         const raw_first_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = capture_info.prong_index, .item = 0 } };
                         const first_item_src = raw_first_item_src.resolve(mod, sema.mod.declPtr(block.src_decl), switch_info.src_node, .first);
@@ -10294,20 +10266,16 @@ fn zirSwitchCapture(
         },
         .ErrorSet => {
             if (is_multi) {
-                var names: Module.ErrorSet.NameMap = .{};
+                var names: Module.Fn.InferredErrorSet.NameMap = .{};
                 try names.ensureUnusedCapacity(sema.arena, items.len);
                 for (items) |item| {
                     const item_ref = try sema.resolveInst(item);
                     // Previous switch validation ensured this will succeed
                     const item_val = sema.resolveConstValue(block, .unneeded, item_ref, "") catch unreachable;
-                    names.putAssumeCapacityNoClobber(
-                        item_val.getError().?,
-                        {},
-                    );
+                    const name_ip = try mod.intern_pool.getOrPutString(gpa, item_val.getError().?);
+                    names.putAssumeCapacityNoClobber(name_ip, {});
                 }
-                // names must be sorted
-                Module.ErrorSet.sortNames(&names);
-                const else_error_ty = try Type.Tag.error_set_merged.create(sema.arena, names);
+                const else_error_ty = try mod.errorSetFromUnsortedNames(names.keys());
 
                 return sema.bitCast(block, else_error_ty, operand, operand_src, null);
             } else {
@@ -10315,7 +10283,7 @@ fn zirSwitchCapture(
                 // Previous switch validation ensured this will succeed
                 const item_val = sema.resolveConstValue(block, .unneeded, item_ref, "") catch unreachable;
 
-                const item_ty = try Type.Tag.error_set_single.create(sema.arena, item_val.getError().?);
+                const item_ty = try mod.singleErrorSetType(item_val.getError().?);
                 return sema.bitCast(block, item_ty, operand, operand_src, null);
             }
         },
@@ -10678,7 +10646,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
 
             try sema.resolveInferredErrorSetTy(block, src, operand_ty);
 
-            if (operand_ty.isAnyError()) {
+            if (operand_ty.isAnyError(mod)) {
                 if (special_prong != .@"else") {
                     return sema.fail(
                         block,
@@ -10692,7 +10660,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 var maybe_msg: ?*Module.ErrorMsg = null;
                 errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
 
-                for (operand_ty.errorSetNames()) |error_name| {
+                for (operand_ty.errorSetNames(mod)) |error_name_ip| {
+                    const error_name = mod.intern_pool.stringToSlice(error_name_ip);
                     if (!seen_errors.contains(error_name) and special_prong != .@"else") {
                         const msg = maybe_msg orelse blk: {
                             maybe_msg = try sema.errMsg(
@@ -10720,7 +10689,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     return sema.failWithOwnedErrorMsg(msg);
                 }
 
-                if (special_prong == .@"else" and seen_errors.count() == operand_ty.errorSetNames().len) {
+                if (special_prong == .@"else" and seen_errors.count() == operand_ty.errorSetNames(mod).len) {
                     // In order to enable common patterns for generic code allow simple else bodies
                     // else => unreachable,
                     // else => return,
@@ -10757,18 +10726,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     );
                 }
 
-                const error_names = operand_ty.errorSetNames();
-                var names: Module.ErrorSet.NameMap = .{};
+                const error_names = operand_ty.errorSetNames(mod);
+                var names: Module.Fn.InferredErrorSet.NameMap = .{};
                 try names.ensureUnusedCapacity(sema.arena, error_names.len);
-                for (error_names) |error_name| {
+                for (error_names) |error_name_ip| {
+                    const error_name = mod.intern_pool.stringToSlice(error_name_ip);
                     if (seen_errors.contains(error_name)) continue;
 
-                    names.putAssumeCapacityNoClobber(error_name, {});
+                    names.putAssumeCapacityNoClobber(error_name_ip, {});
                 }
-
-                // names must be sorted
-                Module.ErrorSet.sortNames(&names);
-                else_error_ty = try Type.Tag.error_set_merged.create(sema.arena, names);
+                // No need to keep the hash map metadata correct; here we
+                // extract the (sorted) keys only.
+                else_error_ty = try mod.errorSetFromUnsortedNames(names.keys());
             }
         },
         .Int, .ComptimeInt => {
@@ -11513,12 +11482,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 }
             },
             .ErrorSet => {
-                if (operand_ty.isAnyError()) {
+                if (operand_ty.isAnyError(mod)) {
                     return sema.fail(block, special_prong_src, "cannot enumerate values of type '{}' for 'inline else'", .{
                         operand_ty.fmt(mod),
                     });
                 }
-                for (operand_ty.errorSetNames()) |error_name| {
+                for (operand_ty.errorSetNames(mod)) |error_name_ip| {
+                    const error_name = mod.intern_pool.stringToSlice(error_name_ip);
                     if (seen_errors.contains(error_name)) continue;
                     cases_len += 1;
 
@@ -11931,7 +11901,8 @@ fn validateSwitchNoRange(
 }
 
 fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, operand: Air.Inst.Ref) !bool {
-    if (!sema.mod.backendSupportsFeature(.panic_unwrap_error)) return false;
+    const mod = sema.mod;
+    if (!mod.backendSupportsFeature(.panic_unwrap_error)) return false;
 
     const tags = sema.code.instructions.items(.tag);
     for (body) |inst| {
@@ -11967,7 +11938,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
             .as_node => try sema.zirAsNode(block, inst),
             .field_val => try sema.zirFieldVal(block, inst),
             .@"unreachable" => {
-                if (!sema.mod.comp.formatted_panics) {
+                if (!mod.comp.formatted_panics) {
                     try sema.safetyPanic(block, .unwrap_error);
                     return true;
                 }
@@ -11990,7 +11961,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
             },
             else => unreachable,
         };
-        if (sema.typeOf(air_inst).isNoReturn())
+        if (sema.typeOf(air_inst).isNoReturn(mod))
             return true;
         sema.inst_map.putAssumeCapacity(inst, air_inst);
     }
@@ -12194,13 +12165,14 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
 }
 
 fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
     const err_name = inst_data.get(sema.code);
 
     // Return the error code from the function.
-    const kv = try sema.mod.getErrorValue(err_name);
+    const kv = try mod.getErrorValue(err_name);
     const result_inst = try sema.addConstant(
-        try Type.Tag.error_set_single.create(sema.arena, kv.key),
+        try mod.singleErrorSetType(kv.key),
         try Value.Tag.@"error".create(sema.arena, .{ .name = kv.key }),
     );
     return result_inst;
@@ -15737,7 +15709,7 @@ fn zirClosureCapture(
         Value.@"unreachable";
 
     try block.wip_capture_scope.captures.putNoClobber(sema.gpa, inst, .{
-        .ty = try sema.typeOf(operand).copy(sema.perm_arena),
+        .ty = sema.typeOf(operand),
         .val = try val.copy(sema.perm_arena),
     });
 }
@@ -16223,10 +16195,10 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 try mod.declareDeclDependency(sema.owner_decl_index, set_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(set_field_ty_decl_index);
                 const set_field_ty_decl = mod.declPtr(set_field_ty_decl_index);
-                break :t try set_field_ty_decl.val.toType().copy(fields_anon_decl.arena());
+                break :t set_field_ty_decl.val.toType();
             };
 
-            try sema.queueFullTypeResolution(try error_field_ty.copy(sema.arena));
+            try sema.queueFullTypeResolution(error_field_ty);
 
             // If the error set is inferred it must be resolved at this point
             try sema.resolveInferredErrorSetTy(block, src, ty);
@@ -16234,11 +16206,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             // Build our list of Error values
             // Optional value is only null if anyerror
             // Value can be zero-length slice otherwise
-            const error_field_vals: ?[]Value = if (ty.isAnyError()) null else blk: {
-                const names = ty.errorSetNames();
+            const error_field_vals: ?[]Value = if (ty.isAnyError(mod)) null else blk: {
+                const names = ty.errorSetNames(mod);
                 const vals = try fields_anon_decl.arena().alloc(Value, names.len);
-                for (vals, 0..) |*field_val, i| {
-                    const name = names[i];
+                for (vals, names) |*field_val, name_ip| {
+                    const name = mod.intern_pool.stringToSlice(name_ip);
                     const name_val = v: {
                         var anon_decl = try block.startAnonDecl();
                         defer anon_decl.deinit();
@@ -16301,9 +16273,9 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         .ErrorUnion => {
             const field_values = try sema.arena.alloc(Value, 2);
             // error_set: type,
-            field_values[0] = try Value.Tag.ty.create(sema.arena, ty.errorUnionSet());
+            field_values[0] = try Value.Tag.ty.create(sema.arena, ty.errorUnionSet(mod));
             // payload: type,
-            field_values[1] = try Value.Tag.ty.create(sema.arena, ty.errorUnionPayload());
+            field_values[1] = try Value.Tag.ty.create(sema.arena, ty.errorUnionPayload(mod));
 
             return sema.addConstant(
                 type_info_ty,
@@ -16332,7 +16304,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 try mod.declareDeclDependency(sema.owner_decl_index, enum_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(enum_field_ty_decl_index);
                 const enum_field_ty_decl = mod.declPtr(enum_field_ty_decl_index);
-                break :t try enum_field_ty_decl.val.toType().copy(fields_anon_decl.arena());
+                break :t enum_field_ty_decl.val.toType();
             };
 
             const enum_field_vals = try fields_anon_decl.arena().alloc(Value, enum_type.names.len);
@@ -16416,7 +16388,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 try mod.declareDeclDependency(sema.owner_decl_index, union_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(union_field_ty_decl_index);
                 const union_field_ty_decl = mod.declPtr(union_field_ty_decl_index);
-                break :t try union_field_ty_decl.val.toType().copy(fields_anon_decl.arena());
+                break :t union_field_ty_decl.val.toType();
             };
 
             const union_ty = try sema.resolveTypeFields(ty);
@@ -16523,7 +16495,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 try mod.declareDeclDependency(sema.owner_decl_index, struct_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(struct_field_ty_decl_index);
                 const struct_field_ty_decl = mod.declPtr(struct_field_ty_decl_index);
-                break :t try struct_field_ty_decl.val.toType().copy(fields_anon_decl.arena());
+                break :t struct_field_ty_decl.val.toType();
             };
             const struct_ty = try sema.resolveTypeFields(ty);
             try sema.resolveTypeLayout(ty); // Getting alignment requires type layout
@@ -16733,9 +16705,9 @@ fn typeInfoDecls(
         try mod.declareDeclDependency(sema.owner_decl_index, declaration_ty_decl_index);
         try sema.ensureDeclAnalyzed(declaration_ty_decl_index);
         const declaration_ty_decl = mod.declPtr(declaration_ty_decl_index);
-        break :t try declaration_ty_decl.val.toType().copy(decls_anon_decl.arena());
+        break :t declaration_ty_decl.val.toType();
     };
-    try sema.queueFullTypeResolution(try declaration_ty.copy(sema.arena));
+    try sema.queueFullTypeResolution(declaration_ty);
 
     var decl_vals = std.ArrayList(Value).init(sema.gpa);
     defer decl_vals.deinit();
@@ -17018,12 +16990,12 @@ fn zirBoolBr(
     _ = try lhs_block.addBr(block_inst, lhs_result);
 
     const rhs_result = try sema.resolveBody(rhs_block, body, inst);
-    if (!sema.typeOf(rhs_result).isNoReturn()) {
+    if (!sema.typeOf(rhs_result).isNoReturn(mod)) {
         _ = try rhs_block.addBr(block_inst, rhs_result);
     }
 
     const result = sema.finishCondBr(parent_block, &child_block, &then_block, &else_block, lhs, block_inst);
-    if (!sema.typeOf(rhs_result).isNoReturn()) {
+    if (!sema.typeOf(rhs_result).isNoReturn(mod)) {
         if (try sema.resolveDefinedValue(rhs_block, sema.src, rhs_result)) |rhs_val| {
             if (is_bool_or and rhs_val.toBool(mod)) {
                 return Air.Inst.Ref.bool_true;
@@ -17211,7 +17183,7 @@ fn zirCondbr(
         const err_operand = try sema.resolveInst(err_inst_data.operand);
         const operand_ty = sema.typeOf(err_operand);
         assert(operand_ty.zigTypeTag(mod) == .ErrorUnion);
-        const result_ty = operand_ty.errorUnionSet();
+        const result_ty = operand_ty.errorUnionSet(mod);
         break :blk try sub_block.addTyOp(.unwrap_errunion_err, result_ty, err_operand);
     };
 
@@ -17318,7 +17290,7 @@ fn zirTryPtr(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErr
     const operand_ty = sema.typeOf(operand);
     const ptr_info = operand_ty.ptrInfo(mod);
     const res_ty = try Type.ptr(sema.arena, sema.mod, .{
-        .pointee_type = err_union_ty.errorUnionPayload(),
+        .pointee_type = err_union_ty.errorUnionPayload(mod),
         .@"addrspace" = ptr_info.@"addrspace",
         .mutable = ptr_info.mutable,
         .@"allowzero" = ptr_info.@"allowzero",
@@ -17414,14 +17386,15 @@ fn zirRetErrValue(
     block: *Block,
     inst: Zir.Inst.Index,
 ) CompileError!Zir.Inst.Index {
+    const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
     const err_name = inst_data.get(sema.code);
     const src = inst_data.src();
 
     // Return the error code from the function.
-    const kv = try sema.mod.getErrorValue(err_name);
+    const kv = try mod.getErrorValue(err_name);
     const result_inst = try sema.addConstant(
-        try Type.Tag.error_set_single.create(sema.arena, kv.key),
+        try mod.singleErrorSetType(err_name),
         try Value.Tag.@"error".create(sema.arena, .{ .name = kv.key }),
     );
     return sema.analyzeRet(block, result_inst, src);
@@ -17632,17 +17605,15 @@ fn zirRestoreErrRetIndex(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index)
 
 fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     assert(sema.fn_ret_ty.zigTypeTag(mod) == .ErrorUnion);
 
-    if (sema.fn_ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| {
+    if (mod.typeToInferredErrorSet(sema.fn_ret_ty.errorUnionSet(mod))) |ies| {
         const op_ty = sema.typeOf(uncasted_operand);
         switch (op_ty.zigTypeTag(mod)) {
-            .ErrorSet => {
-                try payload.data.addErrorSet(sema.gpa, op_ty);
-            },
-            .ErrorUnion => {
-                try payload.data.addErrorSet(sema.gpa, op_ty.errorUnionSet());
-            },
+            .ErrorSet => try ies.addErrorSet(op_ty, ip, gpa),
+            .ErrorUnion => try ies.addErrorSet(op_ty.errorUnionSet(mod), ip, gpa),
             else => {},
         }
     }
@@ -18521,7 +18492,7 @@ fn addConstantMaybeRef(
     var anon_decl = try block.startAnonDecl();
     defer anon_decl.deinit();
     const decl = try anon_decl.finish(
-        try ty.copy(anon_decl.arena()),
+        ty,
         try val.copy(anon_decl.arena()),
         0, // default alignment
     );
@@ -18595,7 +18566,7 @@ fn fieldType(
                 continue;
             },
             .ErrorUnion => {
-                cur_ty = cur_ty.errorUnionPayload();
+                cur_ty = cur_ty.errorUnionPayload(mod);
                 continue;
             },
             else => {},
@@ -18641,7 +18612,7 @@ fn zirAlignOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
     const ty = try sema.resolveType(block, operand_src, inst_data.operand);
-    if (ty.isNoReturn()) {
+    if (ty.isNoReturn(mod)) {
         return sema.fail(block, operand_src, "no align available for type '{}'", .{ty.fmt(sema.mod)});
     }
     const val = try ty.lazyAbiAlignment(mod, sema.arena);
@@ -18929,7 +18900,7 @@ fn zirReify(
                     const sentinel_ptr_val = sentinel_val.castTag(.opt_payload).?.data;
                     const ptr_ty = try Type.ptr(sema.arena, mod, .{
                         .@"addrspace" = .generic,
-                        .pointee_type = try elem_ty.copy(sema.arena),
+                        .pointee_type = elem_ty,
                     });
                     const sent_val = (try sema.pointerDeref(block, src, sentinel_ptr_val, ptr_ty)).?;
                     break :s sent_val.toIntern();
@@ -18993,7 +18964,7 @@ fn zirReify(
             const sentinel_val = struct_val[2];
 
             const len = len_val.toUnsignedInt(mod);
-            const child_ty = try child_val.toType().copy(sema.arena);
+            const child_ty = child_val.toType();
             const sentinel = if (sentinel_val.castTag(.opt_payload)) |p| blk: {
                 const ptr_ty = try Type.ptr(sema.arena, mod, .{
                     .@"addrspace" = .generic,
@@ -19011,7 +18982,7 @@ fn zirReify(
             // child: type,
             const child_val = struct_val[0];
 
-            const child_ty = try child_val.toType().copy(sema.arena);
+            const child_ty = child_val.toType();
 
             const ty = try Type.optional(sema.arena, child_ty, mod);
             return sema.addType(ty);
@@ -19024,17 +18995,14 @@ fn zirReify(
             // payload: type,
             const payload_val = struct_val[1];
 
-            const error_set_ty = try error_set_val.toType().copy(sema.arena);
-            const payload_ty = try payload_val.toType().copy(sema.arena);
+            const error_set_ty = error_set_val.toType();
+            const payload_ty = payload_val.toType();
 
             if (error_set_ty.zigTypeTag(mod) != .ErrorSet) {
                 return sema.fail(block, src, "Type.ErrorUnion.error_set must be an error set type", .{});
             }
 
-            const ty = try Type.Tag.error_union.create(sema.arena, .{
-                .error_set = error_set_ty,
-                .payload = payload_ty,
-            });
+            const ty = try mod.errorUnionType(error_set_ty, payload_ty);
             return sema.addType(ty);
         },
         .ErrorSet => {
@@ -19043,27 +19011,23 @@ fn zirReify(
             const slice_val = payload_val.castTag(.slice).?.data;
 
             const len = try sema.usizeCast(block, src, slice_val.len.toUnsignedInt(mod));
-            var names: Module.ErrorSet.NameMap = .{};
+            var names: Module.Fn.InferredErrorSet.NameMap = .{};
             try names.ensureUnusedCapacity(sema.arena, len);
-            var i: usize = 0;
-            while (i < len) : (i += 1) {
+            for (0..len) |i| {
                 const elem_val = try slice_val.ptr.elemValue(mod, i);
                 const struct_val = elem_val.castTag(.aggregate).?.data;
                 // TODO use reflection instead of magic numbers here
                 // error_set: type,
                 const name_val = struct_val[0];
                 const name_str = try name_val.toAllocatedBytes(Type.const_slice_u8, sema.arena, mod);
-
-                const kv = try mod.getErrorValue(name_str);
-                const gop = names.getOrPutAssumeCapacity(kv.key);
+                const name_ip = try mod.intern_pool.getOrPutString(gpa, name_str);
+                const gop = names.getOrPutAssumeCapacity(name_ip);
                 if (gop.found_existing) {
                     return sema.fail(block, src, "duplicate error '{s}'", .{name_str});
                 }
             }
 
-            // names must be sorted
-            Module.ErrorSet.sortNames(&names);
-            const ty = try Type.Tag.error_set_merged.create(sema.arena, names);
+            const ty = try mod.errorSetFromUnsortedNames(names.keys());
             return sema.addType(ty);
         },
         .Struct => {
@@ -19378,7 +19342,7 @@ fn zirReify(
                     return sema.fail(block, src, "duplicate union field {s}", .{field_name});
                 }
 
-                const field_ty = try type_val.toType().copy(new_decl_arena_allocator);
+                const field_ty = type_val.toType();
                 gop.value_ptr.* = .{
                     .ty = field_ty,
                     .abi_align = @intCast(u32, (try alignment_val.getUnsignedIntAdvanced(mod, sema)).?),
@@ -19673,7 +19637,7 @@ fn reifyStruct(
             return sema.fail(block, src, "comptime field without default initialization value", .{});
         }
 
-        const field_ty = try type_val.toType().copy(new_decl_arena_allocator);
+        const field_ty = type_val.toType();
         gop.value_ptr.* = .{
             .ty = field_ty,
             .abi_align = abi_align,
@@ -19751,7 +19715,7 @@ fn reifyStruct(
         if (backing_int_val.optionalValue(mod)) |payload| {
             const backing_int_ty = payload.toType();
             try sema.checkBackingIntType(block, src, backing_int_ty, fields_bit_sum);
-            struct_obj.backing_int_ty = try backing_int_ty.copy(new_decl_arena_allocator);
+            struct_obj.backing_int_ty = backing_int_ty;
         } else {
             struct_obj.backing_int_ty = try mod.intType(.unsigned, @intCast(u16, fields_bit_sum));
         }
@@ -20035,6 +19999,8 @@ fn zirIntToPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 }
 
 fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
+    const mod = sema.mod;
+    const ip = &mod.intern_pool;
     const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
     const src = LazySrcLoc.nodeOffset(extra.node);
     const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
@@ -20050,22 +20016,27 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
 
     if (disjoint: {
         // Try avoiding resolving inferred error sets if we can
-        if (!dest_ty.isAnyError() and dest_ty.errorSetNames().len == 0) break :disjoint true;
-        if (!operand_ty.isAnyError() and operand_ty.errorSetNames().len == 0) break :disjoint true;
-        if (dest_ty.isAnyError()) break :disjoint false;
-        if (operand_ty.isAnyError()) break :disjoint false;
-        for (dest_ty.errorSetNames()) |dest_err_name|
-            if (operand_ty.errorSetHasField(dest_err_name))
+        if (!dest_ty.isAnyError(mod) and dest_ty.errorSetNames(mod).len == 0) break :disjoint true;
+        if (!operand_ty.isAnyError(mod) and operand_ty.errorSetNames(mod).len == 0) break :disjoint true;
+        if (dest_ty.isAnyError(mod)) break :disjoint false;
+        if (operand_ty.isAnyError(mod)) break :disjoint false;
+        for (dest_ty.errorSetNames(mod)) |dest_err_name| {
+            if (Type.errorSetHasFieldIp(ip, operand_ty.toIntern(), dest_err_name))
                 break :disjoint false;
+        }
 
-        if (dest_ty.tag() != .error_set_inferred and operand_ty.tag() != .error_set_inferred)
+        if (!ip.isInferredErrorSetType(dest_ty.ip_index) and
+            !ip.isInferredErrorSetType(operand_ty.ip_index))
+        {
             break :disjoint true;
+        }
 
         try sema.resolveInferredErrorSetTy(block, dest_ty_src, dest_ty);
         try sema.resolveInferredErrorSetTy(block, operand_src, operand_ty);
-        for (dest_ty.errorSetNames()) |dest_err_name|
-            if (operand_ty.errorSetHasField(dest_err_name))
+        for (dest_ty.errorSetNames(mod)) |dest_err_name| {
+            if (Type.errorSetHasFieldIp(ip, operand_ty.toIntern(), dest_err_name))
                 break :disjoint false;
+        }
 
         break :disjoint true;
     }) {
@@ -20085,9 +20056,9 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
     }
 
     if (maybe_operand_val) |val| {
-        if (!dest_ty.isAnyError()) {
+        if (!dest_ty.isAnyError(mod)) {
             const error_name = val.castTag(.@"error").?.data.name;
-            if (!dest_ty.errorSetHasField(error_name)) {
+            if (!dest_ty.errorSetHasField(error_name, mod)) {
                 const msg = msg: {
                     const msg = try sema.errMsg(
                         block,
@@ -20107,7 +20078,7 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
     }
 
     try sema.requireRuntimeBlock(block, src, operand_src);
-    if (block.wantSafety() and !dest_ty.isAnyError() and sema.mod.backendSupportsFeature(.error_set_has_value)) {
+    if (block.wantSafety() and !dest_ty.isAnyError(mod) and sema.mod.backendSupportsFeature(.error_set_has_value)) {
         const err_int_inst = try block.addBitCast(Type.err_int, operand);
         const ok = try block.addTyOp(.error_set_has_value, dest_ty, err_int_inst);
         try sema.addSafetyCheck(block, ok, .invalid_error_code);
@@ -22862,7 +22833,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         extra_index += body.len;
 
         const val = try sema.resolveGenericBody(block, ret_src, body, inst, Type.type, "return type must be comptime-known");
-        const ty = try val.toType().copy(sema.arena);
+        const ty = val.toType();
         break :blk ty;
     } else if (extra.data.bits.has_ret_ty_ref) blk: {
         const ret_ty_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
@@ -22873,7 +22844,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
             },
             else => |e| return e,
         };
-        const ty = try ret_ty_tv.val.toType().copy(sema.arena);
+        const ty = ret_ty_tv.val.toType();
         break :blk ty;
     } else Type.void;
 
@@ -23360,7 +23331,7 @@ fn validateRunTimeType(
         },
         .Array, .Vector => ty = ty.childType(mod),
 
-        .ErrorUnion => ty = ty.errorUnionPayload(),
+        .ErrorUnion => ty = ty.errorUnionPayload(mod),
 
         .Struct, .Union => {
             const resolved_ty = try sema.resolveTypeFields(ty);
@@ -23452,7 +23423,7 @@ fn explainWhyTypeIsComptimeInner(
             try sema.explainWhyTypeIsComptimeInner(msg, src_loc, ty.optionalChild(mod), type_set);
         },
         .ErrorUnion => {
-            try sema.explainWhyTypeIsComptimeInner(msg, src_loc, ty.errorUnionPayload(), type_set);
+            try sema.explainWhyTypeIsComptimeInner(msg, src_loc, ty.errorUnionPayload(mod), type_set);
         },
 
         .Struct => {
@@ -24065,7 +24036,9 @@ fn fieldVal(
     // in `fieldPtr`. This function takes a value and returns a value.
 
     const mod = sema.mod;
+    const gpa = sema.gpa;
     const arena = sema.arena;
+    const ip = &mod.intern_pool;
     const object_src = src; // TODO better source location
     const object_ty = sema.typeOf(object);
 
@@ -24147,27 +24120,33 @@ fn fieldVal(
 
             switch (try child_type.zigTypeTagOrPoison(mod)) {
                 .ErrorSet => {
-                    const name: []const u8 = if (child_type.castTag(.error_set)) |payload| blk: {
-                        if (payload.data.names.getEntry(field_name)) |entry| {
-                            break :blk entry.key_ptr.*;
-                        }
-                        const msg = msg: {
-                            const msg = try sema.errMsg(block, src, "no error named '{s}' in '{}'", .{
-                                field_name, child_type.fmt(mod),
-                            });
-                            errdefer msg.destroy(sema.gpa);
-                            try sema.addDeclaredHereNote(msg, child_type);
-                            break :msg msg;
-                        };
-                        return sema.failWithOwnedErrorMsg(msg);
-                    } else (try mod.getErrorValue(field_name)).key;
+                    const name = try ip.getOrPutString(gpa, field_name);
+                    switch (ip.indexToKey(child_type.ip_index)) {
+                        .error_set_type => |error_set_type| blk: {
+                            if (error_set_type.nameIndex(ip, name) != null) break :blk;
+                            const msg = msg: {
+                                const msg = try sema.errMsg(block, src, "no error named '{s}' in '{}'", .{
+                                    field_name, child_type.fmt(mod),
+                                });
+                                errdefer msg.destroy(sema.gpa);
+                                try sema.addDeclaredHereNote(msg, child_type);
+                                break :msg msg;
+                            };
+                            return sema.failWithOwnedErrorMsg(msg);
+                        },
+                        .inferred_error_set_type => {
+                            return sema.fail(block, src, "TODO handle inferred error sets here", .{});
+                        },
+                        .simple_type => |t| assert(t == .anyerror),
+                        else => unreachable,
+                    }
 
                     return sema.addConstant(
-                        if (!child_type.isAnyError())
-                            try child_type.copy(arena)
+                        if (!child_type.isAnyError(mod))
+                            child_type
                         else
-                            try Type.Tag.error_set_single.create(arena, name),
-                        try Value.Tag.@"error".create(arena, .{ .name = name }),
+                            try mod.singleErrorSetTypeNts(name),
+                        try Value.Tag.@"error".create(arena, .{ .name = ip.stringToSlice(name) }),
                     );
                 },
                 .Union => {
@@ -24252,6 +24231,8 @@ fn fieldPtr(
     // in `fieldVal`. This function takes a pointer and returns a pointer.
 
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const object_ptr_src = src; // TODO better source location
     const object_ptr_ty = sema.typeOf(object_ptr);
     const object_ty = switch (object_ptr_ty.zigTypeTag(mod)) {
@@ -24362,24 +24343,33 @@ fn fieldPtr(
 
             switch (child_type.zigTypeTag(mod)) {
                 .ErrorSet => {
-                    // TODO resolve inferred error sets
-                    const name: []const u8 = if (child_type.castTag(.error_set)) |payload| blk: {
-                        if (payload.data.names.getEntry(field_name)) |entry| {
-                            break :blk entry.key_ptr.*;
-                        }
-                        return sema.fail(block, src, "no error named '{s}' in '{}'", .{
-                            field_name, child_type.fmt(mod),
-                        });
-                    } else (try mod.getErrorValue(field_name)).key;
+                    const name = try ip.getOrPutString(gpa, field_name);
+                    switch (ip.indexToKey(child_type.ip_index)) {
+                        .error_set_type => |error_set_type| blk: {
+                            if (error_set_type.nameIndex(ip, name) != null) {
+                                break :blk;
+                            }
+                            return sema.fail(block, src, "no error named '{s}' in '{}'", .{
+                                field_name, child_type.fmt(mod),
+                            });
+                        },
+                        .inferred_error_set_type => {
+                            return sema.fail(block, src, "TODO handle inferred error sets here", .{});
+                        },
+                        .simple_type => |t| assert(t == .anyerror),
+                        else => unreachable,
+                    }
 
                     var anon_decl = try block.startAnonDecl();
                     defer anon_decl.deinit();
                     return sema.analyzeDeclRef(try anon_decl.finish(
-                        if (!child_type.isAnyError())
-                            try child_type.copy(anon_decl.arena())
+                        if (!child_type.isAnyError(mod))
+                            child_type
                         else
-                            try Type.Tag.error_set_single.create(anon_decl.arena(), name),
-                        try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }),
+                            try mod.singleErrorSetTypeNts(name),
+                        try Value.Tag.@"error".create(anon_decl.arena(), .{
+                            .name = ip.stringToSlice(name),
+                        }),
                         0, // default alignment
                     ));
                 },
@@ -24589,7 +24579,7 @@ fn fieldCallBind(
                                 } };
                             }
                         } else if (first_param_type.zigTypeTag(mod) == .ErrorUnion and
-                            first_param_type.errorUnionPayload().eql(concrete_ty, mod))
+                            first_param_type.errorUnionPayload(mod).eql(concrete_ty, mod))
                         {
                             const deref = try sema.analyzeLoad(block, src, object_ptr, src);
                             return .{ .method = .{
@@ -24832,7 +24822,7 @@ fn structFieldPtrByIndex(
 
     if (field.is_comptime) {
         const val = try Value.Tag.comptime_field_ptr.create(sema.arena, .{
-            .field_ty = try field.ty.copy(sema.arena),
+            .field_ty = field.ty,
             .field_val = try field.default_val.copy(sema.arena),
         });
         return sema.addConstant(ptr_field_ty, val);
@@ -26227,7 +26217,7 @@ fn coerceExtra(
                         .none => switch (inst_val.tag()) {
                             .eu_payload => {
                                 const payload = try sema.addConstant(
-                                    inst_ty.errorUnionPayload(),
+                                    inst_ty.errorUnionPayload(mod),
                                     inst_val.castTag(.eu_payload).?.data,
                                 );
                                 return sema.wrapErrorUnionPayload(block, dest_ty, payload, inst_src) catch |err| switch (err) {
@@ -26240,7 +26230,7 @@ fn coerceExtra(
                         else => {},
                     }
                     const error_set = try sema.addConstant(
-                        inst_ty.errorUnionSet(),
+                        inst_ty.errorUnionSet(mod),
                         inst_val,
                     );
                     return sema.wrapErrorUnionSet(block, dest_ty, error_set, inst_src);
@@ -26342,7 +26332,7 @@ fn coerceExtra(
 
         // E!T to T
         if (inst_ty.zigTypeTag(mod) == .ErrorUnion and
-            (try sema.coerceInMemoryAllowed(block, inst_ty.errorUnionPayload(), dest_ty, false, target, dest_ty_src, inst_src)) == .ok)
+            (try sema.coerceInMemoryAllowed(block, inst_ty.errorUnionPayload(mod), dest_ty, false, target, dest_ty_src, inst_src)) == .ok)
         {
             try sema.errNote(block, inst_src, msg, "cannot convert error union to payload type", .{});
             try sema.errNote(block, inst_src, msg, "consider using 'try', 'catch', or 'if'", .{});
@@ -26393,7 +26383,7 @@ const InMemoryCoercionResult = union(enum) {
     optional_shape: Pair,
     optional_child: PairAndChild,
     from_anyerror,
-    missing_error: []const []const u8,
+    missing_error: []const InternPool.NullTerminatedString,
     /// true if wanted is var args
     fn_var_args: bool,
     /// true if wanted is generic
@@ -26567,7 +26557,8 @@ const InMemoryCoercionResult = union(enum) {
                 break;
             },
             .missing_error => |missing_errors| {
-                for (missing_errors) |err| {
+                for (missing_errors) |err_index| {
+                    const err = mod.intern_pool.stringToSlice(err_index);
                     try sema.errNote(block, src, msg, "'error.{s}' not a member of destination error set", .{err});
                 }
                 break;
@@ -26813,8 +26804,8 @@ fn coerceInMemoryAllowed(
 
     // Error Unions
     if (dest_tag == .ErrorUnion and src_tag == .ErrorUnion) {
-        const dest_payload = dest_ty.errorUnionPayload();
-        const src_payload = src_ty.errorUnionPayload();
+        const dest_payload = dest_ty.errorUnionPayload(mod);
+        const src_payload = src_ty.errorUnionPayload(mod);
         const child = try sema.coerceInMemoryAllowed(block, dest_payload, src_payload, dest_is_mut, target, dest_src, src_src);
         if (child != .ok) {
             return InMemoryCoercionResult{ .error_union_payload = .{
@@ -26823,7 +26814,7 @@ fn coerceInMemoryAllowed(
                 .wanted = dest_payload,
             } };
         }
-        return try sema.coerceInMemoryAllowed(block, dest_ty.errorUnionSet(), src_ty.errorUnionSet(), dest_is_mut, target, dest_src, src_src);
+        return try sema.coerceInMemoryAllowed(block, dest_ty.errorUnionSet(mod), src_ty.errorUnionSet(mod), dest_is_mut, target, dest_src, src_src);
     }
 
     // Error Sets
@@ -26903,8 +26894,8 @@ fn coerceInMemoryAllowed(
         if (child != .ok) {
             return InMemoryCoercionResult{ .optional_child = .{
                 .child = try child.dupe(sema.arena),
-                .actual = try src_child_type.copy(sema.arena),
-                .wanted = try dest_child_type.copy(sema.arena),
+                .actual = src_child_type,
+                .wanted = dest_child_type,
             } };
         }
 
@@ -26926,133 +26917,100 @@ fn coerceInMemoryAllowedErrorSets(
     src_src: LazySrcLoc,
 ) !InMemoryCoercionResult {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
     // Coercion to `anyerror`. Note that this check can return false negatives
     // in case the error sets did not get resolved.
-    if (dest_ty.isAnyError()) {
+    if (dest_ty.isAnyError(mod)) {
         return .ok;
     }
 
-    if (dest_ty.castTag(.error_set_inferred)) |dst_payload| {
-        const dst_ies = dst_payload.data;
+    if (mod.typeToInferredErrorSetIndex(dest_ty).unwrap()) |dst_ies_index| {
+        const dst_ies = mod.inferredErrorSetPtr(dst_ies_index);
         // We will make an effort to return `ok` without resolving either error set, to
         // avoid unnecessary "unable to resolve error set" dependency loop errors.
         switch (src_ty.ip_index) {
-            .none => switch (src_ty.tag()) {
-                .error_set_inferred => {
+            .anyerror_type => {},
+            else => switch (ip.indexToKey(src_ty.ip_index)) {
+                .inferred_error_set_type => |src_index| {
                     // If both are inferred error sets of functions, and
                     // the dest includes the source function, the coercion is OK.
                     // This check is important because it works without forcing a full resolution
                     // of inferred error sets.
-                    const src_ies = src_ty.castTag(.error_set_inferred).?.data;
-
-                    if (dst_ies.inferred_error_sets.contains(src_ies)) {
+                    if (dst_ies.inferred_error_sets.contains(src_index)) {
                         return .ok;
                     }
                 },
-                .error_set_single => {
-                    const name = src_ty.castTag(.error_set_single).?.data;
-                    if (dst_ies.errors.contains(name)) return .ok;
-                },
-                .error_set_merged => {
-                    const names = src_ty.castTag(.error_set_merged).?.data.keys();
-                    for (names) |name| {
-                        if (!dst_ies.errors.contains(name)) break;
-                    } else return .ok;
-                },
-                .error_set => {
-                    const names = src_ty.castTag(.error_set).?.data.names.keys();
-                    for (names) |name| {
+                .error_set_type => |error_set_type| {
+                    for (error_set_type.names) |name| {
                         if (!dst_ies.errors.contains(name)) break;
                     } else return .ok;
                 },
                 else => unreachable,
             },
-            .anyerror_type => {},
-            else => switch (mod.intern_pool.indexToKey(src_ty.ip_index)) {
-                else => @panic("TODO"),
-            },
         }
 
         if (dst_ies.func == sema.owner_func) {
             // We are trying to coerce an error set to the current function's
             // inferred error set.
-            try dst_ies.addErrorSet(sema.gpa, src_ty);
+            try dst_ies.addErrorSet(src_ty, ip, gpa);
             return .ok;
         }
 
-        try sema.resolveInferredErrorSet(block, dest_src, dst_payload.data);
+        try sema.resolveInferredErrorSet(block, dest_src, dst_ies_index);
         // isAnyError might have changed from a false negative to a true positive after resolution.
-        if (dest_ty.isAnyError()) {
+        if (dest_ty.isAnyError(mod)) {
             return .ok;
         }
     }
 
-    var missing_error_buf = std.ArrayList([]const u8).init(sema.gpa);
+    var missing_error_buf = std.ArrayList(InternPool.NullTerminatedString).init(gpa);
     defer missing_error_buf.deinit();
 
     switch (src_ty.ip_index) {
-        .none => switch (src_ty.tag()) {
-            .error_set_inferred => {
-                const src_data = src_ty.castTag(.error_set_inferred).?.data;
+        .anyerror_type => switch (ip.indexToKey(dest_ty.ip_index)) {
+            .inferred_error_set_type => unreachable, // Caught by dest_ty.isAnyError(mod) above.
+            .simple_type => unreachable, // filtered out above
+            .error_set_type => return .from_anyerror,
+            else => unreachable,
+        },
+
+        else => switch (ip.indexToKey(src_ty.ip_index)) {
+            .inferred_error_set_type => |src_index| {
+                const src_data = mod.inferredErrorSetPtr(src_index);
 
-                try sema.resolveInferredErrorSet(block, src_src, src_data);
+                try sema.resolveInferredErrorSet(block, src_src, src_index);
                 // src anyerror status might have changed after the resolution.
-                if (src_ty.isAnyError()) {
-                    // dest_ty.isAnyError() == true is already checked for at this point.
+                if (src_ty.isAnyError(mod)) {
+                    // dest_ty.isAnyError(mod) == true is already checked for at this point.
                     return .from_anyerror;
                 }
 
                 for (src_data.errors.keys()) |key| {
-                    if (!dest_ty.errorSetHasField(key)) {
+                    if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), key)) {
                         try missing_error_buf.append(key);
                     }
                 }
 
                 if (missing_error_buf.items.len != 0) {
                     return InMemoryCoercionResult{
-                        .missing_error = try sema.arena.dupe([]const u8, missing_error_buf.items),
-                    };
-                }
-
-                return .ok;
-            },
-            .error_set_single => {
-                const name = src_ty.castTag(.error_set_single).?.data;
-                if (dest_ty.errorSetHasField(name)) {
-                    return .ok;
-                }
-                const list = try sema.arena.alloc([]const u8, 1);
-                list[0] = name;
-                return InMemoryCoercionResult{ .missing_error = list };
-            },
-            .error_set_merged => {
-                const names = src_ty.castTag(.error_set_merged).?.data.keys();
-                for (names) |name| {
-                    if (!dest_ty.errorSetHasField(name)) {
-                        try missing_error_buf.append(name);
-                    }
-                }
-
-                if (missing_error_buf.items.len != 0) {
-                    return InMemoryCoercionResult{
-                        .missing_error = try sema.arena.dupe([]const u8, missing_error_buf.items),
+                        .missing_error = try sema.arena.dupe(InternPool.NullTerminatedString, missing_error_buf.items),
                     };
                 }
 
                 return .ok;
             },
-            .error_set => {
-                const names = src_ty.castTag(.error_set).?.data.names.keys();
-                for (names) |name| {
-                    if (!dest_ty.errorSetHasField(name)) {
+            .error_set_type => |error_set_type| {
+                for (error_set_type.names) |name| {
+                    if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), name)) {
                         try missing_error_buf.append(name);
                     }
                 }
 
                 if (missing_error_buf.items.len != 0) {
                     return InMemoryCoercionResult{
-                        .missing_error = try sema.arena.dupe([]const u8, missing_error_buf.items),
+                        .missing_error = try sema.arena.dupe(InternPool.NullTerminatedString, missing_error_buf.items),
                     };
                 }
 
@@ -27060,18 +27018,6 @@ fn coerceInMemoryAllowedErrorSets(
             },
             else => unreachable,
         },
-
-        .anyerror_type => switch (dest_ty.ip_index) {
-            .none => switch (dest_ty.tag()) {
-                .error_set_inferred => unreachable, // Caught by dest_ty.isAnyError() above.
-                .error_set_single, .error_set_merged, .error_set => return .from_anyerror,
-                else => unreachable,
-            },
-            .anyerror_type => unreachable, // Filtered out above.
-            else => @panic("TODO"),
-        },
-
-        else => @panic("TODO"),
     }
 
     unreachable;
@@ -28029,7 +27975,7 @@ fn beginComptimePtrMutation(
             var parent = try sema.beginComptimePtrMutation(block, src, eu_ptr.container_ptr, eu_ptr.container_ty);
             switch (parent.pointee) {
                 .direct => |val_ptr| {
-                    const payload_ty = parent.ty.errorUnionPayload();
+                    const payload_ty = parent.ty.errorUnionPayload(mod);
                     if (val_ptr.ip_index == .none and val_ptr.tag() == .eu_payload) {
                         return ComptimePtrMutationKit{
                             .decl_ref_mut = parent.decl_ref_mut,
@@ -28402,7 +28348,7 @@ fn beginComptimePtrLoad(
             => blk: {
                 const payload_ptr = ptr_val.cast(Value.Payload.PayloadPtr).?.data;
                 const payload_ty = switch (ptr_val.tag()) {
-                    .eu_payload_ptr => payload_ptr.container_ty.errorUnionPayload(),
+                    .eu_payload_ptr => payload_ptr.container_ty.errorUnionPayload(mod),
                     .opt_payload_ptr => payload_ptr.container_ty.optionalChild(mod),
                     else => unreachable,
                 };
@@ -29301,7 +29247,7 @@ fn refValue(sema: *Sema, block: *Block, ty: Type, val: Value) !Value {
     var anon_decl = try block.startAnonDecl();
     defer anon_decl.deinit();
     const decl = try anon_decl.finish(
-        try ty.copy(anon_decl.arena()),
+        ty,
         try val.copy(anon_decl.arena()),
         0, // default alignment
     );
@@ -29387,7 +29333,7 @@ fn analyzeRef(
         var anon_decl = try block.startAnonDecl();
         defer anon_decl.deinit();
         return sema.analyzeDeclRef(try anon_decl.finish(
-            try operand_ty.copy(anon_decl.arena()),
+            operand_ty,
             try val.copy(anon_decl.arena()),
             0, // default alignment
         ));
@@ -29555,7 +29501,7 @@ fn analyzeIsNonErrComptimeOnly(
     if (ot == .ErrorSet) return Air.Inst.Ref.bool_false;
     assert(ot == .ErrorUnion);
 
-    const payload_ty = operand_ty.errorUnionPayload();
+    const payload_ty = operand_ty.errorUnionPayload(mod);
     if (payload_ty.zigTypeTag(mod) == .NoReturn) {
         return Air.Inst.Ref.bool_false;
     }
@@ -29577,23 +29523,28 @@ fn analyzeIsNonErrComptimeOnly(
 
     // exception if the error union error set is known to be empty,
     // we allow the comparison but always make it comptime-known.
-    const set_ty = operand_ty.errorUnionSet();
+    const set_ty = operand_ty.errorUnionSet(mod);
     switch (set_ty.ip_index) {
-        .none => switch (set_ty.tag()) {
-            .error_set_inferred => blk: {
+        .anyerror_type => {},
+        else => switch (mod.intern_pool.indexToKey(set_ty.ip_index)) {
+            .error_set_type => |error_set_type| {
+                if (error_set_type.names.len == 0) return Air.Inst.Ref.bool_true;
+            },
+            .inferred_error_set_type => |ies_index| blk: {
                 // If the error set is empty, we must return a comptime true or false.
                 // However we want to avoid unnecessarily resolving an inferred error set
                 // in case it is already non-empty.
-                const ies = set_ty.castTag(.error_set_inferred).?.data;
+                const ies = mod.inferredErrorSetPtr(ies_index);
                 if (ies.is_anyerror) break :blk;
                 if (ies.errors.count() != 0) break :blk;
                 if (maybe_operand_val == null) {
                     // Try to avoid resolving inferred error set if possible.
                     if (ies.errors.count() != 0) break :blk;
                     if (ies.is_anyerror) break :blk;
-                    for (ies.inferred_error_sets.keys()) |other_ies| {
-                        if (ies == other_ies) continue;
-                        try sema.resolveInferredErrorSet(block, src, other_ies);
+                    for (ies.inferred_error_sets.keys()) |other_ies_index| {
+                        if (ies_index == other_ies_index) continue;
+                        try sema.resolveInferredErrorSet(block, src, other_ies_index);
+                        const other_ies = mod.inferredErrorSetPtr(other_ies_index);
                         if (other_ies.is_anyerror) {
                             ies.is_anyerror = true;
                             ies.is_resolved = true;
@@ -29608,18 +29559,12 @@ fn analyzeIsNonErrComptimeOnly(
                         // so far with this type can't contain errors either.
                         return Air.Inst.Ref.bool_true;
                     }
-                    try sema.resolveInferredErrorSet(block, src, ies);
+                    try sema.resolveInferredErrorSet(block, src, ies_index);
                     if (ies.is_anyerror) break :blk;
                     if (ies.errors.count() == 0) return Air.Inst.Ref.bool_true;
                 }
             },
-            else => if (set_ty.errorSetNames().len == 0) return Air.Inst.Ref.bool_true,
-        },
-
-        .anyerror_type => {},
-
-        else => switch (mod.intern_pool.indexToKey(set_ty.ip_index)) {
-            else => @panic("TODO"),
+            else => unreachable,
         },
     }
 
@@ -30516,7 +30461,8 @@ fn wrapErrorUnionPayload(
     inst: Air.Inst.Ref,
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
-    const dest_payload_ty = dest_ty.errorUnionPayload();
+    const mod = sema.mod;
+    const dest_payload_ty = dest_ty.errorUnionPayload(mod);
     const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, .{ .report_err = false });
     if (try sema.resolveMaybeUndefVal(coerced)) |val| {
         return sema.addConstant(dest_ty, try Value.Tag.eu_payload.create(sema.arena, val));
@@ -30533,51 +30479,41 @@ fn wrapErrorUnionSet(
     inst: Air.Inst.Ref,
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
+    const mod = sema.mod;
+    const ip = &mod.intern_pool;
     const inst_ty = sema.typeOf(inst);
-    const dest_err_set_ty = dest_ty.errorUnionSet();
+    const dest_err_set_ty = dest_ty.errorUnionSet(mod);
     if (try sema.resolveMaybeUndefVal(inst)) |val| {
         switch (dest_err_set_ty.ip_index) {
             .anyerror_type => {},
-
-            .none => switch (dest_err_set_ty.tag()) {
-                .error_set_single => ok: {
-                    const expected_name = val.castTag(.@"error").?.data.name;
-                    const n = dest_err_set_ty.castTag(.error_set_single).?.data;
-                    if (mem.eql(u8, expected_name, n)) break :ok;
-                    return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
-                },
-                .error_set => {
+            else => switch (ip.indexToKey(dest_err_set_ty.ip_index)) {
+                .error_set_type => |error_set_type| ok: {
                     const expected_name = val.castTag(.@"error").?.data.name;
-                    const error_set = dest_err_set_ty.castTag(.error_set).?.data;
-                    if (!error_set.names.contains(expected_name)) {
-                        return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
+                    if (ip.getString(expected_name).unwrap()) |expected_name_interned| {
+                        if (error_set_type.nameIndex(ip, expected_name_interned) != null)
+                            break :ok;
                     }
+                    return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
                 },
-                .error_set_inferred => ok: {
+                .inferred_error_set_type => |ies_index| ok: {
+                    const ies = mod.inferredErrorSetPtr(ies_index);
                     const expected_name = val.castTag(.@"error").?.data.name;
-                    const ies = dest_err_set_ty.castTag(.error_set_inferred).?.data;
 
                     // We carefully do this in an order that avoids unnecessarily
                     // resolving the destination error set type.
                     if (ies.is_anyerror) break :ok;
-                    if (ies.errors.contains(expected_name)) break :ok;
+
+                    if (ip.getString(expected_name).unwrap()) |expected_name_interned| {
+                        if (ies.errors.contains(expected_name_interned)) break :ok;
+                    }
                     if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) {
                         break :ok;
                     }
 
                     return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
                 },
-                .error_set_merged => {
-                    const expected_name = val.castTag(.@"error").?.data.name;
-                    const error_set = dest_err_set_ty.castTag(.error_set_merged).?.data;
-                    if (!error_set.contains(expected_name)) {
-                        return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
-                    }
-                },
                 else => unreachable,
             },
-
-            else => @panic("TODO"),
         }
         return sema.addConstant(dest_ty, val);
     }
@@ -30743,11 +30679,11 @@ fn resolvePeerTypes(
                         continue;
                     }
 
-                    err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty);
+                    err_set_ty = try sema.errorSetMerge(chosen_set_ty, candidate_ty);
                     continue;
                 },
                 .ErrorUnion => {
-                    const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet();
+                    const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet(mod);
 
                     if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) {
                         continue;
@@ -30757,7 +30693,7 @@ fn resolvePeerTypes(
                         continue;
                     }
 
-                    err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty);
+                    err_set_ty = try sema.errorSetMerge(chosen_set_ty, candidate_ty);
                     continue;
                 },
                 else => {
@@ -30770,7 +30706,7 @@ fn resolvePeerTypes(
                             continue;
                         }
 
-                        err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty);
+                        err_set_ty = try sema.errorSetMerge(chosen_set_ty, candidate_ty);
                         continue;
                     } else {
                         err_set_ty = candidate_ty;
@@ -30781,14 +30717,14 @@ fn resolvePeerTypes(
             .ErrorUnion => switch (chosen_ty_tag) {
                 .ErrorSet => {
                     const chosen_set_ty = err_set_ty orelse chosen_ty;
-                    const candidate_set_ty = candidate_ty.errorUnionSet();
+                    const candidate_set_ty = candidate_ty.errorUnionSet(mod);
 
                     if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) {
                         err_set_ty = chosen_set_ty;
                     } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) {
                         err_set_ty = null;
                     } else {
-                        err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty);
+                        err_set_ty = try sema.errorSetMerge(chosen_set_ty, candidate_set_ty);
                     }
                     chosen = candidate;
                     chosen_i = candidate_i + 1;
@@ -30796,8 +30732,8 @@ fn resolvePeerTypes(
                 },
 
                 .ErrorUnion => {
-                    const chosen_payload_ty = chosen_ty.errorUnionPayload();
-                    const candidate_payload_ty = candidate_ty.errorUnionPayload();
+                    const chosen_payload_ty = chosen_ty.errorUnionPayload(mod);
+                    const candidate_payload_ty = candidate_ty.errorUnionPayload(mod);
 
                     const coerce_chosen = (try sema.coerceInMemoryAllowed(block, chosen_payload_ty, candidate_payload_ty, false, target, src, src)) == .ok;
                     const coerce_candidate = (try sema.coerceInMemoryAllowed(block, candidate_payload_ty, chosen_payload_ty, false, target, src, src)) == .ok;
@@ -30811,15 +30747,15 @@ fn resolvePeerTypes(
                             chosen_i = candidate_i + 1;
                         }
 
-                        const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet();
-                        const candidate_set_ty = candidate_ty.errorUnionSet();
+                        const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet(mod);
+                        const candidate_set_ty = candidate_ty.errorUnionSet(mod);
 
                         if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) {
                             err_set_ty = chosen_set_ty;
                         } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) {
                             err_set_ty = candidate_set_ty;
                         } else {
-                            err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty);
+                            err_set_ty = try sema.errorSetMerge(chosen_set_ty, candidate_set_ty);
                         }
                         continue;
                     }
@@ -30827,13 +30763,13 @@ fn resolvePeerTypes(
 
                 else => {
                     if (err_set_ty) |chosen_set_ty| {
-                        const candidate_set_ty = candidate_ty.errorUnionSet();
+                        const candidate_set_ty = candidate_ty.errorUnionSet(mod);
                         if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) {
                             err_set_ty = chosen_set_ty;
                         } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) {
                             err_set_ty = null;
                         } else {
-                            err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty);
+                            err_set_ty = try sema.errorSetMerge(chosen_set_ty, candidate_set_ty);
                         }
                     }
                     seen_const = seen_const or chosen_ty.isConstPtr(mod);
@@ -30963,7 +30899,7 @@ fn resolvePeerTypes(
                         }
                     },
                     .ErrorUnion => {
-                        const chosen_ptr_ty = chosen_ty.errorUnionPayload();
+                        const chosen_ptr_ty = chosen_ty.errorUnionPayload(mod);
                         if (chosen_ptr_ty.zigTypeTag(mod) == .Pointer) {
                             const chosen_info = chosen_ptr_ty.ptrInfo(mod);
 
@@ -31073,7 +31009,7 @@ fn resolvePeerTypes(
                 }
             },
             .ErrorUnion => {
-                const payload_ty = chosen_ty.errorUnionPayload();
+                const payload_ty = chosen_ty.errorUnionPayload(mod);
                 if ((try sema.coerceInMemoryAllowed(block, payload_ty, candidate_ty, false, target, src, src)) == .ok) {
                     continue;
                 }
@@ -31090,7 +31026,7 @@ fn resolvePeerTypes(
                         continue;
                     }
 
-                    err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, chosen_ty);
+                    err_set_ty = try sema.errorSetMerge(chosen_set_ty, chosen_ty);
                     continue;
                 } else {
                     err_set_ty = chosen_ty;
@@ -31148,14 +31084,14 @@ fn resolvePeerTypes(
         else
             new_ptr_ty;
         const set_ty = err_set_ty orelse return opt_ptr_ty;
-        return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, mod);
+        return try mod.errorUnionType(set_ty, opt_ptr_ty);
     }
 
     if (seen_const) {
         // turn []T => []const T
         switch (chosen_ty.zigTypeTag(mod)) {
             .ErrorUnion => {
-                const ptr_ty = chosen_ty.errorUnionPayload();
+                const ptr_ty = chosen_ty.errorUnionPayload(mod);
                 var info = ptr_ty.ptrInfo(mod);
                 info.mutable = false;
                 const new_ptr_ty = try Type.ptr(sema.arena, mod, info);
@@ -31163,8 +31099,8 @@ fn resolvePeerTypes(
                     try Type.optional(sema.arena, new_ptr_ty, mod)
                 else
                     new_ptr_ty;
-                const set_ty = err_set_ty orelse chosen_ty.errorUnionSet();
-                return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, mod);
+                const set_ty = err_set_ty orelse chosen_ty.errorUnionSet(mod);
+                return try mod.errorUnionType(set_ty, opt_ptr_ty);
             },
             .Pointer => {
                 var info = chosen_ty.ptrInfo(mod);
@@ -31175,7 +31111,7 @@ fn resolvePeerTypes(
                 else
                     new_ptr_ty;
                 const set_ty = err_set_ty orelse return opt_ptr_ty;
-                return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, mod);
+                return try mod.errorUnionType(set_ty, opt_ptr_ty);
             },
             else => return chosen_ty,
         }
@@ -31187,16 +31123,16 @@ fn resolvePeerTypes(
             else => try Type.optional(sema.arena, chosen_ty, mod),
         };
         const set_ty = err_set_ty orelse return opt_ty;
-        return try Type.errorUnion(sema.arena, set_ty, opt_ty, mod);
+        return try mod.errorUnionType(set_ty, opt_ty);
     }
 
     if (err_set_ty) |ty| switch (chosen_ty.zigTypeTag(mod)) {
         .ErrorSet => return ty,
         .ErrorUnion => {
-            const payload_ty = chosen_ty.errorUnionPayload();
-            return try Type.errorUnion(sema.arena, ty, payload_ty, mod);
+            const payload_ty = chosen_ty.errorUnionPayload(mod);
+            return try mod.errorUnionType(ty, payload_ty);
         },
-        else => return try Type.errorUnion(sema.arena, ty, chosen_ty, mod),
+        else => return try mod.errorUnionType(ty, chosen_ty),
     };
 
     return chosen_ty;
@@ -31279,7 +31215,7 @@ pub fn resolveTypeLayout(sema: *Sema, ty: Type) CompileError!void {
             return sema.resolveTypeLayout(payload_ty);
         },
         .ErrorUnion => {
-            const payload_ty = ty.errorUnionPayload();
+            const payload_ty = ty.errorUnionPayload(mod);
             return sema.resolveTypeLayout(payload_ty);
         },
         .Fn => {
@@ -31465,7 +31401,7 @@ fn semaBackingIntType(mod: *Module, struct_obj: *Module.Struct) CompileError!voi
         };
 
         try sema.checkBackingIntType(&block, backing_int_src, backing_int_ty, fields_bit_sum);
-        struct_obj.backing_int_ty = try backing_int_ty.copy(decl_arena_allocator);
+        struct_obj.backing_int_ty = backing_int_ty;
         try wip_captures.finalize();
     } else {
         if (fields_bit_sum > std.math.maxInt(u16)) {
@@ -31605,18 +31541,6 @@ pub fn resolveTypeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
 
     return switch (ty.ip_index) {
         .empty_struct_type => false,
-        .none => switch (ty.tag()) {
-            .error_set,
-            .error_set_single,
-            .error_set_inferred,
-            .error_set_merged,
-            => false,
-
-            .inferred_alloc_mut => unreachable,
-            .inferred_alloc_const => unreachable,
-
-            .error_union => return sema.resolveTypeRequiresComptime(ty.errorUnionPayload()),
-        },
         else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
             .int_type => false,
             .ptr_type => |ptr_type| {
@@ -31635,6 +31559,8 @@ pub fn resolveTypeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
             .vector_type => |vector_type| return sema.resolveTypeRequiresComptime(vector_type.child.toType()),
             .opt_type => |child| return sema.resolveTypeRequiresComptime(child.toType()),
             .error_union_type => |error_union_type| return sema.resolveTypeRequiresComptime(error_union_type.payload_type.toType()),
+            .error_set_type, .inferred_error_set_type => false,
+
             .func_type => true,
 
             .simple_type => |t| switch (t) {
@@ -31780,7 +31706,7 @@ pub fn resolveTypeFully(sema: *Sema, ty: Type) CompileError!void {
         .Optional => {
             return sema.resolveTypeFully(ty.optionalChild(mod));
         },
-        .ErrorUnion => return sema.resolveTypeFully(ty.errorUnionPayload()),
+        .ErrorUnion => return sema.resolveTypeFully(ty.errorUnionPayload(mod)),
         .Fn => {
             const info = mod.typeToFunc(ty).?;
             if (info.is_generic) {
@@ -32048,16 +31974,17 @@ fn resolveInferredErrorSet(
     sema: *Sema,
     block: *Block,
     src: LazySrcLoc,
-    ies: *Module.Fn.InferredErrorSet,
+    ies_index: Module.Fn.InferredErrorSet.Index,
 ) CompileError!void {
+    const mod = sema.mod;
+    const ies = mod.inferredErrorSetPtr(ies_index);
+
     if (ies.is_resolved) return;
 
     if (ies.func.state == .in_progress) {
         return sema.fail(block, src, "unable to resolve inferred error set", .{});
     }
 
-    const mod = sema.mod;
-
     // In order to ensure that all dependencies are properly added to the set, we
     // need to ensure the function body is analyzed of the inferred error set.
     // However, in the case of comptime/inline function calls with inferred error sets,
@@ -32072,7 +31999,7 @@ fn resolveInferredErrorSet(
     // so here we can simply skip this case.
     if (ies_func_info.return_type == .generic_poison_type) {
         assert(ies_func_info.cc == .Inline);
-    } else if (ies_func_info.return_type.toType().errorUnionSet().castTag(.error_set_inferred).?.data == ies) {
+    } else if (mod.typeToInferredErrorSet(ies_func_info.return_type.toType().errorUnionSet(mod)).? == ies) {
         if (ies_func_info.is_generic) {
             const msg = msg: {
                 const msg = try sema.errMsg(block, src, "unable to resolve inferred error set of generic function", .{});
@@ -32090,10 +32017,11 @@ fn resolveInferredErrorSet(
 
     ies.is_resolved = true;
 
-    for (ies.inferred_error_sets.keys()) |other_ies| {
-        if (ies == other_ies) continue;
-        try sema.resolveInferredErrorSet(block, src, other_ies);
+    for (ies.inferred_error_sets.keys()) |other_ies_index| {
+        if (ies_index == other_ies_index) continue;
+        try sema.resolveInferredErrorSet(block, src, other_ies_index);
 
+        const other_ies = mod.inferredErrorSetPtr(other_ies_index);
         for (other_ies.errors.keys()) |key| {
             try ies.errors.put(sema.gpa, key, {});
         }
@@ -32108,8 +32036,9 @@ fn resolveInferredErrorSetTy(
     src: LazySrcLoc,
     ty: Type,
 ) CompileError!void {
-    if (ty.castTag(.error_set_inferred)) |inferred| {
-        try sema.resolveInferredErrorSet(block, src, inferred.data);
+    const mod = sema.mod;
+    if (mod.typeToInferredErrorSetIndex(ty).unwrap()) |ies_index| {
+        try sema.resolveInferredErrorSet(block, src, ies_index);
     }
 }
 
@@ -32333,7 +32262,7 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
         }
 
         const field = &struct_obj.fields.values()[field_i];
-        field.ty = try field_ty.copy(decl_arena_allocator);
+        field.ty = field_ty;
 
         if (field_ty.zigTypeTag(mod) == .Opaque) {
             const msg = msg: {
@@ -32809,7 +32738,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         }
 
         gop.value_ptr.* = .{
-            .ty = try field_ty.copy(decl_arena_allocator),
+            .ty = field_ty,
             .abi_align = 0,
         };
 
@@ -33038,13 +32967,6 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
         .empty_struct_type => return Value.empty_struct,
 
         .none => switch (ty.tag()) {
-            .error_set_single,
-            .error_set,
-            .error_set_merged,
-            .error_union,
-            .error_set_inferred,
-            => return null,
-
             .inferred_alloc_const => unreachable,
             .inferred_alloc_mut => unreachable,
         },
@@ -33062,6 +32984,8 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
             .error_union_type,
             .func_type,
             .anyframe_type,
+            .error_set_type,
+            .inferred_error_set_type,
             => null,
 
             .array_type => |array_type| {
@@ -33389,7 +33313,7 @@ fn analyzeComptimeAlloc(
     defer anon_decl.deinit();
 
     const decl_index = try anon_decl.finish(
-        try var_type.copy(anon_decl.arena()),
+        var_type,
         // There will be stores before the first load, but they may be to sub-elements or
         // sub-fields. So we need to initialize with undef to allow the mechanism to expand
         // into fields/elements and have those overridden with stored values.
@@ -33600,8 +33524,6 @@ fn typePtrOrOptionalPtrTy(sema: *Sema, ty: Type) !?Type {
     switch (ty.tag()) {
         .inferred_alloc_const => unreachable,
         .inferred_alloc_mut => unreachable,
-
-        else => return null,
     }
 }
 
@@ -33616,18 +33538,6 @@ pub fn typeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
     return switch (ty.ip_index) {
         .empty_struct_type => false,
 
-        .none => switch (ty.tag()) {
-            .error_set,
-            .error_set_single,
-            .error_set_inferred,
-            .error_set_merged,
-            => false,
-
-            .inferred_alloc_mut => unreachable,
-            .inferred_alloc_const => unreachable,
-
-            .error_union => return sema.typeRequiresComptime(ty.errorUnionPayload()),
-        },
         else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
             .int_type => return false,
             .ptr_type => |ptr_type| {
@@ -33649,6 +33559,9 @@ pub fn typeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
             .error_union_type => |error_union_type| {
                 return sema.typeRequiresComptime(error_union_type.payload_type.toType());
             },
+
+            .error_set_type, .inferred_error_set_type => false,
+
             .func_type => true,
 
             .simple_type => |t| return switch (t) {
@@ -34410,3 +34323,23 @@ fn elemPtrType(sema: *Sema, ptr_ty: Type, offset: ?usize) !Type {
         .vector_index = vector_info.vector_index,
     });
 }
+
+/// Merge lhs with rhs.
+/// Asserts that lhs and rhs are both error sets and are resolved.
+fn errorSetMerge(sema: *Sema, lhs: Type, rhs: Type) !Type {
+    const mod = sema.mod;
+    const arena = sema.arena;
+    const lhs_names = lhs.errorSetNames(mod);
+    const rhs_names = rhs.errorSetNames(mod);
+    var names: Module.Fn.InferredErrorSet.NameMap = .{};
+    try names.ensureUnusedCapacity(arena, lhs_names.len);
+
+    for (lhs_names) |name| {
+        names.putAssumeCapacityNoClobber(name, {});
+    }
+    for (rhs_names) |name| {
+        try names.put(arena, name, {});
+    }
+
+    return mod.errorSetFromUnsortedNames(names.keys());
+}
src/type.zig
@@ -36,17 +36,9 @@ pub const Type = struct {
     pub fn zigTypeTagOrPoison(ty: Type, mod: *const Module) error{GenericPoison}!std.builtin.TypeId {
         switch (ty.ip_index) {
             .none => switch (ty.tag()) {
-                .error_set,
-                .error_set_single,
-                .error_set_inferred,
-                .error_set_merged,
-                => return .ErrorSet,
-
                 .inferred_alloc_const,
                 .inferred_alloc_mut,
                 => return .Pointer,
-
-                .error_union => return .ErrorUnion,
             },
             else => return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
                 .int_type => .Int,
@@ -55,6 +47,7 @@ pub const Type = struct {
                 .vector_type => .Vector,
                 .opt_type => .Optional,
                 .error_union_type => .ErrorUnion,
+                .error_set_type, .inferred_error_set_type => .ErrorSet,
                 .struct_type, .anon_struct_type => .Struct,
                 .union_type => .Union,
                 .opaque_type => .Opaque,
@@ -130,9 +123,9 @@ pub const Type = struct {
         }
     }
 
-    pub fn baseZigTypeTag(self: Type, mod: *const Module) std.builtin.TypeId {
+    pub fn baseZigTypeTag(self: Type, mod: *Module) std.builtin.TypeId {
         return switch (self.zigTypeTag(mod)) {
-            .ErrorUnion => self.errorUnionPayload().baseZigTypeTag(mod),
+            .ErrorUnion => self.errorUnionPayload(mod).baseZigTypeTag(mod),
             .Optional => {
                 return self.optionalChild(mod).baseZigTypeTag(mod);
             },
@@ -294,35 +287,6 @@ pub const Type = struct {
         if (a.legacy.tag_if_small_enough == b.legacy.tag_if_small_enough) return true;
 
         switch (a.tag()) {
-            .error_set_inferred => {
-                // Inferred error sets are only equal if both are inferred
-                // and they share the same pointer.
-                const a_ies = a.castTag(.error_set_inferred).?.data;
-                const b_ies = (b.castTag(.error_set_inferred) orelse return false).data;
-                return a_ies == b_ies;
-            },
-
-            .error_set,
-            .error_set_single,
-            .error_set_merged,
-            => {
-                switch (b.tag()) {
-                    .error_set, .error_set_single, .error_set_merged => {},
-                    else => return false,
-                }
-
-                // Two resolved sets match if their error set names match.
-                // Since they are pre-sorted we compare them element-wise.
-                const a_set = a.errorSetNames();
-                const b_set = b.errorSetNames();
-                if (a_set.len != b_set.len) return false;
-                for (a_set, 0..) |a_item, i| {
-                    const b_item = b_set[i];
-                    if (!std.mem.eql(u8, a_item, b_item)) return false;
-                }
-                return true;
-            },
-
             .inferred_alloc_const,
             .inferred_alloc_mut,
             => {
@@ -367,20 +331,6 @@ pub const Type = struct {
 
                 return true;
             },
-
-            .error_union => {
-                if (b.zigTypeTag(mod) != .ErrorUnion) return false;
-
-                const a_set = a.errorUnionSet();
-                const b_set = b.errorUnionSet();
-                if (!a_set.eql(b_set, mod)) return false;
-
-                const a_payload = a.errorUnionPayload();
-                const b_payload = b.errorUnionPayload();
-                if (!a_payload.eql(b_payload, mod)) return false;
-
-                return true;
-            },
         }
     }
 
@@ -399,28 +349,6 @@ pub const Type = struct {
             return;
         }
         switch (ty.tag()) {
-            .error_set,
-            .error_set_single,
-            .error_set_merged,
-            => {
-                // all are treated like an "error set" for hashing
-                std.hash.autoHash(hasher, std.builtin.TypeId.ErrorSet);
-                std.hash.autoHash(hasher, Tag.error_set);
-
-                const names = ty.errorSetNames();
-                std.hash.autoHash(hasher, names.len);
-                assert(std.sort.isSorted([]const u8, names, u8, std.mem.lessThan));
-                for (names) |name| hasher.update(name);
-            },
-
-            .error_set_inferred => {
-                // inferred error sets are compared using their data pointer
-                const ies: *Module.Fn.InferredErrorSet = ty.castTag(.error_set_inferred).?.data;
-                std.hash.autoHash(hasher, std.builtin.TypeId.ErrorSet);
-                std.hash.autoHash(hasher, Tag.error_set_inferred);
-                std.hash.autoHash(hasher, ies);
-            },
-
             .inferred_alloc_const,
             .inferred_alloc_mut,
             => {
@@ -439,16 +367,6 @@ pub const Type = struct {
                 std.hash.autoHash(hasher, info.@"volatile");
                 std.hash.autoHash(hasher, info.size);
             },
-
-            .error_union => {
-                std.hash.autoHash(hasher, std.builtin.TypeId.ErrorUnion);
-
-                const set_ty = ty.errorUnionSet();
-                hashWithHasher(set_ty, hasher, mod);
-
-                const payload_ty = ty.errorUnionPayload();
-                hashWithHasher(payload_ty, hasher, mod);
-            },
         }
     }
 
@@ -484,52 +402,6 @@ pub const Type = struct {
         }
     };
 
-    pub fn copy(self: Type, allocator: Allocator) error{OutOfMemory}!Type {
-        if (self.ip_index != .none) {
-            return Type{ .ip_index = self.ip_index, .legacy = undefined };
-        }
-        if (@enumToInt(self.legacy.tag_if_small_enough) < Tag.no_payload_count) {
-            return Type{
-                .ip_index = .none,
-                .legacy = .{ .tag_if_small_enough = self.legacy.tag_if_small_enough },
-            };
-        } else switch (self.legacy.ptr_otherwise.tag) {
-            .inferred_alloc_const,
-            .inferred_alloc_mut,
-            => unreachable,
-
-            .error_union => {
-                const payload = self.castTag(.error_union).?.data;
-                return Tag.error_union.create(allocator, .{
-                    .error_set = try payload.error_set.copy(allocator),
-                    .payload = try payload.payload.copy(allocator),
-                });
-            },
-            .error_set_merged => {
-                const names = self.castTag(.error_set_merged).?.data.keys();
-                var duped_names = Module.ErrorSet.NameMap{};
-                try duped_names.ensureTotalCapacity(allocator, names.len);
-                for (names) |name| {
-                    duped_names.putAssumeCapacityNoClobber(name, {});
-                }
-                return Tag.error_set_merged.create(allocator, duped_names);
-            },
-            .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet),
-            .error_set_inferred => return self.copyPayloadShallow(allocator, Payload.ErrorSetInferred),
-            .error_set_single => return self.copyPayloadShallow(allocator, Payload.Name),
-        }
-    }
-
-    fn copyPayloadShallow(self: Type, allocator: Allocator, comptime T: type) error{OutOfMemory}!Type {
-        const payload = self.cast(T).?;
-        const new_payload = try allocator.create(T);
-        new_payload.* = payload.*;
-        return Type{
-            .ip_index = .none,
-            .legacy = .{ .ptr_otherwise = &new_payload.base },
-        };
-    }
-
     pub fn format(ty: Type, comptime unused_fmt_string: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
         _ = ty;
         _ = unused_fmt_string;
@@ -575,62 +447,7 @@ pub const Type = struct {
     ) @TypeOf(writer).Error!void {
         _ = options;
         comptime assert(unused_format_string.len == 0);
-        if (start_type.ip_index != .none) {
-            return writer.print("(intern index: {d})", .{@enumToInt(start_type.ip_index)});
-        }
-        if (true) {
-            // This is disabled to work around a stage2 bug where this function recursively
-            // causes more generic function instantiations resulting in an infinite loop
-            // in the compiler.
-            try writer.writeAll("[TODO fix internal compiler bug regarding dump]");
-            return;
-        }
-        var ty = start_type;
-        while (true) {
-            const t = ty.tag();
-            switch (t) {
-                .error_union => {
-                    const payload = ty.castTag(.error_union).?.data;
-                    try payload.error_set.dump("", .{}, writer);
-                    try writer.writeAll("!");
-                    ty = payload.payload;
-                    continue;
-                },
-                .error_set => {
-                    const names = ty.castTag(.error_set).?.data.names.keys();
-                    try writer.writeAll("error{");
-                    for (names, 0..) |name, i| {
-                        if (i != 0) try writer.writeByte(',');
-                        try writer.writeAll(name);
-                    }
-                    try writer.writeAll("}");
-                    return;
-                },
-                .error_set_inferred => {
-                    const func = ty.castTag(.error_set_inferred).?.data.func;
-                    return writer.print("({s} func={d})", .{
-                        @tagName(t), func.owner_decl,
-                    });
-                },
-                .error_set_merged => {
-                    const names = ty.castTag(.error_set_merged).?.data.keys();
-                    try writer.writeAll("error{");
-                    for (names, 0..) |name, i| {
-                        if (i != 0) try writer.writeByte(',');
-                        try writer.writeAll(name);
-                    }
-                    try writer.writeAll("}");
-                    return;
-                },
-                .error_set_single => {
-                    const name = ty.castTag(.error_set_single).?.data;
-                    return writer.print("error{{{s}}}", .{name});
-                },
-                .inferred_alloc_const => return writer.writeAll("(inferred_alloc_const)"),
-                .inferred_alloc_mut => return writer.writeAll("(inferred_alloc_mut)"),
-            }
-            unreachable;
-        }
+        return writer.print("{any}", .{start_type.ip_index});
     }
 
     pub const nameAllocArena = nameAlloc;
@@ -648,45 +465,6 @@ pub const Type = struct {
             .none => switch (ty.tag()) {
                 .inferred_alloc_const => unreachable,
                 .inferred_alloc_mut => unreachable,
-
-                .error_set_inferred => {
-                    const func = ty.castTag(.error_set_inferred).?.data.func;
-
-                    try writer.writeAll("@typeInfo(@typeInfo(@TypeOf(");
-                    const owner_decl = mod.declPtr(func.owner_decl);
-                    try owner_decl.renderFullyQualifiedName(mod, writer);
-                    try writer.writeAll(")).Fn.return_type.?).ErrorUnion.error_set");
-                },
-
-                .error_union => {
-                    const error_union = ty.castTag(.error_union).?.data;
-                    try print(error_union.error_set, writer, mod);
-                    try writer.writeAll("!");
-                    try print(error_union.payload, writer, mod);
-                },
-
-                .error_set => {
-                    const names = ty.castTag(.error_set).?.data.names.keys();
-                    try writer.writeAll("error{");
-                    for (names, 0..) |name, i| {
-                        if (i != 0) try writer.writeByte(',');
-                        try writer.writeAll(name);
-                    }
-                    try writer.writeAll("}");
-                },
-                .error_set_single => {
-                    const name = ty.castTag(.error_set_single).?.data;
-                    return writer.print("error{{{s}}}", .{name});
-                },
-                .error_set_merged => {
-                    const names = ty.castTag(.error_set_merged).?.data.keys();
-                    try writer.writeAll("error{");
-                    for (names, 0..) |name, i| {
-                        if (i != 0) try writer.writeByte(',');
-                        try writer.writeAll(name);
-                    }
-                    try writer.writeAll("}");
-                },
             },
             else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
                 .int_type => |int_type| {
@@ -766,6 +544,24 @@ pub const Type = struct {
                     try print(error_union_type.payload_type.toType(), writer, mod);
                     return;
                 },
+                .inferred_error_set_type => |index| {
+                    const ies = mod.inferredErrorSetPtr(index);
+                    const func = ies.func;
+
+                    try writer.writeAll("@typeInfo(@typeInfo(@TypeOf(");
+                    const owner_decl = mod.declPtr(func.owner_decl);
+                    try owner_decl.renderFullyQualifiedName(mod, writer);
+                    try writer.writeAll(")).Fn.return_type.?).ErrorUnion.error_set");
+                },
+                .error_set_type => |error_set_type| {
+                    const names = error_set_type.names;
+                    try writer.writeAll("error{");
+                    for (names, 0..) |name, i| {
+                        if (i != 0) try writer.writeByte(',');
+                        try writer.writeAll(mod.intern_pool.stringToSlice(name));
+                    }
+                    try writer.writeAll("}");
+                },
                 .simple_type => |s| return writer.writeAll(@tagName(s)),
                 .struct_type => |struct_type| {
                     if (mod.structPtrUnwrap(struct_type.index)) |struct_obj| {
@@ -881,13 +677,8 @@ pub const Type = struct {
         return ty.ip_index;
     }
 
-    pub fn toValue(self: Type, allocator: Allocator) Allocator.Error!Value {
-        if (self.ip_index != .none) return self.ip_index.toValue();
-        switch (self.tag()) {
-            .inferred_alloc_const => unreachable,
-            .inferred_alloc_mut => unreachable,
-            else => return Value.Tag.ty.create(allocator, self),
-        }
+    pub fn toValue(self: Type) Value {
+        return self.toIntern().toValue();
     }
 
     const RuntimeBitsError = Module.CompileError || error{NeedLazy};
@@ -914,14 +705,6 @@ pub const Type = struct {
             .empty_struct_type => return false,
 
             .none => switch (ty.tag()) {
-                .error_set_inferred,
-
-                .error_set_single,
-                .error_union,
-                .error_set,
-                .error_set_merged,
-                => return true,
-
                 .inferred_alloc_const => unreachable,
                 .inferred_alloc_mut => unreachable,
             },
@@ -951,7 +734,7 @@ pub const Type = struct {
                 },
                 .opt_type => |child| {
                     const child_ty = child.toType();
-                    if (child_ty.isNoReturn()) {
+                    if (child_ty.isNoReturn(mod)) {
                         // Then the optional is comptime-known to be null.
                         return false;
                     }
@@ -963,7 +746,10 @@ pub const Type = struct {
                         return !comptimeOnly(child_ty, mod);
                     }
                 },
-                .error_union_type => @panic("TODO"),
+                .error_union_type,
+                .error_set_type,
+                .inferred_error_set_type,
+                => true,
 
                 // These are function *bodies*, not pointers.
                 // They return false here because they are comptime-only types.
@@ -1103,112 +889,99 @@ pub const Type = struct {
     /// readFrom/writeToMemory are supported only for types with a well-
     /// defined memory layout
     pub fn hasWellDefinedLayout(ty: Type, mod: *Module) bool {
-        return switch (ty.ip_index) {
-            .empty_struct_type => false,
+        return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+            .int_type,
+            .ptr_type,
+            .vector_type,
+            => true,
 
-            .none => switch (ty.tag()) {
-                .error_set,
-                .error_set_single,
-                .error_set_inferred,
-                .error_set_merged,
-                .error_union,
-                => false,
+            .error_union_type,
+            .error_set_type,
+            .inferred_error_set_type,
+            .anon_struct_type,
+            .opaque_type,
+            .anyframe_type,
+            // These are function bodies, not function pointers.
+            .func_type,
+            => false,
 
-                .inferred_alloc_mut => unreachable,
-                .inferred_alloc_const => unreachable,
-            },
-            else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
-                .int_type,
-                .ptr_type,
-                .vector_type,
+            .array_type => |array_type| array_type.child.toType().hasWellDefinedLayout(mod),
+            .opt_type => ty.isPtrLikeOptional(mod),
+
+            .simple_type => |t| switch (t) {
+                .f16,
+                .f32,
+                .f64,
+                .f80,
+                .f128,
+                .usize,
+                .isize,
+                .c_char,
+                .c_short,
+                .c_ushort,
+                .c_int,
+                .c_uint,
+                .c_long,
+                .c_ulong,
+                .c_longlong,
+                .c_ulonglong,
+                .c_longdouble,
+                .bool,
+                .void,
                 => true,
 
-                .error_union_type,
-                .anon_struct_type,
-                .opaque_type,
-                .anyframe_type,
-                // These are function bodies, not function pointers.
-                .func_type,
+                .anyerror,
+                .anyopaque,
+                .atomic_order,
+                .atomic_rmw_op,
+                .calling_convention,
+                .address_space,
+                .float_mode,
+                .reduce_op,
+                .call_modifier,
+                .prefetch_options,
+                .export_options,
+                .extern_options,
+                .type,
+                .comptime_int,
+                .comptime_float,
+                .noreturn,
+                .null,
+                .undefined,
+                .enum_literal,
+                .type_info,
+                .generic_poison,
                 => false,
 
-                .array_type => |array_type| array_type.child.toType().hasWellDefinedLayout(mod),
-                .opt_type => ty.isPtrLikeOptional(mod),
-
-                .simple_type => |t| switch (t) {
-                    .f16,
-                    .f32,
-                    .f64,
-                    .f80,
-                    .f128,
-                    .usize,
-                    .isize,
-                    .c_char,
-                    .c_short,
-                    .c_ushort,
-                    .c_int,
-                    .c_uint,
-                    .c_long,
-                    .c_ulong,
-                    .c_longlong,
-                    .c_ulonglong,
-                    .c_longdouble,
-                    .bool,
-                    .void,
-                    => true,
-
-                    .anyerror,
-                    .anyopaque,
-                    .atomic_order,
-                    .atomic_rmw_op,
-                    .calling_convention,
-                    .address_space,
-                    .float_mode,
-                    .reduce_op,
-                    .call_modifier,
-                    .prefetch_options,
-                    .export_options,
-                    .extern_options,
-                    .type,
-                    .comptime_int,
-                    .comptime_float,
-                    .noreturn,
-                    .null,
-                    .undefined,
-                    .enum_literal,
-                    .type_info,
-                    .generic_poison,
-                    => false,
-
-                    .var_args_param => unreachable,
-                },
-                .struct_type => |struct_type| {
-                    const struct_obj = mod.structPtrUnwrap(struct_type.index) orelse {
-                        // Struct with no fields has a well-defined layout of no bits.
-                        return true;
-                    };
-                    return struct_obj.layout != .Auto;
-                },
-                .union_type => |union_type| switch (union_type.runtime_tag) {
-                    .none, .safety => mod.unionPtr(union_type.index).layout != .Auto,
-                    .tagged => false,
-                },
-                .enum_type => |enum_type| switch (enum_type.tag_mode) {
-                    .auto => false,
-                    .explicit, .nonexhaustive => true,
-                },
-
-                // values, not types
-                .undef => unreachable,
-                .un => unreachable,
-                .simple_value => unreachable,
-                .extern_func => unreachable,
-                .int => unreachable,
-                .float => unreachable,
-                .ptr => unreachable,
-                .opt => unreachable,
-                .enum_tag => unreachable,
-                .aggregate => unreachable,
+                .var_args_param => unreachable,
+            },
+            .struct_type => |struct_type| {
+                const struct_obj = mod.structPtrUnwrap(struct_type.index) orelse {
+                    // Struct with no fields has a well-defined layout of no bits.
+                    return true;
+                };
+                return struct_obj.layout != .Auto;
+            },
+            .union_type => |union_type| switch (union_type.runtime_tag) {
+                .none, .safety => mod.unionPtr(union_type.index).layout != .Auto,
+                .tagged => false,
             },
+            .enum_type => |enum_type| switch (enum_type.tag_mode) {
+                .auto => false,
+                .explicit, .nonexhaustive => true,
+            },
+
+            // values, not types
+            .undef => unreachable,
+            .un => unreachable,
+            .simple_value => unreachable,
+            .extern_func => unreachable,
+            .int => unreachable,
+            .float => unreachable,
+            .ptr => unreachable,
+            .opt => unreachable,
+            .enum_tag => unreachable,
+            .aggregate => unreachable,
         };
     }
 
@@ -1247,35 +1020,8 @@ pub const Type = struct {
         };
     }
 
-    pub fn isNoReturn(ty: Type) bool {
-        switch (@enumToInt(ty.ip_index)) {
-            @enumToInt(InternPool.Index.first_type)...@enumToInt(InternPool.Index.noreturn_type) - 1 => return false,
-
-            @enumToInt(InternPool.Index.noreturn_type) => return true,
-
-            @enumToInt(InternPool.Index.noreturn_type) + 1...@enumToInt(InternPool.Index.last_type) => return false,
-
-            @enumToInt(InternPool.Index.first_value)...@enumToInt(InternPool.Index.last_value) => unreachable,
-            @enumToInt(InternPool.Index.generic_poison) => unreachable,
-
-            // TODO add empty error sets here
-            // TODO add enums with no fields here
-            else => return false,
-
-            @enumToInt(InternPool.Index.none) => switch (ty.tag()) {
-                .error_set => {
-                    const err_set_obj = ty.castTag(.error_set).?.data;
-                    const names = err_set_obj.names.keys();
-                    return names.len == 0;
-                },
-                .error_set_merged => {
-                    const name_map = ty.castTag(.error_set_merged).?.data;
-                    const names = name_map.keys();
-                    return names.len == 0;
-                },
-                else => return false,
-            },
-        }
+    pub fn isNoReturn(ty: Type, mod: *Module) bool {
+        return mod.intern_pool.isNoReturn(ty.ip_index);
     }
 
     /// Returns 0 if the pointer is naturally aligned and the element type is 0-bit.
@@ -1353,21 +1099,6 @@ pub const Type = struct {
 
         switch (ty.ip_index) {
             .empty_struct_type => return AbiAlignmentAdvanced{ .scalar = 0 },
-            .none => switch (ty.tag()) {
-
-                // TODO revisit this when we have the concept of the error tag type
-                .error_set_inferred,
-                .error_set_single,
-                .error_set,
-                .error_set_merged,
-                => return AbiAlignmentAdvanced{ .scalar = 2 },
-
-                .error_union => return abiAlignmentAdvancedErrorUnion(ty, mod, strat),
-
-                .inferred_alloc_const,
-                .inferred_alloc_mut,
-                => unreachable,
-            },
             else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
                 .int_type => |int_type| {
                     if (int_type.bits == 0) return AbiAlignmentAdvanced{ .scalar = 0 };
@@ -1388,7 +1119,11 @@ pub const Type = struct {
                 },
 
                 .opt_type => return abiAlignmentAdvancedOptional(ty, mod, strat),
-                .error_union_type => return abiAlignmentAdvancedErrorUnion(ty, mod, strat),
+                .error_union_type => |info| return abiAlignmentAdvancedErrorUnion(ty, mod, strat, info.payload_type.toType()),
+
+                // TODO revisit this when we have the concept of the error tag type
+                .error_set_type, .inferred_error_set_type => return AbiAlignmentAdvanced{ .scalar = 2 },
+
                 // represents machine code; not a pointer
                 .func_type => |func_type| return AbiAlignmentAdvanced{
                     .scalar = if (func_type.alignment.toByteUnitsOptional()) |a|
@@ -1572,14 +1307,14 @@ pub const Type = struct {
         ty: Type,
         mod: *Module,
         strat: AbiAlignmentAdvancedStrat,
+        payload_ty: Type,
     ) Module.CompileError!AbiAlignmentAdvanced {
         // This code needs to be kept in sync with the equivalent switch prong
         // in abiSizeAdvanced.
-        const data = ty.castTag(.error_union).?.data;
         const code_align = abiAlignment(Type.anyerror, mod);
         switch (strat) {
             .eager, .sema => {
-                if (!(data.payload.hasRuntimeBitsAdvanced(mod, false, strat) catch |err| switch (err) {
+                if (!(payload_ty.hasRuntimeBitsAdvanced(mod, false, strat) catch |err| switch (err) {
                     error.NeedLazy => return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(strat.lazy, ty) },
                     else => |e| return e,
                 })) {
@@ -1587,11 +1322,11 @@ pub const Type = struct {
                 }
                 return AbiAlignmentAdvanced{ .scalar = @max(
                     code_align,
-                    (try data.payload.abiAlignmentAdvanced(mod, strat)).scalar,
+                    (try payload_ty.abiAlignmentAdvanced(mod, strat)).scalar,
                 ) };
             },
             .lazy => |arena| {
-                switch (try data.payload.abiAlignmentAdvanced(mod, strat)) {
+                switch (try payload_ty.abiAlignmentAdvanced(mod, strat)) {
                     .scalar => |payload_align| {
                         return AbiAlignmentAdvanced{
                             .scalar = @max(code_align, payload_align),
@@ -1728,55 +1463,6 @@ pub const Type = struct {
         switch (ty.ip_index) {
             .empty_struct_type => return AbiSizeAdvanced{ .scalar = 0 },
 
-            .none => switch (ty.tag()) {
-                .inferred_alloc_const => unreachable,
-                .inferred_alloc_mut => unreachable,
-
-                // TODO revisit this when we have the concept of the error tag type
-                .error_set_inferred,
-                .error_set,
-                .error_set_merged,
-                .error_set_single,
-                => return AbiSizeAdvanced{ .scalar = 2 },
-
-                .error_union => {
-                    // This code needs to be kept in sync with the equivalent switch prong
-                    // in abiAlignmentAdvanced.
-                    const data = ty.castTag(.error_union).?.data;
-                    const code_size = abiSize(Type.anyerror, mod);
-                    if (!(data.payload.hasRuntimeBitsAdvanced(mod, false, strat) catch |err| switch (err) {
-                        error.NeedLazy => return AbiSizeAdvanced{ .val = try Value.Tag.lazy_size.create(strat.lazy, ty) },
-                        else => |e| return e,
-                    })) {
-                        // Same as anyerror.
-                        return AbiSizeAdvanced{ .scalar = code_size };
-                    }
-                    const code_align = abiAlignment(Type.anyerror, mod);
-                    const payload_align = abiAlignment(data.payload, mod);
-                    const payload_size = switch (try data.payload.abiSizeAdvanced(mod, strat)) {
-                        .scalar => |elem_size| elem_size,
-                        .val => switch (strat) {
-                            .sema => unreachable,
-                            .eager => unreachable,
-                            .lazy => |arena| return AbiSizeAdvanced{ .val = try Value.Tag.lazy_size.create(arena, ty) },
-                        },
-                    };
-
-                    var size: u64 = 0;
-                    if (code_align > payload_align) {
-                        size += code_size;
-                        size = std.mem.alignForwardGeneric(u64, size, payload_align);
-                        size += payload_size;
-                        size = std.mem.alignForwardGeneric(u64, size, code_align);
-                    } else {
-                        size += payload_size;
-                        size = std.mem.alignForwardGeneric(u64, size, code_align);
-                        size += code_size;
-                        size = std.mem.alignForwardGeneric(u64, size, payload_align);
-                    }
-                    return AbiSizeAdvanced{ .scalar = size };
-                },
-            },
             else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
                 .int_type => |int_type| {
                     if (int_type.bits == 0) return AbiSizeAdvanced{ .scalar = 0 };
@@ -1816,12 +1502,52 @@ pub const Type = struct {
                             .val = try Value.Tag.lazy_size.create(strat.lazy, ty),
                         },
                     };
-                    const result = std.mem.alignForwardGeneric(u32, total_bytes, alignment);
-                    return AbiSizeAdvanced{ .scalar = result };
-                },
+                    const result = std.mem.alignForwardGeneric(u32, total_bytes, alignment);
+                    return AbiSizeAdvanced{ .scalar = result };
+                },
+
+                .opt_type => return ty.abiSizeAdvancedOptional(mod, strat),
+
+                // TODO revisit this when we have the concept of the error tag type
+                .error_set_type, .inferred_error_set_type => return AbiSizeAdvanced{ .scalar = 2 },
+
+                .error_union_type => |error_union_type| {
+                    const payload_ty = error_union_type.payload_type.toType();
+                    // This code needs to be kept in sync with the equivalent switch prong
+                    // in abiAlignmentAdvanced.
+                    const code_size = abiSize(Type.anyerror, mod);
+                    if (!(payload_ty.hasRuntimeBitsAdvanced(mod, false, strat) catch |err| switch (err) {
+                        error.NeedLazy => return AbiSizeAdvanced{ .val = try Value.Tag.lazy_size.create(strat.lazy, ty) },
+                        else => |e| return e,
+                    })) {
+                        // Same as anyerror.
+                        return AbiSizeAdvanced{ .scalar = code_size };
+                    }
+                    const code_align = abiAlignment(Type.anyerror, mod);
+                    const payload_align = abiAlignment(payload_ty, mod);
+                    const payload_size = switch (try payload_ty.abiSizeAdvanced(mod, strat)) {
+                        .scalar => |elem_size| elem_size,
+                        .val => switch (strat) {
+                            .sema => unreachable,
+                            .eager => unreachable,
+                            .lazy => |arena| return AbiSizeAdvanced{ .val = try Value.Tag.lazy_size.create(arena, ty) },
+                        },
+                    };
 
-                .opt_type => return ty.abiSizeAdvancedOptional(mod, strat),
-                .error_union_type => @panic("TODO"),
+                    var size: u64 = 0;
+                    if (code_align > payload_align) {
+                        size += code_size;
+                        size = std.mem.alignForwardGeneric(u64, size, payload_align);
+                        size += payload_size;
+                        size = std.mem.alignForwardGeneric(u64, size, code_align);
+                    } else {
+                        size += payload_size;
+                        size = std.mem.alignForwardGeneric(u64, size, code_align);
+                        size += code_size;
+                        size = std.mem.alignForwardGeneric(u64, size, payload_align);
+                    }
+                    return AbiSizeAdvanced{ .scalar = size };
+                },
                 .func_type => unreachable, // represents machine code; not a pointer
                 .simple_type => |t| switch (t) {
                     .bool,
@@ -1982,7 +1708,7 @@ pub const Type = struct {
     ) Module.CompileError!AbiSizeAdvanced {
         const child_ty = ty.optionalChild(mod);
 
-        if (child_ty.isNoReturn()) {
+        if (child_ty.isNoReturn(mod)) {
             return AbiSizeAdvanced{ .scalar = 0 };
         }
 
@@ -2041,147 +1767,137 @@ pub const Type = struct {
 
         const strat: AbiAlignmentAdvancedStrat = if (opt_sema) |sema| .{ .sema = sema } else .eager;
 
-        switch (ty.ip_index) {
-            .none => switch (ty.tag()) {
-                .inferred_alloc_const => unreachable,
-                .inferred_alloc_mut => unreachable,
-
-                .error_set,
-                .error_set_single,
-                .error_set_inferred,
-                .error_set_merged,
-                => return 16, // TODO revisit this when we have the concept of the error tag type
+        switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+            .int_type => |int_type| return int_type.bits,
+            .ptr_type => |ptr_type| switch (ptr_type.size) {
+                .Slice => return target.ptrBitWidth() * 2,
+                else => return target.ptrBitWidth() * 2,
+            },
+            .anyframe_type => return target.ptrBitWidth(),
+
+            .array_type => |array_type| {
+                const len = array_type.len + @boolToInt(array_type.sentinel != .none);
+                if (len == 0) return 0;
+                const elem_ty = array_type.child.toType();
+                const elem_size = std.math.max(elem_ty.abiAlignment(mod), elem_ty.abiSize(mod));
+                if (elem_size == 0) return 0;
+                const elem_bit_size = try bitSizeAdvanced(elem_ty, mod, opt_sema);
+                return (len - 1) * 8 * elem_size + elem_bit_size;
+            },
+            .vector_type => |vector_type| {
+                const child_ty = vector_type.child.toType();
+                const elem_bit_size = try bitSizeAdvanced(child_ty, mod, opt_sema);
+                return elem_bit_size * vector_type.len;
+            },
+            .opt_type => {
+                // Optionals and error unions are not packed so their bitsize
+                // includes padding bits.
+                return (try abiSizeAdvanced(ty, mod, strat)).scalar * 8;
+            },
+
+            // TODO revisit this when we have the concept of the error tag type
+            .error_set_type, .inferred_error_set_type => return 16,
+
+            .error_union_type => {
+                // Optionals and error unions are not packed so their bitsize
+                // includes padding bits.
+                return (try abiSizeAdvanced(ty, mod, strat)).scalar * 8;
+            },
+            .func_type => unreachable, // represents machine code; not a pointer
+            .simple_type => |t| switch (t) {
+                .f16 => return 16,
+                .f32 => return 32,
+                .f64 => return 64,
+                .f80 => return 80,
+                .f128 => return 128,
+
+                .usize,
+                .isize,
+                => return target.ptrBitWidth(),
+
+                .c_char => return target.c_type_bit_size(.char),
+                .c_short => return target.c_type_bit_size(.short),
+                .c_ushort => return target.c_type_bit_size(.ushort),
+                .c_int => return target.c_type_bit_size(.int),
+                .c_uint => return target.c_type_bit_size(.uint),
+                .c_long => return target.c_type_bit_size(.long),
+                .c_ulong => return target.c_type_bit_size(.ulong),
+                .c_longlong => return target.c_type_bit_size(.longlong),
+                .c_ulonglong => return target.c_type_bit_size(.ulonglong),
+                .c_longdouble => return target.c_type_bit_size(.longdouble),
+
+                .bool => return 1,
+                .void => return 0,
 
-                .error_union => {
-                    // Optionals and error unions are not packed so their bitsize
-                    // includes padding bits.
-                    return (try abiSizeAdvanced(ty, mod, strat)).scalar * 8;
-                },
+                // TODO revisit this when we have the concept of the error tag type
+                .anyerror => return 16,
+
+                .anyopaque => unreachable,
+                .type => unreachable,
+                .comptime_int => unreachable,
+                .comptime_float => unreachable,
+                .noreturn => unreachable,
+                .null => unreachable,
+                .undefined => unreachable,
+                .enum_literal => unreachable,
+                .generic_poison => unreachable,
+                .var_args_param => unreachable,
+
+                .atomic_order => unreachable, // missing call to resolveTypeFields
+                .atomic_rmw_op => unreachable, // missing call to resolveTypeFields
+                .calling_convention => unreachable, // missing call to resolveTypeFields
+                .address_space => unreachable, // missing call to resolveTypeFields
+                .float_mode => unreachable, // missing call to resolveTypeFields
+                .reduce_op => unreachable, // missing call to resolveTypeFields
+                .call_modifier => unreachable, // missing call to resolveTypeFields
+                .prefetch_options => unreachable, // missing call to resolveTypeFields
+                .export_options => unreachable, // missing call to resolveTypeFields
+                .extern_options => unreachable, // missing call to resolveTypeFields
+                .type_info => unreachable, // missing call to resolveTypeFields
             },
-            else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
-                .int_type => |int_type| return int_type.bits,
-                .ptr_type => |ptr_type| switch (ptr_type.size) {
-                    .Slice => return target.ptrBitWidth() * 2,
-                    else => return target.ptrBitWidth() * 2,
-                },
-                .anyframe_type => return target.ptrBitWidth(),
-
-                .array_type => |array_type| {
-                    const len = array_type.len + @boolToInt(array_type.sentinel != .none);
-                    if (len == 0) return 0;
-                    const elem_ty = array_type.child.toType();
-                    const elem_size = std.math.max(elem_ty.abiAlignment(mod), elem_ty.abiSize(mod));
-                    if (elem_size == 0) return 0;
-                    const elem_bit_size = try bitSizeAdvanced(elem_ty, mod, opt_sema);
-                    return (len - 1) * 8 * elem_size + elem_bit_size;
-                },
-                .vector_type => |vector_type| {
-                    const child_ty = vector_type.child.toType();
-                    const elem_bit_size = try bitSizeAdvanced(child_ty, mod, opt_sema);
-                    return elem_bit_size * vector_type.len;
-                },
-                .opt_type => {
-                    // Optionals and error unions are not packed so their bitsize
-                    // includes padding bits.
-                    return (try abiSizeAdvanced(ty, mod, strat)).scalar * 8;
-                },
-                .error_union_type => @panic("TODO"),
-                .func_type => unreachable, // represents machine code; not a pointer
-                .simple_type => |t| switch (t) {
-                    .f16 => return 16,
-                    .f32 => return 32,
-                    .f64 => return 64,
-                    .f80 => return 80,
-                    .f128 => return 128,
-
-                    .usize,
-                    .isize,
-                    => return target.ptrBitWidth(),
-
-                    .c_char => return target.c_type_bit_size(.char),
-                    .c_short => return target.c_type_bit_size(.short),
-                    .c_ushort => return target.c_type_bit_size(.ushort),
-                    .c_int => return target.c_type_bit_size(.int),
-                    .c_uint => return target.c_type_bit_size(.uint),
-                    .c_long => return target.c_type_bit_size(.long),
-                    .c_ulong => return target.c_type_bit_size(.ulong),
-                    .c_longlong => return target.c_type_bit_size(.longlong),
-                    .c_ulonglong => return target.c_type_bit_size(.ulonglong),
-                    .c_longdouble => return target.c_type_bit_size(.longdouble),
-
-                    .bool => return 1,
-                    .void => return 0,
-
-                    // TODO revisit this when we have the concept of the error tag type
-                    .anyerror => return 16,
-
-                    .anyopaque => unreachable,
-                    .type => unreachable,
-                    .comptime_int => unreachable,
-                    .comptime_float => unreachable,
-                    .noreturn => unreachable,
-                    .null => unreachable,
-                    .undefined => unreachable,
-                    .enum_literal => unreachable,
-                    .generic_poison => unreachable,
-                    .var_args_param => unreachable,
-
-                    .atomic_order => unreachable, // missing call to resolveTypeFields
-                    .atomic_rmw_op => unreachable, // missing call to resolveTypeFields
-                    .calling_convention => unreachable, // missing call to resolveTypeFields
-                    .address_space => unreachable, // missing call to resolveTypeFields
-                    .float_mode => unreachable, // missing call to resolveTypeFields
-                    .reduce_op => unreachable, // missing call to resolveTypeFields
-                    .call_modifier => unreachable, // missing call to resolveTypeFields
-                    .prefetch_options => unreachable, // missing call to resolveTypeFields
-                    .export_options => unreachable, // missing call to resolveTypeFields
-                    .extern_options => unreachable, // missing call to resolveTypeFields
-                    .type_info => unreachable, // missing call to resolveTypeFields
-                },
-                .struct_type => |struct_type| {
-                    const struct_obj = mod.structPtrUnwrap(struct_type.index) orelse return 0;
-                    if (struct_obj.layout != .Packed) {
-                        return (try ty.abiSizeAdvanced(mod, strat)).scalar * 8;
-                    }
-                    if (opt_sema) |sema| _ = try sema.resolveTypeLayout(ty);
-                    assert(struct_obj.haveLayout());
-                    return try struct_obj.backing_int_ty.bitSizeAdvanced(mod, opt_sema);
-                },
-
-                .anon_struct_type => {
-                    if (opt_sema) |sema| _ = try sema.resolveTypeFields(ty);
+            .struct_type => |struct_type| {
+                const struct_obj = mod.structPtrUnwrap(struct_type.index) orelse return 0;
+                if (struct_obj.layout != .Packed) {
                     return (try ty.abiSizeAdvanced(mod, strat)).scalar * 8;
-                },
+                }
+                if (opt_sema) |sema| _ = try sema.resolveTypeLayout(ty);
+                assert(struct_obj.haveLayout());
+                return try struct_obj.backing_int_ty.bitSizeAdvanced(mod, opt_sema);
+            },
 
-                .union_type => |union_type| {
-                    if (opt_sema) |sema| _ = try sema.resolveTypeFields(ty);
-                    if (ty.containerLayout(mod) != .Packed) {
-                        return (try ty.abiSizeAdvanced(mod, strat)).scalar * 8;
-                    }
-                    const union_obj = mod.unionPtr(union_type.index);
-                    assert(union_obj.haveFieldTypes());
+            .anon_struct_type => {
+                if (opt_sema) |sema| _ = try sema.resolveTypeFields(ty);
+                return (try ty.abiSizeAdvanced(mod, strat)).scalar * 8;
+            },
 
-                    var size: u64 = 0;
-                    for (union_obj.fields.values()) |field| {
-                        size = @max(size, try bitSizeAdvanced(field.ty, mod, opt_sema));
-                    }
-                    return size;
-                },
-                .opaque_type => unreachable,
-                .enum_type => |enum_type| return bitSizeAdvanced(enum_type.tag_ty.toType(), mod, opt_sema),
+            .union_type => |union_type| {
+                if (opt_sema) |sema| _ = try sema.resolveTypeFields(ty);
+                if (ty.containerLayout(mod) != .Packed) {
+                    return (try ty.abiSizeAdvanced(mod, strat)).scalar * 8;
+                }
+                const union_obj = mod.unionPtr(union_type.index);
+                assert(union_obj.haveFieldTypes());
 
-                // values, not types
-                .undef => unreachable,
-                .un => unreachable,
-                .simple_value => unreachable,
-                .extern_func => unreachable,
-                .int => unreachable,
-                .float => unreachable,
-                .ptr => unreachable,
-                .opt => unreachable,
-                .enum_tag => unreachable,
-                .aggregate => unreachable,
+                var size: u64 = 0;
+                for (union_obj.fields.values()) |field| {
+                    size = @max(size, try bitSizeAdvanced(field.ty, mod, opt_sema));
+                }
+                return size;
             },
+            .opaque_type => unreachable,
+            .enum_type => |enum_type| return bitSizeAdvanced(enum_type.tag_ty.toType(), mod, opt_sema),
+
+            // values, not types
+            .undef => unreachable,
+            .un => unreachable,
+            .simple_value => unreachable,
+            .extern_func => unreachable,
+            .int => unreachable,
+            .float => unreachable,
+            .ptr => unreachable,
+            .opt => unreachable,
+            .enum_tag => unreachable,
+            .aggregate => unreachable,
         }
     }
 
@@ -2210,7 +1926,7 @@ pub const Type = struct {
                 return payload_ty.layoutIsResolved(mod);
             },
             .ErrorUnion => {
-                const payload_ty = ty.errorUnionPayload();
+                const payload_ty = ty.errorUnionPayload(mod);
                 return payload_ty.layoutIsResolved(mod);
             },
             else => return true,
@@ -2223,8 +1939,6 @@ pub const Type = struct {
                 .inferred_alloc_const,
                 .inferred_alloc_mut,
                 => true,
-
-                else => false,
             },
             else => return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
                 .ptr_type => |ptr_info| ptr_info.size == .One,
@@ -2245,8 +1959,6 @@ pub const Type = struct {
                 .inferred_alloc_const,
                 .inferred_alloc_mut,
                 => .One,
-
-                else => null,
             },
             else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
                 .ptr_type => |ptr_info| ptr_info.size,
@@ -2534,69 +2246,43 @@ pub const Type = struct {
     }
 
     /// Asserts that the type is an error union.
-    pub fn errorUnionPayload(ty: Type) Type {
-        return switch (ty.ip_index) {
-            .anyerror_void_error_union_type => Type.void,
-            .none => switch (ty.tag()) {
-                .error_union => ty.castTag(.error_union).?.data.payload,
-                else => unreachable,
-            },
-            else => @panic("TODO"),
-        };
+    pub fn errorUnionPayload(ty: Type, mod: *Module) Type {
+        return mod.intern_pool.indexToKey(ty.ip_index).error_union_type.payload_type.toType();
     }
 
-    pub fn errorUnionSet(ty: Type) Type {
-        return switch (ty.ip_index) {
-            .anyerror_void_error_union_type => Type.anyerror,
-            .none => switch (ty.tag()) {
-                .error_union => ty.castTag(.error_union).?.data.error_set,
-                else => unreachable,
-            },
-            else => @panic("TODO"),
-        };
+    /// Asserts that the type is an error union.
+    pub fn errorUnionSet(ty: Type, mod: *Module) Type {
+        return mod.intern_pool.indexToKey(ty.ip_index).error_union_type.error_set_type.toType();
     }
 
     /// Returns false for unresolved inferred error sets.
-    pub fn errorSetIsEmpty(ty: Type, mod: *const Module) bool {
-        switch (ty.ip_index) {
-            .none => switch (ty.tag()) {
-                .error_set_inferred => {
-                    const inferred_error_set = ty.castTag(.error_set_inferred).?.data;
+    pub fn errorSetIsEmpty(ty: Type, mod: *Module) bool {
+        return switch (ty.ip_index) {
+            .anyerror_type => false,
+            else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+                .error_set_type => |error_set_type| error_set_type.names.len == 0,
+                .inferred_error_set_type => |index| {
+                    const inferred_error_set = mod.inferredErrorSetPtr(index);
                     // Can't know for sure.
                     if (!inferred_error_set.is_resolved) return false;
                     if (inferred_error_set.is_anyerror) return false;
                     return inferred_error_set.errors.count() == 0;
                 },
-                .error_set_single => return false,
-                .error_set => {
-                    const err_set_obj = ty.castTag(.error_set).?.data;
-                    return err_set_obj.names.count() == 0;
-                },
-                .error_set_merged => {
-                    const name_map = ty.castTag(.error_set_merged).?.data;
-                    return name_map.count() == 0;
-                },
                 else => unreachable,
             },
-            .anyerror_type => return false,
-            else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
-                else => @panic("TODO"),
-            },
-        }
+        };
     }
 
     /// Returns true if it is an error set that includes anyerror, false otherwise.
     /// Note that the result may be a false negative if the type did not get error set
     /// resolution prior to this call.
-    pub fn isAnyError(ty: Type) bool {
+    pub fn isAnyError(ty: Type, mod: *Module) bool {
         return switch (ty.ip_index) {
-            .none => switch (ty.tag()) {
-                .error_set_inferred => ty.castTag(.error_set_inferred).?.data.is_anyerror,
+            .anyerror_type => true,
+            else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+                .inferred_error_set_type => |i| mod.inferredErrorSetPtr(i).is_anyerror,
                 else => false,
             },
-            .anyerror_type => true,
-            // TODO handle error_set_inferred here
-            else => false,
         };
     }
 
@@ -2610,30 +2296,50 @@ pub const Type = struct {
     /// Returns whether ty, which must be an error set, includes an error `name`.
     /// Might return a false negative if `ty` is an inferred error set and not fully
     /// resolved yet.
-    pub fn errorSetHasField(ty: Type, name: []const u8) bool {
-        if (ty.isAnyError()) {
-            return true;
-        }
-
-        switch (ty.tag()) {
-            .error_set_single => {
-                const data = ty.castTag(.error_set_single).?.data;
-                return std.mem.eql(u8, data, name);
-            },
-            .error_set_inferred => {
-                const data = ty.castTag(.error_set_inferred).?.data;
-                return data.errors.contains(name);
-            },
-            .error_set_merged => {
-                const data = ty.castTag(.error_set_merged).?.data;
-                return data.contains(name);
+    pub fn errorSetHasFieldIp(
+        ip: *const InternPool,
+        ty: InternPool.Index,
+        name: InternPool.NullTerminatedString,
+    ) bool {
+        return switch (ty) {
+            .anyerror_type => true,
+            else => switch (ip.indexToKey(ty)) {
+                .error_set_type => |error_set_type| {
+                    return error_set_type.nameIndex(ip, name) != null;
+                },
+                .inferred_error_set_type => |index| {
+                    const ies = ip.inferredErrorSetPtrConst(index);
+                    if (ies.is_anyerror) return true;
+                    return ies.errors.contains(name);
+                },
+                else => unreachable,
             },
-            .error_set => {
-                const data = ty.castTag(.error_set).?.data;
-                return data.names.contains(name);
+        };
+    }
+
+    /// Returns whether ty, which must be an error set, includes an error `name`.
+    /// Might return a false negative if `ty` is an inferred error set and not fully
+    /// resolved yet.
+    pub fn errorSetHasField(ty: Type, name: []const u8, mod: *Module) bool {
+        const ip = &mod.intern_pool;
+        return switch (ty.ip_index) {
+            .anyerror_type => true,
+            else => switch (ip.indexToKey(ty.ip_index)) {
+                .error_set_type => |error_set_type| {
+                    // If the string is not interned, then the field certainly is not present.
+                    const field_name_interned = ip.getString(name).unwrap() orelse return false;
+                    return error_set_type.nameIndex(ip, field_name_interned) != null;
+                },
+                .inferred_error_set_type => |index| {
+                    const ies = ip.inferredErrorSetPtr(index);
+                    if (ies.is_anyerror) return true;
+                    // If the string is not interned, then the field certainly is not present.
+                    const field_name_interned = ip.getString(name).unwrap() orelse return false;
+                    return ies.errors.contains(field_name_interned);
+                },
+                else => unreachable,
             },
-            else => unreachable,
-        }
+        };
     }
 
     /// Asserts the type is an array or vector or struct.
@@ -2727,14 +2433,6 @@ pub const Type = struct {
         var ty = starting_ty;
 
         while (true) switch (ty.ip_index) {
-            .none => switch (ty.tag()) {
-                .error_set, .error_set_single, .error_set_inferred, .error_set_merged => {
-                    // TODO revisit this when error sets support custom int types
-                    return .{ .signedness = .unsigned, .bits = 16 };
-                },
-
-                else => unreachable,
-            },
             .anyerror_type => {
                 // TODO revisit this when error sets support custom int types
                 return .{ .signedness = .unsigned, .bits = 16 };
@@ -2760,6 +2458,9 @@ pub const Type = struct {
                 .enum_type => |enum_type| ty = enum_type.tag_ty.toType(),
                 .vector_type => |vector_type| ty = vector_type.child.toType(),
 
+                // TODO revisit this when error sets support custom int types
+                .error_set_type, .inferred_error_set_type => return .{ .signedness = .unsigned, .bits = 16 },
+
                 .anon_struct_type => unreachable,
 
                 .ptr_type => unreachable,
@@ -2932,13 +2633,6 @@ pub const Type = struct {
             .empty_struct_type => return Value.empty_struct,
 
             .none => switch (ty.tag()) {
-                .error_union,
-                .error_set_single,
-                .error_set,
-                .error_set_merged,
-                .error_set_inferred,
-                => return null,
-
                 .inferred_alloc_const => unreachable,
                 .inferred_alloc_mut => unreachable,
             },
@@ -2955,6 +2649,8 @@ pub const Type = struct {
                 .error_union_type,
                 .func_type,
                 .anyframe_type,
+                .error_set_type,
+                .inferred_error_set_type,
                 => return null,
 
                 .array_type => |array_type| {
@@ -3130,18 +2826,6 @@ pub const Type = struct {
         return switch (ty.ip_index) {
             .empty_struct_type => false,
 
-            .none => switch (ty.tag()) {
-                .error_set,
-                .error_set_single,
-                .error_set_inferred,
-                .error_set_merged,
-                => false,
-
-                .inferred_alloc_mut => unreachable,
-                .inferred_alloc_const => unreachable,
-
-                .error_union => return ty.errorUnionPayload().comptimeOnly(mod),
-            },
             else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
                 .int_type => false,
                 .ptr_type => |ptr_type| {
@@ -3160,6 +2844,11 @@ pub const Type = struct {
                 .vector_type => |vector_type| vector_type.child.toType().comptimeOnly(mod),
                 .opt_type => |child| child.toType().comptimeOnly(mod),
                 .error_union_type => |error_union_type| error_union_type.payload_type.toType().comptimeOnly(mod),
+
+                .error_set_type,
+                .inferred_error_set_type,
+                => false,
+
                 // These are function bodies, not function pointers.
                 .func_type => true,
 
@@ -3418,17 +3107,11 @@ pub const Type = struct {
     }
 
     // Asserts that `ty` is an error set and not `anyerror`.
-    pub fn errorSetNames(ty: Type) []const []const u8 {
-        return switch (ty.tag()) {
-            .error_set_single => blk: {
-                // Work around coercion problems
-                const tmp: *const [1][]const u8 = &ty.castTag(.error_set_single).?.data;
-                break :blk tmp;
-            },
-            .error_set_merged => ty.castTag(.error_set_merged).?.data.keys(),
-            .error_set => ty.castTag(.error_set).?.data.names.keys(),
-            .error_set_inferred => {
-                const inferred_error_set = ty.castTag(.error_set_inferred).?.data;
+    pub fn errorSetNames(ty: Type, mod: *Module) []const InternPool.NullTerminatedString {
+        return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+            .error_set_type => |x| x.names,
+            .inferred_error_set_type => |index| {
+                const inferred_error_set = mod.inferredErrorSetPtr(index);
                 assert(inferred_error_set.is_resolved);
                 assert(!inferred_error_set.is_anyerror);
                 return inferred_error_set.errors.keys();
@@ -3437,26 +3120,6 @@ pub const Type = struct {
         };
     }
 
-    /// Merge lhs with rhs.
-    /// Asserts that lhs and rhs are both error sets and are resolved.
-    pub fn errorSetMerge(lhs: Type, arena: Allocator, rhs: Type) !Type {
-        const lhs_names = lhs.errorSetNames();
-        const rhs_names = rhs.errorSetNames();
-        var names: Module.ErrorSet.NameMap = .{};
-        try names.ensureUnusedCapacity(arena, lhs_names.len);
-        for (lhs_names) |name| {
-            names.putAssumeCapacityNoClobber(name, {});
-        }
-        for (rhs_names) |name| {
-            try names.put(arena, name, {});
-        }
-
-        // names must be sorted
-        Module.ErrorSet.sortNames(&names);
-
-        return try Tag.error_set_merged.create(arena, names);
-    }
-
     pub fn enumFields(ty: Type, mod: *Module) []const InternPool.NullTerminatedString {
         return mod.intern_pool.indexToKey(ty.ip_index).enum_type.names;
     }
@@ -3748,30 +3411,19 @@ pub const Type = struct {
     }
 
     pub fn declSrcLocOrNull(ty: Type, mod: *Module) ?Module.SrcLoc {
-        switch (ty.ip_index) {
-            .empty_struct_type => return null,
-            .none => switch (ty.tag()) {
-                .error_set => {
-                    const error_set = ty.castTag(.error_set).?.data;
-                    return error_set.srcLoc(mod);
-                },
-
-                else => return null,
+        return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+            .struct_type => |struct_type| {
+                const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
+                return struct_obj.srcLoc(mod);
             },
-            else => return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
-                .struct_type => |struct_type| {
-                    const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
-                    return struct_obj.srcLoc(mod);
-                },
-                .union_type => |union_type| {
-                    const union_obj = mod.unionPtr(union_type.index);
-                    return union_obj.srcLoc(mod);
-                },
-                .opaque_type => |opaque_type| mod.opaqueSrcLoc(opaque_type),
-                .enum_type => |enum_type| mod.declPtr(enum_type.decl).srcLoc(mod),
-                else => null,
+            .union_type => |union_type| {
+                const union_obj = mod.unionPtr(union_type.index);
+                return union_obj.srcLoc(mod);
             },
-        }
+            .opaque_type => |opaque_type| mod.opaqueSrcLoc(opaque_type),
+            .enum_type => |enum_type| mod.declPtr(enum_type.decl).srcLoc(mod),
+            else => null,
+        };
     }
 
     pub fn getOwnerDecl(ty: Type, mod: *Module) Module.Decl.Index {
@@ -3779,39 +3431,25 @@ pub const Type = struct {
     }
 
     pub fn getOwnerDeclOrNull(ty: Type, mod: *Module) ?Module.Decl.Index {
-        switch (ty.ip_index) {
-            .none => switch (ty.tag()) {
-                .error_set => {
-                    const error_set = ty.castTag(.error_set).?.data;
-                    return error_set.owner_decl;
-                },
-
-                else => return null,
+        return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+            .struct_type => |struct_type| {
+                const struct_obj = mod.structPtrUnwrap(struct_type.index) orelse return null;
+                return struct_obj.owner_decl;
             },
-            else => return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
-                .struct_type => |struct_type| {
-                    const struct_obj = mod.structPtrUnwrap(struct_type.index) orelse return null;
-                    return struct_obj.owner_decl;
-                },
-                .union_type => |union_type| {
-                    const union_obj = mod.unionPtr(union_type.index);
-                    return union_obj.owner_decl;
-                },
-                .opaque_type => |opaque_type| opaque_type.decl,
-                .enum_type => |enum_type| enum_type.decl,
-                else => null,
+            .union_type => |union_type| {
+                const union_obj = mod.unionPtr(union_type.index);
+                return union_obj.owner_decl;
             },
-        }
+            .opaque_type => |opaque_type| opaque_type.decl,
+            .enum_type => |enum_type| enum_type.decl,
+            else => null,
+        };
     }
 
     pub fn isGenericPoison(ty: Type) bool {
         return ty.ip_index == .generic_poison_type;
     }
 
-    pub fn isBoundFn(ty: Type) bool {
-        return ty.ip_index == .none and ty.tag() == .bound_fn;
-    }
-
     /// This enum does not directly correspond to `std.builtin.TypeId` because
     /// it has extra enum tags in it, as a way of using less memory. For example,
     /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
@@ -3827,54 +3465,8 @@ pub const Type = struct {
         inferred_alloc_const, // See last_no_payload_tag below.
         // After this, the tag requires a payload.
 
-        error_union,
-        error_set,
-        error_set_single,
-        /// The type is the inferred error set of a specific function.
-        error_set_inferred,
-        error_set_merged,
-
         pub const last_no_payload_tag = Tag.inferred_alloc_const;
         pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
-
-        pub fn Type(comptime t: Tag) type {
-            return switch (t) {
-                .inferred_alloc_const,
-                .inferred_alloc_mut,
-                => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
-
-                .error_set => Payload.ErrorSet,
-                .error_set_inferred => Payload.ErrorSetInferred,
-                .error_set_merged => Payload.ErrorSetMerged,
-
-                .error_union => Payload.ErrorUnion,
-                .error_set_single => Payload.Name,
-            };
-        }
-
-        pub fn init(comptime t: Tag) file_struct.Type {
-            comptime std.debug.assert(@enumToInt(t) < Tag.no_payload_count);
-            return file_struct.Type{
-                .ip_index = .none,
-                .legacy = .{ .tag_if_small_enough = t },
-            };
-        }
-
-        pub fn create(comptime t: Tag, ally: Allocator, data: Data(t)) error{OutOfMemory}!file_struct.Type {
-            const p = try ally.create(t.Type());
-            p.* = .{
-                .base = .{ .tag = t },
-                .data = data,
-            };
-            return file_struct.Type{
-                .ip_index = .none,
-                .legacy = .{ .ptr_otherwise = &p.base },
-            };
-        }
-
-        pub fn Data(comptime t: Tag) type {
-            return std.meta.fieldInfo(t.Type(), .data).type;
-        }
     };
 
     pub fn isTuple(ty: Type, mod: *Module) bool {
@@ -3928,37 +3520,6 @@ pub const Type = struct {
     pub const Payload = struct {
         tag: Tag,
 
-        pub const Len = struct {
-            base: Payload,
-            data: u64,
-        };
-
-        pub const Bits = struct {
-            base: Payload,
-            data: u16,
-        };
-
-        pub const ErrorSet = struct {
-            pub const base_tag = Tag.error_set;
-
-            base: Payload = Payload{ .tag = base_tag },
-            data: *Module.ErrorSet,
-        };
-
-        pub const ErrorSetMerged = struct {
-            pub const base_tag = Tag.error_set_merged;
-
-            base: Payload = Payload{ .tag = base_tag },
-            data: Module.ErrorSet.NameMap,
-        };
-
-        pub const ErrorSetInferred = struct {
-            pub const base_tag = Tag.error_set_inferred;
-
-            base: Payload = Payload{ .tag = base_tag },
-            data: *Module.Fn.InferredErrorSet,
-        };
-
         /// TODO: remove this data structure since we have `InternPool.Key.PtrType`.
         pub const Pointer = struct {
             data: Data,
@@ -4010,27 +3571,6 @@ pub const Type = struct {
                 }
             };
         };
-
-        pub const ErrorUnion = struct {
-            pub const base_tag = Tag.error_union;
-
-            base: Payload = Payload{ .tag = base_tag },
-            data: struct {
-                error_set: Type,
-                payload: Type,
-            },
-        };
-
-        pub const Decl = struct {
-            base: Payload,
-            data: *Module.Decl,
-        };
-
-        pub const Name = struct {
-            base: Payload,
-            /// memory is owned by `Module`
-            data: []const u8,
-        };
     };
 
     pub const @"u1": Type = .{ .ip_index = .u1_type, .legacy = undefined };
@@ -4164,19 +3704,6 @@ pub const Type = struct {
         return mod.optionalType(child_type.ip_index);
     }
 
-    pub fn errorUnion(
-        arena: Allocator,
-        error_set: Type,
-        payload: Type,
-        mod: *Module,
-    ) Allocator.Error!Type {
-        assert(error_set.zigTypeTag(mod) == .ErrorSet);
-        return Type.Tag.error_union.create(arena, .{
-            .error_set = error_set,
-            .payload = payload,
-        });
-    }
-
     pub fn smallestUnsignedBits(max: u64) u16 {
         if (max == 0) return 0;
         const base = std.math.log2(max);
src/TypedValue.zig
@@ -27,13 +27,13 @@ pub const Managed = struct {
 /// Assumes arena allocation. Does a recursive copy.
 pub fn copy(self: TypedValue, arena: Allocator) error{OutOfMemory}!TypedValue {
     return TypedValue{
-        .ty = try self.ty.copy(arena),
+        .ty = self.ty,
         .val = try self.val.copy(arena),
     };
 }
 
 pub fn eql(a: TypedValue, b: TypedValue, mod: *Module) bool {
-    if (!a.ty.eql(b.ty, mod)) return false;
+    if (a.ty.ip_index != b.ty.ip_index) return false;
     return a.val.eql(b.val, a.ty, mod);
 }
 
@@ -286,7 +286,7 @@ pub fn print(
             .@"error" => return writer.print("error.{s}", .{val.castTag(.@"error").?.data.name}),
             .eu_payload => {
                 val = val.castTag(.eu_payload).?.data;
-                ty = ty.errorUnionPayload();
+                ty = ty.errorUnionPayload(mod);
             },
             .opt_payload => {
                 val = val.castTag(.opt_payload).?.data;
src/value.zig
@@ -260,7 +260,7 @@ pub const Value = struct {
                 const new_payload = try arena.create(Payload.Ty);
                 new_payload.* = .{
                     .base = payload.base,
-                    .data = try payload.data.copy(arena),
+                    .data = payload.data,
                 };
                 return Value{
                     .ip_index = .none,
@@ -281,7 +281,7 @@ pub const Value = struct {
                     .base = payload.base,
                     .data = .{
                         .container_ptr = try payload.data.container_ptr.copy(arena),
-                        .container_ty = try payload.data.container_ty.copy(arena),
+                        .container_ty = payload.data.container_ty,
                     },
                 };
                 return Value{
@@ -296,7 +296,7 @@ pub const Value = struct {
                     .base = payload.base,
                     .data = .{
                         .field_val = try payload.data.field_val.copy(arena),
-                        .field_ty = try payload.data.field_ty.copy(arena),
+                        .field_ty = payload.data.field_ty,
                     },
                 };
                 return Value{
@@ -311,7 +311,7 @@ pub const Value = struct {
                     .base = payload.base,
                     .data = .{
                         .array_ptr = try payload.data.array_ptr.copy(arena),
-                        .elem_ty = try payload.data.elem_ty.copy(arena),
+                        .elem_ty = payload.data.elem_ty,
                         .index = payload.data.index,
                     },
                 };
@@ -327,7 +327,7 @@ pub const Value = struct {
                     .base = payload.base,
                     .data = .{
                         .container_ptr = try payload.data.container_ptr.copy(arena),
-                        .container_ty = try payload.data.container_ty.copy(arena),
+                        .container_ty = payload.data.container_ty,
                         .field_index = payload.data.field_index,
                     },
                 };
@@ -1870,7 +1870,7 @@ pub const Value = struct {
             .eu_payload => {
                 const a_payload = a.castTag(.eu_payload).?.data;
                 const b_payload = b.castTag(.eu_payload).?.data;
-                const payload_ty = ty.errorUnionPayload();
+                const payload_ty = ty.errorUnionPayload(mod);
                 return eqlAdvanced(a_payload, payload_ty, b_payload, payload_ty, mod, opt_sema);
             },
             .eu_payload_ptr => {
@@ -2163,14 +2163,14 @@ pub const Value = struct {
             .ErrorUnion => {
                 if (val.tag() == .@"error") {
                     std.hash.autoHash(hasher, false); // error
-                    const sub_ty = ty.errorUnionSet();
+                    const sub_ty = ty.errorUnionSet(mod);
                     val.hash(sub_ty, hasher, mod);
                     return;
                 }
 
                 if (val.castTag(.eu_payload)) |payload| {
                     std.hash.autoHash(hasher, true); // payload
-                    const sub_ty = ty.errorUnionPayload();
+                    const sub_ty = ty.errorUnionPayload(mod);
                     payload.data.hash(sub_ty, hasher, mod);
                     return;
                 } else unreachable;
@@ -2272,7 +2272,7 @@ pub const Value = struct {
                 payload.data.hashUncoerced(child_ty, hasher, mod);
             } else std.hash.autoHash(hasher, std.builtin.TypeId.Null),
             .ErrorSet, .ErrorUnion => if (val.getError()) |err| hasher.update(err) else {
-                const pl_ty = ty.errorUnionPayload();
+                const pl_ty = ty.errorUnionPayload(mod);
                 val.castTag(.eu_payload).?.data.hashUncoerced(pl_ty, hasher, mod);
             },
             .Enum, .EnumLiteral, .Union => {