Commit 8ec6f730ef

mlugg <mlugg@mlugg.co.uk>
2024-02-17 04:34:47
compiler: represent captures directly in InternPool
These were previously associated with the type's namespace, but we need to store them directly in the InternPool for #18816.
1 parent 975b859
src/InternPool.zig
@@ -501,6 +501,42 @@ pub const OptionalNullTerminatedString = enum(u32) {
     }
 };
 
+/// A single value captured in the closure of a namespace type. This is not a plain
+/// `Index` because we must differentiate between runtime-known values (where we
+/// store the type) and comptime-known values (where we store the value).
+pub const CaptureValue = packed struct(u32) {
+    tag: enum { @"comptime", runtime },
+    idx: u31,
+
+    pub fn wrap(val: Unwrapped) CaptureValue {
+        return switch (val) {
+            .@"comptime" => |i| .{ .tag = .@"comptime", .idx = @intCast(@intFromEnum(i)) },
+            .runtime => |i| .{ .tag = .runtime, .idx = @intCast(@intFromEnum(i)) },
+        };
+    }
+    pub fn unwrap(val: CaptureValue) Unwrapped {
+        return switch (val.tag) {
+            .@"comptime" => .{ .@"comptime" = @enumFromInt(val.idx) },
+            .runtime => .{ .runtime = @enumFromInt(val.idx) },
+        };
+    }
+
+    pub const Unwrapped = union(enum) {
+        /// Index refers to the value.
+        @"comptime": Index,
+        /// Index refers to the type.
+        runtime: Index,
+    };
+
+    pub const Slice = struct {
+        start: u32,
+        len: u32,
+        pub fn get(slice: Slice, ip: *const InternPool) []CaptureValue {
+            return @ptrCast(ip.extra.items[slice.start..][0..slice.len]);
+        }
+    };
+};
+
 pub const Key = union(enum) {
     int_type: IntType,
     ptr_type: PtrType,
@@ -707,6 +743,7 @@ pub const Key = union(enum) {
         /// This may be updated via `setTagType` later.
         tag_ty: Index = .none,
         zir_index: TrackedInst.Index.Optional,
+        captures: []const CaptureValue,
 
         pub fn toEnumType(self: @This()) LoadedEnumType {
             if (true) @compileError("AHHHH");
@@ -1660,6 +1697,7 @@ pub const LoadedUnionType = struct {
     field_aligns: Alignment.Slice,
     /// Index of the union_decl ZIR instruction.
     zir_index: TrackedInst.Index.Optional,
+    captures: CaptureValue.Slice,
 
     pub const RuntimeTag = enum(u2) {
         none,
@@ -1791,24 +1829,47 @@ pub const LoadedUnionType = struct {
 };
 
 pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType {
-    const extra_index = ip.items.items(.data)[@intFromEnum(index)];
-    const type_union = ip.extraDataTrail(Tag.TypeUnion, extra_index);
+    const data = ip.items.items(.data)[@intFromEnum(index)];
+    const type_union = ip.extraDataTrail(Tag.TypeUnion, data);
     const fields_len = type_union.data.fields_len;
 
+    var extra_index = type_union.end;
+    const captures_len = if (type_union.data.flags.any_captures) c: {
+        const len = ip.extra.items[extra_index];
+        extra_index += 1;
+        break :c len;
+    } else 0;
+
+    const captures: CaptureValue.Slice = .{
+        .start = extra_index,
+        .len = captures_len,
+    };
+    extra_index += captures_len;
+
+    const field_types: Index.Slice = .{
+        .start = extra_index,
+        .len = fields_len,
+    };
+    extra_index += fields_len;
+
+    const field_aligns: Alignment.Slice = if (type_union.data.flags.any_aligned_fields) a: {
+        const a: Alignment.Slice = .{
+            .start = extra_index,
+            .len = fields_len,
+        };
+        extra_index += std.math.divCeil(u32, fields_len, 4) catch unreachable;
+        break :a a;
+    } else .{ .start = 0, .len = 0 };
+
     return .{
-        .extra_index = extra_index,
+        .extra_index = data,
         .decl = type_union.data.decl,
         .namespace = type_union.data.namespace,
         .enum_tag_ty = type_union.data.tag_ty,
-        .field_types = .{
-            .start = type_union.end,
-            .len = fields_len,
-        },
-        .field_aligns = .{
-            .start = type_union.end + fields_len,
-            .len = if (type_union.data.flags.any_aligned_fields) fields_len else 0,
-        },
+        .field_types = field_types,
+        .field_aligns = field_aligns,
         .zir_index = type_union.data.zir_index,
+        .captures = captures,
     };
 }
 
@@ -1830,6 +1891,7 @@ pub const LoadedStructType = struct {
     comptime_bits: ComptimeBits,
     offsets: Offsets,
     names_map: OptionalMapIndex,
+    captures: CaptureValue.Slice,
 
     pub const ComptimeBits = struct {
         start: u32,
@@ -2162,10 +2224,26 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType {
                 .comptime_bits = .{ .start = 0, .len = 0 },
                 .offsets = .{ .start = 0, .len = 0 },
                 .names_map = .none,
+                .captures = .{ .start = 0, .len = 0 },
             };
             const extra = ip.extraDataTrail(Tag.TypeStruct, item.data);
             const fields_len = extra.data.fields_len;
-            var extra_index = extra.end + fields_len; // skip field types
+            var extra_index = extra.end;
+            const captures_len = if (extra.data.flags.any_captures) c: {
+                const len = ip.extra.items[extra_index];
+                extra_index += 1;
+                break :c len;
+            } else 0;
+            const captures: CaptureValue.Slice = .{
+                .start = extra_index,
+                .len = captures_len,
+            };
+            extra_index += captures_len;
+            const field_types: Index.Slice = .{
+                .start = extra_index,
+                .len = fields_len,
+            };
+            extra_index += fields_len;
             const names_map: OptionalMapIndex, const names: NullTerminatedString.Slice = if (!extra.data.flags.is_tuple) n: {
                 const names_map: OptionalMapIndex = @enumFromInt(ip.extra.items[extra_index]);
                 extra_index += 1;
@@ -2211,42 +2289,64 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType {
                 .zir_index = extra.data.zir_index,
                 .layout = if (extra.data.flags.is_extern) .Extern else .Auto,
                 .field_names = names,
-                .field_types = .{ .start = extra.end, .len = fields_len },
+                .field_types = field_types,
                 .field_inits = inits,
                 .field_aligns = aligns,
                 .runtime_order = runtime_order,
                 .comptime_bits = comptime_bits,
                 .offsets = offsets,
                 .names_map = names_map,
+                .captures = captures,
             };
         },
         .type_struct_packed, .type_struct_packed_inits => {
             const extra = ip.extraDataTrail(Tag.TypeStructPacked, item.data);
             const has_inits = item.tag == .type_struct_packed_inits;
             const fields_len = extra.data.fields_len;
+            var extra_index = extra.end;
+            const captures_len = if (extra.data.flags.any_captures) c: {
+                const len = ip.extra.items[extra_index];
+                extra_index += 1;
+                break :c len;
+            } else 0;
+            const captures: CaptureValue.Slice = .{
+                .start = extra_index,
+                .len = captures_len,
+            };
+            extra_index += captures_len;
+            const field_types: Index.Slice = .{
+                .start = extra_index,
+                .len = fields_len,
+            };
+            extra_index += fields_len;
+            const field_names: NullTerminatedString.Slice = .{
+                .start = extra_index,
+                .len = fields_len,
+            };
+            extra_index += fields_len;
+            const field_inits: Index.Slice = if (has_inits) inits: {
+                const i: Index.Slice = .{
+                    .start = extra_index,
+                    .len = fields_len,
+                };
+                extra_index += fields_len;
+                break :inits i;
+            } else .{ .start = 0, .len = 0 };
             return .{
                 .extra_index = item.data,
                 .decl = extra.data.decl.toOptional(),
                 .namespace = extra.data.namespace,
                 .zir_index = extra.data.zir_index,
                 .layout = .Packed,
-                .field_names = .{
-                    .start = extra.end + fields_len,
-                    .len = fields_len,
-                },
-                .field_types = .{
-                    .start = extra.end,
-                    .len = fields_len,
-                },
-                .field_inits = if (has_inits) .{
-                    .start = extra.end + 2 * fields_len,
-                    .len = fields_len,
-                } else .{ .start = 0, .len = 0 },
+                .field_names = field_names,
+                .field_types = field_types,
+                .field_inits = field_inits,
                 .field_aligns = .{ .start = 0, .len = 0 },
                 .runtime_order = .{ .start = 0, .len = 0 },
                 .comptime_bits = .{ .start = 0, .len = 0 },
                 .offsets = .{ .start = 0, .len = 0 },
                 .names_map = extra.data.names_map.toOptional(),
+                .captures = captures,
             };
         },
         else => unreachable,
@@ -2273,6 +2373,7 @@ const LoadedEnumType = struct {
     /// This is guaranteed to not be `.none` if explicit values are provided.
     values_map: OptionalMapIndex,
     zir_index: TrackedInst.Index.Optional,
+    captures: CaptureValue.Slice,
 
     pub const TagMode = enum {
         /// The integer tag type was auto-numbered by zig.
@@ -2332,7 +2433,7 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType {
                 .namespace = extra.data.namespace,
                 .tag_ty = extra.data.int_tag_type,
                 .names = .{
-                    .start = @intCast(extra.end),
+                    .start = @intCast(extra.end + extra.data.captures_len),
                     .len = extra.data.fields_len,
                 },
                 .values = .{ .start = 0, .len = 0 },
@@ -2340,6 +2441,10 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType {
                 .names_map = extra.data.names_map,
                 .values_map = .none,
                 .zir_index = extra.data.zir_index,
+                .captures = .{
+                    .start = @intCast(extra.end),
+                    .len = extra.data.captures_len,
+                },
             };
         },
         .type_enum_explicit, .type_enum_nonexhaustive => {
@@ -2349,11 +2454,11 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType {
                 .namespace = extra.data.namespace,
                 .tag_ty = extra.data.int_tag_type,
                 .names = .{
-                    .start = @intCast(extra.end),
+                    .start = @intCast(extra.end + extra.data.captures_len),
                     .len = extra.data.fields_len,
                 },
                 .values = .{
-                    .start = @intCast(extra.end + extra.data.fields_len),
+                    .start = @intCast(extra.end + extra.data.captures_len + extra.data.fields_len),
                     .len = if (extra.data.values_map != .none) extra.data.fields_len else 0,
                 },
                 .tag_mode = switch (item.tag) {
@@ -2364,6 +2469,10 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType {
                 .names_map = extra.data.names_map,
                 .values_map = extra.data.values_map,
                 .zir_index = extra.data.zir_index,
+                .captures = .{
+                    .start = @intCast(extra.end),
+                    .len = extra.data.captures_len,
+                },
             };
         },
         else => unreachable,
@@ -2378,12 +2487,22 @@ pub const LoadedOpaqueType = struct {
     namespace: NamespaceIndex,
     /// The index of the `opaque_decl` instruction.
     zir_index: TrackedInst.Index.Optional,
+    captures: CaptureValue.Slice,
 };
 
 pub fn loadOpaqueType(ip: *const InternPool, index: Index) LoadedOpaqueType {
     assert(ip.items.items(.tag)[@intFromEnum(index)] == .type_opaque);
     const extra_index = ip.items.items(.data)[@intFromEnum(index)];
-    return ip.extraData(LoadedOpaqueType, extra_index);
+    const extra = ip.extraDataTrail(Tag.TypeOpaque, extra_index);
+    return .{
+        .decl = extra.data.decl,
+        .namespace = extra.data.namespace,
+        .zir_index = extra.data.zir_index,
+        .captures = .{
+            .start = extra.end,
+            .len = extra.data.captures_len,
+        },
+    };
 }
 
 pub const Item = struct {
@@ -2601,7 +2720,7 @@ pub const Index = enum(u32) {
         type_enum_explicit: DataIsExtraIndexOfEnumExplicit,
         type_enum_nonexhaustive: DataIsExtraIndexOfEnumExplicit,
         simple_type: struct { data: SimpleType },
-        type_opaque: struct { data: *Key.OpaqueType },
+        type_opaque: struct { data: *Tag.TypeOpaque },
         type_struct: struct { data: *Tag.TypeStruct },
         type_struct_anon: DataIsExtraIndexOfTypeStructAnon,
         type_struct_packed: struct { data: *Tag.TypeStructPacked },
@@ -3036,7 +3155,7 @@ pub const Tag = enum(u8) {
     /// data is SimpleType enum value.
     simple_type,
     /// An opaque type.
-    /// data is index of Key.OpaqueType in extra.
+    /// data is index of Tag.TypeOpaque in extra.
     type_opaque,
     /// A non-packed struct type.
     /// data is 0 or extra index of `TypeStruct`.
@@ -3239,7 +3358,6 @@ pub const Tag = enum(u8) {
     memoized_call,
 
     const ErrorUnionType = Key.ErrorUnionType;
-    const OpaqueType = LoadedOpaqueType;
     const TypeValue = Key.TypeValue;
     const Error = Key.Error;
     const EnumTag = Key.EnumTag;
@@ -3266,7 +3384,7 @@ pub const Tag = enum(u8) {
             .type_enum_explicit => EnumExplicit,
             .type_enum_nonexhaustive => EnumExplicit,
             .simple_type => unreachable,
-            .type_opaque => OpaqueType,
+            .type_opaque => TypeOpaque,
             .type_struct => TypeStruct,
             .type_struct_anon => TypeStructAnon,
             .type_struct_packed, .type_struct_packed_inits => TypeStructPacked,
@@ -3424,8 +3542,10 @@ pub const Tag = enum(u8) {
     };
 
     /// Trailing:
-    /// 0. field type: Index for each field; declaration order
-    /// 1. field align: Alignment for each field; declaration order
+    /// 0. captures_len: u32 // if `any_captures`
+    /// 1. capture: CaptureValue // for each `captures_len`
+    /// 2. field type: Index for each field; declaration order
+    /// 3. field align: Alignment for each field; declaration order
     pub const TypeUnion = struct {
         flags: Flags,
         /// This could be provided through the tag type, but it is more convenient
@@ -3443,6 +3563,7 @@ pub const Tag = enum(u8) {
         zir_index: TrackedInst.Index.Optional,
 
         pub const Flags = packed struct(u32) {
+            any_captures: bool,
             runtime_tag: LoadedUnionType.RuntimeTag,
             /// If false, the field alignment trailing data is omitted.
             any_aligned_fields: bool,
@@ -3452,14 +3573,16 @@ pub const Tag = enum(u8) {
             assumed_runtime_bits: bool,
             assumed_pointer_aligned: bool,
             alignment: Alignment,
-            _: u14 = 0,
+            _: u13 = 0,
         };
     };
 
     /// Trailing:
-    /// 0. type: Index for each fields_len
-    /// 1. name: NullTerminatedString for each fields_len
-    /// 2. init: Index for each fields_len // if tag is type_struct_packed_inits
+    /// 0. captures_len: u32 // if `any_captures`
+    /// 1. capture: CaptureValue // for each `captures_len`
+    /// 2. type: Index for each fields_len
+    /// 3. name: NullTerminatedString for each fields_len
+    /// 4. init: Index for each fields_len // if tag is type_struct_packed_inits
     pub const TypeStructPacked = struct {
         decl: DeclIndex,
         zir_index: TrackedInst.Index.Optional,
@@ -3470,10 +3593,11 @@ pub const Tag = enum(u8) {
         flags: Flags,
 
         pub const Flags = packed struct(u32) {
+            any_captures: bool,
             /// Dependency loop detection when resolving field inits.
             field_inits_wip: bool,
             inits_resolved: bool,
-            _: u30 = 0,
+            _: u29 = 0,
         };
     };
 
@@ -3492,21 +3616,23 @@ pub const Tag = enum(u8) {
     /// than coming up with some other scheme for the data.
     ///
     /// Trailing:
-    /// 0. type: Index for each field in declared order
-    /// 1. if not is_tuple:
+    /// 0. captures_len: u32 // if `any_captures`
+    /// 1. capture: CaptureValue // for each `captures_len`
+    /// 2. type: Index for each field in declared order
+    /// 3. if not is_tuple:
     ///    names_map: MapIndex,
     ///    name: NullTerminatedString // for each field in declared order
-    /// 2. if any_default_inits:
+    /// 4. if any_default_inits:
     ///    init: Index // for each field in declared order
-    /// 3. if has_namespace:
+    /// 5. if has_namespace:
     ///    namespace: NamespaceIndex
-    /// 4. if any_aligned_fields:
+    /// 6. if any_aligned_fields:
     ///    align: Alignment // for each field in declared order
-    /// 5. if any_comptime_fields:
+    /// 7. if any_comptime_fields:
     ///    field_is_comptime_bits: u32 // minimal number of u32s needed, LSB is field 0
-    /// 6. if not is_extern:
+    /// 8. if not is_extern:
     ///    field_index: RuntimeOrder // for each field in runtime order
-    /// 7. field_offset: u32 // for each field in declared order, undef until layout_resolved
+    /// 9. field_offset: u32 // for each field in declared order, undef until layout_resolved
     pub const TypeStruct = struct {
         decl: DeclIndex,
         zir_index: TrackedInst.Index.Optional,
@@ -3515,6 +3641,7 @@ pub const Tag = enum(u8) {
         size: u32,
 
         pub const Flags = packed struct(u32) {
+            any_captures: bool,
             is_extern: bool,
             known_non_opv: bool,
             requires_comptime: RequiresComptime,
@@ -3544,9 +3671,21 @@ pub const Tag = enum(u8) {
             // which `layout_resolved` does not ensure.
             fully_resolved: bool,
 
-            _: u8 = 0,
+            _: u7 = 0,
         };
     };
+
+    /// Trailing:
+    /// 0. capture: CaptureValue // for each `captures_len`
+    pub const TypeOpaque = struct {
+        /// The opaque's owner Decl.
+        decl: DeclIndex,
+        /// Contains the declarations inside this opaque.
+        namespace: NamespaceIndex,
+        /// The index of the `opaque_decl` instruction.
+        zir_index: TrackedInst.Index.Optional,
+        captures_len: u32,
+    };
 };
 
 /// State that is mutable during semantic analysis. This data is not used for
@@ -3853,11 +3992,13 @@ pub const Array = struct {
 };
 
 /// Trailing:
-/// 0. field name: NullTerminatedString for each fields_len; declaration order
-/// 1. tag value: Index for each fields_len; declaration order
+/// 0. capture: CaptureValue // for each `captures_len`
+/// 1. field name: NullTerminatedString for each fields_len; declaration order
+/// 2. tag value: Index for each fields_len; declaration order
 pub const EnumExplicit = struct {
     /// The Decl that corresponds to the enum itself.
     decl: DeclIndex,
+    captures_len: u32,
     /// This may be `none` if there are no declarations.
     namespace: OptionalNamespaceIndex,
     /// An integer type which is used for the numerical value of the enum, which
@@ -3874,10 +4015,12 @@ pub const EnumExplicit = struct {
 };
 
 /// Trailing:
-/// 0. field name: NullTerminatedString for each fields_len; declaration order
+/// 0. capture: CaptureValue // for each `captures_len`
+/// 1. field name: NullTerminatedString for each fields_len; declaration order
 pub const EnumAuto = struct {
     /// The Decl that corresponds to the enum itself.
     decl: DeclIndex,
+    captures_len: u32,
     /// This may be `none` if there are no declarations.
     namespace: OptionalNamespaceIndex,
     /// An integer type which is used for the numerical value of the enum, which
@@ -4187,7 +4330,9 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
             .inferred_error_set_type = @enumFromInt(data),
         },
 
-        .type_opaque => .{ .opaque_type = ip.extraData(Key.OpaqueType, data) },
+        .type_opaque => .{ .opaque_type = .{
+            .decl = ip.extraData(Tag.TypeOpaque, data).decl,
+        } },
 
         .type_struct => .{ .struct_type = if (data == 0) .{
             .decl = .none,
@@ -5497,7 +5642,16 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
 }
 
 pub const UnionTypeInit = struct {
-    flags: Tag.TypeUnion.Flags,
+    flags: packed struct {
+        runtime_tag: LoadedUnionType.RuntimeTag,
+        any_aligned_fields: bool,
+        layout: std.builtin.Type.ContainerLayout,
+        status: LoadedUnionType.Status,
+        requires_comptime: RequiresComptime,
+        assumed_runtime_bits: bool,
+        assumed_pointer_aligned: bool,
+        alignment: Alignment,
+    },
     decl: DeclIndex,
     namespace: NamespaceIndex,
     zir_index: TrackedInst.Index.Optional,
@@ -5509,6 +5663,7 @@ pub const UnionTypeInit = struct {
     /// The logic for `any_aligned_fields` is asserted to have been done before
     /// calling this function.
     field_aligns: []const Alignment,
+    captures: []const CaptureValue,
 };
 
 pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocator.Error!Index {
@@ -5516,12 +5671,24 @@ pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocat
     const align_elements_len = if (ini.flags.any_aligned_fields) (ini.fields_len + 3) / 4 else 0;
     const align_element: u32 = @bitCast([1]u8{@intFromEnum(Alignment.none)} ** 4);
     try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeUnion).Struct.fields.len +
+        @intFromBool(ini.captures.len != 0) + // captures_len
+        ini.captures.len + // captures
         ini.fields_len + // field types
         align_elements_len);
     try ip.items.ensureUnusedCapacity(gpa, 1);
 
     const union_type_extra_index = ip.addExtraAssumeCapacity(Tag.TypeUnion{
-        .flags = ini.flags,
+        .flags = .{
+            .any_captures = ini.captures.len != 0,
+            .runtime_tag = ini.flags.runtime_tag,
+            .any_aligned_fields = ini.flags.any_aligned_fields,
+            .layout = ini.flags.layout,
+            .status = ini.flags.status,
+            .requires_comptime = ini.flags.requires_comptime,
+            .assumed_runtime_bits = ini.flags.assumed_runtime_bits,
+            .assumed_pointer_aligned = ini.flags.assumed_pointer_aligned,
+            .alignment = ini.flags.alignment,
+        },
         .fields_len = ini.fields_len,
         .size = std.math.maxInt(u32),
         .padding = std.math.maxInt(u32),
@@ -5531,6 +5698,11 @@ pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocat
         .zir_index = ini.zir_index,
     });
 
+    if (ini.captures.len != 0) {
+        ip.extra.appendAssumeCapacity(@intCast(ini.captures.len));
+        ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures));
+    }
+
     // field types
     if (ini.field_types.len > 0) {
         assert(ini.field_types.len == ini.fields_len);
@@ -5582,6 +5754,7 @@ pub const StructTypeInit = struct {
     any_default_inits: bool,
     inits_resolved: bool,
     any_aligned_fields: bool,
+    captures: []const CaptureValue,
 };
 
 pub fn getStructType(
@@ -5605,6 +5778,8 @@ pub fn getStructType(
         .Extern => true,
         .Packed => {
             try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeStructPacked).Struct.fields.len +
+                @intFromBool(ini.captures.len != 0) + // captures_len
+                ini.captures.len + // captures
                 ini.fields_len + // types
                 ini.fields_len + // names
                 ini.fields_len); // inits
@@ -5618,11 +5793,16 @@ pub fn getStructType(
                     .backing_int_ty = .none,
                     .names_map = names_map,
                     .flags = .{
+                        .any_captures = ini.captures.len != 0,
                         .field_inits_wip = false,
                         .inits_resolved = ini.inits_resolved,
                     },
                 }),
             });
+            if (ini.captures.len != 0) {
+                ip.extra.appendAssumeCapacity(@intCast(ini.captures.len));
+                ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures));
+            }
             ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len);
             ip.extra.appendNTimesAssumeCapacity(@intFromEnum(OptionalNullTerminatedString.none), ini.fields_len);
             if (ini.any_default_inits) {
@@ -5637,6 +5817,8 @@ pub fn getStructType(
     const comptime_elements_len = if (ini.any_comptime_fields) (ini.fields_len + 31) / 32 else 0;
 
     try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeStruct).Struct.fields.len +
+        @intFromBool(ini.captures.len != 0) + // captures_len
+        ini.captures.len + // captures
         (ini.fields_len * 5) + // types, names, inits, runtime order, offsets
         align_elements_len + comptime_elements_len +
         2); // names_map + namespace
@@ -5648,6 +5830,7 @@ pub fn getStructType(
             .fields_len = ini.fields_len,
             .size = std.math.maxInt(u32),
             .flags = .{
+                .any_captures = ini.captures.len != 0,
                 .is_extern = is_extern,
                 .known_non_opv = ini.known_non_opv,
                 .requires_comptime = ini.requires_comptime,
@@ -5669,6 +5852,10 @@ pub fn getStructType(
             },
         }),
     });
+    if (ini.captures.len != 0) {
+        ip.extra.appendAssumeCapacity(@intCast(ini.captures.len));
+        ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures));
+    }
     ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len);
     if (!ini.is_tuple) {
         ip.extra.appendAssumeCapacity(@intFromEnum(names_map));
@@ -6405,11 +6592,12 @@ fn getIncompleteEnumAuto(
     const names_map = try ip.addMap(gpa, enum_type.fields_len);
 
     const extra_fields_len: u32 = @typeInfo(EnumAuto).Struct.fields.len;
-    try ip.extra.ensureUnusedCapacity(gpa, extra_fields_len + enum_type.fields_len);
+    try ip.extra.ensureUnusedCapacity(gpa, extra_fields_len + enum_type.captures.len + enum_type.fields_len);
     try ip.items.ensureUnusedCapacity(gpa, 1);
 
     const extra_index = ip.addExtraAssumeCapacity(EnumAuto{
         .decl = enum_type.decl,
+        .captures_len = @intCast(enum_type.captures.len),
         .namespace = enum_type.namespace,
         .int_tag_type = int_tag_type,
         .names_map = names_map,
@@ -6421,6 +6609,7 @@ fn getIncompleteEnumAuto(
         .tag = .type_enum_auto,
         .data = extra_index,
     });
+    ip.extra.appendSliceAssumeCapacity(@ptrCast(enum_type.captures));
     ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), enum_type.fields_len);
     return .{
         .index = @enumFromInt(ip.items.len - 1),
@@ -6455,11 +6644,12 @@ fn getIncompleteEnumExplicit(
         if (enum_type.has_values) enum_type.fields_len else 0;
 
     const extra_fields_len: u32 = @typeInfo(EnumExplicit).Struct.fields.len;
-    try ip.extra.ensureUnusedCapacity(gpa, extra_fields_len + reserved_len);
+    try ip.extra.ensureUnusedCapacity(gpa, extra_fields_len + enum_type.captures.len + reserved_len);
     try ip.items.ensureUnusedCapacity(gpa, 1);
 
     const extra_index = ip.addExtraAssumeCapacity(EnumExplicit{
         .decl = enum_type.decl,
+        .captures_len = @intCast(enum_type.captures.len),
         .namespace = enum_type.namespace,
         .int_tag_type = enum_type.tag_ty,
         .fields_len = enum_type.fields_len,
@@ -6472,6 +6662,7 @@ fn getIncompleteEnumExplicit(
         .tag = tag,
         .data = extra_index,
     });
+    ip.extra.appendSliceAssumeCapacity(@ptrCast(enum_type.captures));
     // This is both fields and values (if present).
     ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), reserved_len);
     return .{
@@ -6492,6 +6683,7 @@ pub const GetEnumInit = struct {
     values: []const Index,
     tag_mode: LoadedEnumType.TagMode,
     zir_index: TrackedInst.Index.Optional,
+    captures: []const CaptureValue,
 };
 
 pub fn getEnum(ip: *InternPool, gpa: Allocator, ini: GetEnumInit) Allocator.Error!Index {
@@ -6513,11 +6705,12 @@ pub fn getEnum(ip: *InternPool, gpa: Allocator, ini: GetEnumInit) Allocator.Erro
 
             const fields_len: u32 = @intCast(ini.names.len);
             try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumAuto).Struct.fields.len +
-                fields_len);
+                ini.captures.len + fields_len);
             ip.items.appendAssumeCapacity(.{
                 .tag = .type_enum_auto,
                 .data = ip.addExtraAssumeCapacity(EnumAuto{
                     .decl = ini.decl,
+                    .captures_len = @intCast(ini.captures.len),
                     .namespace = ini.namespace,
                     .int_tag_type = ini.tag_ty,
                     .names_map = names_map,
@@ -6525,6 +6718,7 @@ pub fn getEnum(ip: *InternPool, gpa: Allocator, ini: GetEnumInit) Allocator.Erro
                     .zir_index = ini.zir_index,
                 }),
             });
+            ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures));
             ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.names));
             return @enumFromInt(ip.items.len - 1);
         },
@@ -6533,7 +6727,7 @@ pub fn getEnum(ip: *InternPool, gpa: Allocator, ini: GetEnumInit) Allocator.Erro
     }
 }
 
-pub fn finishGetEnum(
+fn finishGetEnum(
     ip: *InternPool,
     gpa: Allocator,
     ini: GetEnumInit,
@@ -6549,11 +6743,12 @@ pub fn finishGetEnum(
     };
     const fields_len: u32 = @intCast(ini.names.len);
     try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumExplicit).Struct.fields.len +
-        fields_len);
+        ini.captures.len + fields_len);
     ip.items.appendAssumeCapacity(.{
         .tag = tag,
         .data = ip.addExtraAssumeCapacity(EnumExplicit{
             .decl = ini.decl,
+            .captures_len = @intCast(ini.captures.len),
             .namespace = ini.namespace,
             .int_tag_type = ini.tag_ty,
             .fields_len = fields_len,
@@ -6562,23 +6757,37 @@ pub fn finishGetEnum(
             .zir_index = ini.zir_index,
         }),
     });
+    ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures));
     ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.names));
     ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.values));
     return @enumFromInt(ip.items.len - 1);
 }
 
