Commit c5383173a0

Matthew Lugg <mlugg@mlugg.co.uk>
2025-11-22 22:59:46
compiler: replace `@Type` with individual type-creating builtins
The new builtins are: * `@EnumLiteral` * `@Int` * `@Fn` * `@Pointer` * `@Tuple` * `@Enum` * `@Union` * `@Struct` Their usage is documented in the language reference. There is no `@Array` because arrays can be created like this: if (sentinel) |s| [n:s]T else [n]T There is also no `@Float`. Instead, `std.meta.Float` can serve this use case if necessary. There is no `@ErrorSet` and intentionally no way to achieve this. Likewise, there is intentionally no way to reify tuples with comptime fields, or function types with comptime parameters. These decisions simplify the Zig language specification, and moreover make Zig code more readable by discouraging overly complex metaprogramming. Co-authored-by: Ali Cheraghi <alichraghi@proton.me> Resolves: #10710
1 parent 3f2cf1c
doc/langref.html.in
@@ -638,7 +638,7 @@
       {#syntax#}i7{#endsyntax#} refers to a signed 7-bit integer. The maximum allowed bit-width of an
       integer type is {#syntax#}65535{#endsyntax#}.
       </p>
-      {#see_also|Integers|Floats|void|Errors|@Type#}
+      {#see_also|Integers|Floats|void|Errors|@Int#}
       {#header_close#}
       {#header_open|Primitive Values#}
       <div class="table-wrapper">
@@ -3723,9 +3723,9 @@ void do_a_thing(struct Foo *foo) {
             <td>{#syntax#}x{#endsyntax#} is a {#syntax#}@FieldType(T, "a"){#endsyntax#}</td>
           </tr>
           <tr>
-            <th scope="row">{#syntax#}@Type(x){#endsyntax#}</th>
+            <th scope="row">{#syntax#}@Int(x, y){#endsyntax#}</th>
             <td>-</td>
-            <td>{#syntax#}x{#endsyntax#} is a {#syntax#}std.builtin.Type{#endsyntax#}</td>
+            <td>{#syntax#}x{#endsyntax#} is a {#syntax#}std.builtin.Signedness{#endsyntax#}, {#syntax#}y{#endsyntax#} is a {#syntax#}u16{#endsyntax#}</td>
           </tr>
           <tr>
             <th scope="row">{#syntax#}@typeInfo(x){#endsyntax#}</th>
@@ -3839,9 +3839,9 @@ void do_a_thing(struct Foo *foo) {
             <td>{#syntax#}x{#endsyntax#} has no result location (typed initializers do not propagate result locations)</td>
           </tr>
           <tr>
-            <th scope="row">{#syntax#}@Type(x){#endsyntax#}</th>
-            <td>{#syntax#}ptr{#endsyntax#}</td>
-            <td>{#syntax#}x{#endsyntax#} has no result location</td>
+            <th scope="row">{#syntax#}@Int(x, y){#endsyntax#}</th>
+            <td>-</td>
+            <td>{#syntax#}x{#endsyntax#} and {#syntax#}y{#endsyntax#} do not have result locations</td>
           </tr>
           <tr>
             <th scope="row">{#syntax#}@typeInfo(x){#endsyntax#}</th>
@@ -5755,41 +5755,75 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       {#header_close#}
 
-      {#header_open|@Type#}
-      <pre>{#syntax#}@Type(comptime info: std.builtin.Type) type{#endsyntax#}</pre>
-      <p>
-      This function is the inverse of {#link|@typeInfo#}. It reifies type information
-      into a {#syntax#}type{#endsyntax#}.
-      </p>
-      <p>
-      It is available for the following types:
-      </p>
-      <ul>
-          <li>{#syntax#}type{#endsyntax#}</li>
-          <li>{#syntax#}noreturn{#endsyntax#}</li>
-          <li>{#syntax#}void{#endsyntax#}</li>
-          <li>{#syntax#}bool{#endsyntax#}</li>
-          <li>{#link|Integers#} - The maximum bit count for an integer type is {#syntax#}65535{#endsyntax#}.</li>
-          <li>{#link|Floats#}</li>
-          <li>{#link|Pointers#}</li>
-          <li>{#syntax#}comptime_int{#endsyntax#}</li>
-          <li>{#syntax#}comptime_float{#endsyntax#}</li>
-          <li>{#syntax#}@TypeOf(undefined){#endsyntax#}</li>
-          <li>{#syntax#}@TypeOf(null){#endsyntax#}</li>
-          <li>{#link|Arrays#}</li>
-          <li>{#link|Optionals#}</li>
-          <li>{#link|Error Set Type#}</li>
-          <li>{#link|Error Union Type#}</li>
-          <li>{#link|Vectors#}</li>
-          <li>{#link|opaque#}</li>
-          <li>{#syntax#}anyframe{#endsyntax#}</li>
-          <li>{#link|struct#}</li>
-          <li>{#link|enum#}</li>
-          <li>{#link|Enum Literals#}</li>
-          <li>{#link|union#}</li>
-          <li>{#link|Functions#}</li>
-      </ul>
+      {#header_open|@EnumLiteral#}
+      <pre>{#syntax#}@EnumLiteral() type{#endsyntax#}</pre>
+      <p>Returns the comptime-only "enum literal" type. This is the type of uncoerced {#link|Enum Literals#}. Values of this type can coerce to any {#link|enum#} with a matching field.</p>
+      {#header_close#}
+
+      {#header_open|@Int#}
+      <pre>{#syntax#}@Int(comptime signedness: std.builtin.Signedness, comptime bits: u16) type{#endsyntax#}</pre>
+      <p>Returns an integer type with the given signedness and bit width.</p>
+      <p>For instance, {#syntax#}@Int(.unsigned, 18){#endsyntax#} returns the type {#syntax#}u18{#endsyntax#}.</p>
       {#header_close#}
+
+      {#header_open|@Tuple#}
+      <pre>{#syntax#}@Tuple(comptime field_types: []const type) type{#endsyntax#}</pre>
+      <p>Returns a {#link|tuple|Tuples#} type with the given field types.</p>
+      {#header_close#}
+
+      {#header_open|@Pointer#}
+      <pre>{#syntax#}@Pointer(
+    comptime size: std.builtin.Type.Pointer.Size,
+    comptime attrs: std.builtin.Type.Pointer.Attributes,
+    comptime Element: type,
+    comptime sentinel: ?Element,
+) type{#endsyntax#}</pre>
+      <p>Returns a {#link|pointer|Pointers#} type with the properties specified by the arguments.</p>
+      {#header_close#}
+
+      {#header_open|@Fn#}
+      <pre>{#syntax#}@Fn(
+    comptime param_types: []const type,
+    comptime param_attrs: *const [param_types.len]std.builtin.Type.Fn.Param.Attributes,
+    comptime ReturnType: type,
+    comptime attrs: std.builtin.Type.Fn.Attributes,
+) type{#endsyntax#}</pre>
+      <p>Returns a {#link|function|Functions#} type with the properties specified by the arguments.</p>
+      {#header_close#}
+
+      {#header_open|@Struct#}
+      <pre>{#syntax#}@Struct(
+    comptime layout: std.builtin.Type.ContainerLayout,
+    comptime BackingInt: ?type,
+    comptime field_names: []const []const u8,
+    comptime field_types: *const [field_names.len]type,
+    comptime field_attrs: *const [field_names.len]std.builtin.Type.StructField.Attributes,
+) type{#endsyntax#}</pre>
+      <p>Returns a {#link|struct#} type with the properties specified by the arguments.</p>
+      {#header_close#}
+
+      {#header_open|@Union#}
+      <pre>{#syntax#}@Union(
+    comptime layout: std.builtin.Type.ContainerLayout,
+    /// Either the integer tag type, or the integer backing type, depending on `layout`.
+    comptime ArgType: ?type,
+    comptime field_names: []const []const u8,
+    comptime field_types: *const [field_names.len]type,
+    comptime field_attrs: *const [field_names.len]std.builtin.Type.UnionField.Attributes,
+) type{#endsyntax#}</pre>
+      <p>Returns a {#link|union#} type with the properties specified by the arguments.</p>
+      {#header_close#}
+
+      {#header_open|@Enum#}
+      <pre>{#syntax#}@Enum(
+    comptime TagInt: type,
+    comptime mode: std.builtin.Type.Enum.Mode,
+    comptime field_names: []const []const u8,
+    comptime field_values: *const [field_names.len]TagInt,
+) type{#endsyntax#}</pre>
+      <p>Returns an {#link|enum#} type with the properties specified by the arguments.</p>
+      {#header_close#}
+
       {#header_open|@typeInfo#}
       <pre>{#syntax#}@typeInfo(comptime T: type) std.builtin.Type{#endsyntax#}</pre>
       <p>
lib/std/zig/AstGen.zig
@@ -833,7 +833,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
         => {
             var buf: [2]Ast.Node.Index = undefined;
             const params = tree.builtinCallParams(&buf, node).?;
-            return builtinCall(gz, scope, ri, node, params, false);
+            return builtinCall(gz, scope, ri, node, params, false, .anon);
         },
 
         .call_one,
@@ -1194,14 +1194,20 @@ fn nameStratExpr(
         },
         .builtin_call_two,
         .builtin_call_two_comma,
+        .builtin_call,
+        .builtin_call_comma,
         => {
             const builtin_token = tree.nodeMainToken(node);
             const builtin_name = tree.tokenSlice(builtin_token);
-            if (!std.mem.eql(u8, builtin_name, "@Type")) return null;
-            var buf: [2]Ast.Node.Index = undefined;
-            const params = tree.builtinCallParams(&buf, node).?;
-            if (params.len != 1) return null; // let `builtinCall` error
-            return try builtinReify(gz, scope, ri, node, params[0], name_strat);
+            const info = BuiltinFn.list.get(builtin_name) orelse return null;
+            switch (info.tag) {
+                .Enum, .Struct, .Union => {
+                    var buf: [2]Ast.Node.Index = undefined;
+                    const params = tree.builtinCallParams(&buf, node).?;
+                    return try builtinCall(gz, scope, ri, node, params, false, name_strat);
+                },
+                else => return null,
+            }
         },
         else => return null,
     }
@@ -1406,7 +1412,7 @@ fn fnProtoExprInner(
         .none;
 
     const ret_ty_node = fn_proto.ast.return_type.unwrap().?;
-    const ret_ty = try comptimeExpr(&block_scope, scope, coerced_type_ri, ret_ty_node, .function_ret_ty);
+    const ret_ty = try comptimeExpr(&block_scope, scope, coerced_type_ri, ret_ty_node, .fn_ret_ty);
 
     const result = try block_scope.addFunc(.{
         .src_node = fn_proto.ast.proto_node,
@@ -2629,7 +2635,7 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
                     const params = tree.builtinCallParams(&buf, inner_node).?;
 
                     try emitDbgNode(gz, inner_node);
-                    const result = try builtinCall(gz, scope, .{ .rl = .none }, inner_node, params, allow_branch_hint);
+                    const result = try builtinCall(gz, scope, .{ .rl = .none }, inner_node, params, allow_branch_hint, .anon);
                     noreturn_src_node = try addEnsureResult(gz, result, inner_node);
                 },
 
@@ -2707,6 +2713,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
             .elem_type,
             .indexable_ptr_elem_type,
             .splat_op_result_ty,
+            .reify_int,
             .vector_type,
             .indexable_ptr_len,
             .anyframe_type,
@@ -8942,7 +8949,7 @@ fn unionInit(
     params: []const Ast.Node.Index,
 ) InnerError!Zir.Inst.Ref {
     const union_type = try typeExpr(gz, scope, params[0]);
-    const field_name = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }, params[1], .union_field_name);
+    const field_name = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }, params[1], .union_field_names);
     const field_type = try gz.addPlNode(.field_type_ref, node, Zir.Inst.FieldTypeRef{
         .container_type = union_type,
         .field_name = field_name,
@@ -9210,6 +9217,7 @@ fn builtinCall(
     node: Ast.Node.Index,
     params: []const Ast.Node.Index,
     allow_branch_hint: bool,
+    reify_name_strat: Zir.Inst.NameStrategy,
 ) InnerError!Zir.Inst.Ref {
     const astgen = gz.astgen;
     const tree = astgen.tree;
@@ -9443,9 +9451,140 @@ fn builtinCall(
             return rvalue(gz, ri, try gz.addNodeExtended(.in_comptime, node), node);
         },
 
-        .Type => {
-            return builtinReify(gz, scope, ri, node, params[0], .anon);
+        .EnumLiteral => return rvalue(gz, ri, .enum_literal_type, node),
+        .Int => {
+            const signedness_ty = try gz.addBuiltinValue(node, .signedness);
+            const result = try gz.addPlNode(.reify_int, node, Zir.Inst.Bin{
+                .lhs = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = signedness_ty } }, params[0], .int_signedness),
+                .rhs = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .u16_type } }, params[1], .int_bit_width),
+            });
+            return rvalue(gz, ri, result, node);
+        },
+        .Tuple => {
+            const result = try gz.addExtendedPayload(.reify_tuple, Zir.Inst.UnNode{
+                .node = gz.nodeIndexToRelative(node),
+                .operand = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_type_type } }, params[0], .tuple_field_types),
+            });
+            return rvalue(gz, ri, result, node);
+        },
+        .Pointer => {
+            const ptr_size_ty = try gz.addBuiltinValue(node, .pointer_size);
+            const ptr_attrs_ty = try gz.addBuiltinValue(node, .pointer_attributes);
+            const size = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = ptr_size_ty } }, params[0], .pointer_size);
+            const attrs = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = ptr_attrs_ty } }, params[1], .pointer_attrs);
+            const elem_ty = try typeExpr(gz, scope, params[2]);
+            const sentinel_ty = try gz.addExtendedPayload(.reify_pointer_sentinel_ty, Zir.Inst.UnNode{
+                .node = gz.nodeIndexToRelative(params[2]),
+                .operand = elem_ty,
+            });
+            const sentinel = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = sentinel_ty } }, params[3], .pointer_sentinel);
+            const result = try gz.addExtendedPayload(.reify_pointer, Zir.Inst.ReifyPointer{
+                .node = gz.nodeIndexToRelative(node),
+                .size = size,
+                .attrs = attrs,
+                .elem_ty = elem_ty,
+                .sentinel = sentinel,
+            });
+            return rvalue(gz, ri, result, node);
         },
+        .Fn => {
+            const fn_attrs_ty = try gz.addBuiltinValue(node, .fn_attributes);
+            const param_types = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_type_type } }, params[0], .fn_param_types);
+            const param_attrs_ty = try gz.addExtendedPayloadSmall(
+                .reify_slice_arg_ty,
+                @intFromEnum(Zir.Inst.ReifySliceArgInfo.type_to_fn_param_attrs),
+                Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(params[0]), .operand = param_types },
+            );
+            const param_attrs = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = param_attrs_ty } }, params[1], .fn_param_attrs);
+            const ret_ty = try comptimeExpr(gz, scope, coerced_type_ri, params[2], .fn_ret_ty);
+            const fn_attrs = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = fn_attrs_ty } }, params[3], .fn_attrs);
+            const result = try gz.addExtendedPayload(.reify_fn, Zir.Inst.ReifyFn{
+                .node = gz.nodeIndexToRelative(node),
+                .param_types = param_types,
+                .param_attrs = param_attrs,
+                .ret_ty = ret_ty,
+                .fn_attrs = fn_attrs,
+            });
+            return rvalue(gz, ri, result, node);
+        },
+        .Struct => {
+            const container_layout_ty = try gz.addBuiltinValue(node, .container_layout);
+            const layout = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = container_layout_ty } }, params[0], .struct_layout);
+            const backing_ty = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .optional_type_type } }, params[1], .type);
+            const field_names = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_slice_const_u8_type } }, params[2], .struct_field_names);
+            const field_types_ty = try gz.addExtendedPayloadSmall(
+                .reify_slice_arg_ty,
+                @intFromEnum(Zir.Inst.ReifySliceArgInfo.string_to_struct_field_type),
+                Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(params[2]), .operand = field_names },
+            );
+            const field_attrs_ty = try gz.addExtendedPayloadSmall(
+                .reify_slice_arg_ty,
+                @intFromEnum(Zir.Inst.ReifySliceArgInfo.string_to_struct_field_attrs),
+                Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(params[2]), .operand = field_names },
+            );
+            const field_types = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = field_types_ty } }, params[3], .struct_field_types);
+            const field_attrs = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = field_attrs_ty } }, params[4], .struct_field_attrs);
+            const result = try gz.addExtendedPayloadSmall(.reify_struct, @intFromEnum(reify_name_strat), Zir.Inst.ReifyStruct{
+                .src_line = gz.astgen.source_line,
+                .node = node,
+                .layout = layout,
+                .backing_ty = backing_ty,
+                .field_names = field_names,
+                .field_types = field_types,
+                .field_attrs = field_attrs,
+            });
+            return rvalue(gz, ri, result, node);
+        },
+        .Union => {
+            const container_layout_ty = try gz.addBuiltinValue(node, .container_layout);
+            const layout = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = container_layout_ty } }, params[0], .union_layout);
+            const arg_ty = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .optional_type_type } }, params[1], .type);
+            const field_names = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_slice_const_u8_type } }, params[2], .union_field_names);
+            const field_types_ty = try gz.addExtendedPayloadSmall(
+                .reify_slice_arg_ty,
+                @intFromEnum(Zir.Inst.ReifySliceArgInfo.string_to_union_field_type),
+                Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(params[2]), .operand = field_names },
+            );
+            const field_attrs_ty = try gz.addExtendedPayloadSmall(
+                .reify_slice_arg_ty,
+                @intFromEnum(Zir.Inst.ReifySliceArgInfo.string_to_union_field_attrs),
+                Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(params[2]), .operand = field_names },
+            );
+            const field_types = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = field_types_ty } }, params[3], .union_field_types);
+            const field_attrs = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = field_attrs_ty } }, params[4], .union_field_attrs);
+            const result = try gz.addExtendedPayloadSmall(.reify_union, @intFromEnum(reify_name_strat), Zir.Inst.ReifyUnion{
+                .src_line = gz.astgen.source_line,
+                .node = node,
+                .layout = layout,
+                .arg_ty = arg_ty,
+                .field_names = field_names,
+                .field_types = field_types,
+                .field_attrs = field_attrs,
+            });
+            return rvalue(gz, ri, result, node);
+        },
+        .Enum => {
+            const enum_mode_ty = try gz.addBuiltinValue(node, .enum_mode);
+            const tag_ty = try typeExpr(gz, scope, params[0]);
+            const mode = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = enum_mode_ty } }, params[1], .type);
+            const field_names = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_slice_const_u8_type } }, params[2], .enum_field_names);
+            const field_values_ty = try gz.addExtendedPayload(.reify_enum_value_slice_ty, Zir.Inst.BinNode{
+                .node = gz.nodeIndexToRelative(node),
+                .lhs = tag_ty,
+                .rhs = field_names,
+            });
+            const field_values = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = field_values_ty } }, params[3], .enum_field_values);
+            const result = try gz.addExtendedPayloadSmall(.reify_enum, @intFromEnum(reify_name_strat), Zir.Inst.ReifyEnum{
+                .src_line = gz.astgen.source_line,
+                .node = node,
+                .tag_ty = tag_ty,
+                .mode = mode,
+                .field_names = field_names,
+                .field_values = field_values,
+            });
+            return rvalue(gz, ri, result, node);
+        },
+
         .panic => {
             try emitDbgNode(gz, node);
             return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }, params[0], .panic);
@@ -9764,41 +9903,6 @@ fn builtinCall(
         },
     }
 }
-fn builtinReify(
-    gz: *GenZir,
-    scope: *Scope,
-    ri: ResultInfo,
-    node: Ast.Node.Index,
-    arg_node: Ast.Node.Index,
-    name_strat: Zir.Inst.NameStrategy,
-) InnerError!Zir.Inst.Ref {
-    const astgen = gz.astgen;
-    const gpa = astgen.gpa;
-
-    const type_info_ty = try gz.addBuiltinValue(node, .type_info);
-    const operand = try expr(gz, scope, .{ .rl = .{ .coerced_ty = type_info_ty } }, arg_node);
-
-    try gz.instructions.ensureUnusedCapacity(gpa, 1);
-    try astgen.instructions.ensureUnusedCapacity(gpa, 1);
-
-    const payload_index = try astgen.addExtra(Zir.Inst.Reify{
-        .node = node, // Absolute node index -- see the definition of `Reify`.
-        .operand = operand,
-        .src_line = astgen.source_line,
-    });
-    const new_index: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
-    astgen.instructions.appendAssumeCapacity(.{
-        .tag = .extended,
-        .data = .{ .extended = .{
-            .opcode = .reify,
-            .small = @intFromEnum(name_strat),
-            .operand = payload_index,
-        } },
-    });
-    gz.instructions.appendAssumeCapacity(new_index);
-    const result = new_index.toRef();
-    return rvalue(gz, ri, result, node);
-}
 
 fn hasDeclOrField(
     gz: *GenZir,
lib/std/zig/AstRlAnnotate.zig
@@ -866,6 +866,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
         // These builtins take no args and do not consume the result pointer.
         .src,
         .This,
+        .EnumLiteral,
         .return_address,
         .error_return_trace,
         .frame,
@@ -906,7 +907,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
         .embed_file,
         .error_name,
         .set_runtime_safety,
-        .Type,
+        .Tuple,
         .c_undef,
         .c_include,
         .wasm_memory_size,
@@ -1058,6 +1059,48 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
             _ = try astrl.expr(args[3], block, ResultInfo.none);
             return false;
         },
+        .Int => {
+            _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+            return false;
+        },
+        .Pointer => {
+            _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[3], block, ResultInfo.type_only);
+            return false;
+        },
+        .Fn => {
+            _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[3], block, ResultInfo.type_only);
+            return false;
+        },
+        .Struct => {
+            _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[3], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[4], block, ResultInfo.type_only);
+            return false;
+        },
+        .Union => {
+            _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[3], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[4], block, ResultInfo.type_only);
+            return false;
+        },
+        .Enum => {
+            _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+            _ = try astrl.expr(args[3], block, ResultInfo.type_only);
+            return false;
+        },
         .Vector => {
             _ = try astrl.expr(args[0], block, ResultInfo.type_only);
             _ = try astrl.expr(args[1], block, ResultInfo.type_only);
lib/std/zig/BuiltinFn.zig
@@ -110,7 +110,14 @@ pub const Tag = enum {
     This,
     trap,
     truncate,
-    Type,
+    EnumLiteral,
+    Int,
+    Tuple,
+    Pointer,
+    Fn,
+    Struct,
+    Union,
+    Enum,
     type_info,
     type_name,
     TypeOf,
@@ -937,12 +944,61 @@ pub const list = list: {
             },
         },
         .{
-            "@Type",
+            "@EnumLiteral",
             .{
-                .tag = .Type,
+                .tag = .EnumLiteral,
+                .param_count = 0,
+            },
+        },
+        .{
+            "@Int",
+            .{
+                .tag = .Int,
+                .param_count = 2,
+            },
+        },
+        .{
+            "@Tuple",
+            .{
+                .tag = .Tuple,
                 .param_count = 1,
             },
         },
+        .{
+            "@Pointer",
+            .{
+                .tag = .Pointer,
+                .param_count = 4,
+            },
+        },
+        .{
+            "@Fn",
+            .{
+                .tag = .Fn,
+                .param_count = 4,
+            },
+        },
+        .{
+            "@Struct",
+            .{
+                .tag = .Struct,
+                .param_count = 5,
+            },
+        },
+        .{
+            "@Union",
+            .{
+                .tag = .Union,
+                .param_count = 5,
+            },
+        },
+        .{
+            "@Enum",
+            .{
+                .tag = .Enum,
+                .param_count = 4,
+            },
+        },
         .{
             "@typeInfo",
             .{
lib/std/zig/Zir.zig
@@ -260,6 +260,10 @@ pub const Inst = struct {
         /// `[N:S]T` syntax. Source location is the array type expression node.
         /// Uses the `pl_node` union field. Payload is `ArrayTypeSentinel`.
         array_type_sentinel,
+        /// `@Int` builtin.
+        /// Uses the `pl_node` union field with `Bin` payload.
+        /// lhs is signedness, rhs is bit count.
+        reify_int,
         /// `@Vector` builtin.
         /// Uses the `pl_node` union field with `Bin` payload.
         /// lhs is length, rhs is element type.
@@ -1112,6 +1116,7 @@ pub const Inst = struct {
                 .array_mul,
                 .array_type,
                 .array_type_sentinel,
+                .reify_int,
                 .vector_type,
                 .elem_type,
                 .indexable_ptr_elem_type,
@@ -1409,6 +1414,7 @@ pub const Inst = struct {
                 .array_mul,
                 .array_type,
                 .array_type_sentinel,
+                .reify_int,
                 .vector_type,
                 .elem_type,
                 .indexable_ptr_elem_type,
@@ -1644,6 +1650,7 @@ pub const Inst = struct {
                 .array_mul = .pl_node,
                 .array_type = .pl_node,
                 .array_type_sentinel = .pl_node,
+                .reify_int = .pl_node,
                 .vector_type = .pl_node,
                 .elem_type = .un_node,
                 .indexable_ptr_elem_type = .un_node,
@@ -2035,10 +2042,43 @@ pub const Inst = struct {
         /// Implement builtin `@errorFromInt`.
         /// `operand` is payload index to `UnNode`.
         error_from_int,
-        /// Implement builtin `@Type`.
-        /// `operand` is payload index to `Reify`.
+        /// Given a comptime-known operand of type `[]const A`, returns the type `*const [operand.len]B`.
+        /// The types `A` and `B` are determined from `ReifySliceArgInfo`.
+        /// This instruction is used to provide result types to arguments of `@Fn`, `@Struct`, etc.
+        /// `operand` is payload index to `UnNode`.
+        /// `small` is a bitcast `ReifySliceArgInfo`.
+        reify_slice_arg_ty,
+        /// Like `reify_slice_arg_ty` for the specific case of `[]const []const u8` to `[]const TagInt`,
+        /// as needed for `@Enum`.
+        /// `operand` is payload index to `BinNode`. lhs is the type `TagInt`. rhs is the `[]const []const u8` value.
+        /// `small` is unused.
+        reify_enum_value_slice_ty,
+        /// Given a comptime-known operand of type `type`, returns the type `?operand` if possible, otherwise `?noreturn`.
+        /// Used for the final arg of `@Pointer` to allow reifying pointers to opaque types.
+        /// `operand` is payload index to `UnNode`.
+        /// `small` is unused.
+        reify_pointer_sentinel_ty,
+        /// Implements builtin `@Tuple`.
+        /// `operand` is payload index to `UnNode`.
+        reify_tuple,
+        /// Implements builtin `@Pointer`.
+        /// `operand` is payload index to `ReifyPointer`.
+        reify_pointer,
+        /// Implements builtin `@Fn`.
+        /// `operand` is payload index to `ReifyFn`.
+        reify_fn,
+        /// Implements builtin `@Struct`.
+        /// `operand` is payload index to `ReifyStruct`.
+        /// `small` contains `NameStrategy`.
+        reify_struct,
+        /// Implements builtin `@Union`.
+        /// `operand` is payload index to `ReifyUnion`.
+        /// `small` contains `NameStrategy`.
+        reify_union,
+        /// Implements builtin `@Enum`.
+        /// `operand` is payload index to `ReifyEnum`.
         /// `small` contains `NameStrategy`.
-        reify,
+        reify_enum,
         /// Implements the `@cmpxchgStrong` and `@cmpxchgWeak` builtins.
         /// `small` 0=>weak 1=>strong
         /// `operand` is payload index to `Cmpxchg`.
@@ -2226,6 +2266,11 @@ pub const Inst = struct {
         manyptr_const_u8_sentinel_0_type,
         slice_const_u8_type,
         slice_const_u8_sentinel_0_type,
+        manyptr_const_slice_const_u8_type,
+        slice_const_slice_const_u8_type,
+        optional_type_type,
+        manyptr_const_type_type,
+        slice_const_type_type,
         vector_8_i8_type,
         vector_16_i8_type,
         vector_32_i8_type,
@@ -3169,6 +3214,23 @@ pub const Inst = struct {
         rhs: Ref,
     };
 
+    pub const ReifySliceArgInfo = enum(u16) {
+        /// Input element type is `type`.
+        /// Output element type is `std.builtin.Type.Fn.Param.Attributes`.
+        type_to_fn_param_attrs,
+        /// Input element type is `[]const u8`.
+        /// Output element type is `type`.
+        string_to_struct_field_type,
+        /// Identical to `string_to_struct_field_type` aside from emitting slightly different error messages.
+        string_to_union_field_type,
+        /// Input element type is `[]const u8`.
+        /// Output element type is `std.builtin.Type.StructField.Attributes`.
+        string_to_struct_field_attrs,
+        /// Input element type is `[]const u8`.
+        /// Output element type is `std.builtin.Type.UnionField.Attributes`.
+        string_to_union_field_attrs,
+    };
+
     pub const UnNode = struct {
         node: Ast.Node.Offset,
         operand: Ref,
@@ -3179,12 +3241,55 @@ pub const Inst = struct {
         index: u32,
     };
 
-    pub const Reify = struct {
+    pub const ReifyPointer = struct {
+        node: Ast.Node.Offset,
+        size: Ref,
+        attrs: Ref,
+        elem_ty: Ref,
+        sentinel: Ref,
+    };
+
+    pub const ReifyFn = struct {
+        node: Ast.Node.Offset,
+        param_types: Ref,
+        param_attrs: Ref,
+        ret_ty: Ref,
+        fn_attrs: Ref,
+    };
+
+    pub const ReifyStruct = struct {
+        src_line: u32,
         /// This node is absolute, because `reify` instructions are tracked across updates, and
         /// this simplifies the logic for getting source locations for types.
         node: Ast.Node.Index,
-        operand: Ref,
+        layout: Ref,
+        backing_ty: Ref,
+        field_names: Ref,
+        field_types: Ref,
+        field_attrs: Ref,
+    };
+
+    pub const ReifyUnion = struct {
         src_line: u32,
+        /// This node is absolute, because `reify` instructions are tracked across updates, and
+        /// this simplifies the logic for getting source locations for types.
+        node: Ast.Node.Index,
+        layout: Ref,
+        arg_ty: Ref,
+        field_names: Ref,
+        field_types: Ref,
+        field_attrs: Ref,
+    };
+
+    pub const ReifyEnum = struct {
+        src_line: u32,
+        /// This node is absolute, because `reify` instructions are tracked across updates, and
+        /// this simplifies the logic for getting source locations for types.
+        node: Ast.Node.Index,
+        tag_ty: Ref,
+        mode: Ref,
+        field_names: Ref,
+        field_values: Ref,
     };
 
     /// Trailing:
@@ -3496,14 +3601,19 @@ pub const Inst = struct {
         calling_convention,
         address_space,
         float_mode,
+        signedness,
         reduce_op,
         call_modifier,
         prefetch_options,
         export_options,
         extern_options,
-        type_info,
         branch_hint,
         clobbers,
+        pointer_size,
+        pointer_attributes,
+        fn_attributes,
+        container_layout,
+        enum_mode,
         // Values
         calling_convention_c,
         calling_convention_inline,
@@ -4190,6 +4300,7 @@ fn findTrackableInner(
         .array_mul,
         .array_type,
         .array_type_sentinel,
+        .reify_int,
         .vector_type,
         .elem_type,
         .indexable_ptr_elem_type,
@@ -4432,6 +4543,12 @@ fn findTrackableInner(
                 .select,
                 .int_from_error,
                 .error_from_int,
+                .reify_slice_arg_ty,
+                .reify_enum_value_slice_ty,
+                .reify_pointer_sentinel_ty,
+                .reify_tuple,
+                .reify_pointer,
+                .reify_fn,
                 .cmpxchg,
                 .c_va_arg,
                 .c_va_copy,
@@ -4463,7 +4580,11 @@ fn findTrackableInner(
                 },
 
                 // Reifications and opaque declarations need tracking, but have no body.
-                .reify, .opaque_decl => return contents.other.append(gpa, inst),
+                .reify_enum,
+                .reify_struct,
+                .reify_union,
+                .opaque_decl,
+                => return contents.other.append(gpa, inst),
 
                 // Struct declarations need tracking and have bodies.
                 .struct_decl => {
@@ -5246,7 +5367,9 @@ pub fn assertTrackable(zir: Zir, inst_idx: Zir.Inst.Index) void {
             .union_decl,
             .enum_decl,
             .opaque_decl,
-            .reify,
+            .reify_enum,
+            .reify_struct,
+            .reify_union,
             => {}, // tracked in order, as the owner instructions of explicit container types
             else => unreachable, // assertion failure; not trackable
         },
lib/std/builtin.zig
@@ -548,19 +548,19 @@ pub const TypeId = std.meta.Tag(Type);
 /// This data structure is used by the Zig language code generation and
 /// therefore must be kept in sync with the compiler implementation.
 pub const Type = union(enum) {
-    type: void,
-    void: void,
-    bool: void,
-    noreturn: void,
+    type,
+    void,
+    bool,
+    noreturn,
     int: Int,
     float: Float,
     pointer: Pointer,
     array: Array,
     @"struct": Struct,
-    comptime_float: void,
-    comptime_int: void,
-    undefined: void,
-    null: void,
+    comptime_float,
+    comptime_int,
+    undefined,
+    null,
     optional: Optional,
     error_union: ErrorUnion,
     error_set: ErrorSet,
@@ -571,7 +571,7 @@ pub const Type = union(enum) {
     frame: Frame,
     @"anyframe": AnyFrame,
     vector: Vector,
-    enum_literal: void,
+    enum_literal,
 
     /// This data structure is used by the Zig language code generation and
     /// therefore must be kept in sync with the compiler implementation.
@@ -619,6 +619,16 @@ pub const Type = union(enum) {
             slice,
             c,
         };
+
+        /// This data structure is used by the Zig language code generation and
+        /// therefore must be kept in sync with the compiler implementation.
+        pub const Attributes = struct {
+            @"const": bool = false,
+            @"volatile": bool = false,
+            @"allowzero": bool = false,
+            @"addrspace": ?AddressSpace = null,
+            @"align": ?usize = null,
+        };
     };
 
     /// This data structure is used by the Zig language code generation and
@@ -668,6 +678,14 @@ pub const Type = union(enum) {
             const dp: *const sf.type = @ptrCast(@alignCast(sf.default_value_ptr orelse return null));
             return dp.*;
         }
+
+        /// This data structure is used by the Zig language code generation and
+        /// therefore must be kept in sync with the compiler implementation.
+        pub const Attributes = struct {
+            @"comptime": bool = false,
+            @"align": ?usize = null,
+            default_value_ptr: ?*const anyopaque = null,
+        };
     };
 
     /// This data structure is used by the Zig language code generation and
@@ -718,6 +736,10 @@ pub const Type = union(enum) {
         fields: []const EnumField,
         decls: []const Declaration,
         is_exhaustive: bool,
+
+        /// This data structure is used by the Zig language code generation and
+        /// therefore must be kept in sync with the compiler implementation.
+        pub const Mode = enum { exhaustive, nonexhaustive };
     };
 
     /// This data structure is used by the Zig language code generation and
@@ -726,6 +748,12 @@ pub const Type = union(enum) {
         name: [:0]const u8,
         type: type,
         alignment: comptime_int,
+
+        /// This data structure is used by the Zig language code generation and
+        /// therefore must be kept in sync with the compiler implementation.
+        pub const Attributes = struct {
+            @"align": ?usize = null,
+        };
     };
 
     /// This data structure is used by the Zig language code generation and
@@ -753,6 +781,19 @@ pub const Type = union(enum) {
             is_generic: bool,
             is_noalias: bool,
             type: ?type,
+
+            /// This data structure is used by the Zig language code generation and
+            /// therefore must be kept in sync with the compiler implementation.
+            pub const Attributes = struct {
+                @"noalias": bool = false,
+            };
+        };
+
+        /// This data structure is used by the Zig language code generation and
+        /// therefore must be kept in sync with the compiler implementation.
+        pub const Attributes = struct {
+            @"callconv": CallingConvention = .auto,
+            varargs: bool = false,
         };
     };
 
lib/std/zig.zig
@@ -773,7 +773,6 @@ pub const EnvVar = enum {
 pub const SimpleComptimeReason = enum(u32) {
     // Evaluating at comptime because a builtin operand must be comptime-known.
     // These messages all mention a specific builtin.
-    operand_Type,
     operand_setEvalBranchQuota,
     operand_setFloatMode,
     operand_branchHint,
@@ -809,25 +808,34 @@ pub const SimpleComptimeReason = enum(u32) {
     // Evaluating at comptime because types must be comptime-known.
     // Reasons other than `.type` are just more specific messages.
     type,
+    int_signedness,
+    int_bit_width,
     array_sentinel,
+    array_length,
+    pointer_size,
+    pointer_attrs,
     pointer_sentinel,
     slice_sentinel,
-    array_length,
     vector_length,
-    error_set_contents,
-    struct_fields,
-    enum_fields,
-    union_fields,
-    function_ret_ty,
-    function_parameters,
+    fn_ret_ty,
+    fn_param_types,
+    fn_param_attrs,
+    fn_attrs,
+    struct_layout,
+    struct_field_names,
+    struct_field_types,
+    struct_field_attrs,
+    union_layout,
+    union_field_names,
+    union_field_types,
+    union_field_attrs,
+    tuple_field_types,
+    enum_field_names,
+    enum_field_values,
 
     // Evaluating at comptime because decl/field name must be comptime-known.
     decl_name,
     field_name,
-    struct_field_name,
-    enum_field_name,
-    union_field_name,
-    tuple_field_name,
     tuple_field_index,
 
     // Evaluating at comptime because it is an attribute of a global declaration.
@@ -856,7 +864,6 @@ pub const SimpleComptimeReason = enum(u32) {
     pub fn message(r: SimpleComptimeReason) []const u8 {
         return switch (r) {
             // zig fmt: off
-            .operand_Type                => "operand to '@Type' must be comptime-known",
             .operand_setEvalBranchQuota  => "operand to '@setEvalBranchQuota' must be comptime-known",
             .operand_setFloatMode        => "operand to '@setFloatMode' must be comptime-known",
             .operand_branchHint          => "operand to '@branchHint' must be comptime-known",
@@ -888,24 +895,33 @@ pub const SimpleComptimeReason = enum(u32) {
             .clobber              => "clobber must be comptime-known",
 
             .type                => "types must be comptime-known",
+            .int_signedness      => "integer signedness must be comptime-known",
+            .int_bit_width       => "integer bit width must be comptime-known",
             .array_sentinel      => "array sentinel value must be comptime-known",
+            .array_length        => "array length must be comptime-known",
+            .pointer_size        => "pointer size must be comptime-known",
+            .pointer_attrs       => "pointer attributes must be comptime-known",
             .pointer_sentinel    => "pointer sentinel value must be comptime-known",
             .slice_sentinel      => "slice sentinel value must be comptime-known",
-            .array_length        => "array length must be comptime-known",
             .vector_length       => "vector length must be comptime-known",
-            .error_set_contents  => "error set contents must be comptime-known",
-            .struct_fields       => "struct fields must be comptime-known",
-            .enum_fields         => "enum fields must be comptime-known",
-            .union_fields        => "union fields must be comptime-known",
-            .function_ret_ty     => "function return type must be comptime-known",
-            .function_parameters => "function parameters must be comptime-known",
+            .fn_ret_ty           => "function return type must be comptime-known",
+            .fn_param_types      => "function parameter types must be comptime-known",
+            .fn_param_attrs      => "function parameter attributes must be comptime-known",
+            .fn_attrs            => "function attributes must be comptime-known",
+            .struct_layout       => "struct layout must be comptime-known",
+            .struct_field_names  => "struct field names must be comptime-known",
+            .struct_field_types  => "struct field types must be comptime-known",
+            .struct_field_attrs  => "struct field attributes must be comptime-known",
+            .union_layout        => "union layout must be comptime-known",
+            .union_field_names   => "union field names must be comptime-known",
+            .union_field_types   => "union field types must be comptime-known",
+            .union_field_attrs   => "union field attributes must be comptime-known",
+            .tuple_field_types   => "tuple field types must be comptime-known",
+            .enum_field_names    => "enum field names must be comptime-known",
+            .enum_field_values   => "enum field values must be comptime-known",
 
             .decl_name         => "declaration name must be comptime-known",
             .field_name        => "field name must be comptime-known",
-            .struct_field_name => "struct field name must be comptime-known",
-            .enum_field_name   => "enum field name must be comptime-known",
-            .union_field_name  => "union field name must be comptime-known",
-            .tuple_field_name  => "tuple field name must be comptime-known",
             .tuple_field_index => "tuple field index must be comptime-known",
 
             .container_var_init => "initializer of container-level variable must be comptime-known",
src/codegen/c/Type.zig
@@ -1416,6 +1416,9 @@ pub const Pool = struct {
             .null_type,
             .undefined_type,
             .enum_literal_type,
+            .optional_type_type,
+            .manyptr_const_type_type,
+            .slice_const_type_type,
             => return .void,
             .u1_type, .u8_type => return .u8,
             .i8_type => return .i8,
@@ -1525,6 +1528,73 @@ pub const Pool = struct {
                 return pool.fromFields(allocator, .@"struct", &fields, kind);
             },
 
+            .manyptr_const_slice_const_u8_type => {
+                const target = &mod.resolved_target.result;
+                var fields: [2]Info.Field = .{
+                    .{
+                        .name = .{ .index = .ptr },
+                        .ctype = try pool.getPointer(allocator, .{
+                            .elem_ctype = .u8,
+                            .@"const" = true,
+                            .nonstring = true,
+                        }),
+                        .alignas = AlignAs.fromAbiAlignment(Type.ptrAbiAlignment(target)),
+                    },
+                    .{
+                        .name = .{ .index = .len },
+                        .ctype = .usize,
+                        .alignas = AlignAs.fromAbiAlignment(
+                            .fromByteUnits(std.zig.target.intAlignment(target, target.ptrBitWidth())),
+                        ),
+                    },
+                };
+                const slice_const_u8 = try pool.fromFields(allocator, .@"struct", &fields, kind);
+                return pool.getPointer(allocator, .{
+                    .elem_ctype = slice_const_u8,
+                    .@"const" = true,
+                });
+            },
+            .slice_const_slice_const_u8_type => {
+                const target = &mod.resolved_target.result;
+                var fields: [2]Info.Field = .{
+                    .{
+                        .name = .{ .index = .ptr },
+                        .ctype = try pool.getPointer(allocator, .{
+                            .elem_ctype = .u8,
+                            .@"const" = true,
+                            .nonstring = true,
+                        }),
+                        .alignas = AlignAs.fromAbiAlignment(Type.ptrAbiAlignment(target)),
+                    },
+                    .{
+                        .name = .{ .index = .len },
+                        .ctype = .usize,
+                        .alignas = AlignAs.fromAbiAlignment(
+                            .fromByteUnits(std.zig.target.intAlignment(target, target.ptrBitWidth())),
+                        ),
+                    },
+                };
+                const slice_const_u8 = try pool.fromFields(allocator, .@"struct", &fields, .forward);
+                fields = .{
+                    .{
+                        .name = .{ .index = .ptr },
+                        .ctype = try pool.getPointer(allocator, .{
+                            .elem_ctype = slice_const_u8,
+                            .@"const" = true,
+                        }),
+                        .alignas = AlignAs.fromAbiAlignment(Type.ptrAbiAlignment(target)),
+                    },
+                    .{
+                        .name = .{ .index = .len },
+                        .ctype = .usize,
+                        .alignas = AlignAs.fromAbiAlignment(
+                            .fromByteUnits(std.zig.target.intAlignment(target, target.ptrBitWidth())),
+                        ),
+                    },
+                };
+                return pool.fromFields(allocator, .@"struct", &fields, kind);
+            },
+
             .vector_8_i8_type => {
                 const vector_ctype = try pool.getVector(allocator, .{
                     .elem_ctype = .i8,
src/link/Dwarf.zig
@@ -4490,7 +4490,12 @@ fn updateContainerTypeWriterError(
                     .enum_decl => @as(Zir.Inst.EnumDecl.Small, @bitCast(decl_inst.data.extended.small)).name_strategy,
                     .union_decl => @as(Zir.Inst.UnionDecl.Small, @bitCast(decl_inst.data.extended.small)).name_strategy,
                     .opaque_decl => @as(Zir.Inst.OpaqueDecl.Small, @bitCast(decl_inst.data.extended.small)).name_strategy,
-                    .reify => @as(Zir.Inst.NameStrategy, @enumFromInt(decl_inst.data.extended.small)),
+
+                    .reify_enum,
+                    .reify_struct,
+                    .reify_union,
+                    => @enumFromInt(decl_inst.data.extended.small),
+
                     else => unreachable,
                 },
                 else => unreachable,
src/Air.zig
@@ -1062,6 +1062,11 @@ pub const Inst = struct {
         manyptr_const_u8_sentinel_0_type = @intFromEnum(InternPool.Index.manyptr_const_u8_sentinel_0_type),
         slice_const_u8_type = @intFromEnum(InternPool.Index.slice_const_u8_type),
         slice_const_u8_sentinel_0_type = @intFromEnum(InternPool.Index.slice_const_u8_sentinel_0_type),
+        manyptr_const_slice_const_u8_type = @intFromEnum(InternPool.Index.manyptr_const_slice_const_u8_type),
+        slice_const_slice_const_u8_type = @intFromEnum(InternPool.Index.slice_const_slice_const_u8_type),
+        optional_type_type = @intFromEnum(InternPool.Index.optional_type_type),
+        manyptr_const_type_type = @intFromEnum(InternPool.Index.manyptr_const_type_type),
+        slice_const_type_type = @intFromEnum(InternPool.Index.slice_const_type_type),
         vector_8_i8_type = @intFromEnum(InternPool.Index.vector_8_i8_type),
         vector_16_i8_type = @intFromEnum(InternPool.Index.vector_16_i8_type),
         vector_32_i8_type = @intFromEnum(InternPool.Index.vector_32_i8_type),
src/InternPool.zig
@@ -2017,8 +2017,7 @@ pub const Key = union(enum) {
     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.
-    /// It may be a tuple, if declared like this: `struct {A, B, C}`.
+    /// or was created with `@Struct`. It is unique and based on a declaration.
     struct_type: NamespaceType,
     /// This is a tuple type. Tuples are logically similar to structs, but have some
     /// important differences in semantics; they do not undergo staged type resolution,
@@ -2175,7 +2174,7 @@ pub const Key = union(enum) {
             /// The union for which this is a tag type.
             union_type: Index,
         },
-        /// This type originates from a reification via `@Type`, or from an anonymous initialization.
+        /// This type originates from a reification via `@Enum`, `@Struct`, `@Union` or from an anonymous initialization.
         /// It is hashed based on its ZIR instruction index and fields, attributes, etc.
         /// To avoid making this key overly complex, the type-specific data is hashed by Sema.
         reified: struct {
@@ -4641,6 +4640,13 @@ pub const Index = enum(u32) {
     slice_const_u8_type,
     slice_const_u8_sentinel_0_type,
 
+    manyptr_const_slice_const_u8_type,
+    slice_const_slice_const_u8_type,
+
+    optional_type_type,
+    manyptr_const_type_type,
+    slice_const_type_type,
+
     vector_8_i8_type,
     vector_16_i8_type,
     vector_32_i8_type,
@@ -5201,6 +5207,45 @@ pub const static_keys: [static_len]Key = .{
         },
     } },
 
+    // [*]const []const u8
+    .{ .ptr_type = .{
+        .child = .slice_const_u8_type,
+        .flags = .{
+            .size = .many,
+            .is_const = true,
+        },
+    } },
+
+    // []const []const u8
+    .{ .ptr_type = .{
+        .child = .slice_const_u8_type,
+        .flags = .{
+            .size = .slice,
+            .is_const = true,
+        },
+    } },
+
+    // ?type
+    .{ .opt_type = .type_type },
+
+    // [*]const type
+    .{ .ptr_type = .{
+        .child = .type_type,
+        .flags = .{
+            .size = .many,
+            .is_const = true,
+        },
+    } },
+
+    // []const type
+    .{ .ptr_type = .{
+        .child = .type_type,
+        .flags = .{
+            .size = .slice,
+            .is_const = true,
+        },
+    } },
+
     // @Vector(8, i8)
     .{ .vector_type = .{ .len = 8, .child = .i8_type } },
     // @Vector(16, i8)
@@ -10225,16 +10270,8 @@ pub fn getGeneratedTagEnumType(
 }
 
 pub const OpaqueTypeInit = struct {
-    key: union(enum) {
-        declared: struct {
-            zir_index: TrackedInst.Index,
-            captures: []const CaptureValue,
-        },
-        reified: struct {
-            zir_index: TrackedInst.Index,
-            // No type hash since reifid opaques have no data other than the `@Type` location
-        },
-    },
+    zir_index: TrackedInst.Index,
+    captures: []const CaptureValue,
 };
 
 pub fn getOpaqueType(
@@ -10243,16 +10280,10 @@ pub fn getOpaqueType(
     tid: Zcu.PerThread.Id,
     ini: OpaqueTypeInit,
 ) Allocator.Error!WipNamespaceType.Result {
-    var gop = try ip.getOrPutKey(gpa, tid, .{ .opaque_type = switch (ini.key) {
-        .declared => |d| .{ .declared = .{
-            .zir_index = d.zir_index,
-            .captures = .{ .external = d.captures },
-        } },
-        .reified => |r| .{ .reified = .{
-            .zir_index = r.zir_index,
-            .type_hash = 0,
-        } },
-    } });
+    var gop = try ip.getOrPutKey(gpa, tid, .{ .opaque_type = .{ .declared = .{
+        .zir_index = ini.zir_index,
+        .captures = .{ .external = ini.captures },
+    } } });
     defer gop.deinit();
     if (gop == .existing) return .{ .existing = gop.existing };
 
@@ -10261,30 +10292,19 @@ pub fn getOpaqueType(
     const extra = local.getMutableExtra(gpa);
     try items.ensureUnusedCapacity(1);
 
-    try extra.ensureUnusedCapacity(@typeInfo(Tag.TypeOpaque).@"struct".fields.len + switch (ini.key) {
-        .declared => |d| d.captures.len,
-        .reified => 0,
-    });
+    try extra.ensureUnusedCapacity(@typeInfo(Tag.TypeOpaque).@"struct".fields.len + ini.captures.len);
     const extra_index = addExtraAssumeCapacity(extra, Tag.TypeOpaque{
         .name = undefined, // set by `finish`
         .name_nav = undefined, // set by `finish`
         .namespace = undefined, // set by `finish`
-        .zir_index = switch (ini.key) {
-            inline else => |x| x.zir_index,
-        },
-        .captures_len = switch (ini.key) {
-            .declared => |d| @intCast(d.captures.len),
-            .reified => std.math.maxInt(u32),
-        },
+        .zir_index = ini.zir_index,
+        .captures_len = @intCast(ini.captures.len),
     });
     items.appendAssumeCapacity(.{
         .tag = .type_opaque,
         .data = extra_index,
     });
-    switch (ini.key) {
-        .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}),
-        .reified => {},
-    }
+    extra.appendSliceAssumeCapacity(.{@ptrCast(ini.captures)});
     return .{
         .wip = .{
             .tid = tid,
@@ -10555,6 +10575,8 @@ pub fn slicePtrType(ip: *const InternPool, index: Index) Index {
     switch (index) {
         .slice_const_u8_type => return .manyptr_const_u8_type,
         .slice_const_u8_sentinel_0_type => return .manyptr_const_u8_sentinel_0_type,
+        .slice_const_slice_const_u8_type => return .manyptr_const_slice_const_u8_type,
+        .slice_const_type_type => return .manyptr_const_type_type,
         else => {},
     }
     const item = index.unwrap(ip).getItem(ip);
@@ -12013,8 +12035,13 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index {
         .manyptr_u8_type,
         .manyptr_const_u8_type,
         .manyptr_const_u8_sentinel_0_type,
+        .manyptr_const_slice_const_u8_type,
         .slice_const_u8_type,
         .slice_const_u8_sentinel_0_type,
+        .slice_const_slice_const_u8_type,
+        .optional_type_type,
+        .manyptr_const_type_type,
+        .slice_const_type_type,
         .vector_8_i8_type,
         .vector_16_i8_type,
         .vector_32_i8_type,
@@ -12355,8 +12382,12 @@ pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId {
         .manyptr_u8_type,
         .manyptr_const_u8_type,
         .manyptr_const_u8_sentinel_0_type,
+        .manyptr_const_slice_const_u8_type,
         .slice_const_u8_type,
         .slice_const_u8_sentinel_0_type,
+        .slice_const_slice_const_u8_type,
+        .manyptr_const_type_type,
+        .slice_const_type_type,
         => .pointer,
 
         .vector_8_i8_type,
@@ -12408,6 +12439,7 @@ pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId {
         .vector_8_f64_type,
         => .vector,
 
+        .optional_type_type => .optional,
         .optional_noreturn_type => .optional,
         .anyerror_void_error_union_type => .error_union,
         .empty_tuple_type => .@"struct",
src/print_zir.zig
@@ -399,6 +399,7 @@ const Writer = struct {
             .splat,
             .reduce,
             .bitcast,
+            .reify_int,
             .vector_type,
             .max,
             .min,
@@ -568,6 +569,8 @@ const Writer = struct {
             .work_group_id,
             .branch_hint,
             .float_op_result_ty,
+            .reify_tuple,
+            .reify_pointer_sentinel_ty,
             => {
                 const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data;
                 try self.writeInstRef(stream, inst_data.operand);
@@ -575,23 +578,13 @@ const Writer = struct {
                 try self.writeSrcNode(stream, inst_data.node);
             },
 
-            .reify => {
-                const inst_data = self.code.extraData(Zir.Inst.Reify, extended.operand).data;
-                try stream.print("line({d}), ", .{inst_data.src_line});
-                try self.writeInstRef(stream, inst_data.operand);
-                try stream.writeAll(")) ");
-                const prev_parent_decl_node = self.parent_decl_node;
-                self.parent_decl_node = inst_data.node;
-                defer self.parent_decl_node = prev_parent_decl_node;
-                try self.writeSrcNode(stream, .zero);
-            },
-
             .builtin_extern,
             .c_define,
             .error_cast,
             .wasm_memory_grow,
             .prefetch,
             .c_va_arg,
+            .reify_enum_value_slice_ty,
             => {
                 const inst_data = self.code.extraData(Zir.Inst.BinNode, extended.operand).data;
                 try self.writeInstRef(stream, inst_data.lhs);
@@ -601,6 +594,95 @@ const Writer = struct {
                 try self.writeSrcNode(stream, inst_data.node);
             },
 
+            .reify_slice_arg_ty => {
+                const reify_slice_arg_info: Zir.Inst.ReifySliceArgInfo = @enumFromInt(extended.operand);
+                const extra = self.code.extraData(Zir.Inst.UnNode, extended.operand).data;
+                try stream.print("{t}, ", .{reify_slice_arg_info});
+                try self.writeInstRef(stream, extra.operand);
+                try stream.writeAll(")) ");
+                try self.writeSrcNode(stream, extra.node);
+            },
+
+            .reify_pointer => {
+                const extra = self.code.extraData(Zir.Inst.ReifyPointer, extended.operand).data;
+                try self.writeInstRef(stream, extra.size);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.attrs);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.elem_ty);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.sentinel);
+                try stream.writeAll(")) ");
+                try self.writeSrcNode(stream, extra.node);
+            },
+            .reify_fn => {
+                const extra = self.code.extraData(Zir.Inst.ReifyFn, extended.operand).data;
+                try self.writeInstRef(stream, extra.param_types);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.param_attrs);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.ret_ty);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.fn_attrs);
+                try stream.writeAll(")) ");
+                try self.writeSrcNode(stream, extra.node);
+            },
+            .reify_struct => {
+                const extra = self.code.extraData(Zir.Inst.ReifyStruct, extended.operand).data;
+                const name_strat: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
+                try stream.print("line({d}), {t}, ", .{ extra.src_line, name_strat });
+                try self.writeInstRef(stream, extra.layout);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.backing_ty);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.field_names);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.field_types);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.field_attrs);
+                try stream.writeAll(")) ");
+                const prev_parent_decl_node = self.parent_decl_node;
+                self.parent_decl_node = extra.node;
+                defer self.parent_decl_node = prev_parent_decl_node;
+                try self.writeSrcNode(stream, .zero);
+            },
+            .reify_union => {
+                const extra = self.code.extraData(Zir.Inst.ReifyUnion, extended.operand).data;
+                const name_strat: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
+                try stream.print("line({d}), {t}, ", .{ extra.src_line, name_strat });
+                try self.writeInstRef(stream, extra.layout);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.arg_ty);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.field_names);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.field_types);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.field_attrs);
+                try stream.writeAll(")) ");
+                const prev_parent_decl_node = self.parent_decl_node;
+                self.parent_decl_node = extra.node;
+                defer self.parent_decl_node = prev_parent_decl_node;
+                try self.writeSrcNode(stream, .zero);
+            },
+            .reify_enum => {
+                const extra = self.code.extraData(Zir.Inst.ReifyEnum, extended.operand).data;
+                const name_strat: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
+                try stream.print("line({d}), {t}, ", .{ extra.src_line, name_strat });
+                try self.writeInstRef(stream, extra.tag_ty);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.mode);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.field_names);
+                try stream.writeAll(", ");
+                try self.writeInstRef(stream, extra.field_values);
+                try stream.writeAll(")) ");
+                const prev_parent_decl_node = self.parent_decl_node;
+                self.parent_decl_node = extra.node;
+                defer self.parent_decl_node = prev_parent_decl_node;
+                try self.writeSrcNode(stream, .zero);
+            },
+
             .cmpxchg => try self.writeCmpxchg(stream, extended),
             .ptr_cast_full => try self.writePtrCastFull(stream, extended),
             .ptr_cast_no_dest => try self.writePtrCastNoDest(stream, extended),
src/Sema.zig
@@ -1167,6 +1167,7 @@ fn analyzeBodyInner(
             .array_mul                    => try sema.zirArrayMul(block, inst),
             .array_type                   => try sema.zirArrayType(block, inst),
             .array_type_sentinel          => try sema.zirArrayTypeSentinel(block, inst),
+            .reify_int                    => try sema.zirReifyInt(block, inst),
             .vector_type                  => try sema.zirVectorType(block, inst),
             .as_node                      => try sema.zirAsNode(block, inst),
             .as_shift_operand             => try sema.zirAsShiftOperand(block, inst),
@@ -1411,7 +1412,6 @@ fn analyzeBodyInner(
                     .select             => try sema.zirSelect(            block, extended),
                     .int_from_error     => try sema.zirIntFromError(      block, extended),
                     .error_from_int     => try sema.zirErrorFromInt(      block, extended),
-                    .reify              => try sema.zirReify(             block, extended, inst),
                     .cmpxchg            => try sema.zirCmpxchg(           block, extended),
                     .c_va_arg           => try sema.zirCVaArg(            block, extended),
                     .c_va_copy          => try sema.zirCVaCopy(           block, extended),
@@ -1424,6 +1424,16 @@ fn analyzeBodyInner(
                     .work_group_id      => try sema.zirWorkItem(          block, extended, extended.opcode),
                     .in_comptime        => try sema.zirInComptime(        block),
                     .closure_get        => try sema.zirClosureGet(        block, extended),
+
+                    .reify_slice_arg_ty        => try sema.zirReifySliceArgTy(      block, extended),
+                    .reify_enum_value_slice_ty => try sema.zirReifyEnumValueSliceTy(block, extended),
+                    .reify_pointer_sentinel_ty => try sema.zirReifyPointerSentinelTy(block, extended),
+                    .reify_tuple               => try sema.zirReifyTuple(           block, extended),
+                    .reify_pointer             => try sema.zirReifyPointer(         block, extended),
+                    .reify_fn                  => try sema.zirReifyFn(              block, extended),
+                    .reify_struct              => try sema.zirReifyStruct(          block, extended, inst),
+                    .reify_union               => try sema.zirReifyUnion(           block, extended, inst),
+                    .reify_enum                => try sema.zirReifyEnum(            block, extended, inst),
                     // zig fmt: on
 
                     .set_float_mode => {
@@ -3517,10 +3527,8 @@ fn zirOpaqueDecl(
     extra_index += captures_len * 2;
 
     const opaque_init: InternPool.OpaqueTypeInit = .{
-        .key = .{ .declared = .{
-            .zir_index = tracked_inst,
-            .captures = captures,
-        } },
+        .zir_index = tracked_inst,
+        .captures = captures,
     };
     const wip_ty = switch (try ip.getOpaqueType(gpa, pt.tid, opaque_init)) {
         .existing => |ty| {
@@ -7386,7 +7394,7 @@ fn analyzeCall(
             const body = sema.code.bodySlice(extra.end, extra.data.type.body_len);
 
             generic_block.comptime_reason = .{ .reason = .{
-                .r = .{ .simple = .function_parameters },
+                .r = .{ .simple = .fn_param_types },
                 .src = param_src,
             } };
 
@@ -7470,7 +7478,7 @@ fn analyzeCall(
         sema.inst_map = generic_inst_map;
 
         generic_block.comptime_reason = .{ .reason = .{
-            .r = .{ .simple = .function_ret_ty },
+            .r = .{ .simple = .fn_ret_ty },
             .src = func_ret_ty_src,
         } };
 
@@ -8927,7 +8935,7 @@ fn zirFunc(
             const ret_ty_body = sema.code.bodySlice(extra_index, extra.data.ret_ty.body_len);
             extra_index += ret_ty_body.len;
 
-            const ret_ty_val = try sema.resolveGenericBody(block, ret_ty_src, ret_ty_body, inst, .type, .{ .simple = .function_ret_ty });
+            const ret_ty_val = try sema.resolveGenericBody(block, ret_ty_src, ret_ty_body, inst, .type, .{ .simple = .fn_ret_ty });
             break :blk ret_ty_val.toType();
         },
     };
@@ -9171,6 +9179,181 @@ fn checkCallConvSupportsVarArgs(sema: *Sema, block: *Block, src: LazySrcLoc, cc:
     }
 }
 
+fn checkParamTypeCommon(
+    sema: *Sema,
+    block: *Block,
+    param_idx: u32,
+    param_ty: Type,
+    param_is_noalias: bool,
+    param_src: LazySrcLoc,
+    cc: std.builtin.CallingConvention,
+) CompileError!void {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+    const target = zcu.getTarget();
+
+    if (!param_ty.isValidParamType(zcu)) {
+        const opaque_str = if (param_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else "";
+        return sema.fail(block, param_src, "parameter of {s}type '{f}' not allowed", .{
+            opaque_str, param_ty.fmt(pt),
+        });
+    }
+    if (!param_ty.isGenericPoison() and
+        !target_util.fnCallConvAllowsZigTypes(cc) and
+        !try sema.validateExternType(param_ty, .param_ty))
+    {
+        return sema.failWithOwnedErrorMsg(block, msg: {
+            const msg = try sema.errMsg(param_src, "parameter of type '{f}' not allowed in function with calling convention '{s}'", .{
+                param_ty.fmt(pt), @tagName(cc),
+            });
+            errdefer msg.destroy(sema.gpa);
+
+            try sema.explainWhyTypeIsNotExtern(msg, param_src, param_ty, .param_ty);
+
+            try sema.addDeclaredHereNote(msg, param_ty);
+            break :msg msg;
+        });
+    }
+    switch (cc) {
+        .x86_64_interrupt, .x86_interrupt => {
+            const err_code_size = target.ptrBitWidth();
+            switch (param_idx) {
+                0 => if (param_ty.zigTypeTag(zcu) != .pointer) return sema.fail(block, param_src, "first parameter of function with '{s}' calling convention must be a pointer type", .{@tagName(cc)}),
+                1 => if (param_ty.bitSize(zcu) != err_code_size) return sema.fail(block, param_src, "second parameter of function with '{s}' calling convention must be a {d}-bit integer", .{ @tagName(cc), err_code_size }),
+                else => return sema.fail(block, param_src, "'{s}' calling convention supports up to 2 parameters, found {d}", .{ @tagName(cc), param_idx + 1 }),
+            }
+        },
+        .arc_interrupt,
+        .arm_interrupt,
+        .microblaze_interrupt,
+        .mips64_interrupt,
+        .mips_interrupt,
+        .riscv64_interrupt,
+        .riscv32_interrupt,
+        .sh_interrupt,
+        .avr_interrupt,
+        .csky_interrupt,
+        .m68k_interrupt,
+        .msp430_interrupt,
+        .avr_signal,
+        => return sema.fail(block, param_src, "parameters are not allowed with '{s}' calling convention", .{@tagName(cc)}),
+        else => {},
+    }
+    if (param_is_noalias and !param_ty.isGenericPoison() and !param_ty.isPtrAtRuntime(zcu) and !param_ty.isSliceAtRuntime(zcu)) {
+        return sema.fail(block, param_src, "non-pointer parameter declared noalias", .{});
+    }
+}
+
+fn checkReturnTypeAndCallConvCommon(
+    sema: *Sema,
+    block: *Block,
+    bare_ret_ty: Type,
+    ret_ty_src: LazySrcLoc,
+    @"callconv": std.builtin.CallingConvention,
+    callconv_src: LazySrcLoc,
+    /// non-`null` only if the function is varargs.
+    opt_varargs_src: ?LazySrcLoc,
+    inferred_error_set: bool,
+    is_noinline: bool,
+) CompileError!void {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    if (opt_varargs_src) |varargs_src| {
+        try sema.checkCallConvSupportsVarArgs(block, varargs_src, @"callconv");
+    }
+    if (inferred_error_set and !bare_ret_ty.isGenericPoison()) {
+        try sema.validateErrorUnionPayloadType(block, bare_ret_ty, ret_ty_src);
+    }
+    const ies_ret_ty_prefix: []const u8 = if (inferred_error_set) "!" else "";
+    if (!bare_ret_ty.isValidReturnType(zcu)) {
+        const opaque_str = if (bare_ret_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else "";
+        return sema.fail(block, ret_ty_src, "{s}return type '{s}{f}' not allowed", .{
+            opaque_str, ies_ret_ty_prefix, bare_ret_ty.fmt(pt),
+        });
+    }
+    if (!bare_ret_ty.isGenericPoison() and
+        !target_util.fnCallConvAllowsZigTypes(@"callconv") and
+        (inferred_error_set or !try sema.validateExternType(bare_ret_ty, .ret_ty)))
+    {
+        return sema.failWithOwnedErrorMsg(block, msg: {
+            const msg = try sema.errMsg(ret_ty_src, "return type '{s}{f}' not allowed in function with calling convention '{s}'", .{
+                ies_ret_ty_prefix, bare_ret_ty.fmt(pt), @tagName(@"callconv"),
+            });
+            errdefer msg.destroy(gpa);
+            if (!inferred_error_set) {
+                try sema.explainWhyTypeIsNotExtern(msg, ret_ty_src, bare_ret_ty, .ret_ty);
+                try sema.addDeclaredHereNote(msg, bare_ret_ty);
+            }
+            break :msg msg;
+        });
+    }
+    validate_incoming_stack_align: {
+        const a: u64 = switch (@"callconv") {
+            inline else => |payload| if (@TypeOf(payload) != void and @hasField(@TypeOf(payload), "incoming_stack_alignment"))
+                payload.incoming_stack_alignment orelse break :validate_incoming_stack_align
+            else
+                break :validate_incoming_stack_align,
+        };
+        if (!std.math.isPowerOfTwo(a)) {
+            return sema.fail(block, callconv_src, "calling convention incoming stack alignment '{d}' is not a power of two", .{a});
+        }
+    }
+    switch (@"callconv") {
+        .x86_64_interrupt,
+        .x86_interrupt,
+        .arm_interrupt,
+        .mips64_interrupt,
+        .mips_interrupt,
+        .riscv64_interrupt,
+        .riscv32_interrupt,
+        .sh_interrupt,
+        .arc_interrupt,
+        .avr_interrupt,
+        .csky_interrupt,
+        .m68k_interrupt,
+        .microblaze_interrupt,
+        .msp430_interrupt,
+        .avr_signal,
+        => {
+            const ret_ok = !inferred_error_set and switch (bare_ret_ty.toIntern()) {
+                .void_type, .noreturn_type => true,
+                else => false,
+            };
+            if (!ret_ok) {
+                return sema.fail(block, ret_ty_src, "function with calling convention '{s}' must return 'void' or 'noreturn'", .{@tagName(@"callconv")});
+            }
+        },
+        .@"inline" => if (is_noinline) {
+            return sema.fail(block, callconv_src, "'noinline' function cannot have calling convention 'inline'", .{});
+        },
+        else => {},
+    }
+    switch (zcu.callconvSupported(@"callconv")) {
+        .ok => {},
+        .bad_arch => |allowed_archs| {
+            const ArchListFormatter = struct {
+                archs: []const std.Target.Cpu.Arch,
+                pub fn format(formatter: @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
+                    for (formatter.archs, 0..) |arch, i| {
+                        if (i != 0)
+                            try w.writeAll(", ");
+                        try w.print("'{s}'", .{@tagName(arch)});
+                    }
+                }
+            };
+            return sema.fail(block, callconv_src, "calling convention '{s}' only available on architectures {f}", .{
+                @tagName(@"callconv"),
+                ArchListFormatter{ .archs = allowed_archs },
+            });
+        },
+        .bad_backend => |bad_backend| return sema.fail(block, callconv_src, "calling convention '{s}' not supported by compiler backend '{s}'", .{
+            @tagName(@"callconv"),
+            @tagName(bad_backend),
+        }),
+    }
+}
+
 fn callConvIsCallable(cc: std.builtin.CallingConvention.Tag) bool {
     return switch (cc) {
         .naked,
@@ -9259,7 +9442,6 @@ fn funcCommon(
     const pt = sema.pt;
     const zcu = pt.zcu;
     const gpa = sema.gpa;
-    const target = zcu.getTarget();
     const ip = &zcu.intern_pool;
     const ret_ty_src = block.src(.{ .node_offset_fn_type_ret_ty = src_node_offset });
     const cc_src = block.src(.{ .node_offset_fn_type_cc = src_node_offset });
@@ -9293,26 +9475,14 @@ fn funcCommon(
         if (param_ty_generic and !target_util.fnCallConvAllowsZigTypes(cc)) {
             return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)});
         }
-        if (!param_ty.isValidParamType(zcu)) {
-            const opaque_str = if (param_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else "";
-            return sema.fail(block, param_src, "parameter of {s}type '{f}' not allowed", .{
-                opaque_str, param_ty.fmt(pt),
-            });
-        }
-        if (!param_ty_generic and !target_util.fnCallConvAllowsZigTypes(cc) and !try sema.validateExternType(param_ty, .param_ty)) {
-            const msg = msg: {
-                const msg = try sema.errMsg(param_src, "parameter of type '{f}' not allowed in function with calling convention '{s}'", .{
-                    param_ty.fmt(pt), @tagName(cc),
-                });
-                errdefer msg.destroy(sema.gpa);
-
-                try sema.explainWhyTypeIsNotExtern(msg, param_src, param_ty, .param_ty);
-
-                try sema.addDeclaredHereNote(msg, param_ty);
-                break :msg msg;
-            };
-            return sema.failWithOwnedErrorMsg(block, msg);
-        }
+        try sema.checkParamTypeCommon(
+            block,
+            @intCast(i),
+            param_ty,
+            is_noalias,
+            param_src,
+            cc,
+        );
         if (param_ty_comptime and !param_is_comptime and has_body and !block.isComptime()) {
             const msg = msg: {
                 const msg = try sema.errMsg(param_src, "parameter of type '{f}' must be declared comptime", .{
@@ -9327,209 +9497,40 @@ fn funcCommon(
             };
             return sema.failWithOwnedErrorMsg(block, msg);
         }
-        if (!param_ty_generic and is_noalias and
-            !(param_ty.zigTypeTag(zcu) == .pointer or param_ty.isPtrLikeOptional(zcu)))
-        {
-            return sema.fail(block, param_src, "non-pointer parameter declared noalias", .{});
-        }
-        switch (cc) {
-            .x86_64_interrupt, .x86_interrupt => {
-                const err_code_size = target.ptrBitWidth();
-                switch (i) {
-                    0 => if (param_ty.zigTypeTag(zcu) != .pointer) return sema.fail(block, param_src, "first parameter of function with '{s}' calling convention must be a pointer type", .{@tagName(cc)}),
-                    1 => if (param_ty.bitSize(zcu) != err_code_size) return sema.fail(block, param_src, "second parameter of function with '{s}' calling convention must be a {d}-bit integer", .{ @tagName(cc), err_code_size }),
-                    else => return sema.fail(block, param_src, "'{s}' calling convention supports up to 2 parameters, found {d}", .{ @tagName(cc), i + 1 }),
-                }
-            },
-            .arc_interrupt,
-            .arm_interrupt,
-            .microblaze_interrupt,
-            .mips64_interrupt,
-            .mips_interrupt,
-            .riscv64_interrupt,
-            .riscv32_interrupt,
-            .sh_interrupt,
-            .avr_interrupt,
-            .csky_interrupt,
-            .m68k_interrupt,
-            .msp430_interrupt,
-            .avr_signal,
-            => return sema.fail(block, param_src, "parameters are not allowed with '{s}' calling convention", .{@tagName(cc)}),
-            else => {},
-        }
-    }
-
-    if (var_args) {
-        if (is_generic) {
-            return sema.fail(block, func_src, "generic function cannot be variadic", .{});
-        }
-        const va_args_src = block.src(.{
-            .fn_proto_param = .{
-                .fn_proto_node_offset = src_node_offset,
-                .param_index = @intCast(block.params.len), // va_arg must be the last parameter
-            },
-        });
-        try sema.checkCallConvSupportsVarArgs(block, va_args_src, cc);
-    }
-
-    const ret_poison = bare_return_type.isGenericPoison();
-
-    const param_types = block.params.items(.ty);
-
-    if (inferred_error_set) {
-        assert(has_body);
-        if (!ret_poison)
-            try sema.validateErrorUnionPayloadType(block, bare_return_type, ret_ty_src);
-        const func_index = try ip.getFuncDeclIes(gpa, pt.tid, .{
-            .owner_nav = sema.owner.unwrap().nav_val,
-
-            .param_types = param_types,
-            .noalias_bits = noalias_bits,
-            .comptime_bits = comptime_bits,
-            .bare_return_type = bare_return_type.toIntern(),
-            .cc = cc,
-            .is_var_args = var_args,
-            .is_generic = is_generic,
-            .is_noinline = is_noinline,
-
-            .zir_body_inst = try block.trackZir(func_inst),
-            .lbrace_line = src_locs.lbrace_line,
-            .rbrace_line = src_locs.rbrace_line,
-            .lbrace_column = @as(u16, @truncate(src_locs.columns)),
-            .rbrace_column = @as(u16, @truncate(src_locs.columns >> 16)),
-        });
-        return finishFunc(
-            sema,
-            block,
-            func_index,
-            .none,
-            ret_poison,
-            bare_return_type,
-            ret_ty_src,
-            cc,
-            ret_ty_requires_comptime,
-            func_inst,
-            cc_src,
-            is_noinline,
-        );
     }
 
-    const func_ty = try ip.getFuncType(gpa, pt.tid, .{
-        .param_types = param_types,
-        .noalias_bits = noalias_bits,
-        .comptime_bits = comptime_bits,
-        .return_type = bare_return_type.toIntern(),
-        .cc = cc,
-        .is_var_args = var_args,
-        .is_generic = is_generic,
-        .is_noinline = is_noinline,
-    });
-
-    if (has_body) {
-        const func_index = try ip.getFuncDecl(gpa, pt.tid, .{
-            .owner_nav = sema.owner.unwrap().nav_val,
-            .ty = func_ty,
-            .cc = cc,
-            .is_noinline = is_noinline,
-            .zir_body_inst = try block.trackZir(func_inst),
-            .lbrace_line = src_locs.lbrace_line,
-            .rbrace_line = src_locs.rbrace_line,
-            .lbrace_column = @as(u16, @truncate(src_locs.columns)),
-            .rbrace_column = @as(u16, @truncate(src_locs.columns >> 16)),
-        });
-        return finishFunc(
-            sema,
-            block,
-            func_index,
-            func_ty,
-            ret_poison,
-            bare_return_type,
-            ret_ty_src,
-            cc,
-            ret_ty_requires_comptime,
-            func_inst,
-            cc_src,
-            is_noinline,
-        );
+    if (var_args and is_generic) {
+        return sema.fail(block, func_src, "generic function cannot be variadic", .{});
     }
 
-    return finishFunc(
-        sema,
+    try sema.checkReturnTypeAndCallConvCommon(
         block,
-        .none,
-        func_ty,
-        ret_poison,
         bare_return_type,
         ret_ty_src,
         cc,
-        ret_ty_requires_comptime,
-        func_inst,
         cc_src,
+        if (var_args) block.src(.{ .fn_proto_param = .{
+            .fn_proto_node_offset = src_node_offset,
+            .param_index = @intCast(block.params.len),
+        } }) else null,
+        inferred_error_set,
         is_noinline,
     );
-}
-
-fn finishFunc(
-    sema: *Sema,
-    block: *Block,
-    opt_func_index: InternPool.Index,
-    func_ty: InternPool.Index,
-    ret_poison: bool,
-    bare_return_type: Type,
-    ret_ty_src: LazySrcLoc,
-    cc_resolved: std.builtin.CallingConvention,
-    ret_ty_requires_comptime: bool,
-    func_inst: Zir.Inst.Index,
-    cc_src: LazySrcLoc,
-    is_noinline: bool,
-) CompileError!Air.Inst.Ref {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const gpa = sema.gpa;
-
-    const return_type: Type = if (opt_func_index == .none or ret_poison)
-        bare_return_type
-    else
-        .fromInterned(ip.funcTypeReturnType(ip.typeOf(opt_func_index)));
-
-    if (!return_type.isValidReturnType(zcu)) {
-        const opaque_str = if (return_type.zigTypeTag(zcu) == .@"opaque") "opaque " else "";
-        return sema.fail(block, ret_ty_src, "{s}return type '{f}' not allowed", .{
-            opaque_str, return_type.fmt(pt),
-        });
-    }
-    if (!ret_poison and !target_util.fnCallConvAllowsZigTypes(cc_resolved) and
-        !try sema.validateExternType(return_type, .ret_ty))
-    {
-        const msg = msg: {
-            const msg = try sema.errMsg(ret_ty_src, "return type '{f}' not allowed in function with calling convention '{s}'", .{
-                return_type.fmt(pt), @tagName(cc_resolved),
-            });
-            errdefer msg.destroy(gpa);
-
-            try sema.explainWhyTypeIsNotExtern(msg, ret_ty_src, return_type, .ret_ty);
-
-            try sema.addDeclaredHereNote(msg, return_type);
-            break :msg msg;
-        };
-        return sema.failWithOwnedErrorMsg(block, msg);
-    }
 
     // If the return type is comptime-only but not dependent on parameters then
     // all parameter types also need to be comptime.
-    if (opt_func_index != .none and ret_ty_requires_comptime and !block.isComptime()) comptime_check: {
+    if (has_body and ret_ty_requires_comptime and !block.isComptime()) comptime_check: {
         for (block.params.items(.is_comptime)) |is_comptime| {
             if (!is_comptime) break;
         } else break :comptime_check;
-
+        const ies_ret_ty_prefix: []const u8 = if (inferred_error_set) "!" else "";
         const msg = try sema.errMsg(
             ret_ty_src,
-            "function with comptime-only return type '{f}' requires all parameters to be comptime",
-            .{return_type.fmt(pt)},
+            "function with comptime-only return type '{s}{f}' requires all parameters to be comptime",
+            .{ ies_ret_ty_prefix, bare_return_type.fmt(pt) },
         );
         errdefer msg.destroy(sema.gpa);
-        try sema.explainWhyTypeIsComptime(msg, ret_ty_src, return_type);
+        try sema.explainWhyTypeIsComptime(msg, ret_ty_src, bare_return_type);
 
         const tags = sema.code.instructions.items(.tag);
         const data = sema.code.instructions.items(.data);
@@ -9556,68 +9557,56 @@ fn finishFunc(
         return sema.failWithOwnedErrorMsg(block, msg);
     }
 
-    validate_incoming_stack_align: {
-        const a: u64 = switch (cc_resolved) {
-            inline else => |payload| if (@TypeOf(payload) != void and @hasField(@TypeOf(payload), "incoming_stack_alignment"))
-                payload.incoming_stack_alignment orelse break :validate_incoming_stack_align
-            else
-                break :validate_incoming_stack_align,
-        };
-        if (!std.math.isPowerOfTwo(a)) {
-            return sema.fail(block, cc_src, "calling convention incoming stack alignment '{d}' is not a power of two", .{a});
-        }
-    }
+    const param_types = block.params.items(.ty);
 
-    switch (cc_resolved) {
-        .x86_64_interrupt,
-        .x86_interrupt,
-        .arm_interrupt,
-        .mips64_interrupt,
-        .mips_interrupt,
-        .riscv64_interrupt,
-        .riscv32_interrupt,
-        .sh_interrupt,
-        .arc_interrupt,
-        .avr_interrupt,
-        .csky_interrupt,
-        .m68k_interrupt,
-        .microblaze_interrupt,
-        .msp430_interrupt,
-        .avr_signal,
-        => if (return_type.zigTypeTag(zcu) != .void and return_type.zigTypeTag(zcu) != .noreturn) {
-            return sema.fail(block, ret_ty_src, "function with calling convention '{s}' must return 'void' or 'noreturn'", .{@tagName(cc_resolved)});
-        },
-        .@"inline" => if (is_noinline) {
-            return sema.fail(block, cc_src, "'noinline' function cannot have calling convention 'inline'", .{});
-        },
-        else => {},
+    if (inferred_error_set) {
+        assert(has_body);
+        return .fromIntern(try ip.getFuncDeclIes(gpa, pt.tid, .{
+            .owner_nav = sema.owner.unwrap().nav_val,
+
+            .param_types = param_types,
+            .noalias_bits = noalias_bits,
+            .comptime_bits = comptime_bits,
+            .bare_return_type = bare_return_type.toIntern(),
+            .cc = cc,
+            .is_var_args = var_args,
+            .is_generic = is_generic,
+            .is_noinline = is_noinline,
+
+            .zir_body_inst = try block.trackZir(func_inst),
+            .lbrace_line = src_locs.lbrace_line,
+            .rbrace_line = src_locs.rbrace_line,
+            .lbrace_column = @as(u16, @truncate(src_locs.columns)),
+            .rbrace_column = @as(u16, @truncate(src_locs.columns >> 16)),
+        }));
     }
 
-    switch (zcu.callconvSupported(cc_resolved)) {
-        .ok => {},
-        .bad_arch => |allowed_archs| {
-            const ArchListFormatter = struct {
-                archs: []const std.Target.Cpu.Arch,
-                pub fn format(formatter: @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
-                    for (formatter.archs, 0..) |arch, i| {
-                        if (i != 0)
-                            try w.writeAll(", ");
-                        try w.print("'{s}'", .{@tagName(arch)});
-                    }
-                }
-            };
-            return sema.fail(block, cc_src, "calling convention '{s}' only available on architectures {f}", .{
-                @tagName(cc_resolved),
-                ArchListFormatter{ .archs = allowed_archs },
-            });
-        },
-        .bad_backend => |bad_backend| return sema.fail(block, cc_src, "calling convention '{s}' not supported by compiler backend '{s}'", .{
-            @tagName(cc_resolved),
-            @tagName(bad_backend),
-        }),
+    const func_ty = try ip.getFuncType(gpa, pt.tid, .{
+        .param_types = param_types,
+        .noalias_bits = noalias_bits,
+        .comptime_bits = comptime_bits,
+        .return_type = bare_return_type.toIntern(),
+        .cc = cc,
+        .is_var_args = var_args,
+        .is_generic = is_generic,
+        .is_noinline = is_noinline,
+    });
+
+    if (has_body) {
+        return .fromIntern(try ip.getFuncDecl(gpa, pt.tid, .{
+            .owner_nav = sema.owner.unwrap().nav_val,
+            .ty = func_ty,
+            .cc = cc,
+            .is_noinline = is_noinline,
+            .zir_body_inst = try block.trackZir(func_inst),
+            .lbrace_line = src_locs.lbrace_line,
+            .rbrace_line = src_locs.rbrace_line,
+            .lbrace_column = @as(u16, @truncate(src_locs.columns)),
+            .rbrace_column = @as(u16, @truncate(src_locs.columns >> 16)),
+        }));
     }
 
-    return Air.internedToRef(if (opt_func_index != .none) opt_func_index else func_ty);
+    return .fromIntern(func_ty);
 }
 
 fn zirParam(
@@ -19395,7 +19384,7 @@ fn zirUnionInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
     if (union_ty.zigTypeTag(pt.zcu) != .@"union") {
         return sema.fail(block, ty_src, "expected union type, found '{f}'", .{union_ty.fmt(pt)});
     }
-    const field_name = try sema.resolveConstStringIntern(block, field_src, extra.field_name, .{ .simple = .union_field_name });
+    const field_name = try sema.resolveConstStringIntern(block, field_src, extra.field_name, .{ .simple = .union_field_names });
     const init = try sema.resolveInst(extra.init);
     return sema.unionInit(block, init, init_src, union_ty, ty_src, field_name, field_src);
 }
@@ -20553,589 +20542,529 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     return block.addUnOp(.tag_name, casted_operand);
 }
 
-fn zirReify(
+fn zirReifyInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
+    const signedness_src = block.builtinCallArgSrc(inst_data.src_node, 0);
+    const bits_src = block.builtinCallArgSrc(inst_data.src_node, 1);
+    const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+    const signedness = try sema.resolveBuiltinEnum(block, signedness_src, extra.lhs, .Signedness, .{ .simple = .int_signedness });
+    const bits: u16 = @intCast(try sema.resolveInt(block, bits_src, extra.rhs, .u16, .{ .simple = .int_bit_width }));
+    return .fromType(try sema.pt.intType(signedness, bits));
+}
+
+fn zirReifySliceArgTy(
     sema: *Sema,
     block: *Block,
     extended: Zir.Inst.Extended.InstData,
-    inst: Zir.Inst.Index,
 ) CompileError!Air.Inst.Ref {
     const pt = sema.pt;
     const zcu = pt.zcu;
-    const gpa = sema.gpa;
-    const ip = &zcu.intern_pool;
-    const name_strategy: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
-    const extra = sema.code.extraData(Zir.Inst.Reify, extended.operand).data;
-    const tracked_inst = try block.trackZir(inst);
-    const src: LazySrcLoc = .{
-        .base_node_inst = tracked_inst,
-        .offset = LazySrcLoc.Offset.nodeOffset(.zero),
-    };
-    const operand_src: LazySrcLoc = .{
-        .base_node_inst = tracked_inst,
-        .offset = .{
-            .node_offset_builtin_call_arg = .{
-                .builtin_call_node = .zero, // `tracked_inst` is precisely the `reify` instruction, so offset is 0
-                .arg_index = 0,
-            },
-        },
+
+    const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
+    const info: Zir.Inst.ReifySliceArgInfo = @enumFromInt(extended.small);
+
+    const src = block.nodeOffset(extra.node);
+
+    const comptime_reason: std.zig.SimpleComptimeReason, const in_scalar_ty: Type, const out_scalar_ty: Type = switch (info) {
+        // zig fmt: off
+        .type_to_fn_param_attrs       => .{ .fn_param_attrs,     .type,           try sema.getBuiltinType(src, .@"Type.Fn.Param.Attributes") },
+        .string_to_struct_field_type  => .{ .struct_field_types, .slice_const_u8, .type },
+        .string_to_union_field_type   => .{ .union_field_types,  .slice_const_u8, .type },
+        .string_to_struct_field_attrs => .{ .struct_field_attrs, .slice_const_u8, try sema.getBuiltinType(src, .@"Type.StructField.Attributes") },
+        .string_to_union_field_attrs  => .{ .union_field_attrs,  .slice_const_u8, try sema.getBuiltinType(src, .@"Type.UnionField.Attributes") },
+        // zig fmt: on
     };
-    const type_info_ty = try sema.getBuiltinType(src, .Type);
-    const uncasted_operand = try sema.resolveInst(extra.operand);
-    const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src);
-    const val = try sema.resolveConstDefinedValue(block, operand_src, type_info, .{ .simple = .operand_Type });
-    const union_val = ip.indexToKey(val.toIntern()).un;
-    if (try sema.anyUndef(block, operand_src, Value.fromInterned(union_val.val))) {
-        return sema.failWithUseOfUndef(block, operand_src, null);
-    }
-    const tag_index = type_info_ty.unionTagFieldIndex(Value.fromInterned(union_val.tag), zcu).?;
-    switch (@as(std.builtin.TypeId, @enumFromInt(tag_index))) {
-        .type => return .type_type,
-        .void => return .void_type,
-        .bool => return .bool_type,
-        .noreturn => return .noreturn_type,
-        .comptime_float => return .comptime_float_type,
-        .comptime_int => return .comptime_int_type,
-        .undefined => return .undefined_type,
-        .null => return .null_type,
-        .@"anyframe" => return sema.failWithUseOfAsync(block, src),
-        .enum_literal => return .enum_literal_type,
-        .int => {
-            const int = try sema.interpretBuiltinType(block, operand_src, .fromInterned(union_val.val), std.builtin.Type.Int);
-            const ty = try pt.intType(int.signedness, int.bits);
-            return Air.internedToRef(ty.toIntern());
-        },
-        .vector => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const len_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls),
-            ).?);
-            const child_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "child", .no_embedded_nulls),
-            ).?);
-
-            const len: u32 = @intCast(try len_val.toUnsignedIntSema(pt));
-            const child_ty = child_val.toType();
-
-            try sema.checkVectorElemType(block, src, child_ty);
-
-            const ty = try pt.vectorType(.{
-                .len = len,
-                .child = child_ty.toIntern(),
-            });
-            return Air.internedToRef(ty.toIntern());
-        },
-        .float => {
-            const float = try sema.interpretBuiltinType(block, operand_src, .fromInterned(union_val.val), std.builtin.Type.Float);
-
-            const ty: Type = switch (float.bits) {
-                16 => .f16,
-                32 => .f32,
-                64 => .f64,
-                80 => .f80,
-                128 => .f128,
-                else => return sema.fail(block, src, "{d}-bit float unsupported", .{float.bits}),
-            };
-            return Air.internedToRef(ty.toIntern());
-        },
-        .pointer => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const size_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "size", .no_embedded_nulls),
-            ).?);
-            const is_const_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "is_const", .no_embedded_nulls),
-            ).?);
-            const is_volatile_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "is_volatile", .no_embedded_nulls),
-            ).?);
-            const alignment_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "alignment", .no_embedded_nulls),
-            ).?);
-            const address_space_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "address_space", .no_embedded_nulls),
-            ).?);
-            const child_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "child", .no_embedded_nulls),
-            ).?);
-            const is_allowzero_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "is_allowzero", .no_embedded_nulls),
-            ).?);
-            const sentinel_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "sentinel_ptr", .no_embedded_nulls),
-            ).?);
-
-            if (!try sema.intFitsInType(alignment_val, align_ty, null)) {
-                return sema.fail(block, src, "alignment must fit in '{f}'", .{align_ty.fmt(pt)});
-            }
-            const alignment_val_int = try alignment_val.toUnsignedIntSema(pt);
-            const abi_align = try sema.validateAlign(block, src, alignment_val_int);
-
-            const elem_ty = child_val.toType();
-            if (abi_align != .none) {
-                try elem_ty.resolveLayout(pt);
-            }
-
-            const ptr_size = try sema.interpretBuiltinType(block, operand_src, size_val, std.builtin.Type.Pointer.Size);
-
-            const actual_sentinel: InternPool.Index = s: {
-                if (!sentinel_val.isNull(zcu)) {
-                    if (ptr_size == .one or ptr_size == .c) {
-                        return sema.fail(block, src, "sentinels are only allowed on slices and unknown-length pointers", .{});
-                    }
-                    const sentinel_ptr_val = sentinel_val.optionalValue(zcu).?;
-                    const ptr_ty = try pt.singleMutPtrType(elem_ty);
-                    const sent_val = (try sema.pointerDeref(block, src, sentinel_ptr_val, ptr_ty)).?;
-                    try sema.checkSentinelType(block, src, elem_ty);
-                    if (sent_val.canMutateComptimeVarState(zcu)) {
-                        const sentinel_name = try ip.getOrPutString(gpa, pt.tid, "sentinel_ptr", .no_embedded_nulls);
-                        return sema.failWithContainsReferenceToComptimeVar(block, src, sentinel_name, "sentinel", sent_val);
-                    }
-                    break :s sent_val.toIntern();
-                }
-                break :s .none;
-            };
 
-            if (elem_ty.zigTypeTag(zcu) == .noreturn) {
-                return sema.fail(block, src, "pointer to noreturn not allowed", .{});
-            } else if (elem_ty.zigTypeTag(zcu) == .@"fn") {
-                if (ptr_size != .one) {
-                    return sema.fail(block, src, "function pointers must be single pointers", .{});
-                }
-            } else if (ptr_size != .one and elem_ty.zigTypeTag(zcu) == .@"opaque") {
-                return sema.fail(block, src, "indexable pointer to opaque type '{f}' not allowed", .{elem_ty.fmt(pt)});
-            } else if (ptr_size == .c) {
-                if (!try sema.validateExternType(elem_ty, .other)) {
-                    const msg = msg: {
-                        const msg = try sema.errMsg(src, "C pointers cannot point to non-C-ABI-compatible type '{f}'", .{elem_ty.fmt(pt)});
-                        errdefer msg.destroy(gpa);
+    const operand_ty = try pt.ptrTypeSema(.{
+        .child = in_scalar_ty.toIntern(),
+        .flags = .{ .size = .slice, .is_const = true },
+    });
 
-                        try sema.explainWhyTypeIsNotExtern(msg, src, elem_ty, .other);
+    const operand_uncoerced = try sema.resolveInst(extra.operand);
+    const operand_coerced = try sema.coerce(block, operand_ty, operand_uncoerced, src);
+    const operand_val = try sema.resolveConstDefinedValue(block, src, operand_coerced, .{ .simple = comptime_reason });
+    const len_val: Value = .fromInterned(zcu.intern_pool.indexToKey(operand_val.toIntern()).slice.len);
+    if (len_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src, null);
+    const len = try len_val.toUnsignedIntSema(pt);
 
-                        try sema.addDeclaredHereNote(msg, elem_ty);
-                        break :msg msg;
-                    };
-                    return sema.failWithOwnedErrorMsg(block, msg);
-                }
-            }
+    return .fromType(try pt.singleConstPtrType(try pt.arrayType(.{
+        .len = len,
+        .child = out_scalar_ty.toIntern(),
+    })));
+}
 
-            const ty = try pt.ptrTypeSema(.{
-                .child = elem_ty.toIntern(),
-                .sentinel = actual_sentinel,
-                .flags = .{
-                    .size = ptr_size,
-                    .is_const = is_const_val.toBool(),
-                    .is_volatile = is_volatile_val.toBool(),
-                    .alignment = abi_align,
-                    .address_space = try sema.interpretBuiltinType(block, operand_src, address_space_val, std.builtin.AddressSpace),
-                    .is_allowzero = is_allowzero_val.toBool(),
-                },
-            });
-            return Air.internedToRef(ty.toIntern());
-        },
-        .array => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const len_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls),
-            ).?);
-            const child_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "child", .no_embedded_nulls),
-            ).?);
-            const sentinel_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "sentinel_ptr", .no_embedded_nulls),
-            ).?);
-
-            const len = try len_val.toUnsignedIntSema(pt);
-            const child_ty = child_val.toType();
-            const sentinel = if (sentinel_val.optionalValue(zcu)) |p| blk: {
-                const ptr_ty = try pt.singleMutPtrType(child_ty);
-                try sema.checkSentinelType(block, src, child_ty);
-                const sentinel = (try sema.pointerDeref(block, src, p, ptr_ty)).?;
-                if (sentinel.canMutateComptimeVarState(zcu)) {
-                    const sentinel_name = try ip.getOrPutString(gpa, pt.tid, "sentinel_ptr", .no_embedded_nulls);
-                    return sema.failWithContainsReferenceToComptimeVar(block, src, sentinel_name, "sentinel", sentinel);
-                }
-                break :blk sentinel;
-            } else null;
-
-            const ty = try pt.arrayType(.{
-                .len = len,
-                .sentinel = if (sentinel) |s| s.toIntern() else .none,
-                .child = child_ty.toIntern(),
-            });
-            return Air.internedToRef(ty.toIntern());
-        },
-        .optional => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const child_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "child", .no_embedded_nulls),
-            ).?);
+fn zirReifyEnumValueSliceTy(
+    sema: *Sema,
+    block: *Block,
+    extended: Zir.Inst.Extended.InstData,
+) CompileError!Air.Inst.Ref {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
 
-            const child_ty = child_val.toType();
+    const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
 
-            const ty = try pt.optionalType(child_ty.toIntern());
-            return Air.internedToRef(ty.toIntern());
-        },
-        .error_union => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const error_set_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "error_set", .no_embedded_nulls),
-            ).?);
-            const payload_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "payload", .no_embedded_nulls),
-            ).?);
+    const int_tag_ty_src = block.builtinCallArgSrc(extra.node, 0);
+    const field_names_src = block.builtinCallArgSrc(extra.node, 2);
 
-            const error_set_ty = error_set_val.toType();
-            const payload_ty = payload_val.toType();
+    const int_tag_ty = try sema.resolveType(block, int_tag_ty_src, extra.lhs);
 
-            if (error_set_ty.zigTypeTag(zcu) != .error_set) {
-                return sema.fail(block, src, "Type.ErrorUnion.error_set must be an error set type", .{});
-            }
+    const operand_uncoerced = try sema.resolveInst(extra.rhs);
+    const operand_coerced = try sema.coerce(block, .slice_const_slice_const_u8, operand_uncoerced, field_names_src);
+    const operand_val = try sema.resolveConstDefinedValue(block, field_names_src, operand_coerced, .{ .simple = .enum_field_names });
+    const len_val: Value = .fromInterned(zcu.intern_pool.indexToKey(operand_val.toIntern()).slice.len);
+    if (len_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, field_names_src, null);
+    const len = try len_val.toUnsignedIntSema(pt);
 
-            const ty = try pt.errorUnionType(error_set_ty, payload_ty);
-            return Air.internedToRef(ty.toIntern());
-        },
-        .error_set => {
-            const payload_val = Value.fromInterned(union_val.val).optionalValue(zcu) orelse
-                return .anyerror_type;
+    return .fromType(try pt.singleConstPtrType(try pt.arrayType(.{
+        .len = len,
+        .child = int_tag_ty.toIntern(),
+    })));
+}
+
+fn zirReifyPointerSentinelTy(
+    sema: *Sema,
+    block: *Block,
+    extended: Zir.Inst.Extended.InstData,
+) CompileError!Air.Inst.Ref {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+    const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
+    const src = block.nodeOffset(extra.node);
+    const elem_ty = try sema.resolveType(block, src, extra.operand);
+    return .fromType(switch (elem_ty.zigTypeTag(zcu)) {
+        else => try pt.optionalType(elem_ty.toIntern()),
+        // These types cannot be the child of an optional. To allow reifying pointers to them still,
+        // we treat the "sentinel" argument to `@Pointer` as `?noreturn` instead of `?T`.
+        .@"opaque", .null => .optional_noreturn,
+    });
+}
 