-pub fn getOpaqueType(ip: *InternPool, gpa: Allocator, key: LoadedOpaqueType) Allocator.Error!Index {
+pub const OpaqueTypeIni = struct {
+    decl: DeclIndex,
+    namespace: NamespaceIndex,
+    zir_index: TrackedInst.Index.Optional,
+    captures: []const CaptureValue,
+};
+
+pub fn getOpaqueType(ip: *InternPool, gpa: Allocator, ini: OpaqueTypeIni) Allocator.Error!Index {
     const adapter: KeyAdapter = .{ .intern_pool = ip };
-    try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(LoadedOpaqueType).Struct.fields.len);
+    try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(LoadedOpaqueType).Struct.fields.len + ini.captures.len);
     try ip.items.ensureUnusedCapacity(gpa, 1);
     const gop = try ip.map.getOrPutAdapted(gpa, Key{
-        .opaque_type = .{ .decl = key.decl },
+        .opaque_type = .{ .decl = ini.decl },
     }, adapter);
     if (gop.found_existing) return @enumFromInt(gop.index);
     ip.items.appendAssumeCapacity(.{
         .tag = .type_opaque,
-        .data = ip.addExtraAssumeCapacity(key),
+        .data = ip.addExtraAssumeCapacity(Tag.TypeOpaque{
+            .decl = ini.decl,
+            .namespace = ini.namespace,
+            .zir_index = ini.zir_index,
+            .captures_len = @intCast(ini.captures.len),
+        }),
     });