-            const names_val = try sema.derefSliceAsArray(block, src, payload_val, .{ .simple = .error_set_contents });
+fn zirReifyTuple(
+    sema: *Sema,
+    block: *Block,
+    extended: Zir.Inst.Extended.InstData,
+) CompileError!Air.Inst.Ref {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
 
-            const len = try sema.usizeCast(block, src, names_val.typeOf(zcu).arrayLen(zcu));
-            var names: InferredErrorSet.NameMap = .{};
-            try names.ensureUnusedCapacity(sema.arena, len);
-            for (0..len) |i| {
-                const elem_val = try names_val.elemValue(pt, i);
-                const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern()));
-                const name_val = try elem_val.fieldValue(pt, elem_struct_type.nameIndex(
-                    ip,
-                    try ip.getOrPutString(gpa, pt.tid, "name", .no_embedded_nulls),
-                ).?);
-
-                const name = try sema.sliceToIpString(block, src, name_val, .{ .simple = .error_set_contents });
-                _ = try pt.getErrorValue(name);
-                const gop = names.getOrPutAssumeCapacity(name);
-                if (gop.found_existing) {
-                    return sema.fail(block, src, "duplicate error '{f}'", .{
-                        name.fmt(ip),
-                    });
-                }
-            }
+    const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
+    const operand_src = block.builtinCallArgSrc(extra.node, 0);
 
-            const ty = try pt.errorSetFromUnsortedNames(names.keys());
-            return Air.internedToRef(ty.toIntern());
-        },
-        .@"struct" => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const layout_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "layout", .no_embedded_nulls),
-            ).?);
-            const backing_integer_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "backing_integer", .no_embedded_nulls),
-            ).?);
-            const fields_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "fields", .no_embedded_nulls),
-            ).?);
-            const decls_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "decls", .no_embedded_nulls),
-            ).?);
-            const is_tuple_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "is_tuple", .no_embedded_nulls),
-            ).?);
-
-            const layout = try sema.interpretBuiltinType(block, operand_src, layout_val, std.builtin.Type.ContainerLayout);
-
-            // Decls
-            if (try decls_val.sliceLen(pt) > 0) {
-                return sema.fail(block, src, "reified structs must have no decls", .{});
-            }
-
-            if (layout != .@"packed" and !backing_integer_val.isNull(zcu)) {
-                return sema.fail(block, src, "non-packed struct does not support backing integer type", .{});
-            }
-
-            const fields_arr = try sema.derefSliceAsArray(block, operand_src, fields_val, .{ .simple = .struct_fields });
-
-            if (is_tuple_val.toBool()) {
-                switch (layout) {
-                    .@"extern" => return sema.fail(block, src, "extern tuples are not supported", .{}),
-                    .@"packed" => return sema.fail(block, src, "packed tuples are not supported", .{}),
-                    .auto => {},
-                }
-                return sema.reifyTuple(block, src, fields_arr);
-            } else {
-                return sema.reifyStruct(block, inst, src, layout, backing_integer_val, fields_arr, name_strategy);
-            }
-        },
-        .@"enum" => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const tag_type_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "tag_type", .no_embedded_nulls),
-            ).?);
-            const fields_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "fields", .no_embedded_nulls),
-            ).?);
-            const decls_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "decls", .no_embedded_nulls),
-            ).?);
-            const is_exhaustive_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "is_exhaustive", .no_embedded_nulls),
-            ).?);
-
-            if (try decls_val.sliceLen(pt) > 0) {
-                return sema.fail(block, src, "reified enums must have no decls", .{});
-            }
-
-            const fields_arr = try sema.derefSliceAsArray(block, operand_src, fields_val, .{ .simple = .enum_fields });
-
-            return sema.reifyEnum(block, inst, src, tag_type_val.toType(), is_exhaustive_val.toBool(), fields_arr, name_strategy);
-        },
-        .@"opaque" => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const decls_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "decls", .no_embedded_nulls),
-            ).?);
+    const types_uncoerced = try sema.resolveInst(extra.operand);
+    const types_coerced = try sema.coerce(block, .slice_const_type, types_uncoerced, operand_src);
+    const types_slice_val = try sema.resolveConstDefinedValue(block, operand_src, types_coerced, .{ .simple = .tuple_field_types });
+    const types_array_val = try sema.derefSliceAsArray(block, operand_src, types_slice_val, .{ .simple = .tuple_field_types });
+    const fields_len: u32 = @intCast(types_array_val.typeOf(zcu).arrayLen(zcu));
 