+    ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures));
     return @enumFromInt(gop.index);
 }
 
@@ -7442,12 +7651,31 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
                 break :b @sizeOf(Tag.ErrorSet) + (@sizeOf(u32) * info.names_len);
             },
             .type_inferred_error_set => 0,
-            .type_enum_explicit, .type_enum_nonexhaustive => @sizeOf(EnumExplicit),
-            .type_enum_auto => @sizeOf(EnumAuto),
-            .type_opaque => @sizeOf(Key.OpaqueType),
+            .type_enum_explicit, .type_enum_nonexhaustive => b: {
+                const info = ip.extraData(EnumExplicit, data);
+                var ints = @typeInfo(EnumExplicit).Struct.fields.len + info.captures_len + info.fields_len;
+                if (info.values_map != .none) ints += info.fields_len;
+                break :b @sizeOf(u32) * ints;
+            },
+            .type_enum_auto => b: {
+                const info = ip.extraData(EnumAuto, data);
+                const ints = @typeInfo(EnumAuto).Struct.fields.len + info.captures_len + info.fields_len;
+                break :b @sizeOf(u32) * ints;
+            },
+            .type_opaque => b: {
+                const info = ip.extraData(Tag.TypeOpaque, data);
+                const ints = @typeInfo(Tag.TypeOpaque).Struct.fields.len + info.captures_len;
+                break :b @sizeOf(u32) * ints;
+            },
             .type_struct => b: {
-                const info = ip.extraData(Tag.TypeStruct, data);
+                if (data == 0) break :b 0;
+                const extra = ip.extraDataTrail(Tag.TypeStruct, data);
+                const info = extra.data;
                 var ints: usize = @typeInfo(Tag.TypeStruct).Struct.fields.len;
+                if (info.flags.any_captures) {
+                    const captures_len = ip.extra.items[extra.end];
+                    ints += 1 + captures_len;
+                }
                 ints += info.fields_len; // types
                 if (!info.flags.is_tuple) {
                     ints += 1; // names_map
@@ -7470,14 +7698,24 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
                 break :b @sizeOf(TypeStructAnon) + (@sizeOf(u32) * 3 * info.fields_len);
             },
             .type_struct_packed => b: {
-                const info = ip.extraData(Tag.TypeStructPacked, data);
+                const extra = ip.extraDataTrail(Tag.TypeStructPacked, data);
+                const captures_len = if (extra.data.flags.any_captures)
+                    ip.extra.items[extra.end]
+                else
+                    0;
                 break :b @sizeOf(u32) * (@typeInfo(Tag.TypeStructPacked).Struct.fields.len +
-                    info.fields_len + info.fields_len);
+                    @intFromBool(extra.data.flags.any_captures) + captures_len +
+                    extra.data.fields_len * 2);
             },
             .type_struct_packed_inits => b: {
-                const info = ip.extraData(Tag.TypeStructPacked, data);
+                const extra = ip.extraDataTrail(Tag.TypeStructPacked, data);
+                const captures_len = if (extra.data.flags.any_captures)
+                    ip.extra.items[extra.end]
+                else
+                    0;
                 break :b @sizeOf(u32) * (@typeInfo(Tag.TypeStructPacked).Struct.fields.len +
-                    info.fields_len + info.fields_len + info.fields_len);
+                    @intFromBool(extra.data.flags.any_captures) + captures_len +
+                    extra.data.fields_len * 3);
             },
             .type_tuple_anon => b: {
                 const info = ip.extraData(TypeStructAnon, data);
@@ -7485,16 +7723,20 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
             },
 
             .type_union => b: {
-                const info = ip.extraData(Tag.TypeUnion, data);
-                const enum_info = ip.loadEnumType(info.tag_ty);
-                const fields_len: u32 = @intCast(enum_info.names.len);
+                const extra = ip.extraDataTrail(Tag.TypeUnion, data);
+                const captures_len = if (extra.data.flags.any_captures)
+                    ip.extra.items[extra.end]
+                else
+                    0;
                 const per_field = @sizeOf(u32); // field type
                 // 1 byte per field for alignment, rounded up to the nearest 4 bytes
-                const alignments = if (info.flags.any_aligned_fields)
-                    ((fields_len + 3) / 4) * 4
+                const alignments = if (extra.data.flags.any_aligned_fields)
+                    ((extra.data.fields_len + 3) / 4) * 4
                 else
                     0;
-                break :b @sizeOf(Tag.TypeUnion) + (fields_len * per_field) + alignments;
+                break :b @sizeOf(Tag.TypeUnion) +
+                    4 * (@intFromBool(extra.data.flags.any_captures) + captures_len) +
+                    (extra.data.fields_len * per_field) + alignments;
             },
 
             .type_function => b: {
@@ -7802,7 +8044,6 @@ pub fn destroyNamespace(ip: *InternPool, gpa: Allocator, index: NamespaceIndex)
         .parent = undefined,
         .file_scope = undefined,
         .decl_index = undefined,
-        .captures = undefined,
     };
     ip.namespaces_free_list.append(gpa, index) catch {
         // In order to keep `destroyNamespace` a non-fallible function, we ignore memory
src/Module.zig
@@ -761,37 +761,6 @@ pub const Namespace = struct {
     /// the Decl Value has to be resolved as a Type which has a Namespace.
     /// Value is whether the usingnamespace decl is marked `pub`.
     usingnamespace_set: std.AutoHashMapUnmanaged(Decl.Index, bool) = .{},
-    /// Allocated into `gpa`.
-    /// The ordered set of values captured in this type's closure.
-    /// `closure_get` instructions look up values in this list.
-    captures: []CaptureValue,
-
-    /// A single value captured in a container's closure. This is not an
-    /// `InternPool.Index` so we can differentiate between runtime-known values
-    /// (where only the type is comptime-known) and comptime-known values.
-    pub const CaptureValue = enum(u32) {
-        _,
-        pub const Unwrapped = union(enum) {
-            /// Index refers to the value.
-            @"comptime": InternPool.Index,
-            /// Index refers to the type.
-            runtime: InternPool.Index,
-        };
-        pub fn wrap(val: Unwrapped) CaptureValue {
-            return switch (val) {
-                .@"comptime" => |i| @enumFromInt(@intFromEnum(i)),
-                .runtime => |i| @enumFromInt((1 << 31) | @intFromEnum(i)),
-            };
-        }
-        pub fn unwrap(val: CaptureValue) Unwrapped {
-            const tag: u1 = @intCast(@intFromEnum(val) >> 31);
-            const raw = @intFromEnum(val);
-            return switch (tag) {
-                0 => .{ .@"comptime" = @enumFromInt(raw) },
-                1 => .{ .runtime = @enumFromInt(@as(u31, @truncate(raw))) },
-            };
-        }
-    };
 
     const Index = InternPool.NamespaceIndex;
     const OptionalIndex = InternPool.OptionalNamespaceIndex;
@@ -2130,7 +2099,6 @@ pub fn deinit(zcu: *Zcu) void {
         while (it.next()) |namespace| {
             namespace.decls.deinit(gpa);
             namespace.usingnamespace_set.deinit(gpa);
-            gpa.free(namespace.captures);
         }
     }
 
@@ -3354,7 +3322,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
         .parent = .none,
         .decl_index = undefined,
         .file_scope = file,
-        .captures = &.{},
     });
     const new_namespace = mod.namespacePtr(new_namespace_index);
     errdefer mod.destroyNamespace(new_namespace_index);