-            // Decls
-            if (try decls_val.sliceLen(pt) > 0) {
-                return sema.fail(block, src, "reified opaque must have no decls", .{});
-            }
+    const field_types = try sema.arena.alloc(InternPool.Index, fields_len);
+    for (field_types, 0..) |*field_ty, field_idx| {
+        const field_ty_val = try types_array_val.elemValue(pt, field_idx);
+        if (field_ty_val.isUndef(zcu)) {
+            return sema.failWithUseOfUndef(block, operand_src, null);
+        }
+        field_ty.* = field_ty_val.toIntern();
+    }
 
-            const wip_ty = switch (try ip.getOpaqueType(gpa, pt.tid, .{
-                .key = .{ .reified = .{
-                    .zir_index = try block.trackZir(inst),
-                } },
-            })) {
-                .existing => |ty| {
-                    try sema.addTypeReferenceEntry(src, ty);
-                    return Air.internedToRef(ty);
-                },
-                .wip => |wip| wip,
-            };
-            errdefer wip_ty.cancel(ip, pt.tid);
+    const field_values = try sema.arena.alloc(InternPool.Index, fields_len);
+    @memset(field_values, .none);
 
-            const type_name = try sema.createTypeName(
-                block,
-                name_strategy,
-                "opaque",
-                inst,
-                wip_ty.index,
-            );
-            wip_ty.setName(ip, type_name.name, type_name.nav);
+    return .fromIntern(try zcu.intern_pool.getTupleType(zcu.gpa, pt.tid, .{
+        .types = field_types,
+        .values = field_values,
+    }));
+}
 
-            const new_namespace_index = try pt.createNamespace(.{
-                .parent = block.namespace.toOptional(),
-                .owner_type = wip_ty.index,
-                .file_scope = block.getFileScopeIndex(zcu),
-                .generation = zcu.generation,
-            });
+fn zirReifyPointer(
+    sema: *Sema,
+    block: *Block,
+    extended: Zir.Inst.Extended.InstData,
+) CompileError!Air.Inst.Ref {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
 
-            try sema.addTypeReferenceEntry(src, wip_ty.index);
-            if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
-            return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
-        },
-        .@"union" => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const layout_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "layout", .no_embedded_nulls),
-            ).?);
-            const tag_type_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "tag_type", .no_embedded_nulls),
-            ).?);
-            const fields_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "fields", .no_embedded_nulls),
-            ).?);
-            const decls_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "decls", .no_embedded_nulls),
-            ).?);
+    const extra = sema.code.extraData(Zir.Inst.ReifyPointer, extended.operand).data;
+    const src = block.nodeOffset(extra.node);
+    const size_src = block.builtinCallArgSrc(extra.node, 0);
+    const attrs_src = block.builtinCallArgSrc(extra.node, 1);
+    const elem_ty_src = block.builtinCallArgSrc(extra.node, 2);
+    const sentinel_src = block.builtinCallArgSrc(extra.node, 3);
+
+    const size_ty = try sema.getBuiltinType(size_src, .@"Type.Pointer.Size");
+    const attrs_ty = try sema.getBuiltinType(attrs_src, .@"Type.Pointer.Attributes");
+
+    const size_uncoerced = try sema.resolveInst(extra.size);
+    const size_coerced = try sema.coerce(block, size_ty, size_uncoerced, size_src);
+    const size_val = try sema.resolveConstDefinedValue(block, size_src, size_coerced, .{ .simple = .pointer_size });
+    const size = try sema.interpretBuiltinType(block, size_src, size_val, std.builtin.Type.Pointer.Size);
+
+    const attrs_uncoerced = try sema.resolveInst(extra.attrs);
+    const attrs_coerced = try sema.coerce(block, attrs_ty, attrs_uncoerced, attrs_src);
+    const attrs_val = try sema.resolveConstDefinedValue(block, attrs_src, attrs_coerced, .{ .simple = .pointer_attrs });
+    const attrs = try sema.interpretBuiltinType(block, attrs_src, attrs_val, std.builtin.Type.Pointer.Attributes);
+
+    const @"align": Alignment = if (attrs.@"align") |bytes| a: {
+        break :a try sema.validateAlign(block, attrs_src, bytes);
+    } else .none;
 
-            if (try decls_val.sliceLen(pt) > 0) {
-                return sema.fail(block, src, "reified unions must have no decls", .{});
-            }
-            const layout = try sema.interpretBuiltinType(block, operand_src, layout_val, std.builtin.Type.ContainerLayout);
+    const elem_ty = try sema.resolveType(block, elem_ty_src, extra.elem_ty);
 
-            const has_tag = tag_type_val.optionalValue(zcu) != null;
+    switch (elem_ty.zigTypeTag(zcu)) {
+        .noreturn => return sema.fail(block, elem_ty_src, "pointer to noreturn not allowed", .{}),
+        // This needs to be disallowed, because the sentinel parameter would otherwise have type
+        // `?@TypeOf(null)`, which is not a valid type because you cannot differentiate between
+        // constructing the "inner" null value and the "outer" null value.
+        .null => return sema.fail(block, elem_ty_src, "cannot reify pointer to '@TypeOf(null)'", .{}),
+        .@"fn" => switch (size) {
+            .one => {},
+            .many, .c, .slice => return sema.fail(block, src, "function pointers must be single pointers", .{}),
+        },
+        .@"opaque" => switch (size) {
+            .one => {},
+            .many, .c, .slice => return sema.fail(block, src, "indexable pointer to opaque type '{f}' not allowed", .{elem_ty.fmt(pt)}),
+        },
+        else => {},
+    }
 
-            if (has_tag) {
-                switch (layout) {
-                    .@"extern" => return sema.fail(block, src, "extern union does not support enum tag type", .{}),
-                    .@"packed" => return sema.fail(block, src, "packed union does not support enum tag type", .{}),
-                    .auto => {},
-                }
-            }
+    if (size == .c and !try sema.validateExternType(elem_ty, .other)) {
+        return sema.failWithOwnedErrorMsg(block, msg: {
+            const msg = try sema.errMsg(src, "C pointers cannot point to non-C-ABI-compatible type '{f}'", .{elem_ty.fmt(pt)});
+            errdefer msg.destroy(gpa);
+            try sema.explainWhyTypeIsNotExtern(msg, elem_ty_src, elem_ty, .other);
+            try sema.addDeclaredHereNote(msg, elem_ty);
+            break :msg msg;
+        });
+    }
 
-            const fields_arr = try sema.derefSliceAsArray(block, operand_src, fields_val, .{ .simple = .union_fields });
+    const sentinel_ty = try pt.optionalType(elem_ty.toIntern());
+    const sentinel_uncoerced = try sema.resolveInst(extra.sentinel);
+    const sentinel_coerced = try sema.coerce(block, sentinel_ty, sentinel_uncoerced, sentinel_src);
+    const sentinel_val = try sema.resolveConstDefinedValue(block, sentinel_src, sentinel_coerced, .{ .simple = .pointer_sentinel });
+    const opt_sentinel = sentinel_val.optionalValue(zcu);
+    if (opt_sentinel) |sentinel| {
+        switch (size) {
+            .many, .slice => {},
+            .one, .c => return sema.fail(block, sentinel_src, "sentinels are only allowed on slices and unknown-length pointers", .{}),
+        }
+        try checkSentinelType(sema, block, sentinel_src, elem_ty);
+        if (sentinel.canMutateComptimeVarState(zcu)) {
+            const sentinel_name = try ip.getOrPutString(gpa, pt.tid, "sentinel", .no_embedded_nulls);
+            return sema.failWithContainsReferenceToComptimeVar(block, sentinel_src, sentinel_name, "sentinel", sentinel);
+        }
+    }
 
-            return sema.reifyUnion(block, inst, src, layout, tag_type_val, fields_arr, name_strategy);
+    return .fromType(try pt.ptrTypeSema(.{
+        .child = elem_ty.toIntern(),
+        .sentinel = if (opt_sentinel) |s| s.toIntern() else .none,
+        .flags = .{
+            .size = size,
+            .is_const = attrs.@"const",
+            .is_volatile = attrs.@"volatile",
+            .is_allowzero = attrs.@"allowzero",
+            .address_space = attrs.@"addrspace" orelse as: {
+                if (elem_ty.zigTypeTag(zcu) == .@"fn" and zcu.getTarget().cpu.arch == .avr) break :as .flash;
+                break :as .generic;
+            },
+            .alignment = @"align",
         },
-        .@"fn" => {
-            const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
-            const calling_convention_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "calling_convention", .no_embedded_nulls),
-            ).?);
-            const is_generic_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "is_generic", .no_embedded_nulls),
-            ).?);
-            const is_var_args_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "is_var_args", .no_embedded_nulls),
-            ).?);
-            const return_type_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "return_type", .no_embedded_nulls),
-            ).?);
-            const params_slice_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
-                ip,
-                try ip.getOrPutString(gpa, pt.tid, "params", .no_embedded_nulls),
-            ).?);
-
-            const is_generic = is_generic_val.toBool();
-            if (is_generic) {
-                return sema.fail(block, src, "Type.Fn.is_generic must be false for @Type", .{});
-            }
-
-            const is_var_args = is_var_args_val.toBool();
-            const cc = try sema.analyzeValueAsCallconv(block, src, calling_convention_val);
-            if (is_var_args) {
-                try sema.checkCallConvSupportsVarArgs(block, src, cc);
-            }
-
-            const return_type = return_type_val.optionalValue(zcu) orelse
-                return sema.fail(block, src, "Type.Fn.return_type must be non-null for @Type", .{});
-
-            const params_val = try sema.derefSliceAsArray(block, operand_src, params_slice_val, .{ .simple = .function_parameters });
-
-            const args_len = try sema.usizeCast(block, src, params_val.typeOf(zcu).arrayLen(zcu));
-            const param_types = try sema.arena.alloc(InternPool.Index, args_len);
+    }));
+}
 
-            var noalias_bits: u32 = 0;
-            for (param_types, 0..) |*param_type, i| {
-                const elem_val = try params_val.elemValue(pt, i);
-                const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern()));
-                const param_is_generic_val = try elem_val.fieldValue(pt, elem_struct_type.nameIndex(
-                    ip,
-                    try ip.getOrPutString(gpa, pt.tid, "is_generic", .no_embedded_nulls),
-                ).?);
-                const param_is_noalias_val = try elem_val.fieldValue(pt, elem_struct_type.nameIndex(
-                    ip,
-                    try ip.getOrPutString(gpa, pt.tid, "is_noalias", .no_embedded_nulls),
-                ).?);
-                const opt_param_type_val = try elem_val.fieldValue(pt, elem_struct_type.nameIndex(
-                    ip,
-                    try ip.getOrPutString(gpa, pt.tid, "type", .no_embedded_nulls),
-                ).?);
-
-                if (param_is_generic_val.toBool()) {
-                    return sema.fail(block, src, "Type.Fn.Param.is_generic must be false for @Type", .{});
-                }
-
-                const param_type_val = opt_param_type_val.optionalValue(zcu) orelse
-                    return sema.fail(block, src, "Type.Fn.Param.type must be non-null for @Type", .{});
-                param_type.* = param_type_val.toIntern();
-
-                if (param_is_noalias_val.toBool()) {
-                    if (!Type.fromInterned(param_type.*).isPtrAtRuntime(zcu)) {
-                        return sema.fail(block, src, "non-pointer parameter declared noalias", .{});
-                    }
-                    noalias_bits |= @as(u32, 1) << (std.math.cast(u5, i) orelse
-                        return sema.fail(block, src, "this compiler implementation only supports 'noalias' on the first 32 parameters", .{}));
-                }
+fn zirReifyFn(
+    sema: *Sema,
+    block: *Block,
+    extended: Zir.Inst.Extended.InstData,
+) CompileError!Air.Inst.Ref {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
+
+    const extra = sema.code.extraData(Zir.Inst.ReifyFn, extended.operand).data;
+    const param_types_src = block.builtinCallArgSrc(extra.node, 0);
+    const param_attrs_src = block.builtinCallArgSrc(extra.node, 1);
+    const ret_ty_src = block.builtinCallArgSrc(extra.node, 2);
+    const fn_attrs_src = block.builtinCallArgSrc(extra.node, 3);
+
+    const single_param_attrs_ty = try sema.getBuiltinType(param_attrs_src, .@"Type.Fn.Param.Attributes");
+    const fn_attrs_ty = try sema.getBuiltinType(fn_attrs_src, .@"Type.Fn.Attributes");
+
+    const param_types_uncoerced = try sema.resolveInst(extra.param_types);
+    const param_types_coerced = try sema.coerce(block, .slice_const_type, param_types_uncoerced, param_types_src);
+    const param_types_slice = try sema.resolveConstDefinedValue(block, param_types_src, param_types_coerced, .{ .simple = .fn_param_types });
+    const param_types_arr = try sema.derefSliceAsArray(block, param_types_src, param_types_slice, .{ .simple = .fn_param_types });
+
+    const params_len = param_types_arr.typeOf(zcu).arrayLen(zcu);
+
+    const param_attrs_ty = try pt.singleConstPtrType(try pt.arrayType(.{
+        .len = params_len,
+        .child = single_param_attrs_ty.toIntern(),
+    }));
+    const param_attrs_uncoerced = try sema.resolveInst(extra.param_attrs);
+    const param_attrs_coerced = try sema.coerce(block, param_attrs_ty, param_attrs_uncoerced, param_attrs_src);
+    const param_attrs_slice = try sema.resolveConstDefinedValue(block, param_attrs_src, param_attrs_coerced, .{ .simple = .fn_param_attrs });
+    const param_attrs_arr = try sema.derefSliceAsArray(block, param_attrs_src, param_attrs_slice, .{ .simple = .fn_param_attrs });
+
+    const ret_ty = try sema.resolveType(block, ret_ty_src, extra.ret_ty);
+
+    const fn_attrs_uncoerced = try sema.resolveInst(extra.fn_attrs);
+    const fn_attrs_coerced = try sema.coerce(block, fn_attrs_ty, fn_attrs_uncoerced, fn_attrs_src);
+    const fn_attrs_val = try sema.resolveConstDefinedValue(block, fn_attrs_src, fn_attrs_coerced, .{ .simple = .fn_attrs });
+    const fn_attrs = try sema.interpretBuiltinType(block, fn_attrs_src, fn_attrs_val, std.builtin.Type.Fn.Attributes);
+
+    var noalias_bits: u32 = 0;
+    const param_types_ip = try sema.arena.alloc(InternPool.Index, @intCast(params_len));
+    for (param_types_ip, 0..@intCast(params_len)) |*param_ty_ip, param_idx| {
+        const param_ty: Type = (try param_types_arr.elemValue(pt, param_idx)).toType();
+        const param_attrs = try sema.interpretBuiltinType(
+            block,
+            param_attrs_src,
+            try param_attrs_arr.elemValue(pt, param_idx),
+            std.builtin.Type.Fn.Param.Attributes,
+        );
+        try sema.checkParamTypeCommon(
+            block,
+            @intCast(param_idx),
+            param_ty,
+            param_attrs.@"noalias",
+            param_types_src,
+            fn_attrs.@"callconv",
+        );
+        if (try param_ty.comptimeOnlySema(pt)) {
+            return sema.fail(block, param_attrs_src, "cannot reify function type with comptime-only parameter type '{f}'", .{param_ty.fmt(pt)});
+        }
+        if (param_attrs.@"noalias") {
+            if (param_idx > 31) {
+                return sema.fail(block, param_attrs_src, "this compiler implementation only supports 'noalias' on the first 32 parameters", .{});
             }
+            noalias_bits |= @as(u32, 1) << @intCast(param_idx);
+        }
+        param_ty_ip.* = param_ty.toIntern();
+    }
 
-            const ty = try pt.funcType(.{
-                .param_types = param_types,
-                .noalias_bits = noalias_bits,
-                .return_type = return_type.toIntern(),
-                .cc = cc,
-                .is_var_args = is_var_args,
-            });
-            return Air.internedToRef(ty.toIntern());
-        },
-        .frame => return sema.failWithUseOfAsync(block, src),
+    if (fn_attrs.varargs) {
+        try sema.checkCallConvSupportsVarArgs(block, fn_attrs_src, fn_attrs.@"callconv");
     }
+
+    try sema.checkReturnTypeAndCallConvCommon(
+        block,
+        ret_ty,
+        ret_ty_src,
+        fn_attrs.@"callconv",
+        fn_attrs_src,
+        if (fn_attrs.varargs) fn_attrs_src else null,
+        false,
+        false,
+    );
+    if (try ret_ty.comptimeOnlySema(pt)) {
+        return sema.fail(block, param_attrs_src, "cannot reify function type with comptime-only return type '{f}'", .{ret_ty.fmt(pt)});
+    }
+
+    return .fromIntern(try ip.getFuncType(gpa, pt.tid, .{
+        .param_types = param_types_ip,
+        .noalias_bits = noalias_bits,
+        .comptime_bits = 0,
+        .return_type = ret_ty.toIntern(),
+        .cc = fn_attrs.@"callconv",
+        .is_var_args = fn_attrs.varargs,
+        .is_generic = false,
+        .is_noinline = false,
+    }));
 }
 
-fn reifyEnum(
+fn zirReifyStruct(
     sema: *Sema,
     block: *Block,
+    extended: Zir.Inst.Extended.InstData,
     inst: Zir.Inst.Index,
-    src: LazySrcLoc,
-    tag_ty: Type,
-    is_exhaustive: bool,
-    fields_val: Value,
-    name_strategy: Zir.Inst.NameStrategy,
 ) CompileError!Air.Inst.Ref {
     const pt = sema.pt;
     const zcu = pt.zcu;
     const gpa = sema.gpa;
     const ip = &zcu.intern_pool;
 
-    // This logic must stay in sync with the structure of `std.builtin.Type.Enum` - search for `fieldValue`.
+    const name_strategy: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
+    const extra = sema.code.extraData(Zir.Inst.ReifyStruct, extended.operand).data;
+    const tracked_inst = try block.trackZir(inst);
+    const src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .nodeOffset(.zero),
+    };
+
+    const layout_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 0,
+        } },
+    };
+    const backing_ty_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 1,
+        } },
+    };
+    const field_names_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 2,
+        } },
+    };
+    const field_types_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 3,
+        } },
+    };
+    const field_attrs_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 4,
+        } },
+    };
+
+    const container_layout_ty = try sema.getBuiltinType(layout_src, .@"Type.ContainerLayout");
+    const single_field_attrs_ty = try sema.getBuiltinType(field_attrs_src, .@"Type.StructField.Attributes");
+
+    const layout_uncoerced = try sema.resolveInst(extra.layout);
+    const layout_coerced = try sema.coerce(block, container_layout_ty, layout_uncoerced, layout_src);
+    const layout_val = try sema.resolveConstDefinedValue(block, layout_src, layout_coerced, .{ .simple = .struct_layout });
+    const layout = try sema.interpretBuiltinType(block, layout_src, layout_val, std.builtin.Type.ContainerLayout);
+
+    const backing_int_ty_uncoerced = try sema.resolveInst(extra.backing_ty);
+    const backing_int_ty_coerced = try sema.coerce(block, .optional_type, backing_int_ty_uncoerced, backing_ty_src);
+    const backing_int_ty_val = try sema.resolveConstDefinedValue(block, backing_ty_src, backing_int_ty_coerced, .{ .simple = .type });
+
+    const field_names_uncoerced = try sema.resolveInst(extra.field_names);
+    const field_names_coerced = try sema.coerce(block, .slice_const_slice_const_u8, field_names_uncoerced, field_names_src);
+    const field_names_slice = try sema.resolveConstDefinedValue(block, field_names_src, field_names_coerced, .{ .simple = .struct_field_names });
+    const field_names_arr = try sema.derefSliceAsArray(block, field_names_src, field_names_slice, .{ .simple = .struct_field_names });
+
+    const fields_len = try sema.usizeCast(block, src, field_names_arr.typeOf(zcu).arrayLen(zcu));
+
+    const field_types_ty = try pt.singleConstPtrType(try pt.arrayType(.{
+        .len = fields_len,
+        .child = .type_type,
+    }));
+    const field_attrs_ty = try pt.singleConstPtrType(try pt.arrayType(.{
+        .len = fields_len,
+        .child = single_field_attrs_ty.toIntern(),
+    }));
+
+    const field_types_uncoerced = try sema.resolveInst(extra.field_types);
+    const field_types_coerced = try sema.coerce(block, field_types_ty, field_types_uncoerced, field_types_src);
+    const field_types_slice = try sema.resolveConstDefinedValue(block, field_types_src, field_types_coerced, .{ .simple = .struct_field_types });
+    const field_types_arr = try sema.derefSliceAsArray(block, field_types_src, field_types_slice, .{ .simple = .struct_field_types });
 
-    const fields_len: u32 = @intCast(fields_val.typeOf(zcu).arrayLen(zcu));
+    const field_attrs_uncoerced = try sema.resolveInst(extra.field_attrs);
+    const field_attrs_coerced = try sema.coerce(block, field_attrs_ty, field_attrs_uncoerced, field_attrs_src);
+    const field_attrs_slice = try sema.resolveConstDefinedValue(block, field_attrs_src, field_attrs_coerced, .{ .simple = .struct_field_attrs });
+    const field_attrs_arr = try sema.derefSliceAsArray(block, field_attrs_src, field_attrs_slice, .{ .simple = .struct_field_attrs });
+
+    // Before we begin, check for undefs...
+    if (try sema.anyUndef(block, field_attrs_src, field_attrs_arr)) {
+        return sema.failWithUseOfUndef(block, field_attrs_src, null);
+    }
+    if (try sema.anyUndef(block, field_types_src, field_types_arr)) {
+        return sema.failWithUseOfUndef(block, field_types_src, null);
+    }
+    // We don't need to check `field_names_arr`, because `sliceToIpString` will check that for us.
+    if (try sema.anyUndef(block, backing_ty_src, backing_int_ty_val)) {
+        return sema.failWithUseOfUndef(block, backing_ty_src, null);
+    }
 
     // The validation work here is non-trivial, and it's possible the type already exists.
     // So in this first pass, let's just construct a hash to optimize for this case. If the
     // inputs turn out to be invalid, we can cancel the WIP type later.
 
+    var any_comptime_fields = false;
+    var any_default_inits = false;
+    var any_aligned_fields = false;
+
     // For deduplication purposes, we must create a hash including all details of this type.
     // TODO: use a longer hash!
     var hasher = std.hash.Wyhash.init(0);
-    std.hash.autoHash(&hasher, tag_ty.toIntern());
-    std.hash.autoHash(&hasher, is_exhaustive);
-    std.hash.autoHash(&hasher, fields_len);
-
+    std.hash.autoHash(&hasher, layout);
+    std.hash.autoHash(&hasher, backing_int_ty_val);
+    // The field *type* array has already been deduplicated for us thanks to the InternPool!
+    std.hash.autoHash(&hasher, field_types_arr);
+    // However, for field names and attributes, we need to actually iterate the individual fields,
+    // because the presence of pointers (the `[]const u8` for the name and the `*const anyopaque`
+    // for the default value) means that distinct interned values could ultimately result in the
+    // same struct type.
     for (0..fields_len) |field_idx| {
-        const field_info = try fields_val.elemValue(pt, field_idx);
+        const field_name_val = try field_names_arr.elemValue(pt, field_idx);
+        const field_attrs_val = try field_attrs_arr.elemValue(pt, field_idx);
+
+        const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, .{ .simple = .struct_field_names });
+
+        const field_attr_comptime = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
+            std.builtin.Type.StructField.Attributes,
+            "comptime",
+        ).?);
+        const field_attr_align = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
+            std.builtin.Type.StructField.Attributes,
+            "align",
+        ).?);
+        const field_attr_default_value_ptr = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
+            std.builtin.Type.StructField.Attributes,
+            "default_value_ptr",
+        ).?);
 
-        const field_name_val = try field_info.fieldValue(pt, 0);
-        const field_value_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 1));
-
-        const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .enum_field_name });
+        const field_default: InternPool.Index = d: {
+            const ptr_val = field_attr_default_value_ptr.optionalValue(zcu) orelse break :d .none;
+            const field_ty = (try field_types_arr.elemValue(pt, field_idx)).toType();
+            const ptr_ty = try pt.singleConstPtrType(field_ty);
+            const deref_val = try sema.pointerDeref(block, field_attrs_src, ptr_val, ptr_ty) orelse return sema.failWithNeededComptime(
+                block,
+                field_attrs_src,
+                .{ .simple = .struct_field_default_value },
+            );
+            // Resolve the value so that lazy values do not create distinct types.
+            break :d (try sema.resolveLazyValue(deref_val)).toIntern();
+        };
 
         std.hash.autoHash(&hasher, .{
             field_name,
-            field_value_val.toIntern(),
+            field_attr_comptime,
+            field_attr_align,
+            field_default,
         });
+
+        if (field_attr_comptime.toBool()) any_comptime_fields = true;
+        if (field_attr_align.optionalValue(zcu)) |_| any_aligned_fields = true;
+        if (field_default != .none) any_default_inits = true;
     }
 
-    const tracked_inst = try block.trackZir(inst);
+    // Some basic validation to avoid a bogus `getStructType` call...
+    const backing_int_ty: ?Type = if (backing_int_ty_val.optionalValue(zcu)) |backing| ty: {
+        switch (layout) {
+            .auto, .@"extern" => return sema.fail(block, backing_ty_src, "non-packed struct does not support backing integer type", .{}),
+            .@"packed" => {},
+        }
+        break :ty backing.toType();
+    } else null;
+    if (any_aligned_fields and layout == .@"packed") {
+        return sema.fail(block, field_attrs_src, "packed struct fields cannot be aligned", .{});
+    }
+    if (any_comptime_fields and layout != .auto) {
+        return sema.fail(block, field_attrs_src, "{t} struct fields cannot be marked comptime", .{layout});
+    }
 
-    const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, .{
-        .has_values = true,
-        .tag_mode = if (is_exhaustive) .explicit else .nonexhaustive,
-        .fields_len = fields_len,
+    const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{
+        .layout = layout,
+        .fields_len = @intCast(fields_len),
+        .known_non_opv = false,
+        .requires_comptime = .unknown,
+        .any_comptime_fields = any_comptime_fields,
+        .any_default_inits = any_default_inits,
+        .any_aligned_fields = any_aligned_fields,
+        .inits_resolved = true,
         .key = .{ .reified = .{
             .zir_index = tracked_inst,
             .type_hash = hasher.final(),
@@ -21148,79 +21077,144 @@ fn reifyEnum(
             return Air.internedToRef(ty);
         },
     };
-    var done = false;
-    errdefer if (!done) wip_ty.cancel(ip, pt.tid);
-
-    if (tag_ty.zigTypeTag(zcu) != .int) {
-        return sema.fail(block, src, "Type.Enum.tag_type must be an integer type", .{});
-    }
+    errdefer wip_ty.cancel(ip, pt.tid);
 
     const type_name = try sema.createTypeName(
         block,
         name_strategy,
-        "enum",
+        "struct",
         inst,
         wip_ty.index,
     );
     wip_ty.setName(ip, type_name.name, type_name.nav);
 
-    const new_namespace_index = try pt.createNamespace(.{
-        .parent = block.namespace.toOptional(),
-        .owner_type = wip_ty.index,
-        .file_scope = block.getFileScopeIndex(zcu),
-        .generation = zcu.generation,
-    });
-
-    try sema.declareDependency(.{ .interned = wip_ty.index });
-    try sema.addTypeReferenceEntry(src, wip_ty.index);
-    if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
-    wip_ty.prepare(ip, new_namespace_index);
-    wip_ty.setTagTy(ip, tag_ty.toIntern());
-    done = true;
+    const wip_struct_type = ip.loadStructType(wip_ty.index);
 
     for (0..fields_len) |field_idx| {
-        const field_info = try fields_val.elemValue(pt, field_idx);
+        const field_name_val = try field_names_arr.elemValue(pt, field_idx);
+        const field_attrs_val = try field_attrs_arr.elemValue(pt, field_idx);
 
-        const field_name_val = try field_info.fieldValue(pt, 0);
-        const field_value_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 1));
+        const field_ty = (try field_types_arr.elemValue(pt, field_idx)).toType();
 
-        // Don't pass a reason; first loop acts as an assertion that this is valid.
-        const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);
+        // Don't pass a reason; first loop acts as a check that this is valid.
+        const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, undefined);
+        if (wip_struct_type.addFieldName(ip, field_name)) |prev_index| {
+            _ = prev_index; // TODO: better source location
+            return sema.fail(block, field_names_src, "duplicate struct field name {f}", .{field_name.fmt(ip)});
+        }
+
+        const field_attr_comptime = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
+            std.builtin.Type.StructField.Attributes,
+            "comptime",
+        ).?);
+        const field_attr_align = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
+            std.builtin.Type.StructField.Attributes,
+            "align",
+        ).?);
+        const field_attr_default_value_ptr = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
+            std.builtin.Type.StructField.Attributes,
+            "default_value_ptr",
+        ).?);
+
+        if (field_attr_align.optionalValue(zcu)) |field_align_val| {
+            assert(layout != .@"packed");
+            const bytes = try field_align_val.toUnsignedIntSema(pt);
+            const a = try sema.validateAlign(block, field_attrs_src, bytes);
+            wip_struct_type.field_aligns.get(ip)[field_idx] = a;
+        } else if (any_aligned_fields) {
+            assert(layout != .@"packed");
+            wip_struct_type.field_aligns.get(ip)[field_idx] = .none;
+        }
 
-        if (!try sema.intFitsInType(field_value_val, tag_ty, null)) {
-            // TODO: better source location
-            return sema.fail(block, src, "field '{f}' with enumeration value '{f}' is too large for backing int type '{f}'", .{
-                field_name.fmt(ip),
-                field_value_val.fmtValueSema(pt, sema),
-                tag_ty.fmt(pt),
-            });
+        const field_default: InternPool.Index = d: {
+            const ptr_val = field_attr_default_value_ptr.optionalValue(zcu) orelse break :d .none;
+            assert(any_default_inits);
+            const ptr_ty = try pt.singleConstPtrType(field_ty);
+            // The first loop checked that this is comptime-dereferencable.
+            const deref_val = (try sema.pointerDeref(block, field_attrs_src, ptr_val, ptr_ty)).?;
+            // ...but we've not checked this yet!
+            if (deref_val.canMutateComptimeVarState(zcu)) {
+                return sema.failWithContainsReferenceToComptimeVar(block, field_attrs_src, field_name, "field default value", deref_val);
+            }
+            break :d (try sema.resolveLazyValue(deref_val)).toIntern();
+        };
+
+        if (field_attr_comptime.toBool()) {
+            assert(layout == .auto);
+            if (field_default == .none) {
+                return sema.fail(block, field_attrs_src, "comptime field without default initialization value", .{});
+            }
+            wip_struct_type.setFieldComptime(ip, field_idx);
         }
 
-        const coerced_field_val = try pt.getCoerced(field_value_val, tag_ty);
-        if (wip_ty.nextField(ip, field_name, coerced_field_val.toIntern())) |conflict| {
-            return sema.failWithOwnedErrorMsg(block, switch (conflict.kind) {
-                .name => msg: {
-                    const msg = try sema.errMsg(src, "duplicate enum field '{f}'", .{field_name.fmt(ip)});
+        wip_struct_type.field_types.get(ip)[field_idx] = field_ty.toIntern();
+        if (field_default != .none) {
+            wip_struct_type.field_inits.get(ip)[field_idx] = field_default;
+        }
+
+        switch (field_ty.zigTypeTag(zcu)) {
+            .@"opaque" => return sema.failWithOwnedErrorMsg(block, msg: {
+                const msg = try sema.errMsg(field_types_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
+                errdefer msg.destroy(gpa);
+                try sema.addDeclaredHereNote(msg, field_ty);
+                break :msg msg;
+            }),
+            .noreturn => return sema.failWithOwnedErrorMsg(block, msg: {
+                const msg = try sema.errMsg(field_types_src, "struct fields cannot be 'noreturn'", .{});
+                errdefer msg.destroy(gpa);
+                try sema.addDeclaredHereNote(msg, field_ty);
+                break :msg msg;
+            }),
+            else => {},
+        }
+
+        switch (layout) {
+            .auto => {},
+            .@"extern" => if (!try sema.validateExternType(field_ty, .struct_field)) {
+                return sema.failWithOwnedErrorMsg(block, msg: {
+                    const msg = try sema.errMsg(field_types_src, "extern structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
                     errdefer msg.destroy(gpa);
-                    _ = conflict.prev_field_idx; // TODO: this note is incorrect
-                    try sema.errNote(src, msg, "other field here", .{});
+                    try sema.explainWhyTypeIsNotExtern(msg, field_types_src, field_ty, .struct_field);
+                    try sema.addDeclaredHereNote(msg, field_ty);
                     break :msg msg;
-                },
-                .value => msg: {
-                    const msg = try sema.errMsg(src, "enum tag value {f} already taken", .{field_value_val.fmtValueSema(pt, sema)});
+                });
+            },
+            .@"packed" => if (!try sema.validatePackedType(field_ty)) {
+                return sema.failWithOwnedErrorMsg(block, msg: {
+                    const msg = try sema.errMsg(field_types_src, "packed structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
                     errdefer msg.destroy(gpa);
-                    _ = conflict.prev_field_idx; // TODO: this note is incorrect
-                    try sema.errNote(src, msg, "other enum tag value here", .{});
+                    try sema.explainWhyTypeIsNotPacked(msg, field_types_src, field_ty);
+                    try sema.addDeclaredHereNote(msg, field_ty);
                     break :msg msg;
-                },
-            });
+                });
+            },
         }
     }
 
-    if (!is_exhaustive and fields_len > 1 and std.math.log2_int(u64, fields_len) == tag_ty.bitSize(zcu)) {
-        return sema.fail(block, src, "non-exhaustive enum specified every value", .{});
+    if (layout == .@"packed") {
+        var fields_bit_sum: u64 = 0;
+        for (0..wip_struct_type.field_types.len) |field_idx| {
+            const field_ty: Type = .fromInterned(wip_struct_type.field_types.get(ip)[field_idx]);
+            try field_ty.resolveLayout(pt);
+            fields_bit_sum += field_ty.bitSize(zcu);
+        }
+        if (backing_int_ty) |ty| {
+            try sema.checkBackingIntType(block, src, ty, fields_bit_sum);
+            wip_struct_type.setBackingIntType(ip, ty.toIntern());
+        } else {
+            const ty = try pt.intType(.unsigned, @intCast(fields_bit_sum));
+            wip_struct_type.setBackingIntType(ip, ty.toIntern());
+        }
     }
 
+    const new_namespace_index = try pt.createNamespace(.{
+        .parent = block.namespace.toOptional(),
+        .owner_type = wip_ty.index,
+        .file_scope = block.getFileScopeIndex(zcu),
+        .generation = zcu.generation,
+    });
+
+    try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
     codegen_type: {
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
@@ -21228,75 +21222,177 @@ fn reifyEnum(
         zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
         try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
-    return Air.internedToRef(wip_ty.index);
+    try sema.declareDependency(.{ .interned = wip_ty.index });
+    try sema.addTypeReferenceEntry(src, wip_ty.index);
+    if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
+    return .fromIntern(wip_ty.finish(ip, new_namespace_index));
 }
 
-fn reifyUnion(
+fn zirReifyUnion(
     sema: *Sema,
     block: *Block,
+    extended: Zir.Inst.Extended.InstData,
     inst: Zir.Inst.Index,
-    src: LazySrcLoc,
-    layout: std.builtin.Type.ContainerLayout,
-    opt_tag_type_val: Value,
-    fields_val: Value,
-    name_strategy: Zir.Inst.NameStrategy,
 ) CompileError!Air.Inst.Ref {
     const pt = sema.pt;
     const zcu = pt.zcu;
     const gpa = sema.gpa;
     const ip = &zcu.intern_pool;
 
-    // This logic must stay in sync with the structure of `std.builtin.Type.Union` - search for `fieldValue`.
+    const name_strategy: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
+    const extra = sema.code.extraData(Zir.Inst.ReifyUnion, extended.operand).data;
+    const tracked_inst = try block.trackZir(inst);
+    const src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .nodeOffset(.zero),
+    };
+
+    const layout_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 0,
+        } },
+    };
+    const arg_ty_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 1,
+        } },
+    };
+    const field_names_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 2,
+        } },
+    };
+    const field_types_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 3,
+        } },
+    };
+    const field_attrs_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 4,
+        } },
+    };
+
+    const container_layout_ty = try sema.getBuiltinType(layout_src, .@"Type.ContainerLayout");
+    const single_field_attrs_ty = try sema.getBuiltinType(field_attrs_src, .@"Type.UnionField.Attributes");
+
+    const layout_uncoerced = try sema.resolveInst(extra.layout);
+    const layout_coerced = try sema.coerce(block, container_layout_ty, layout_uncoerced, layout_src);
+    const layout_val = try sema.resolveConstDefinedValue(block, layout_src, layout_coerced, .{ .simple = .union_layout });
+    const layout = try sema.interpretBuiltinType(block, layout_src, layout_val, std.builtin.Type.ContainerLayout);
+
+    const arg_ty_uncoerced = try sema.resolveInst(extra.arg_ty);
+    const arg_ty_coerced = try sema.coerce(block, .optional_type, arg_ty_uncoerced, arg_ty_src);
+    const arg_ty_val = try sema.resolveConstDefinedValue(block, arg_ty_src, arg_ty_coerced, .{ .simple = .type });
+
+    const field_names_uncoerced = try sema.resolveInst(extra.field_names);
+    const field_names_coerced = try sema.coerce(block, .slice_const_slice_const_u8, field_names_uncoerced, field_names_src);
+    const field_names_slice = try sema.resolveConstDefinedValue(block, field_names_src, field_names_coerced, .{ .simple = .union_field_names });
+    const field_names_arr = try sema.derefSliceAsArray(block, field_names_src, field_names_slice, .{ .simple = .union_field_names });
+
+    const fields_len = try sema.usizeCast(block, src, field_names_arr.typeOf(zcu).arrayLen(zcu));
+
+    const field_types_ty = try pt.singleConstPtrType(try pt.arrayType(.{
+        .len = fields_len,
+        .child = .type_type,
+    }));
+    const field_attrs_ty = try pt.singleConstPtrType(try pt.arrayType(.{
+        .len = fields_len,
+        .child = single_field_attrs_ty.toIntern(),
+    }));
+
+    const field_types_uncoerced = try sema.resolveInst(extra.field_types);
+    const field_types_coerced = try sema.coerce(block, field_types_ty, field_types_uncoerced, field_types_src);
+    const field_types_slice = try sema.resolveConstDefinedValue(block, field_types_src, field_types_coerced, .{ .simple = .union_field_types });
+    const field_types_arr = try sema.derefSliceAsArray(block, field_types_src, field_types_slice, .{ .simple = .union_field_types });
 