src/Sema.zig
@@ -2670,28 +2670,27 @@ fn analyzeAsInt(
 }
 
 /// Given a ZIR extra index which points to a list of `Zir.Inst.Capture`,
-/// resolves this into a list of `Namespace.CaptureValue` allocated by `gpa`.
-/// Caller owns returned memory.
-fn getCaptures(sema: *Sema, parent_namespace: ?InternPool.NamespaceIndex, extra_index: usize, captures_len: u32) ![]Namespace.CaptureValue {
-    const gpa = sema.gpa;
-    const parent_captures: []const Namespace.CaptureValue = if (parent_namespace) |p| parent: {
-        break :parent sema.mod.namespacePtr(p).captures;
-    } else &.{};
+/// resolves this into a list of `InternPool.CaptureValue` allocated by `arena`.
+fn getCaptures(sema: *Sema, parent_namespace: ?InternPool.NamespaceIndex, extra_index: usize, captures_len: u32) ![]InternPool.CaptureValue {
+    const zcu = sema.mod;
+    const ip = &zcu.intern_pool;
+    const parent_captures: InternPool.CaptureValue.Slice = if (parent_namespace) |p| parent: {
+        break :parent zcu.namespacePtr(p).ty.getCaptures(zcu);
+    } else undefined; // never used so `undefined` is safe
 
-    const captures = try gpa.alloc(Namespace.CaptureValue, captures_len);
-    errdefer gpa.free(captures);
+    const captures = try sema.arena.alloc(InternPool.CaptureValue, captures_len);
 
     for (sema.code.extra[extra_index..][0..captures_len], captures) |raw, *capture| {
         const zir_capture: Zir.Inst.Capture = @enumFromInt(raw);
         capture.* = switch (zir_capture.unwrap()) {
-            .inst => |inst| Namespace.CaptureValue.wrap(capture: {
+            .inst => |inst| InternPool.CaptureValue.wrap(capture: {
                 const air_ref = try sema.resolveInst(inst.toRef());
                 if (try sema.resolveValue(air_ref)) |val| {
                     break :capture .{ .@"comptime" = val.toIntern() };
                 }
                 break :capture .{ .runtime = sema.typeOf(air_ref).toIntern() };
             }),
-            .nested => |parent_idx| parent_captures[parent_idx],
+            .nested => |parent_idx| parent_captures.get(ip)[parent_idx],
         };
     }
 
@@ -2731,7 +2730,7 @@ pub fn getStructType(
         break :blk decls_len;
     } else 0;
 
-    mod.namespacePtr(namespace).captures = try sema.getCaptures(parent_namespace, extra_index, captures_len);
+    const captures = try sema.getCaptures(parent_namespace, extra_index, captures_len);
     extra_index += captures_len;
 
     if (small.has_backing_int) {
@@ -2761,6 +2760,7 @@ pub fn getStructType(
         .any_comptime_fields = small.any_comptime_fields,
         .inits_resolved = false,
         .any_aligned_fields = small.any_aligned_fields,
+        .captures = captures,
     });
 
     return ty;
@@ -2801,7 +2801,6 @@ fn zirStructDecl(
         .parent = block.namespace.toOptional(),
         .decl_index = new_decl_index,
         .file_scope = block.getFileScope(mod),
-        .captures = &.{}, // Will be set by `getStructType`
     });
     errdefer mod.destroyNamespace(new_namespace_index);
 
@@ -2997,7 +2996,6 @@ fn zirEnumDecl(
         .parent = block.namespace.toOptional(),
         .decl_index = new_decl_index,
         .file_scope = block.getFileScope(mod),
-        .captures = captures,
     });
     errdefer if (!done) mod.destroyNamespace(new_namespace_index);
 
@@ -3029,6 +3027,7 @@ fn zirEnumDecl(
             else
                 .explicit,
             .zir_index = (try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst)).toOptional(),
+            .captures = captures,
         });
         if (sema.builtin_type_target_index != .none) {
             mod.intern_pool.resolveBuiltinType(sema.builtin_type_target_index, incomplete_enum.index);
@@ -3261,7 +3260,6 @@ fn zirUnionDecl(
         .parent = block.namespace.toOptional(),
         .decl_index = new_decl_index,
         .file_scope = block.getFileScope(mod),
-        .captures = captures,
     });
     errdefer mod.destroyNamespace(new_namespace_index);
 