-    const fields_len: u32 = @intCast(fields_val.typeOf(zcu).arrayLen(zcu));
+    const field_attrs_uncoerced = try sema.resolveInst(extra.field_attrs);
+    const field_attrs_coerced = try sema.coerce(block, field_attrs_ty, field_attrs_uncoerced, field_attrs_src);
+    const field_attrs_slice = try sema.resolveConstDefinedValue(block, field_attrs_src, field_attrs_coerced, .{ .simple = .union_field_attrs });
+    const field_attrs_arr = try sema.derefSliceAsArray(block, field_attrs_src, field_attrs_slice, .{ .simple = .union_field_attrs });
+
+    // Before we begin, check for undefs...
+    if (try sema.anyUndef(block, field_attrs_src, field_attrs_arr)) {
+        return sema.failWithUseOfUndef(block, field_attrs_src, null);
+    }
+    if (try sema.anyUndef(block, field_types_src, field_types_arr)) {
+        return sema.failWithUseOfUndef(block, field_types_src, null);
+    }
+    // We don't need to check `field_names_arr`, because `sliceToIpString` will check that for us.
+    if (try sema.anyUndef(block, arg_ty_src, arg_ty_val)) {
+        return sema.failWithUseOfUndef(block, arg_ty_src, null);
+    }
 
     // The validation work here is non-trivial, and it's possible the type already exists.
     // So in this first pass, let's just construct a hash to optimize for this case. If the
     // inputs turn out to be invalid, we can cancel the WIP type later.
 
+    var any_aligned_fields = false;
+
     // For deduplication purposes, we must create a hash including all details of this type.
     // TODO: use a longer hash!
     var hasher = std.hash.Wyhash.init(0);
     std.hash.autoHash(&hasher, layout);
-    std.hash.autoHash(&hasher, opt_tag_type_val.toIntern());
-    std.hash.autoHash(&hasher, fields_len);
-
+    std.hash.autoHash(&hasher, arg_ty_val);
+    // `field_types_arr` and `field_attrs_arr` are already deduplicated by the InternPool!
+    std.hash.autoHash(&hasher, field_types_arr);
+    std.hash.autoHash(&hasher, field_attrs_arr);
+    // However, for field names, we need to iterate the individual fields, because the pointers (the
+    // names are slices) mean that distinct values could ultimately result in the same union type.
     for (0..fields_len) |field_idx| {
-        const field_info = try fields_val.elemValue(pt, field_idx);
-
-        const field_name_val = try field_info.fieldValue(pt, 0);
-        const field_type_val = try field_info.fieldValue(pt, 1);
-        const field_align_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 2));
+        const field_name_val = try field_names_arr.elemValue(pt, field_idx);
+        const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, .{ .simple = .union_field_names });
+        std.hash.autoHash(&hasher, field_name);
 
-        const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .union_field_name });
-        std.hash.autoHash(&hasher, .{
-            field_name,
-            field_type_val.toIntern(),
-            field_align_val.toIntern(),
-        });
+        const field_attrs = try sema.interpretBuiltinType(
+            block,
+            field_attrs_src,
+            try field_attrs_arr.elemValue(pt, field_idx),
+            std.builtin.Type.UnionField.Attributes,
+        );
+        if (field_attrs.@"align" != null) {
+            any_aligned_fields = true;
+        }
     }
 