@@ -3291,6 +3289,7 @@ fn zirUnionDecl(
             .enum_tag_ty = .none,
             .field_types = &.{},
             .field_aligns = &.{},
+            .captures = captures,
         });
         if (sema.builtin_type_target_index != .none) {
             mod.intern_pool.resolveBuiltinType(sema.builtin_type_target_index, ty);
@@ -3367,7 +3366,6 @@ fn zirOpaqueDecl(
         .parent = block.namespace.toOptional(),
         .decl_index = new_decl_index,
         .file_scope = block.getFileScope(mod),
-        .captures = captures,
     });
     errdefer mod.destroyNamespace(new_namespace_index);
 
@@ -3375,6 +3373,7 @@ fn zirOpaqueDecl(
         .decl = new_decl_index,
         .namespace = new_namespace_index,
         .zir_index = (try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst)).toOptional(),
+        .captures = captures,
     });
     // TODO: figure out InternPool removals for incremental compilation
     //errdefer mod.intern_pool.remove(opaque_ty);
@@ -17287,12 +17286,13 @@ fn zirThis(
 
 fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
-    const captures = mod.namespacePtr(block.namespace).captures;
+    const ip = &mod.intern_pool;
+    const captures = mod.namespacePtr(block.namespace).ty.getCaptures(mod);
 
     const src_node: i32 = @bitCast(extended.operand);
     const src = LazySrcLoc.nodeOffset(src_node);
 
-    const capture_ty = switch (captures[extended.small].unwrap()) {
+    const capture_ty = switch (captures.get(ip)[extended.small].unwrap()) {
         .@"comptime" => |index| return Air.internedToRef(index),
         .runtime => |index| index,
     };
@@ -21360,6 +21360,7 @@ fn zirReify(
                     .explicit,
                 .tag_ty = int_tag_ty.toIntern(),
                 .zir_index = .none,
+                .captures = &.{},
             });
             // TODO: figure out InternPool removals for incremental compilation
             //errdefer ip.remove(incomplete_enum.index);
@@ -21450,7 +21451,6 @@ fn zirReify(
                 .parent = block.namespace.toOptional(),
                 .decl_index = new_decl_index,
                 .file_scope = block.getFileScope(mod),
-                .captures = &.{},
             });
             errdefer mod.destroyNamespace(new_namespace_index);
 
@@ -21458,6 +21458,7 @@ fn zirReify(
                 .decl = new_decl_index,
                 .namespace = new_namespace_index,
                 .zir_index = .none,
+                .captures = &.{},
             });
             // TODO: figure out InternPool removals for incremental compilation
             //errdefer ip.remove(opaque_ty);
@@ -21659,7 +21660,6 @@ fn zirReify(
                 .parent = block.namespace.toOptional(),
                 .decl_index = new_decl_index,
                 .file_scope = block.getFileScope(mod),
-                .captures = &.{},
             });
             errdefer mod.destroyNamespace(new_namespace_index);
 
@@ -21688,6 +21688,7 @@ fn zirReify(
                 },
                 .field_types = union_fields.items(.type),
                 .field_aligns = if (any_aligned_fields) union_fields.items(.alignment) else &.{},
+                .captures = &.{},
             });
 
             new_decl.ty = Type.type;
@@ -21849,6 +21850,7 @@ fn reifyStruct(
         .any_default_inits = true,
         .inits_resolved = true,
         .any_aligned_fields = true,
+        .captures = &.{},
     });
     // TODO: figure out InternPool removals for incremental compilation
     //errdefer ip.remove(ty);
@@ -37404,6 +37406,7 @@ fn generateUnionTagTypeNumbered(
         .values = enum_field_vals,
         .tag_mode = .explicit,
         .zir_index = .none,
+        .captures = &.{},
     });
 
     new_decl.ty = Type.type;
@@ -37455,6 +37458,7 @@ fn generateUnionTagTypeSimple(
         .values = &.{},
         .tag_mode = .auto,
         .zir_index = .none,
+        .captures = &.{},
     });
 
     const new_decl = mod.declPtr(new_decl_index);
src/type.zig
@@ -3294,6 +3294,18 @@ pub const Type = struct {
         };
     }
 
+    /// Given a namespace type, returns its list of caotured values.
+    pub fn getCaptures(ty: Type, zcu: *const Zcu) InternPool.CaptureValue.Slice {
+        const ip = &zcu.intern_pool;
+        return switch (ip.indexToKey(ty.toIntern())) {
+            .struct_type => ip.loadStructType(ty.toIntern()).captures,
+            .union_type => ip.loadUnionType(ty.toIntern()).captures,
+            .enum_type => ip.loadEnumType(ty.toIntern()).captures,
+            .opaque_type => ip.loadOpaqueType(ty.toIntern()).captures,
+            else => unreachable,
+        };
+    }
+
     pub const @"u1": Type = .{ .ip_index = .u1_type };
     pub const @"u8": Type = .{ .ip_index = .u8_type };
     pub const @"u16": Type = .{ .ip_index = .u16_type };