-    const tracked_inst = try block.trackZir(inst);
+    // Some basic validation to avoid a bogus `getUnionType` call...
+    const explicit_tag_ty: ?Type = if (arg_ty_val.optionalValue(zcu)) |arg_ty| ty: {
+        switch (layout) {
+            .@"extern", .@"packed" => return sema.fail(block, arg_ty_src, "{t} union does not support enum tag type", .{layout}),
+            .auto => {},
+        }
+        break :ty arg_ty.toType();
+    } else null;
+    if (any_aligned_fields and layout == .@"packed") {
+        return sema.fail(block, field_attrs_src, "packed union fields cannot be aligned", .{});
+    }
 
     const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, .{
         .flags = .{
             .layout = layout,
             .status = .none,
-            .runtime_tag = if (opt_tag_type_val.optionalValue(zcu) != null)
-                .tagged
-            else if (layout != .auto)
-                .none
-            else switch (block.wantSafeTypes()) {
-                true => .safety,
-                false => .none,
+            .runtime_tag = rt: {
+                if (explicit_tag_ty != null) break :rt .tagged;
+                if (layout == .auto and block.wantSafeTypes()) break :rt .safety;
+                break :rt .none;
             },
-            .any_aligned_fields = layout != .@"packed",
+            .any_aligned_fields = any_aligned_fields,
             .requires_comptime = .unknown,
             .assumed_runtime_bits = false,
             .assumed_pointer_aligned = false,
             .alignment = .none,
         },
-        .fields_len = fields_len,
+        .fields_len = @intCast(fields_len),
         .enum_tag_ty = .none, // set later because not yet validated
         .field_types = &.{}, // set later
         .field_aligns = &.{}, // set later
@@ -21325,128 +21421,117 @@ fn reifyUnion(
 
     const loaded_union = ip.loadUnionType(wip_ty.index);
 
-    const enum_tag_ty, const has_explicit_tag = if (opt_tag_type_val.optionalValue(zcu)) |tag_type_val| tag_ty: {
-        switch (ip.indexToKey(tag_type_val.toIntern())) {
-            .enum_type => {},
-            else => return sema.fail(block, src, "Type.Union.tag_type must be an enum type", .{}),
+    const enum_tag_ty, const has_explicit_tag = if (explicit_tag_ty) |enum_tag_ty| tag: {
+        if (enum_tag_ty.zigTypeTag(zcu) != .@"enum") {
+            return sema.fail(block, arg_ty_src, "tag type must be an enum type", .{});
         }
-        const enum_tag_ty = tag_type_val.toType();
 
-        // We simply track which fields of the tag type have been seen.
         const tag_ty_fields_len = enum_tag_ty.enumFieldCount(zcu);
-        var seen_tags = try std.DynamicBitSetUnmanaged.initEmpty(sema.arena, tag_ty_fields_len);
 
         for (0..fields_len) |field_idx| {
-            const field_info = try fields_val.elemValue(pt, field_idx);
+            const field_name_val = try field_names_arr.elemValue(pt, field_idx);
+            // Don't pass a reason; first loop acts as a check that this is valid.
+            const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, undefined);
 
-            const field_name_val = try field_info.fieldValue(pt, 0);
-            const field_type_val = try field_info.fieldValue(pt, 1);
-            const field_alignment_val = try field_info.fieldValue(pt, 2);
-
-            // Don't pass a reason; first loop acts as an assertion that this is valid.
-            const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);
-
-            const enum_index = enum_tag_ty.enumFieldIndex(field_name, zcu) orelse {
-                // TODO: better source location
-                return sema.fail(block, src, "no field named '{f}' in enum '{f}'", .{
+            if (field_idx >= tag_ty_fields_len) {
+                return sema.fail(block, field_names_src, "no field named '{f}' in enum '{f}'", .{
                     field_name.fmt(ip), enum_tag_ty.fmt(pt),
                 });
-            };
-            if (seen_tags.isSet(enum_index)) {
-                // TODO: better source location
-                return sema.fail(block, src, "duplicate union field {f}", .{field_name.fmt(ip)});
             }
-            seen_tags.set(enum_index);
 
-            loaded_union.field_types.get(ip)[field_idx] = field_type_val.toIntern();
-            const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
-            if (layout == .@"packed") {
-                if (byte_align != 0) return sema.fail(block, src, "alignment of a packed union field must be set to 0", .{});
-            } else {
-                loaded_union.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
+            const enum_field_name = enum_tag_ty.enumFieldName(field_idx, zcu);
+            if (enum_field_name != field_name) {
+                return sema.fail(block, field_names_src, "union field name '{f}' does not match enum field name '{f}'", .{
+                    field_name.fmt(ip), enum_field_name.fmt(ip),
+                });
             }
         }
-
         if (tag_ty_fields_len > fields_len) return sema.failWithOwnedErrorMsg(block, msg: {
-            const msg = try sema.errMsg(src, "enum fields missing in union", .{});
+            const msg = try sema.errMsg(field_names_src, "{d} enum fields missing in union", .{
+                tag_ty_fields_len - fields_len,
+            });
             errdefer msg.destroy(gpa);
-            var it = seen_tags.iterator(.{ .kind = .unset });
-            while (it.next()) |enum_index| {
-                const field_name = enum_tag_ty.enumFieldName(enum_index, zcu);
-                try sema.addFieldErrNote(enum_tag_ty, enum_index, msg, "field '{f}' missing, declared here", .{
-                    field_name.fmt(ip),
+            for (fields_len..tag_ty_fields_len) |enum_field_idx| {
+                try sema.addFieldErrNote(enum_tag_ty, enum_field_idx, msg, "field '{f}' missing, declared here", .{
+                    enum_tag_ty.enumFieldName(enum_field_idx, zcu).fmt(ip),
                 });
             }
             try sema.addDeclaredHereNote(msg, enum_tag_ty);
             break :msg msg;
         });
-
-        break :tag_ty .{ enum_tag_ty.toIntern(), true };
-    } else tag_ty: {
+        break :tag .{ enum_tag_ty.toIntern(), true };
+    } else tag: {
         // We must track field names and set up the tag type ourselves.
         var field_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty;
         try field_names.ensureTotalCapacity(sema.arena, fields_len);
 
         for (0..fields_len) |field_idx| {
-            const field_info = try fields_val.elemValue(pt, field_idx);
-
-            const field_name_val = try field_info.fieldValue(pt, 0);
-            const field_type_val = try field_info.fieldValue(pt, 1);
-            const field_alignment_val = try field_info.fieldValue(pt, 2);
-
-            // Don't pass a reason; first loop acts as an assertion that this is valid.
-            const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);
+            const field_name_val = try field_names_arr.elemValue(pt, field_idx);
+            // Don't pass a reason; first loop acts as a check that this is valid.
+            const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, undefined);
             const gop = field_names.getOrPutAssumeCapacity(field_name);
             if (gop.found_existing) {
                 // TODO: better source location
-                return sema.fail(block, src, "duplicate union field {f}", .{field_name.fmt(ip)});
-            }
-
-            loaded_union.field_types.get(ip)[field_idx] = field_type_val.toIntern();
-            const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
-            if (layout == .@"packed") {
-                if (byte_align != 0) return sema.fail(block, src, "alignment of a packed union field must be set to 0", .{});
-            } else {
-                loaded_union.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
+                return sema.fail(block, field_names_src, "duplicate union field {f}", .{field_name.fmt(ip)});
             }
         }
-
         const enum_tag_ty = try sema.generateUnionTagTypeSimple(block, field_names.keys(), wip_ty.index, type_name.name);
-        break :tag_ty .{ enum_tag_ty, false };
+        break :tag .{ enum_tag_ty, false };
     };
     errdefer if (!has_explicit_tag) ip.remove(pt.tid, enum_tag_ty); // remove generated tag type on error
 
-    for (loaded_union.field_types.get(ip)) |field_ty_ip| {
-        const field_ty: Type = .fromInterned(field_ty_ip);
+    for (0..fields_len) |field_idx| {
+        const field_ty = (try field_types_arr.elemValue(pt, field_idx)).toType();
+        const field_attrs = try sema.interpretBuiltinType(
+            block,
+            field_attrs_src,
+            try field_attrs_arr.elemValue(pt, field_idx),
+            std.builtin.Type.UnionField.Attributes,
+        );
+
         if (field_ty.zigTypeTag(zcu) == .@"opaque") {
             return sema.failWithOwnedErrorMsg(block, msg: {
-                const msg = try sema.errMsg(src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{});
+                const msg = try sema.errMsg(field_types_src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{});
                 errdefer msg.destroy(gpa);
-
                 try sema.addDeclaredHereNote(msg, field_ty);
                 break :msg msg;
             });
         }
-        if (layout == .@"extern" and !try sema.validateExternType(field_ty, .union_field)) {
-            return sema.failWithOwnedErrorMsg(block, msg: {
-                const msg = try sema.errMsg(src, "extern unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
-                errdefer msg.destroy(gpa);
 
-                try sema.explainWhyTypeIsNotExtern(msg, src, field_ty, .union_field);
+        switch (layout) {
+            .auto => {},
+            .@"extern" => if (!try sema.validateExternType(field_ty, .union_field)) {
+                return sema.failWithOwnedErrorMsg(block, msg: {
+                    const msg = try sema.errMsg(field_types_src, "extern unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
+                    errdefer msg.destroy(gpa);
+
+                    try sema.explainWhyTypeIsNotExtern(msg, field_types_src, field_ty, .union_field);
 
-                try sema.addDeclaredHereNote(msg, field_ty);
-                break :msg msg;
-            });
-        } else if (layout == .@"packed" and !try sema.validatePackedType(field_ty)) {
-            return sema.failWithOwnedErrorMsg(block, msg: {
-                const msg = try sema.errMsg(src, "packed unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
-                errdefer msg.destroy(gpa);
+                    try sema.addDeclaredHereNote(msg, field_ty);
+                    break :msg msg;
+                });
+            },
+            .@"packed" => if (!try sema.validatePackedType(field_ty)) {
+                return sema.failWithOwnedErrorMsg(block, msg: {
+                    const msg = try sema.errMsg(field_types_src, "packed unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
+                    errdefer msg.destroy(gpa);
 
-                try sema.explainWhyTypeIsNotPacked(msg, src, field_ty);
+                    try sema.explainWhyTypeIsNotPacked(msg, field_types_src, field_ty);
 
-                try sema.addDeclaredHereNote(msg, field_ty);
-                break :msg msg;
-            });
+                    try sema.addDeclaredHereNote(msg, field_ty);
+                    break :msg msg;
+                });
+            },
+        }
+
+        loaded_union.field_types.get(ip)[field_idx] = field_ty.toIntern();
+        if (field_attrs.@"align") |bytes| {
+            assert(layout != .@"packed");
+            const a = try sema.validateAlign(block, field_attrs_src, bytes);
+            loaded_union.field_aligns.get(ip)[field_idx] = a;
+        } else if (any_aligned_fields) {
+            assert(layout != .@"packed");
+            loaded_union.field_aligns.get(ip)[field_idx] = .none;
         }
     }
 
@@ -21471,116 +21556,94 @@ fn reifyUnion(
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
     if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
-    return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
+    return .fromIntern(wip_ty.finish(ip, new_namespace_index));
 }
 
-fn reifyTuple(
+fn zirReifyEnum(
     sema: *Sema,
     block: *Block,
-    src: LazySrcLoc,
-    fields_val: Value,
+    extended: Zir.Inst.Extended.InstData,
+    inst: Zir.Inst.Index,
 ) CompileError!Air.Inst.Ref {
     const pt = sema.pt;
     const zcu = pt.zcu;
     const gpa = sema.gpa;
     const ip = &zcu.intern_pool;
 
-    const fields_len: u32 = @intCast(fields_val.typeOf(zcu).arrayLen(zcu));
-
-    const types = try sema.arena.alloc(InternPool.Index, fields_len);
-    const inits = try sema.arena.alloc(InternPool.Index, fields_len);
-
-    for (types, inits, 0..) |*field_ty, *field_init, field_idx| {
-        const field_info = try fields_val.elemValue(pt, field_idx);
-
-        const field_name_val = try field_info.fieldValue(pt, 0);
-        const field_type_val = try field_info.fieldValue(pt, 1);
-        const field_default_value_val = try field_info.fieldValue(pt, 2);
-        const field_is_comptime_val = try field_info.fieldValue(pt, 3);
-        const field_alignment_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 4));
-
-        const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .tuple_field_name });
-        const field_type = field_type_val.toType();
-        const field_default_value: InternPool.Index = if (field_default_value_val.optionalValue(zcu)) |ptr_val| d: {
-            const ptr_ty = try pt.singleConstPtrType(field_type_val.toType());
-            // We need to do this deref here, so we won't check for this error case later on.
-            const val = try sema.pointerDeref(block, src, ptr_val, ptr_ty) orelse return sema.failWithNeededComptime(
-                block,
-                src,
-                .{ .simple = .tuple_field_default_value },
-            );
-            if (val.canMutateComptimeVarState(zcu)) {
-                return sema.failWithContainsReferenceToComptimeVar(block, src, field_name, "field default value", val);
-            }
-            // Resolve the value so that lazy values do not create distinct types.
-            break :d (try sema.resolveLazyValue(val)).toIntern();
-        } else .none;
+    const name_strategy: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
+    const extra = sema.code.extraData(Zir.Inst.ReifyEnum, extended.operand).data;
+    const tracked_inst = try block.trackZir(inst);
+    const src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .nodeOffset(.zero),
+    };
 
-        const field_name_index = field_name.toUnsigned(ip) orelse return sema.fail(
-            block,
-            src,
-            "tuple cannot have non-numeric field '{f}'",
-            .{field_name.fmt(ip)},
-        );
-        if (field_name_index != field_idx) {
-            return sema.fail(
-                block,
-                src,
-                "tuple field name '{d}' does not match field index {d}",
-                .{ field_name_index, field_idx },
-            );
-        }
+    const tag_ty_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 0,
+        } },
+    };
+    const mode_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 1,
+        } },
+    };
+    const field_names_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 2,
+        } },
+    };
+    const field_values_src: LazySrcLoc = .{
+        .base_node_inst = tracked_inst,
+        .offset = .{ .node_offset_builtin_call_arg = .{
+            .builtin_call_node = .zero,
+            .arg_index = 3,
+        } },
+    };
 
-        try sema.validateTupleFieldType(block, field_type, src);
+    const enum_mode_ty = try sema.getBuiltinType(mode_src, .@"Type.Enum.Mode");
 
-        {
-            const alignment_ok = ok: {
-                if (field_alignment_val.toIntern() == .zero) break :ok true;
-                const given_align = try field_alignment_val.getUnsignedIntSema(pt) orelse break :ok false;
-                const abi_align = (try field_type.abiAlignmentSema(pt)).toByteUnits() orelse 0;
-                break :ok abi_align == given_align;
-            };
-            if (!alignment_ok) {
-                return sema.fail(block, src, "tuple fields cannot specify alignment", .{});
-            }
-        }
+    const tag_ty = try sema.resolveType(block, tag_ty_src, extra.tag_ty);
+    if (tag_ty.zigTypeTag(zcu) != .int) {
+        return sema.fail(block, tag_ty_src, "tag type must be an integer type", .{});
+    }
 
-        if (field_is_comptime_val.toBool() and field_default_value == .none) {
-            return sema.fail(block, src, "comptime field without default initialization value", .{});
-        }
+    const mode_uncoerced = try sema.resolveInst(extra.mode);
+    const mode_coerced = try sema.coerce(block, enum_mode_ty, mode_uncoerced, mode_src);
+    const mode_val = try sema.resolveConstDefinedValue(block, mode_src, mode_coerced, .{ .simple = .type });
+    const nonexhaustive = switch (try sema.interpretBuiltinType(block, mode_src, mode_val, std.builtin.Type.Enum.Mode)) {
+        .exhaustive => false,
+        .nonexhaustive => true,
+    };
 
-        if (!field_is_comptime_val.toBool() and field_default_value != .none) {
-            return sema.fail(block, src, "non-comptime tuple fields cannot specify default initialization value", .{});
-        }
+    const field_names_uncoerced = try sema.resolveInst(extra.field_names);
+    const field_names_coerced = try sema.coerce(block, .slice_const_slice_const_u8, field_names_uncoerced, field_names_src);
+    const field_names_slice = try sema.resolveConstDefinedValue(block, field_names_src, field_names_coerced, .{ .simple = .enum_field_names });
+    const field_names_arr = try sema.derefSliceAsArray(block, field_names_src, field_names_slice, .{ .simple = .enum_field_names });
 
-        field_ty.* = field_type.toIntern();
-        field_init.* = field_default_value;
-    }
+    const fields_len = try sema.usizeCast(block, src, field_names_arr.typeOf(zcu).arrayLen(zcu));
 
-    return Air.internedToRef(try zcu.intern_pool.getTupleType(gpa, pt.tid, .{
-        .types = types,
-        .values = inits,
+    const field_values_ty = try pt.singleConstPtrType(try pt.arrayType(.{
+        .len = fields_len,
+        .child = tag_ty.toIntern(),
     }));
-}
-
-fn reifyStruct(
-    sema: *Sema,
-    block: *Block,
-    inst: Zir.Inst.Index,
-    src: LazySrcLoc,
-    layout: std.builtin.Type.ContainerLayout,
-    opt_backing_int_val: Value,
-    fields_val: Value,
-    name_strategy: Zir.Inst.NameStrategy,
-) CompileError!Air.Inst.Ref {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    const gpa = sema.gpa;
-    const ip = &zcu.intern_pool;
 
-    // This logic must stay in sync with the structure of `std.builtin.Type.Struct` - search for `fieldValue`.
+    const field_values_uncoerced = try sema.resolveInst(extra.field_values);
+    const field_values_coerced = try sema.coerce(block, field_values_ty, field_values_uncoerced, field_values_src);
+    const field_values_slice = try sema.resolveConstDefinedValue(block, field_values_src, field_values_coerced, .{ .simple = .enum_field_values });
+    const field_values_arr = try sema.derefSliceAsArray(block, field_values_src, field_values_slice, .{ .simple = .enum_field_values });
 
-    const fields_len: u32 = @intCast(fields_val.typeOf(zcu).arrayLen(zcu));
+    // Before we begin, check for undefs...
+    if (try sema.anyUndef(block, field_values_src, field_values_arr)) {
+        return sema.failWithUseOfUndef(block, field_values_src, null);
+    }
+    // We don't need to check `field_names_arr`, because `sliceToIpString` will check that for us.
 
     // The validation work here is non-trivial, and it's possible the type already exists.
     // So in this first pass, let's just construct a hash to optimize for this case. If the
@@ -21589,62 +21652,23 @@ fn reifyStruct(
     // For deduplication purposes, we must create a hash including all details of this type.
     // TODO: use a longer hash!
     var hasher = std.hash.Wyhash.init(0);
-    std.hash.autoHash(&hasher, layout);
-    std.hash.autoHash(&hasher, opt_backing_int_val.toIntern());
+    std.hash.autoHash(&hasher, tag_ty.toIntern());
+    std.hash.autoHash(&hasher, nonexhaustive);
     std.hash.autoHash(&hasher, fields_len);
-
-    var any_comptime_fields = false;
-    var any_default_inits = false;
-
+    // `field_values_arr` is already deduplicated by the InternPool!
+    std.hash.autoHash(&hasher, field_values_arr);
+    // However, for field names, we need to iterate the individual fields, because the pointers (the
+    // names are slices) mean that distinct values could ultimately result in the same enum type.
     for (0..fields_len) |field_idx| {
-        const field_info = try fields_val.elemValue(pt, field_idx);
-
-        const field_name_val = try field_info.fieldValue(pt, 0);
-        const field_type_val = try field_info.fieldValue(pt, 1);
-        const field_default_value_val = try field_info.fieldValue(pt, 2);
-        const field_is_comptime_val = try field_info.fieldValue(pt, 3);
-        const field_alignment_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 4));
-
-        const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .struct_field_name });
-        const field_is_comptime = field_is_comptime_val.toBool();
-        const field_default_value: InternPool.Index = if (field_default_value_val.optionalValue(zcu)) |ptr_val| d: {
-            const ptr_ty = try pt.singleConstPtrType(field_type_val.toType());
-            // We need to do this deref here, so we won't check for this error case later on.
-            const val = try sema.pointerDeref(block, src, ptr_val, ptr_ty) orelse return sema.failWithNeededComptime(
-                block,
-                src,
-                .{ .simple = .struct_field_default_value },
-            );
-            if (val.canMutateComptimeVarState(zcu)) {
-                return sema.failWithContainsReferenceToComptimeVar(block, src, field_name, "field default value", val);
-            }
-            // Resolve the value so that lazy values do not create distinct types.
-            break :d (try sema.resolveLazyValue(val)).toIntern();
-        } else .none;
-
-        std.hash.autoHash(&hasher, .{
-            field_name,
-            field_type_val.toIntern(),
-            field_default_value,
-            field_is_comptime,
-            field_alignment_val.toIntern(),
-        });
-
-        if (field_is_comptime) any_comptime_fields = true;
-        if (field_default_value != .none) any_default_inits = true;
+        const field_name_val = try field_names_arr.elemValue(pt, field_idx);
+        const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, .{ .simple = .enum_field_names });
+        std.hash.autoHash(&hasher, field_name);
     }
 
-    const tracked_inst = try block.trackZir(inst);
-
-    const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{
-        .layout = layout,
-        .fields_len = fields_len,
-        .known_non_opv = false,
-        .requires_comptime = .unknown,
-        .any_comptime_fields = any_comptime_fields,
-        .any_default_inits = any_default_inits,
-        .any_aligned_fields = layout != .@"packed",
-        .inits_resolved = true,
+    const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, .{
+        .has_values = true,
+        .tag_mode = if (nonexhaustive) .nonexhaustive else .explicit,
+        .fields_len = @intCast(fields_len),
         .key = .{ .reified = .{
             .zir_index = tracked_inst,
             .type_hash = hasher.final(),
@@ -21654,152 +21678,66 @@ fn reifyStruct(
         .existing => |ty| {
             try sema.declareDependency(.{ .interned = ty });
             try sema.addTypeReferenceEntry(src, ty);
-            return Air.internedToRef(ty);
+            return .fromIntern(ty);
         },
     };
-    errdefer wip_ty.cancel(ip, pt.tid);
+    var done = false;
+    errdefer if (!done) wip_ty.cancel(ip, pt.tid);
 
     const type_name = try sema.createTypeName(
         block,
         name_strategy,
-        "struct",
+        "enum",
         inst,
         wip_ty.index,
     );
     wip_ty.setName(ip, type_name.name, type_name.nav);
 
-    const struct_type = ip.loadStructType(wip_ty.index);
-
-    for (0..fields_len) |field_idx| {
-        const field_info = try fields_val.elemValue(pt, field_idx);
-
-        const field_name_val = try field_info.fieldValue(pt, 0);
-        const field_type_val = try field_info.fieldValue(pt, 1);
-        const field_default_value_val = try field_info.fieldValue(pt, 2);
-        const field_is_comptime_val = try field_info.fieldValue(pt, 3);
-        const field_alignment_val = try field_info.fieldValue(pt, 4);
-
-        const field_ty = field_type_val.toType();
-        // Don't pass a reason; first loop acts as an assertion that this is valid.
-        const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);
-        if (struct_type.addFieldName(ip, field_name)) |prev_index| {
-            _ = prev_index; // TODO: better source location
-            return sema.fail(block, src, "duplicate struct field name {f}", .{field_name.fmt(ip)});
-        }
-
-        if (!try sema.intFitsInType(field_alignment_val, align_ty, null)) {
-            return sema.fail(block, src, "alignment must fit in '{f}'", .{align_ty.fmt(pt)});
-        }
-        const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
-        if (layout == .@"packed") {
-            if (byte_align != 0) return sema.fail(block, src, "alignment of a packed struct field must be set to 0", .{});
-        } else {
-            struct_type.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
-        }
-
-        const field_is_comptime = field_is_comptime_val.toBool();
-        if (field_is_comptime) {
-            assert(any_comptime_fields);
-            switch (layout) {
-                .@"extern" => return sema.fail(block, src, "extern struct fields cannot be marked comptime", .{}),
-                .@"packed" => return sema.fail(block, src, "packed struct fields cannot be marked comptime", .{}),
-                .auto => struct_type.setFieldComptime(ip, field_idx),
-            }
-        }
-
-        const field_default: InternPool.Index = d: {
-            if (!any_default_inits) break :d .none;
-            const ptr_val = field_default_value_val.optionalValue(zcu) orelse break :d .none;
-            const ptr_ty = try pt.singleConstPtrType(field_ty);
-            // Asserted comptime-dereferencable above.
-            const val = (try sema.pointerDeref(block, src, ptr_val, ptr_ty)).?;
-            // We already resolved this for deduplication, so we may as well do it now.
-            break :d (try sema.resolveLazyValue(val)).toIntern();
-        };
-
-        if (field_is_comptime and field_default == .none) {
-            return sema.fail(block, src, "comptime field without default initialization value", .{});
-        }
-
-        struct_type.field_types.get(ip)[field_idx] = field_type_val.toIntern();
-        if (field_default != .none) {
-            struct_type.field_inits.get(ip)[field_idx] = field_default;
-        }
-
-        if (field_ty.zigTypeTag(zcu) == .@"opaque") {
-            return sema.failWithOwnedErrorMsg(block, msg: {
-                const msg = try sema.errMsg(src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
-                errdefer msg.destroy(gpa);
-
-                try sema.addDeclaredHereNote(msg, field_ty);
-                break :msg msg;
-            });
-        }
-        if (field_ty.zigTypeTag(zcu) == .noreturn) {
-            return sema.failWithOwnedErrorMsg(block, msg: {
-                const msg = try sema.errMsg(src, "struct fields cannot be 'noreturn'", .{});
-                errdefer msg.destroy(gpa);
-
-                try sema.addDeclaredHereNote(msg, field_ty);
-                break :msg msg;
-            });
-        }
-        if (layout == .@"extern" and !try sema.validateExternType(field_ty, .struct_field)) {
-            return sema.failWithOwnedErrorMsg(block, msg: {
-                const msg = try sema.errMsg(src, "extern structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
-                errdefer msg.destroy(gpa);
+    const new_namespace_index = try pt.createNamespace(.{
+        .parent = block.namespace.toOptional(),
+        .owner_type = wip_ty.index,
+        .file_scope = block.getFileScopeIndex(zcu),
+        .generation = zcu.generation,
+    });
 
-                try sema.explainWhyTypeIsNotExtern(msg, src, field_ty, .struct_field);
+    try sema.declareDependency(.{ .interned = wip_ty.index });
+    try sema.addTypeReferenceEntry(src, wip_ty.index);
+    if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
+    wip_ty.prepare(ip, new_namespace_index);
+    wip_ty.setTagTy(ip, tag_ty.toIntern());
+    done = true;
 
-                try sema.addDeclaredHereNote(msg, field_ty);
-                break :msg msg;
-            });
-        } else if (layout == .@"packed" and !try sema.validatePackedType(field_ty)) {
-            return sema.failWithOwnedErrorMsg(block, msg: {
-                const msg = try sema.errMsg(src, "packed structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
-                errdefer msg.destroy(gpa);
+    for (0..fields_len) |field_idx| {
+        const field_name_val = try field_names_arr.elemValue(pt, field_idx);
+        // Don't pass a reason; first loop acts as a check that this is valid.
+        const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, undefined);
 
-                try sema.explainWhyTypeIsNotPacked(msg, src, field_ty);
+        const field_val = try field_values_arr.elemValue(pt, field_idx);
 
-                try sema.addDeclaredHereNote(msg, field_ty);
-                break :msg msg;
+        if (wip_ty.nextField(ip, field_name, field_val.toIntern())) |conflict| {
+            return sema.failWithOwnedErrorMsg(block, switch (conflict.kind) {
+                .name => msg: {
+                    const msg = try sema.errMsg(field_names_src, "duplicate enum field '{f}'", .{field_name.fmt(ip)});
+                    errdefer msg.destroy(gpa);
+                    _ = conflict.prev_field_idx; // TODO: this note is incorrect
+                    try sema.errNote(field_names_src, msg, "other field here", .{});
+                    break :msg msg;
+                },
+                .value => msg: {
+                    const msg = try sema.errMsg(field_values_src, "enum tag value {f} already taken", .{field_val.fmtValueSema(pt, sema)});
+                    errdefer msg.destroy(gpa);
+                    _ = conflict.prev_field_idx; // TODO: this note is incorrect
+                    try sema.errNote(field_values_src, msg, "other enum tag value here", .{});
+                    break :msg msg;
+                },
             });
         }
     }
 
-    if (layout == .@"packed") {
-        var fields_bit_sum: u64 = 0;
-        for (0..struct_type.field_types.len) |field_idx| {
-            const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_idx]);
-            field_ty.resolveLayout(pt) catch |err| switch (err) {
-                error.AnalysisFail => {
-                    const msg = sema.err orelse return err;
-                    try sema.errNote(src, msg, "while checking a field of this struct", .{});
-                    return err;
-                },
-                else => return err,
-            };
-            fields_bit_sum += field_ty.bitSize(zcu);
-        }
-
-        if (opt_backing_int_val.optionalValue(zcu)) |backing_int_val| {
-            const backing_int_ty = backing_int_val.toType();
-            try sema.checkBackingIntType(block, src, backing_int_ty, fields_bit_sum);
-            struct_type.setBackingIntType(ip, backing_int_ty.toIntern());
-        } else {
-            const backing_int_ty = try pt.intType(.unsigned, @intCast(fields_bit_sum));
-            struct_type.setBackingIntType(ip, backing_int_ty.toIntern());
-        }
+    if (nonexhaustive and fields_len > 1 and std.math.log2_int(u64, fields_len) == tag_ty.bitSize(zcu)) {
+        return sema.fail(block, src, "non-exhaustive enum specified every value", .{});
     }
 
-    const new_namespace_index = try pt.createNamespace(.{
-        .parent = block.namespace.toOptional(),
-        .owner_type = wip_ty.index,
-        .file_scope = block.getFileScopeIndex(zcu),
-        .generation = zcu.generation,
-    });
-
-    try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
     codegen_type: {
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
@@ -21807,10 +21745,7 @@ fn reifyStruct(
         zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
         try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
-    try sema.declareDependency(.{ .interned = wip_ty.index });
-    try sema.addTypeReferenceEntry(src, wip_ty.index);
-    if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
-    return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
+    return Air.internedToRef(wip_ty.index);
 }
 
 fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref {
@@ -25541,7 +25476,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         extra_index += body.len;
         if (extra.data.bits.ret_ty_is_generic) break :blk .generic_poison;
 
-        const val = try sema.resolveGenericBody(block, ret_src, body, inst, .type, .{ .simple = .function_ret_ty });
+        const val = try sema.resolveGenericBody(block, ret_src, body, inst, .type, .{ .simple = .fn_ret_ty });
         const ty = val.toType();
         break :blk ty;
     } else if (extra.data.bits.has_ret_ty_ref) blk: {
@@ -25968,21 +25903,26 @@ fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD
     const src = block.nodeOffset(src_node);
     const value: Zir.Inst.BuiltinValue = @enumFromInt(extended.small);
 
-    const ty = switch (value) {
+    const builtin_type: Zcu.BuiltinDecl = switch (value) {
         // zig fmt: off
-        .atomic_order       => try sema.getBuiltinType(src, .AtomicOrder),
-        .atomic_rmw_op      => try sema.getBuiltinType(src, .AtomicRmwOp),
-        .calling_convention => try sema.getBuiltinType(src, .CallingConvention),
-        .address_space      => try sema.getBuiltinType(src, .AddressSpace),
-        .float_mode         => try sema.getBuiltinType(src, .FloatMode),
-        .reduce_op          => try sema.getBuiltinType(src, .ReduceOp),
-        .call_modifier      => try sema.getBuiltinType(src, .CallModifier),
-        .prefetch_options   => try sema.getBuiltinType(src, .PrefetchOptions),
-        .export_options     => try sema.getBuiltinType(src, .ExportOptions),
-        .extern_options     => try sema.getBuiltinType(src, .ExternOptions),
-        .type_info          => try sema.getBuiltinType(src, .Type),
-        .branch_hint        => try sema.getBuiltinType(src, .BranchHint),
-        .clobbers           => try sema.getBuiltinType(src, .@"assembly.Clobbers"),
+        .atomic_order       => .AtomicOrder,
+        .atomic_rmw_op      => .AtomicRmwOp,
+        .calling_convention => .CallingConvention,
+        .address_space      => .AddressSpace,
+        .float_mode         => .FloatMode,
+        .signedness         => .Signedness,
+        .reduce_op          => .ReduceOp,
+        .call_modifier      => .CallModifier,
+        .prefetch_options   => .PrefetchOptions,
+        .export_options     => .ExportOptions,
+        .extern_options     => .ExternOptions,
+        .branch_hint        => .BranchHint,
+        .clobbers           => .@"assembly.Clobbers",
+        .pointer_size       => .@"Type.Pointer.Size",
+        .pointer_attributes => .@"Type.Pointer.Attributes",
+        .fn_attributes,     => .@"Type.Fn.Attributes",
+        .container_layout   => .@"Type.ContainerLayout",
+        .enum_mode          => .@"Type.Enum.Mode",
         // zig fmt: on
 
         // Values are handled here.
@@ -26009,7 +25949,7 @@ fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD
             return sema.coerce(block, callconv_ty, Air.internedToRef(inline_tag_val.toIntern()), src);
         },
     };
-    return Air.internedToRef(ty.toIntern());
+    return .fromType(try sema.getBuiltinType(src, builtin_type));
 }
 
 fn zirInplaceArithResultTy(sema: *Sema, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
@@ -35259,7 +35199,7 @@ fn structFields(
                 .base_node_inst = struct_type.zir_index,
                 .offset = .nodeOffset(.zero),
             },
-            .r = .{ .simple = .struct_fields },
+            .r = .{ .simple = .type },
         } },
         .src_base_inst = struct_type.zir_index,
         .type_name_ctx = struct_type.name,
@@ -35614,7 +35554,7 @@ fn unionFields(
         .inlining = null,
         .comptime_reason = .{ .reason = .{
             .src = src,
-            .r = .{ .simple = .union_fields },
+            .r = .{ .simple = .type },
         } },
         .src_base_inst = union_type.zir_index,
         .type_name_ctx = union_type.name,
@@ -36077,8 +36017,13 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
         .manyptr_u8_type,
         .manyptr_const_u8_type,
         .manyptr_const_u8_sentinel_0_type,
+        .manyptr_const_slice_const_u8_type,
         .slice_const_u8_type,
         .slice_const_u8_sentinel_0_type,
+        .slice_const_slice_const_u8_type,
+        .optional_type_type,
+        .manyptr_const_type_type,
+        .slice_const_type_type,
         .vector_8_i8_type,
         .vector_16_i8_type,
         .vector_32_i8_type,
@@ -37230,7 +37175,7 @@ fn sliceToIpString(
 
 /// Given a slice value, attempts to dereference it into a comptime-known array.
 /// Emits a compile error if the contents of the slice are not comptime-known.
-/// Asserts that `slice_val` is a slice.
+/// Asserts that `slice_val` is a slice or a pointer to an array.
 fn derefSliceAsArray(
     sema: *Sema,
     block: *Block,
@@ -37247,7 +37192,7 @@ fn derefSliceAsArray(
 
 /// Given a slice value, attempts to dereference it into a comptime-known array.
 /// Returns `null` if the contents of the slice are not comptime-known.
-/// Asserts that `slice_val` is a slice.
+/// Asserts that `slice_val` is a slice or a pointer to an array.
 fn maybeDerefSliceAsArray(
     sema: *Sema,
     block: *Block,
@@ -37257,7 +37202,13 @@ fn maybeDerefSliceAsArray(
     const pt = sema.pt;
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
-    assert(slice_val.typeOf(zcu).isSlice(zcu));
+    const slice_ty = slice_val.typeOf(zcu);
+    assert(slice_ty.zigTypeTag(zcu) == .pointer);
+    switch (slice_ty.ptrInfo(zcu).flags.size) {
+        .slice => {},
+        .one => return sema.pointerDeref(block, src, slice_val, slice_ty),
+        .many, .c => unreachable,
+    }
     const slice = switch (ip.indexToKey(slice_val.toIntern())) {
         .undef => return sema.failWithUseOfUndef(block, src, null),
         .slice => |slice| slice,
@@ -37393,7 +37344,7 @@ pub fn resolveDeclaredEnum(
         .inlining = null,
         .comptime_reason = .{ .reason = .{
             .src = src,
-            .r = .{ .simple = .enum_fields },
+            .r = .{ .simple = .enum_field_values },
         } },
         .src_base_inst = tracked_inst,
         .type_name_ctx = type_name,
src/Type.zig
@@ -317,7 +317,7 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread, ctx: ?*Compari
             .undefined,
             => try writer.print("@TypeOf({s})", .{@tagName(s)}),
 
-            .enum_literal => try writer.writeAll("@Type(.enum_literal)"),
+            .enum_literal => try writer.writeAll("@EnumLiteral()"),
 
             .generic_poison => unreachable,
         },
@@ -3509,7 +3509,9 @@ pub fn typeDeclSrcLine(ty: Type, zcu: *Zcu) ?u32 {
             .union_decl => zir.extraData(Zir.Inst.UnionDecl, inst.data.extended.operand).data.src_line,
             .enum_decl => zir.extraData(Zir.Inst.EnumDecl, inst.data.extended.operand).data.src_line,
             .opaque_decl => zir.extraData(Zir.Inst.OpaqueDecl, inst.data.extended.operand).data.src_line,
-            .reify => zir.extraData(Zir.Inst.Reify, inst.data.extended.operand).data.src_line,
+            .reify_enum => zir.extraData(Zir.Inst.ReifyEnum, inst.data.extended.operand).data.src_line,
+            .reify_struct => zir.extraData(Zir.Inst.ReifyStruct, inst.data.extended.operand).data.src_line,
+            .reify_union => zir.extraData(Zir.Inst.ReifyUnion, inst.data.extended.operand).data.src_line,
             else => unreachable,
         },
         else => unreachable,
@@ -4280,6 +4282,10 @@ pub const manyptr_const_u8: Type = .{ .ip_index = .manyptr_const_u8_type };
 pub const manyptr_const_u8_sentinel_0: Type = .{ .ip_index = .manyptr_const_u8_sentinel_0_type };
 pub const slice_const_u8: Type = .{ .ip_index = .slice_const_u8_type };
 pub const slice_const_u8_sentinel_0: Type = .{ .ip_index = .slice_const_u8_sentinel_0_type };
+pub const slice_const_slice_const_u8: Type = .{ .ip_index = .slice_const_slice_const_u8_type };
+pub const slice_const_type: Type = .{ .ip_index = .slice_const_type_type };
+pub const optional_type: Type = .{ .ip_index = .optional_type_type };
+pub const optional_noreturn: Type = .{ .ip_index = .optional_noreturn_type };
 
 pub const vector_8_i8: Type = .{ .ip_index = .vector_8_i8_type };
 pub const vector_16_i8: Type = .{ .ip_index = .vector_16_i8_type };
src/Value.zig
@@ -2824,6 +2824,29 @@ pub fn resolveLazy(
                     .val = resolved_val,
                 }));
         },
+        .error_union => |eu| switch (eu.val) {
+            .err_name => return val,
+            .payload => |payload| {
+                const resolved_payload = try Value.fromInterned(payload).resolveLazy(arena, pt);
+                if (resolved_payload.toIntern() == payload) return val;
+                return .fromInterned(try pt.intern(.{ .error_union = .{
+                    .ty = eu.ty,
+                    .val = .{ .payload = resolved_payload.toIntern() },
+                } }));
+            },
+        },
+        .opt => |opt| switch (opt.val) {
+            .none => return val,
+            else => |payload| {
+                const resolved_payload = try Value.fromInterned(payload).resolveLazy(arena, pt);
+                if (resolved_payload.toIntern() == payload) return val;
+                return .fromInterned(try pt.intern(.{ .opt = .{
+                    .ty = opt.ty,
+                    .val = resolved_payload.toIntern(),
+                } }));
+            },
+        },
+
         else => return val,
     }
 }
src/Zcu.zig
@@ -416,10 +416,13 @@ pub const BuiltinDecl = enum {
     Type,
     @"Type.Fn",
     @"Type.Fn.Param",
+    @"Type.Fn.Param.Attributes",
+    @"Type.Fn.Attributes",
     @"Type.Int",
     @"Type.Float",
     @"Type.Pointer",
     @"Type.Pointer.Size",
+    @"Type.Pointer.Attributes",
     @"Type.Array",
     @"Type.Vector",
     @"Type.Optional",
@@ -427,10 +430,13 @@ pub const BuiltinDecl = enum {
     @"Type.ErrorUnion",
     @"Type.EnumField",
     @"Type.Enum",
+    @"Type.Enum.Mode",
     @"Type.Union",
     @"Type.UnionField",
+    @"Type.UnionField.Attributes",
     @"Type.Struct",
     @"Type.StructField",
+    @"Type.StructField.Attributes",
     @"Type.ContainerLayout",
     @"Type.Opaque",
     @"Type.Declaration",
@@ -495,10 +501,13 @@ pub const BuiltinDecl = enum {
             .Type,
             .@"Type.Fn",
             .@"Type.Fn.Param",
+            .@"Type.Fn.Param.Attributes",
+            .@"Type.Fn.Attributes",
             .@"Type.Int",
             .@"Type.Float",
             .@"Type.Pointer",
             .@"Type.Pointer.Size",
+            .@"Type.Pointer.Attributes",
             .@"Type.Array",
             .@"Type.Vector",
             .@"Type.Optional",
@@ -506,10 +515,13 @@ pub const BuiltinDecl = enum {
             .@"Type.ErrorUnion",
             .@"Type.EnumField",
             .@"Type.Enum",
+            .@"Type.Enum.Mode",
             .@"Type.Union",
             .@"Type.UnionField",
+            .@"Type.UnionField.Attributes",
             .@"Type.Struct",
             .@"Type.StructField",
+            .@"Type.StructField.Attributes",
             .@"Type.ContainerLayout",
             .@"Type.Opaque",
             .@"Type.Declaration",
@@ -1745,28 +1757,28 @@ pub const SrcLoc = struct {
                 const node = node_off.toAbsolute(src_loc.base_node);
                 var buf: [1]Ast.Node.Index = undefined;
                 const full = tree.fullFnProto(&buf, node).?;
-                return tree.nodeToSpan(full.ast.align_expr.unwrap().?);
+                return tree.nodeToSpan(full.ast.align_expr.unwrap() orelse node);
             },
             .node_offset_fn_type_addrspace => |node_off| {
                 const tree = try src_loc.file_scope.getTree(zcu);
                 const node = node_off.toAbsolute(src_loc.base_node);
                 var buf: [1]Ast.Node.Index = undefined;
                 const full = tree.fullFnProto(&buf, node).?;
-                return tree.nodeToSpan(full.ast.addrspace_expr.unwrap().?);
+                return tree.nodeToSpan(full.ast.addrspace_expr.unwrap() orelse node);
             },
             .node_offset_fn_type_section => |node_off| {
                 const tree = try src_loc.file_scope.getTree(zcu);
                 const node = node_off.toAbsolute(src_loc.base_node);
                 var buf: [1]Ast.Node.Index = undefined;
                 const full = tree.fullFnProto(&buf, node).?;
-                return tree.nodeToSpan(full.ast.section_expr.unwrap().?);
+                return tree.nodeToSpan(full.ast.section_expr.unwrap() orelse node);
             },
             .node_offset_fn_type_cc => |node_off| {
                 const tree = try src_loc.file_scope.getTree(zcu);
                 const node = node_off.toAbsolute(src_loc.base_node);
                 var buf: [1]Ast.Node.Index = undefined;
                 const full = tree.fullFnProto(&buf, node).?;
-                return tree.nodeToSpan(full.ast.callconv_expr.unwrap().?);
+                return tree.nodeToSpan(full.ast.callconv_expr.unwrap() orelse node);
             },
 
             .node_offset_fn_type_ret_ty => |node_off| {
@@ -2684,7 +2696,9 @@ pub const LazySrcLoc = struct {
                 .union_decl => zir.extraData(Zir.Inst.UnionDecl, inst.data.extended.operand).data.src_node,
                 .enum_decl => zir.extraData(Zir.Inst.EnumDecl, inst.data.extended.operand).data.src_node,
                 .opaque_decl => zir.extraData(Zir.Inst.OpaqueDecl, inst.data.extended.operand).data.src_node,
-                .reify => zir.extraData(Zir.Inst.Reify, inst.data.extended.operand).data.node,
+                .reify_enum => zir.extraData(Zir.Inst.ReifyEnum, inst.data.extended.operand).data.node,
+                .reify_struct => zir.extraData(Zir.Inst.ReifyStruct, inst.data.extended.operand).data.node,
+                .reify_union => zir.extraData(Zir.Inst.ReifyUnion, inst.data.extended.operand).data.node,
                 else => unreachable,
             },
             else => unreachable,