Commit 0e856da224

Ali Chraghi <alichraghi@proton.me>
2023-10-31 10:42:15
add type safety to ZIR for null terminated strings
1 parent deed194
src/AstGen.zig
@@ -46,7 +46,7 @@ fn_block: ?*GenZir = null,
 fn_var_args: bool = false,
 /// Maps string table indexes to the first `@import` ZIR instruction
 /// that uses this string as the operand.
-imports: std.AutoArrayHashMapUnmanaged(u32, Ast.TokenIndex) = .{},
+imports: std.AutoArrayHashMapUnmanaged(Zir.NullTerminatedString, Ast.TokenIndex) = .{},
 /// Used for temporary storage when building payloads.
 scratch: std.ArrayListUnmanaged(u32) = .{},
 /// Whenever a `ref` instruction is needed, it is created and saved in this
@@ -86,6 +86,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
 
             Zir.Inst.Ref,
             Zir.Inst.Index,
+            Zir.NullTerminatedString,
             => @intFromEnum(@field(extra, field.name)),
 
             i32,
@@ -1301,12 +1302,12 @@ fn fnProtoExpr(
                 }
             } else false;
 
-            const param_name: u32 = if (param.name_token) |name_token| blk: {
+            const param_name = if (param.name_token) |name_token| blk: {
                 if (mem.eql(u8, "_", tree.tokenSlice(name_token)))
-                    break :blk 0;
+                    break :blk .empty;
 
                 break :blk try astgen.identAsString(name_token);
-            } else 0;
+            } else .empty;
 
             if (is_anytype) {
                 const name_token = param.name_token orelse param.anytype_ellipsis3.?;
@@ -1379,7 +1380,7 @@ fn fnProtoExpr(
 
         .param_block = block_inst,
         .body_gz = null,
-        .lib_name = 0,
+        .lib_name = .empty,
         .is_var_args = is_var_args,
         .is_inferred_error = false,
         .is_test = false,
@@ -1725,7 +1726,7 @@ fn structInitExpr(
         var sfba = std.heap.stackFallback(256, astgen.arena);
         const sfba_allocator = sfba.get();
 
-        var duplicate_names = std.AutoArrayHashMap(u32, ArrayListUnmanaged(Ast.TokenIndex)).init(sfba_allocator);
+        var duplicate_names = std.AutoArrayHashMap(Zir.NullTerminatedString, ArrayListUnmanaged(Ast.TokenIndex)).init(sfba_allocator);
         defer duplicate_names.deinit();
         try duplicate_names.ensureTotalCapacity(@intCast(struct_init.ast.fields.len));
 
@@ -4068,10 +4069,10 @@ fn fnDecl(
                 }
             } else false;
 
-            const param_name: u32 = if (param.name_token) |name_token| blk: {
+            const param_name: Zir.NullTerminatedString = if (param.name_token) |name_token| blk: {
                 const name_bytes = tree.tokenSlice(name_token);
                 if (mem.eql(u8, "_", name_bytes))
-                    break :blk 0;
+                    break :blk .empty;
 
                 const param_name = try astgen.identAsString(name_token);
                 if (!is_extern) {
@@ -4107,7 +4108,7 @@ fn fnDecl(
                     }
                     return astgen.failNode(param.type_expr, "missing parameter name", .{});
                 }
-            } else 0;
+            } else .empty;
 
             const param_inst = if (is_anytype) param: {
                 const name_token = param.name_token orelse param.anytype_ellipsis3.?;
@@ -4133,7 +4134,7 @@ fn fnDecl(
                 break :param param_inst.toRef();
             };
 
-            if (param_name == 0 or is_extern) continue;
+            if (param_name == .empty or is_extern) continue;
 
             const sub_scope = try astgen.arena.create(Scope.LocalVal);
             sub_scope.* = .{
@@ -4149,16 +4150,16 @@ fn fnDecl(
         break :is_var_args false;
     };
 
-    const lib_name: u32 = if (fn_proto.lib_name) |lib_name_token| blk: {
+    const lib_name = if (fn_proto.lib_name) |lib_name_token| blk: {
         const lib_name_str = try astgen.strLitAsString(lib_name_token);
-        const lib_name_slice = astgen.string_bytes.items[lib_name_str.index..][0..lib_name_str.len];
+        const lib_name_slice = astgen.string_bytes.items[@intFromEnum(lib_name_str.index)..][0..lib_name_str.len];
         if (mem.indexOfScalar(u8, lib_name_slice, 0) != null) {
             return astgen.failTok(lib_name_token, "library name cannot contain null bytes", .{});
         } else if (lib_name_str.len == 0) {
             return astgen.failTok(lib_name_token, "library name cannot be empty", .{});
         }
         break :blk lib_name_str.index;
-    } else 0;
+    } else .empty;
 
     const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
     const is_inferred_error = token_tags[maybe_bang] == .bang;
@@ -4343,9 +4344,9 @@ fn fnDecl(
         const line_delta = decl_gz.decl_line - gz.decl_line;
         wip_members.appendToDecl(line_delta);
     }
-    wip_members.appendToDecl(fn_name_str_index);
+    wip_members.appendToDecl(@intFromEnum(fn_name_str_index));
     wip_members.appendToDecl(@intFromEnum(block_inst));
-    wip_members.appendToDecl(doc_comment_index);
+    wip_members.appendToDecl(@intFromEnum(doc_comment_index));
 }
 
 fn globalVarDecl(
@@ -4408,16 +4409,16 @@ fn globalVarDecl(
         break :blk true;
     } else false;
 
-    const lib_name: u32 = if (var_decl.lib_name) |lib_name_token| blk: {
+    const lib_name = if (var_decl.lib_name) |lib_name_token| blk: {
         const lib_name_str = try astgen.strLitAsString(lib_name_token);
-        const lib_name_slice = astgen.string_bytes.items[lib_name_str.index..][0..lib_name_str.len];
+        const lib_name_slice = astgen.string_bytes.items[@intFromEnum(lib_name_str.index)..][0..lib_name_str.len];
         if (mem.indexOfScalar(u8, lib_name_slice, 0) != null) {
             return astgen.failTok(lib_name_token, "library name cannot contain null bytes", .{});
         } else if (lib_name_str.len == 0) {
             return astgen.failTok(lib_name_token, "library name cannot be empty", .{});
         }
         break :blk lib_name_str.index;
-    } else 0;
+    } else .empty;
 
     const doc_comment_index = try astgen.docCommentAsString(var_decl.firstToken());
 
@@ -4452,7 +4453,7 @@ fn globalVarDecl(
         if (is_mutable) {
             const var_inst = try block_scope.addVar(.{
                 .var_type = type_inst,
-                .lib_name = 0,
+                .lib_name = .empty,
                 .align_inst = .none, // passed via the decls data
                 .init = init_inst,
                 .is_extern = false,
@@ -4495,9 +4496,9 @@ fn globalVarDecl(
         const line_delta = block_scope.decl_line - gz.decl_line;
         wip_members.appendToDecl(line_delta);
     }
-    wip_members.appendToDecl(name_str_index);
+    wip_members.appendToDecl(@intFromEnum(name_str_index));
     wip_members.appendToDecl(@intFromEnum(block_inst));
-    wip_members.appendToDecl(doc_comment_index); // doc_comment wip
+    wip_members.appendToDecl(@intFromEnum(doc_comment_index)); // doc_comment wip
     if (align_inst != .none) {
         wip_members.appendToDecl(@intFromEnum(align_inst));
     }
@@ -4640,7 +4641,7 @@ fn testDecl(
     const test_name_token = test_token + 1;
     const test_name_token_tag = token_tags[test_name_token];
     const is_decltest = test_name_token_tag == .identifier;
-    const test_name: u32 = blk: {
+    const test_name: Zir.NullTerminatedString = blk: {
         if (test_name_token_tag == .string_literal) {
             break :blk try astgen.testNameString(test_name_token);
         } else if (test_name_token_tag == .identifier) {
@@ -4716,7 +4717,7 @@ fn testDecl(
             break :blk name_str_index;
         }
         // String table index 1 has a special meaning here of test decl with no name.
-        break :blk 1;
+        break :blk .unnamed_test_decl;
     };
 
     var fn_block: GenZir = .{
@@ -4766,7 +4767,7 @@ fn testDecl(
         .lbrace_column = lbrace_column,
         .param_block = block_inst,
         .body_gz = &fn_block,
-        .lib_name = 0,
+        .lib_name = .empty,
         .is_var_args = false,
         .is_inferred_error = false,
         .is_test = true,
@@ -4789,10 +4790,10 @@ fn testDecl(
     if (is_decltest)
         wip_members.appendToDecl(2) // 2 here means that it is a decltest, look at doc comment for name
     else
-        wip_members.appendToDecl(test_name);
+        wip_members.appendToDecl(@intFromEnum(test_name));
     wip_members.appendToDecl(@intFromEnum(block_inst));
     if (is_decltest)
-        wip_members.appendToDecl(test_name) // the doc comment on a decltest represents it's name
+        wip_members.appendToDecl(@intFromEnum(test_name)) // the doc comment on a decltest represents it's name
     else
         wip_members.appendToDecl(0); // no doc comments on test decls
 }
@@ -4945,7 +4946,7 @@ fn structDeclInner(
     var sfba = std.heap.stackFallback(256, astgen.arena);
     const sfba_allocator = sfba.get();
 
-    var duplicate_names = std.AutoArrayHashMap(u32, std.ArrayListUnmanaged(Ast.TokenIndex)).init(sfba_allocator);
+    var duplicate_names = std.AutoArrayHashMap(Zir.NullTerminatedString, std.ArrayListUnmanaged(Ast.TokenIndex)).init(sfba_allocator);
     try duplicate_names.ensureTotalCapacity(field_count);
 
     // When there aren't errors, use this to avoid a second iteration.
@@ -4968,7 +4969,7 @@ fn structDeclInner(
             member.convertToNonTupleLike(astgen.tree.nodes);
             assert(!member.ast.tuple_like);
 
-            wip_members.appendToField(field_name);
+            wip_members.appendToField(@intFromEnum(field_name));
 
             const gop = try duplicate_names.getOrPut(field_name);
 
@@ -4984,7 +4985,7 @@ fn structDeclInner(
         }
 
         const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
-        wip_members.appendToField(doc_comment_index);
+        wip_members.appendToField(@intFromEnum(doc_comment_index));
 
         if (member.ast.type_expr == 0) {
             return astgen.failTok(member.ast.main_token, "struct field missing type", .{});
@@ -5196,10 +5197,10 @@ fn unionDeclInner(
         }
 
         const field_name = try astgen.identAsString(member.ast.main_token);
-        wip_members.appendToField(field_name);
+        wip_members.appendToField(@intFromEnum(field_name));
 
         const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
-        wip_members.appendToField(doc_comment_index);
+        wip_members.appendToField(@intFromEnum(doc_comment_index));
 
         const have_type = member.ast.type_expr != 0;
         const have_align = member.ast.align_expr != 0;
@@ -5475,10 +5476,10 @@ fn containerDecl(
                 assert(member.ast.align_expr == 0);
 
                 const field_name = try astgen.identAsString(member.ast.main_token);
-                wip_members.appendToField(field_name);
+                wip_members.appendToField(@intFromEnum(field_name));
 
                 const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
-                wip_members.appendToField(doc_comment_index);
+                wip_members.appendToField(@intFromEnum(doc_comment_index));
 
                 const have_value = member.ast.value_expr != 0;
                 wip_members.nextField(bits_per_field, .{have_value});
@@ -5665,7 +5666,7 @@ fn errorSetDecl(gz: *GenZir, ri: ResultInfo, node: Ast.Node.Index) InnerError!Zi
     const payload_index = try reserveExtra(astgen, @typeInfo(Zir.Inst.ErrorSetDecl).Struct.fields.len);
     var fields_len: usize = 0;
     {
-        var idents: std.AutoHashMapUnmanaged(u32, Ast.TokenIndex) = .{};
+        var idents: std.AutoHashMapUnmanaged(Zir.NullTerminatedString, Ast.TokenIndex) = .{};
         defer idents.deinit(gpa);
 
         const error_token = main_tokens[node];
@@ -5695,9 +5696,9 @@ fn errorSetDecl(gz: *GenZir, ri: ResultInfo, node: Ast.Node.Index) InnerError!Zi
                     gop.value_ptr.* = tok_i;
 
                     try astgen.extra.ensureUnusedCapacity(gpa, 2);
-                    astgen.extra.appendAssumeCapacity(str_index);
+                    astgen.extra.appendAssumeCapacity(@intFromEnum(str_index));
                     const doc_comment_index = try astgen.docCommentAsString(tok_i);
-                    astgen.extra.appendAssumeCapacity(doc_comment_index);
+                    astgen.extra.appendAssumeCapacity(@intFromEnum(doc_comment_index));
                     fields_len += 1;
                 },
                 .r_brace => break,
@@ -6372,7 +6373,7 @@ fn whileExpr(
     then_scope.instructions_top = GenZir.unstacked_top;
     defer then_scope.unstack();
 
-    var dbg_var_name: ?u32 = null;
+    var dbg_var_name: Zir.NullTerminatedString = .empty;
     var dbg_var_inst: Zir.Inst.Ref = undefined;
     var opt_payload_inst: Zir.Inst.OptionalIndex = .none;
     var payload_val_scope: Scope.LocalVal = undefined;
@@ -6464,7 +6465,7 @@ fn whileExpr(
     if (opt_payload_inst.unwrap()) |payload_inst| {
         try then_scope.instructions.append(astgen.gpa, payload_inst);
     }
-    if (dbg_var_name) |name| try then_scope.addDbgVar(.dbg_var_val, name, dbg_var_inst);
+    if (dbg_var_name != .empty) try then_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);
     try then_scope.instructions.append(astgen.gpa, continue_block);
     // This code could be improved to avoid emitting the continue expr when there
     // are no jumps to it. This happens when the last statement of a while body is noreturn
@@ -7069,9 +7070,9 @@ fn switchExpr(
         const is_multi_case = case.ast.values.len > 1 or
             (case.ast.values.len == 1 and node_tags[case.ast.values[0]] == .switch_range);
 
-        var dbg_var_name: ?u32 = null;
+        var dbg_var_name: Zir.NullTerminatedString = .empty;
         var dbg_var_inst: Zir.Inst.Ref = undefined;
-        var dbg_var_tag_name: ?u32 = null;
+        var dbg_var_tag_name: Zir.NullTerminatedString = .empty;
         var dbg_var_tag_inst: Zir.Inst.Ref = undefined;
         var has_tag_capture = false;
         var capture_val_scope: Scope.LocalVal = undefined;
@@ -7193,11 +7194,11 @@ fn switchExpr(
             defer case_scope.unstack();
 
             try case_scope.addDbgBlockBegin();
-            if (dbg_var_name) |some| {
-                try case_scope.addDbgVar(.dbg_var_val, some, dbg_var_inst);
+            if (dbg_var_name != .empty) {
+                try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);
             }
-            if (dbg_var_tag_name) |some| {
-                try case_scope.addDbgVar(.dbg_var_val, some, dbg_var_tag_inst);
+            if (dbg_var_tag_name != .empty) {
+                try case_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst);
             }
             const target_expr_node = case.ast.target_expr;
             const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node);
@@ -7858,7 +7859,7 @@ fn asmExpr(
     const node_tags = tree.nodes.items(.tag);
     const token_tags = tree.tokens.items(.tag);
 
-    const TagAndTmpl = struct { tag: Zir.Inst.Extended, tmpl: u32 };
+    const TagAndTmpl = struct { tag: Zir.Inst.Extended, tmpl: Zir.NullTerminatedString };
     const tag_and_tmpl: TagAndTmpl = switch (node_tags[full.ast.template]) {
         .string_literal => .{
             .tag = .@"asm",
@@ -7870,7 +7871,7 @@ fn asmExpr(
         },
         else => .{
             .tag = .asm_expr,
-            .tmpl = @intFromEnum(try comptimeExpr(gz, scope, .{ .rl = .none }, full.ast.template)),
+            .tmpl = @enumFromInt(@intFromEnum(try comptimeExpr(gz, scope, .{ .rl = .none }, full.ast.template))),
         },
     };
 
@@ -7956,7 +7957,7 @@ fn asmExpr(
             if (clobber_i >= clobbers_buffer.len) {
                 return astgen.failTok(tok_i, "too many asm clobbers", .{});
             }
-            clobbers_buffer[clobber_i] = (try astgen.strLitAsString(tok_i)).index;
+            clobbers_buffer[clobber_i] = @intFromEnum((try astgen.strLitAsString(tok_i)).index);
             clobber_i += 1;
             tok_i += 1;
             switch (token_tags[tok_i]) {
@@ -8290,7 +8291,7 @@ fn builtinCall(
             }
             const str_lit_token = main_tokens[operand_node];
             const str = try astgen.strLitAsString(str_lit_token);
-            const str_slice = astgen.string_bytes.items[str.index..][0..str.len];
+            const str_slice = astgen.string_bytes.items[@intFromEnum(str.index)..][0..str.len];
             if (mem.indexOfScalar(u8, str_slice, 0) != null) {
                 return astgen.failTok(str_lit_token, "import path cannot contain null bytes", .{});
             } else if (str.len == 0) {
@@ -8346,7 +8347,7 @@ fn builtinCall(
             // This function causes a Decl to be exported. The first parameter is not an expression,
             // but an identifier of the Decl to be exported.
             var namespace: Zir.Inst.Ref = .none;
-            var decl_name: u32 = 0;
+            var decl_name: Zir.NullTerminatedString = .empty;
             switch (node_tags[params[0]]) {
                 .identifier => {
                     const ident_token = main_tokens[params[0]];
@@ -9263,7 +9264,7 @@ const Callee = union(enum) {
         /// promote the lvalue to an address if the first parameter requires it.
         obj_ptr: Zir.Inst.Ref,
         /// Offset into `string_bytes`.
-        field_name_start: u32,
+        field_name_start: Zir.NullTerminatedString,
     },
     direct: Zir.Inst.Ref,
 };
@@ -10529,7 +10530,7 @@ fn appendErrorNodeNotes(
 ) Allocator.Error!void {
     @setCold(true);
     const string_bytes = &astgen.string_bytes;
-    const msg: u32 = @intCast(string_bytes.items.len);
+    const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
     try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);
     const notes_index: u32 = if (notes.len != 0) blk: {
         const notes_start = astgen.extra.items.len;
@@ -10621,7 +10622,7 @@ fn appendErrorTokNotesOff(
     @setCold(true);
     const gpa = astgen.gpa;
     const string_bytes = &astgen.string_bytes;
-    const msg: u32 = @intCast(string_bytes.items.len);
+    const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
     try string_bytes.writer(gpa).print(format ++ "\x00", args);
     const notes_index: u32 = if (notes.len != 0) blk: {
         const notes_start = astgen.extra.items.len;
@@ -10657,7 +10658,7 @@ fn errNoteTokOff(
 ) Allocator.Error!u32 {
     @setCold(true);
     const string_bytes = &astgen.string_bytes;
-    const msg: u32 = @intCast(string_bytes.items.len);
+    const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
     try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);
     return astgen.addExtra(Zir.Inst.CompileErrors.Item{
         .msg = msg,
@@ -10676,7 +10677,7 @@ fn errNoteNode(
 ) Allocator.Error!u32 {
     @setCold(true);
     const string_bytes = &astgen.string_bytes;
-    const msg: u32 = @intCast(string_bytes.items.len);
+    const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
     try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);
     return astgen.addExtra(Zir.Inst.CompileErrors.Item{
         .msg = msg,
@@ -10687,7 +10688,7 @@ fn errNoteNode(
     });
 }
 
-fn identAsString(astgen: *AstGen, ident_token: Ast.TokenIndex) !u32 {
+fn identAsString(astgen: *AstGen, ident_token: Ast.TokenIndex) !Zir.NullTerminatedString {
     const gpa = astgen.gpa;
     const string_bytes = &astgen.string_bytes;
     const str_index: u32 = @intCast(string_bytes.items.len);
@@ -10700,19 +10701,19 @@ fn identAsString(astgen: *AstGen, ident_token: Ast.TokenIndex) !u32 {
     });
     if (gop.found_existing) {
         string_bytes.shrinkRetainingCapacity(str_index);
-        return gop.key_ptr.*;
+        return @enumFromInt(gop.key_ptr.*);
     } else {
         gop.key_ptr.* = str_index;
         try string_bytes.append(gpa, 0);
-        return str_index;
+        return @enumFromInt(str_index);
     }
 }
 
 /// Adds a doc comment block to `string_bytes` by walking backwards from `end_token`.
 /// `end_token` must point at the first token after the last doc coment line.
 /// Returns 0 if no doc comment is present.
-fn docCommentAsString(astgen: *AstGen, end_token: Ast.TokenIndex) !u32 {
-    if (end_token == 0) return 0;
+fn docCommentAsString(astgen: *AstGen, end_token: Ast.TokenIndex) !Zir.NullTerminatedString {
+    if (end_token == 0) return .empty;
 
     const token_tags = astgen.tree.tokens.items(.tag);
 
@@ -10723,6 +10724,7 @@ fn docCommentAsString(astgen: *AstGen, end_token: Ast.TokenIndex) !u32 {
     } else {
         tok += 1;
     }
+
     return docCommentAsStringFromFirst(astgen, end_token, tok);
 }
 
@@ -10731,8 +10733,8 @@ fn docCommentAsStringFromFirst(
     astgen: *AstGen,
     end_token: Ast.TokenIndex,
     start_token: Ast.TokenIndex,
-) !u32 {
-    if (start_token == end_token) return 0;
+) !Zir.NullTerminatedString {
+    if (start_token == end_token) return .empty;
 
     const gpa = astgen.gpa;
     const string_bytes = &astgen.string_bytes;
@@ -10766,15 +10768,15 @@ fn docCommentAsStringFromFirst(
 
     if (gop.found_existing) {
         string_bytes.shrinkRetainingCapacity(str_index);
-        return gop.key_ptr.*;
+        return @enumFromInt(gop.key_ptr.*);
     } else {
         gop.key_ptr.* = str_index;
         try string_bytes.append(gpa, 0);
-        return str_index;
+        return @enumFromInt(str_index);
     }
 }
 
-const IndexSlice = struct { index: u32, len: u32 };
+const IndexSlice = struct { index: Zir.NullTerminatedString, len: u32 };
 
 fn strLitAsString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !IndexSlice {
     const gpa = astgen.gpa;
@@ -10791,7 +10793,7 @@ fn strLitAsString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !IndexSlice {
     if (gop.found_existing) {
         string_bytes.shrinkRetainingCapacity(str_index);
         return IndexSlice{
-            .index = gop.key_ptr.*,
+            .index = @enumFromInt(gop.key_ptr.*),
             .len = @intCast(key.len),
         };
     } else {
@@ -10801,7 +10803,7 @@ fn strLitAsString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !IndexSlice {
         // be null terminated for that to work.
         try string_bytes.append(gpa, 0);
         return IndexSlice{
-            .index = str_index,
+            .index = @enumFromInt(str_index),
             .len = @intCast(key.len),
         };
     }
@@ -10839,12 +10841,12 @@ fn strLitNodeAsString(astgen: *AstGen, node: Ast.Node.Index) !IndexSlice {
     const len = string_bytes.items.len - str_index;
     try string_bytes.append(gpa, 0);
     return IndexSlice{
-        .index = @intCast(str_index),
+        .index = @enumFromInt(str_index),
         .len = @intCast(len),
     };
 }
 
-fn testNameString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !u32 {
+fn testNameString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !Zir.NullTerminatedString {
     const gpa = astgen.gpa;
     const string_bytes = &astgen.string_bytes;
     const str_index: u32 = @intCast(string_bytes.items.len);
@@ -10858,7 +10860,7 @@ fn testNameString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !u32 {
         return astgen.failTok(str_lit_token, "empty test name must be omitted", .{});
     }
     try string_bytes.append(gpa, 0);
-    return str_index;
+    return @enumFromInt(str_index);
 }
 
 const Scope = struct {
@@ -10933,7 +10935,7 @@ const Scope = struct {
         /// 0 means never discarded.
         discarded: Ast.TokenIndex = 0,
         /// String table index.
-        name: u32,
+        name: Zir.NullTerminatedString,
         id_cat: IdCat,
     };
 
@@ -10959,7 +10961,7 @@ const Scope = struct {
         /// If not, we know it can be `const`, so will emit a compile error if it is `var`.
         used_as_lvalue: bool = false,
         /// String table index.
-        name: u32,
+        name: Zir.NullTerminatedString,
         id_cat: IdCat,
         /// true means we find out during Sema whether the value is comptime.
         /// false means it is already known at AstGen the value is runtime-known.
@@ -10985,7 +10987,7 @@ const Scope = struct {
         parent: *Scope,
         /// Maps string table index to the source location of declaration,
         /// for the purposes of reporting name shadowing compile errors.
-        decls: std.AutoHashMapUnmanaged(u32, Ast.Node.Index) = .{},
+        decls: std.AutoHashMapUnmanaged(Zir.NullTerminatedString, Ast.Node.Index) = .{},
         node: Ast.Node.Index,
         inst: Zir.Inst.Index,
 
@@ -11250,7 +11252,7 @@ const GenZir = struct {
         cc_ref: Zir.Inst.Ref,
         ret_ref: Zir.Inst.Ref,
 
-        lib_name: u32,
+        lib_name: Zir.NullTerminatedString,
         noalias_bits: u32,
         is_var_args: bool,
         is_inferred_error: bool,
@@ -11298,7 +11300,7 @@ const GenZir = struct {
         }
         const body_len = astgen.countBodyLenAfterFixups(body);
 
-        if (args.cc_ref != .none or args.lib_name != 0 or args.is_var_args or args.is_test or
+        if (args.cc_ref != .none or args.lib_name != .empty or args.is_var_args or args.is_test or
             args.is_extern or args.align_ref != .none or args.section_ref != .none or
             args.addrspace_ref != .none or args.noalias_bits != 0 or args.is_noinline)
         {
@@ -11322,7 +11324,7 @@ const GenZir = struct {
                     fancyFnExprExtraLen(astgen, cc_body, args.cc_ref) +
                     fancyFnExprExtraLen(astgen, ret_body, ret_ref) +
                     body_len + src_locs.len +
-                    @intFromBool(args.lib_name != 0) +
+                    @intFromBool(args.lib_name != .empty) +
                     @intFromBool(args.noalias_bits != 0),
             );
             const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.FuncFancy{
@@ -11334,7 +11336,7 @@ const GenZir = struct {
                     .is_test = args.is_test,
                     .is_extern = args.is_extern,
                     .is_noinline = args.is_noinline,
-                    .has_lib_name = args.lib_name != 0,
+                    .has_lib_name = args.lib_name != .empty,
                     .has_any_noalias = args.noalias_bits != 0,
 
                     .has_align_ref = args.align_ref != .none,
@@ -11350,8 +11352,8 @@ const GenZir = struct {
                     .has_ret_ty_body = ret_body.len != 0,
                 },
             });
-            if (args.lib_name != 0) {
-                astgen.extra.appendAssumeCapacity(args.lib_name);
+            if (args.lib_name != .empty) {
+                astgen.extra.appendAssumeCapacity(@intFromEnum(args.lib_name));
             }
 
             const zir_datas = astgen.instructions.items(.data);
@@ -11493,7 +11495,7 @@ const GenZir = struct {
 
     fn addVar(gz: *GenZir, args: struct {
         align_inst: Zir.Inst.Ref,
-        lib_name: u32,
+        lib_name: Zir.NullTerminatedString,
         var_type: Zir.Inst.Ref,
         init: Zir.Inst.Ref,
         is_extern: bool,
@@ -11509,15 +11511,15 @@ const GenZir = struct {
         try astgen.extra.ensureUnusedCapacity(
             gpa,
             @typeInfo(Zir.Inst.ExtendedVar).Struct.fields.len +
-                @intFromBool(args.lib_name != 0) +
+                @intFromBool(args.lib_name != .empty) +
                 @intFromBool(args.align_inst != .none) +
                 @intFromBool(args.init != .none),
         );
         const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedVar{
             .var_type = args.var_type,
         });
-        if (args.lib_name != 0) {
-            astgen.extra.appendAssumeCapacity(args.lib_name);
+        if (args.lib_name != .empty) {
+            astgen.extra.appendAssumeCapacity(@intFromEnum(args.lib_name));
         }
         if (args.align_inst != .none) {
             astgen.extra.appendAssumeCapacity(@intFromEnum(args.align_inst));
@@ -11532,7 +11534,7 @@ const GenZir = struct {
             .data = .{ .extended = .{
                 .opcode = .variable,
                 .small = @bitCast(Zir.Inst.ExtendedVar.Small{
-                    .has_lib_name = args.lib_name != 0,
+                    .has_lib_name = args.lib_name != .empty,
                     .has_align = args.align_inst != .none,
                     .has_init = args.init != .none,
                     .is_extern = args.is_extern,
@@ -11588,7 +11590,7 @@ const GenZir = struct {
         astgen.instructions.appendAssumeCapacity(.{
             .tag = .int_big,
             .data = .{ .str = .{
-                .start = @intCast(astgen.string_bytes.items.len),
+                .start = @enumFromInt(astgen.string_bytes.items.len),
                 .len = @intCast(limbs.len),
             } },
         });
@@ -11687,7 +11689,7 @@ const GenZir = struct {
         tag: Zir.Inst.Tag,
         /// Absolute token index. This function does the conversion to Decl offset.
         abs_tok_index: Ast.TokenIndex,
-        name: u32,
+        name: Zir.NullTerminatedString,
         first_doc_comment: ?Ast.TokenIndex,
     ) !Zir.Inst.Index {
         const gpa = gz.astgen.gpa;
@@ -11699,7 +11701,7 @@ const GenZir = struct {
         const doc_comment_index = if (first_doc_comment) |first|
             try gz.astgen.docCommentAsStringFromFirst(abs_tok_index, first)
         else
-            0;
+            .empty;
 
         const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Param{
             .name = name,
@@ -11847,7 +11849,7 @@ const GenZir = struct {
     fn addStrTok(
         gz: *GenZir,
         tag: Zir.Inst.Tag,
-        str_index: u32,
+        str_index: Zir.NullTerminatedString,
         /// Absolute token index. This function does the conversion to Decl offset.
         abs_tok_index: Ast.TokenIndex,
     ) !Zir.Inst.Ref {
@@ -12122,7 +12124,7 @@ const GenZir = struct {
             tag: Zir.Inst.Extended,
             /// Absolute node index. This function does the conversion to offset from Decl.
             node: Ast.Node.Index,
-            asm_source: u32,
+            asm_source: Zir.NullTerminatedString,
             output_type_bits: u32,
             is_volatile: bool,
             outputs: []const Zir.Inst.Asm.Output,
@@ -12441,7 +12443,7 @@ const GenZir = struct {
         }
     }
 
-    fn addDbgVar(gz: *GenZir, tag: Zir.Inst.Tag, name: u32, inst: Zir.Inst.Ref) !void {
+    fn addDbgVar(gz: *GenZir, tag: Zir.Inst.Tag, name: Zir.NullTerminatedString, inst: Zir.Inst.Ref) !void {
         if (gz.is_comptime) return;
 
         _ = try gz.add(.{ .tag = tag, .data = .{
@@ -12478,15 +12480,15 @@ const GenZir = struct {
 
 /// This can only be for short-lived references; the memory becomes invalidated
 /// when another string is added.
-fn nullTerminatedString(astgen: AstGen, index: usize) [*:0]const u8 {
-    return @ptrCast(astgen.string_bytes.items[index..]);
+fn nullTerminatedString(astgen: AstGen, index: Zir.NullTerminatedString) [*:0]const u8 {
+    return @ptrCast(astgen.string_bytes.items[@intFromEnum(index)..]);
 }
 
 /// Local variables shadowing detection, including function parameters.
 fn detectLocalShadowing(
     astgen: *AstGen,
     scope: *Scope,
-    ident_name: u32,
+    ident_name: Zir.NullTerminatedString,
     name_token: Ast.TokenIndex,
     token_bytes: []const u8,
     id_cat: Scope.IdCat,
src/Autodoc.zig
@@ -447,7 +447,7 @@ fn generateZirData(self: *Autodoc, output_dir: std.fs.Dir) !void {
 const Scope = struct {
     parent: ?*Scope,
     map: std.AutoHashMapUnmanaged(
-        u32, // index into the current file's string table (decl name)
+        Zir.NullTerminatedString, // index into the current file's string table (decl name)
         *DeclStatus,
     ) = .{},
 
@@ -464,7 +464,7 @@ const Scope = struct {
     /// Another reason is that in some places we use the pointer to uniquely
     /// refer to a decl, as we wait for it to be analyzed. This means that
     /// those pointers must stay stable.
-    pub fn resolveDeclName(self: Scope, string_table_idx: u32, file: *File, inst: Zir.Inst.OptionalIndex) *DeclStatus {
+    pub fn resolveDeclName(self: Scope, string_table_idx: Zir.NullTerminatedString, file: *File, inst: Zir.Inst.OptionalIndex) *DeclStatus {
         var cur: ?*const Scope = &self;
         return while (cur) |s| : (cur = s.parent) {
             break s.map.get(string_table_idx) orelse continue;
@@ -482,7 +482,7 @@ const Scope = struct {
     pub fn insertDeclRef(
         self: *Scope,
         arena: std.mem.Allocator,
-        decl_name_index: u32, // index into the current file's string table
+        decl_name_index: Zir.NullTerminatedString, // index into the current file's string table
         decl_status: DeclStatus,
     ) !void {
         const decl_status_ptr = try arena.create(DeclStatus);
@@ -1250,7 +1250,7 @@ fn walkInstruction(
             // @check
             const str = data[@intFromEnum(inst)].str; //.get(file.zir);
             const byte_count = str.len * @sizeOf(std.math.big.Limb);
-            const limb_bytes = file.zir.string_bytes[str.start..][0..byte_count];
+            const limb_bytes = file.zir.string_bytes[@intFromEnum(str.start)..][0..byte_count];
 
             const limbs = try self.arena.alloc(std.math.big.Limb, str.len);
             @memcpy(std.mem.sliceAsBytes(limbs)[0..limb_bytes.len], limb_bytes);
@@ -2167,7 +2167,7 @@ fn walkInstruction(
             // present in json
             var sentinel: ?DocData.Expr = null;
             if (ptr.flags.has_sentinel) {
-                const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+                const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
                 const ref_result = try self.walkRef(
                     file,
                     parent_scope,
@@ -2182,7 +2182,7 @@ fn walkInstruction(
 
             var @"align": ?DocData.Expr = null;
             if (ptr.flags.has_align) {
-                const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+                const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
                 const ref_result = try self.walkRef(
                     file,
                     parent_scope,
@@ -2196,7 +2196,7 @@ fn walkInstruction(
             }
             var address_space: ?DocData.Expr = null;
             if (ptr.flags.has_addrspace) {
-                const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+                const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
                 const ref_result = try self.walkRef(
                     file,
                     parent_scope,
@@ -2210,7 +2210,7 @@ fn walkInstruction(
             }
             const bit_start: ?DocData.Expr = null;
             if (ptr.flags.has_bit_range) {
-                const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+                const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
                 const ref_result = try self.walkRef(
                     file,
                     parent_scope,
@@ -2225,7 +2225,7 @@ fn walkInstruction(
 
             var host_size: ?DocData.Expr = null;
             if (ptr.flags.has_bit_range) {
-                const ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+                const ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
                 const ref_result = try self.walkRef(
                     file,
                     parent_scope,
@@ -3041,10 +3041,10 @@ fn walkInstruction(
             );
             var idx = extra.end;
             for (fields) |*f| {
-                const name = file.zir.nullTerminatedString(file.zir.extra[idx]);
+                const name = file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[idx]));
                 idx += 1;
 
-                const docs = file.zir.nullTerminatedString(file.zir.extra[idx]);
+                const docs = file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[idx]));
                 idx += 1;
 
                 f.* = .{
@@ -3706,10 +3706,10 @@ fn walkInstruction(
                             const has_value = @as(u1, @truncate(cur_bit_bag)) != 0;
                             cur_bit_bag >>= 1;
 
-                            const field_name_index = file.zir.extra[extra_index];
+                            const field_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
                             extra_index += 1;
 
-                            const doc_comment_index = file.zir.extra[extra_index];
+                            const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
                             extra_index += 1;
 
                             const value_expr: ?DocData.Expr = if (has_value) blk: {
@@ -3730,7 +3730,7 @@ fn walkInstruction(
                             const field_name = file.zir.nullTerminatedString(field_name_index);
 
                             try field_name_indexes.append(self.arena, self.ast_nodes.items.len);
-                            const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
+                            const doc_comment: ?[]const u8 = if (doc_comment_index != .empty)
                                 file.zir.nullTerminatedString(doc_comment_index)
                             else
                                 null;
@@ -4085,10 +4085,13 @@ fn analyzeAllDecls(
     {
         var it = original_it;
         while (it.next()) |d| {
-            const decl_name_index = file.zir.extra[@intFromEnum(d.sub_index) + 5];
+            const decl_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[@intFromEnum(d.sub_index) + 5]);
             switch (decl_name_index) {
-                0, 1, 2 => continue,
-                else => if (file.zir.string_bytes[decl_name_index] == 0) {
+                .empty,
+                .unnamed_test_decl,
+                .decltest,
+                => continue,
+                _ => if (file.zir.nullTerminatedString(decl_name_index).len == 0) {
                     continue;
                 },
             }
@@ -4195,31 +4198,31 @@ fn analyzeDecl(
     // const line = file.zir.extra[extra_index];
 
     extra_index += 1;
-    const decl_name_index = file.zir.extra[extra_index];
+    const decl_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
 
     extra_index += 1;
     const value_index: Zir.Inst.Index = @enumFromInt(file.zir.extra[extra_index]);
 
     extra_index += 1;
-    const doc_comment_index = file.zir.extra[extra_index];
+    const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
 
     extra_index += 1;
     const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: {
-        const inst = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+        const inst: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
         extra_index += 1;
         break :inst inst;
     };
     _ = align_inst;
 
     const section_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: {
-        const inst = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+        const inst: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
         extra_index += 1;
         break :inst inst;
     };
     _ = section_inst;
 
     const addrspace_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: {
-        const inst = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+        const inst: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
         extra_index += 1;
         break :inst inst;
     };
@@ -4230,9 +4233,9 @@ fn analyzeDecl(
     const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src);
 
     const name: []const u8 = switch (decl_name_index) {
-        0, 1, 2 => unreachable, // comptime or usingnamespace decl, decltest
-        else => blk: {
-            if (file.zir.string_bytes[decl_name_index] == 0) {
+        .empty, .unnamed_test_decl, .decltest => unreachable,
+        _ => blk: {
+            if (decl_name_index == .empty) {
                 // test decl
                 unreachable;
             }
@@ -4240,7 +4243,7 @@ fn analyzeDecl(
         },
     };
 
-    const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
+    const doc_comment: ?[]const u8 = if (doc_comment_index != .empty)
         file.zir.nullTerminatedString(doc_comment_index)
     else
         null;
@@ -4319,13 +4322,13 @@ fn analyzeUsingnamespaceDecl(
 
     const is_pub = @as(u1, @truncate(d.flags)) != 0;
     const value_index: Zir.Inst.Index = @enumFromInt(file.zir.extra[@intFromEnum(d.sub_index) + 6]);
-    const doc_comment_index = file.zir.extra[@intFromEnum(d.sub_index) + 7];
+    const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[@intFromEnum(d.sub_index) + 7]);
 
     // This is known to work because decl values are always block_inlines
     const value_pl_node = data[@intFromEnum(value_index)].pl_node;
     const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src);
 
-    const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
+    const doc_comment: ?[]const u8 = if (doc_comment_index != .empty)
         file.zir.nullTerminatedString(doc_comment_index)
     else
         null;
@@ -4379,14 +4382,14 @@ fn analyzeDecltest(
     const data = file.zir.instructions.items(.data);
 
     const value_index = file.zir.extra[@intFromEnum(d.sub_index) + 6];
-    const decl_name_index = file.zir.extra[@intFromEnum(d.sub_index) + 7];
+    const decl_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[@intFromEnum(d.sub_index) + 7]);
 
     const value_pl_node = data[value_index].pl_node;
     const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src);
 
     const test_source_code = try self.getBlockSource(file, parent_src, value_pl_node.src_node);
 
-    const decl_name: ?[]const u8 = if (decl_name_index != 0)
+    const decl_name: ?[]const u8 = if (decl_name_index != .empty)
         file.zir.nullTerminatedString(decl_name_index)
     else
         null;
@@ -5018,7 +5021,7 @@ fn analyzeFancyFunction(
             .param, .param_comptime => {
                 const pl_tok = data[@intFromEnum(param_index)].pl_tok;
                 const extra = file.zir.extraData(Zir.Inst.Param, pl_tok.payload_index);
-                const doc_comment = if (extra.data.doc_comment != 0)
+                const doc_comment = if (extra.data.doc_comment != .empty)
                     file.zir.nullTerminatedString(extra.data.doc_comment)
                 else
                     "";
@@ -5056,13 +5059,14 @@ fn analyzeFancyFunction(
 
     var lib_name: []const u8 = "";
     if (extra.data.bits.has_lib_name) {
-        lib_name = file.zir.nullTerminatedString(file.zir.extra[extra_index]);
+        const lib_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
+        lib_name = file.zir.nullTerminatedString(lib_name_index);
         extra_index += 1;
     }
 
     var align_index: ?usize = null;
     if (extra.data.bits.has_align_ref) {
-        const align_ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+        const align_ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
         align_index = self.exprs.items.len;
         _ = try self.walkRef(
             file,
@@ -5086,7 +5090,7 @@ fn analyzeFancyFunction(
 
     var addrspace_index: ?usize = null;
     if (extra.data.bits.has_addrspace_ref) {
-        const addrspace_ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+        const addrspace_ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
         addrspace_index = self.exprs.items.len;
         _ = try self.walkRef(
             file,
@@ -5110,7 +5114,7 @@ fn analyzeFancyFunction(
 
     var section_index: ?usize = null;
     if (extra.data.bits.has_section_ref) {
-        const section_ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+        const section_ref: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]);
         section_index = self.exprs.items.len;
         _ = try self.walkRef(
             file,
@@ -5310,7 +5314,7 @@ fn analyzeFunction(
             .param, .param_comptime => {
                 const pl_tok = data[@intFromEnum(param_index)].pl_tok;
                 const extra = file.zir.extraData(Zir.Inst.Param, pl_tok.payload_index);
-                const doc_comment = if (extra.data.doc_comment != 0)
+                const doc_comment = if (extra.data.doc_comment != .empty)
                     file.zir.nullTerminatedString(extra.data.doc_comment)
                 else
                     "";
@@ -5497,14 +5501,11 @@ fn collectUnionFieldInfo(
         cur_bit_bag >>= 1;
         _ = unused;
 
-        const field_name = file.zir.nullTerminatedString(file.zir.extra[extra_index]);
+        const field_name = file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[extra_index]));
         extra_index += 1;
-        const doc_comment_index = file.zir.extra[extra_index];
+        const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
         extra_index += 1;
-        const field_type = if (has_type)
-            @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]))
-        else
-            .void_type;
+        const field_type: Zir.Inst.Ref = if (has_type) @enumFromInt(file.zir.extra[extra_index]) else .void_type;
         if (has_type) extra_index += 1;
 
         if (has_align) extra_index += 1;
@@ -5526,7 +5527,7 @@ fn collectUnionFieldInfo(
         // ast node
         {
             try field_name_indexes.append(self.arena, self.ast_nodes.items.len);
-            const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
+            const doc_comment: ?[]const u8 = if (doc_comment_index != .empty)
                 file.zir.nullTerminatedString(doc_comment_index)
             else
                 null;
@@ -5559,8 +5560,8 @@ fn collectStructFieldInfo(
     const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
 
     const Field = struct {
-        field_name: ?u32,
-        doc_comment_index: u32,
+        field_name: Zir.NullTerminatedString,
+        doc_comment_index: Zir.NullTerminatedString,
         type_body_len: u32 = 0,
         align_body_len: u32 = 0,
         init_body_len: u32 = 0,
@@ -5587,13 +5588,13 @@ fn collectStructFieldInfo(
         const has_type_body = @as(u1, @truncate(cur_bit_bag)) != 0;
         cur_bit_bag >>= 1;
 
-        const field_name: ?u32 = if (!is_tuple) blk: {
+        const field_name: Zir.NullTerminatedString = if (!is_tuple) blk: {
             const fname = file.zir.extra[extra_index];
             extra_index += 1;
-            break :blk fname;
-        } else null;
+            break :blk @enumFromInt(fname);
+        } else .empty;
 
-        const doc_comment_index = file.zir.extra[extra_index];
+        const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]);
         extra_index += 1;
 
         fields[field_i] = .{
@@ -5604,7 +5605,7 @@ fn collectStructFieldInfo(
         if (has_type_body) {
             fields[field_i].type_body_len = file.zir.extra[extra_index];
         } else {
-            fields[field_i].type_ref = @as(Zir.Inst.Ref, @enumFromInt(file.zir.extra[extra_index]));
+            fields[field_i].type_ref = @enumFromInt(file.zir.extra[extra_index]);
         }
         extra_index += 1;
 
@@ -5686,12 +5687,12 @@ fn collectStructFieldInfo(
         // ast node
         {
             try field_name_indexes.append(self.arena, self.ast_nodes.items.len);
-            const doc_comment: ?[]const u8 = if (field.doc_comment_index != 0)
+            const doc_comment: ?[]const u8 = if (field.doc_comment_index != .empty)
                 file.zir.nullTerminatedString(field.doc_comment_index)
             else
                 null;
-            const field_name: []const u8 = if (field.field_name) |f_name|
-                file.zir.nullTerminatedString(f_name)
+            const field_name: []const u8 = if (field.field_name != .empty)
+                file.zir.nullTerminatedString(field.field_name)
             else
                 "";
 
src/Module.zig
@@ -4198,7 +4198,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
 
     const line_off = zir.extra[decl_sub_index + 4];
     const line = iter.parent_decl.relativeToLine(line_off);
-    const decl_name_index = zir.extra[decl_sub_index + 5];
+    const decl_name_index: Zir.NullTerminatedString = @enumFromInt(zir.extra[decl_sub_index + 5]);
     const decl_doccomment_index = zir.extra[decl_sub_index + 7];
     const decl_zir_index = zir.extra[decl_sub_index + 6];
     const decl_block_inst_data = zir.instructions.items(.data)[decl_zir_index].pl_node;
@@ -4208,7 +4208,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
     var is_named_test = false;
     var kind: Decl.Kind = .named;
     const decl_name: InternPool.NullTerminatedString = switch (decl_name_index) {
-        0 => name: {
+        .empty => name: {
             if (export_bit) {
                 const i = iter.usingnamespace_index;
                 iter.usingnamespace_index += 1;
@@ -4221,23 +4221,23 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
                 break :name try ip.getOrPutStringFmt(gpa, "comptime_{d}", .{i});
             }
         },
-        1 => name: {
+        .unnamed_test_decl => name: {
             const i = iter.unnamed_test_index;
             iter.unnamed_test_index += 1;
             kind = .@"test";
             break :name try ip.getOrPutStringFmt(gpa, "test_{d}", .{i});
         },
-        2 => name: {
+        .decltest => name: {
             is_named_test = true;
-            const test_name = zir.nullTerminatedString(decl_doccomment_index);
+            const test_name = zir.nullTerminatedString(@enumFromInt(decl_doccomment_index));
             kind = .@"test";
             break :name try ip.getOrPutStringFmt(gpa, "decltest.{s}", .{test_name});
         },
-        else => name: {
+        _ => name: {
             const raw_name = zir.nullTerminatedString(decl_name_index);
             if (raw_name.len == 0) {
                 is_named_test = true;
-                const test_name = zir.nullTerminatedString(decl_name_index + 1);
+                const test_name = zir.nullTerminatedString(@enumFromInt(@intFromEnum(decl_name_index) + 1));
                 kind = .@"test";
                 break :name try ip.getOrPutStringFmt(gpa, "test.{s}", .{test_name});
             } else {
@@ -4246,7 +4246,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
         },
     };
 
-    const is_exported = export_bit and decl_name_index != 0;
+    const is_exported = export_bit and decl_name_index != .empty;
     if (kind == .@"usingnamespace") try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1);
 
     // We create a Decl for it regardless of analysis status.
@@ -4271,8 +4271,8 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
         // test decls if in test mode, get analyzed.
         const decl_mod = namespace.file_scope.mod;
         const want_analysis = is_exported or switch (decl_name_index) {
-            0 => true, // comptime or usingnamespace decl
-            1 => blk: {
+            .empty => true, // comptime or usingnamespace decl
+            .unnamed_test_decl => blk: {
                 // test decl with no name. Skip the part where we check against
                 // the test name filter.
                 if (!comp.config.is_test) break :blk false;
src/print_zir.zig
@@ -752,7 +752,7 @@ const Writer = struct {
     fn writeIntBig(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].str;
         const byte_count = inst_data.len * @sizeOf(std.math.big.Limb);
-        const limb_bytes = self.code.string_bytes[inst_data.start..][0..byte_count];
+        const limb_bytes = self.code.nullTerminatedString(inst_data.start)[0..byte_count];
         // limb_bytes is not aligned properly; we must allocate and copy the bytes
         // in order to accomplish this.
         const limbs = try self.gpa.alloc(std.math.big.Limb, inst_data.len);
@@ -945,7 +945,7 @@ const Writer = struct {
             std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)),
         });
 
-        if (extra.data.doc_comment != 0) {
+        if (extra.data.doc_comment != .empty) {
             try stream.writeAll("\n");
             try self.writeDocComment(stream, extra.data.doc_comment);
             try stream.writeByteNTimes(' ', self.indent);
@@ -1226,7 +1226,7 @@ const Writer = struct {
 
         try self.writeFlag(stream, "volatile, ", is_volatile);
         if (tmpl_is_expr) {
-            try self.writeInstRef(stream, @as(Zir.Inst.Ref, @enumFromInt(extra.data.asm_source)));
+            try self.writeInstRef(stream, @enumFromInt(@intFromEnum(extra.data.asm_source)));
             try stream.writeAll(", ");
         } else {
             const asm_source = self.code.nullTerminatedString(extra.data.asm_source);
@@ -1281,7 +1281,7 @@ const Writer = struct {
             while (i < clobbers_len) : (i += 1) {
                 const str_index = self.code.extra[extra_i];
                 extra_i += 1;
-                const clobber = self.code.nullTerminatedString(str_index);
+                const clobber = self.code.nullTerminatedString(@enumFromInt(str_index));
                 try stream.print("{}", .{std.zig.fmtId(clobber)});
                 if (i + 1 < clobbers_len) {
                     try stream.writeAll(", ");
@@ -1466,12 +1466,12 @@ const Writer = struct {
             const fields_per_u32 = 32 / bits_per_field;
             const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
             const Field = struct {
-                doc_comment_index: u32,
+                doc_comment_index: Zir.NullTerminatedString,
                 type_len: u32 = 0,
                 align_len: u32 = 0,
                 init_len: u32 = 0,
                 type: Zir.Inst.Ref = .none,
-                name: u32,
+                name: Zir.NullTerminatedString,
                 is_comptime: bool,
             };
             const fields = try self.arena.alloc(Field, fields_len);
@@ -1494,24 +1494,24 @@ const Writer = struct {
                     const has_type_body = @as(u1, @truncate(cur_bit_bag)) != 0;
                     cur_bit_bag >>= 1;
 
-                    var field_name: u32 = 0;
+                    var field_name_index: Zir.NullTerminatedString = .empty;
                     if (!small.is_tuple) {
-                        field_name = self.code.extra[extra_index];
+                        field_name_index = @enumFromInt(self.code.extra[extra_index]);
                         extra_index += 1;
                     }
-                    const doc_comment_index = self.code.extra[extra_index];
+                    const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]);
                     extra_index += 1;
 
                     fields[field_i] = .{
                         .doc_comment_index = doc_comment_index,
                         .is_comptime = is_comptime,
-                        .name = field_name,
+                        .name = field_name_index,
                     };
 
                     if (has_type_body) {
                         fields[field_i].type_len = self.code.extra[extra_index];
                     } else {
-                        fields[field_i].type = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
+                        fields[field_i].type = @enumFromInt(self.code.extra[extra_index]);
                     }
                     extra_index += 1;
 
@@ -1536,7 +1536,7 @@ const Writer = struct {
                 try self.writeDocComment(stream, field.doc_comment_index);
                 try stream.writeByteNTimes(' ', self.indent);
                 try self.writeFlag(stream, "comptime ", field.is_comptime);
-                if (field.name != 0) {
+                if (field.name != .empty) {
                     const field_name = self.code.nullTerminatedString(field.name);
                     try stream.print("{}: ", .{std.zig.fmtId(field_name)});
                 } else {
@@ -1684,9 +1684,10 @@ const Writer = struct {
 
             _ = unused;
 
-            const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]);
+            const field_name_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]);
+            const field_name = self.code.nullTerminatedString(field_name_index);
             extra_index += 1;
-            const doc_comment_index = self.code.extra[extra_index];
+            const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]);
             extra_index += 1;
 
             try self.writeDocComment(stream, doc_comment_index);
@@ -1756,7 +1757,7 @@ const Writer = struct {
             extra_index += 1;
             const decl_index: Zir.Inst.Index = @enumFromInt(self.code.extra[extra_index]);
             extra_index += 1;
-            const doc_comment_index = self.code.extra[extra_index];
+            const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]);
             extra_index += 1;
 
             const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: {
@@ -1789,9 +1790,9 @@ const Writer = struct {
                 try stream.writeByteNTimes(' ', self.indent);
                 try stream.print("[{d}] decltest {s}", .{ sub_index, self.code.nullTerminatedString(doc_comment_index) });
             } else {
-                const raw_decl_name = self.code.nullTerminatedString(decl_name_index);
+                const raw_decl_name = self.code.nullTerminatedString(@enumFromInt(decl_name_index));
                 const decl_name = if (raw_decl_name.len == 0)
-                    self.code.nullTerminatedString(decl_name_index + 1)
+                    self.code.nullTerminatedString(@enumFromInt(decl_name_index + 1))
                 else
                     raw_decl_name;
                 const test_str = if (raw_decl_name.len == 0) "test \"" else "";
@@ -1927,10 +1928,10 @@ const Writer = struct {
                 const has_tag_value = @as(u1, @truncate(cur_bit_bag)) != 0;
                 cur_bit_bag >>= 1;
 
-                const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]);
+                const field_name = self.code.nullTerminatedString(@enumFromInt(self.code.extra[extra_index]));
                 extra_index += 1;
 
-                const doc_comment_index = self.code.extra[extra_index];
+                const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]);
                 extra_index += 1;
 
                 try self.writeDocComment(stream, doc_comment_index);
@@ -2011,9 +2012,9 @@ const Writer = struct {
         var extra_index = @as(u32, @intCast(extra.end));
         const extra_index_end = extra_index + (extra.data.fields_len * 2);
         while (extra_index < extra_index_end) : (extra_index += 2) {
-            const str_index = self.code.extra[extra_index];
-            const name = self.code.nullTerminatedString(str_index);
-            const doc_comment_index = self.code.extra[extra_index + 1];
+            const name_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]);
+            const name = self.code.nullTerminatedString(name_index);
+            const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index + 1]);
             try self.writeDocComment(stream, doc_comment_index);
             try stream.writeByteNTimes(' ', self.indent);
             try stream.print("{},\n", .{std.zig.fmtId(name)});
@@ -2292,7 +2293,7 @@ const Writer = struct {
         var ret_ty_body: []const Zir.Inst.Index = &.{};
 
         if (extra.data.bits.has_lib_name) {
-            const lib_name = self.code.nullTerminatedString(self.code.extra[extra_index]);
+            const lib_name = self.code.nullTerminatedString(@enumFromInt(self.code.extra[extra_index]));
             extra_index += 1;
             try stream.print("lib_name=\"{}\", ", .{std.zig.fmtEscapes(lib_name)});
         }
@@ -2388,7 +2389,8 @@ const Writer = struct {
 
         var extra_index: usize = extra.end;
         if (small.has_lib_name) {
-            const lib_name = self.code.nullTerminatedString(self.code.extra[extra_index]);
+            const lib_name_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]);
+            const lib_name = self.code.nullTerminatedString(lib_name_index);
             extra_index += 1;
             try stream.print(", lib_name=\"{}\"", .{std.zig.fmtEscapes(lib_name)});
         }
@@ -2740,8 +2742,8 @@ const Writer = struct {
         }
     }
 
-    fn writeDocComment(self: *Writer, stream: anytype, doc_comment_index: u32) !void {
-        if (doc_comment_index != 0) {
+    fn writeDocComment(self: *Writer, stream: anytype, doc_comment_index: Zir.NullTerminatedString) !void {
+        if (doc_comment_index != .empty) {
             const doc_comment = self.code.nullTerminatedString(doc_comment_index);
             var it = std.mem.tokenizeScalar(u8, doc_comment, '\n');
             while (it.next()) |doc_line| {
src/Sema.zig
@@ -3044,7 +3044,8 @@ fn zirEnumDecl(
         const has_tag_value = @as(u1, @truncate(cur_bit_bag)) != 0;
         cur_bit_bag >>= 1;
 
-        const field_name_zir = sema.code.nullTerminatedString(sema.code.extra[extra_index]);
+        const field_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
+        const field_name_zir = sema.code.nullTerminatedString(field_name_index);
         extra_index += 1;
 
         // doc comment
@@ -3322,8 +3323,8 @@ fn zirErrorSetDecl(
     var extra_index: u32 = @intCast(extra.end);
     const extra_index_end = extra_index + (extra.data.fields_len * 2);
     while (extra_index < extra_index_end) : (extra_index += 2) { // +2 to skip over doc_string
-        const str_index = sema.code.extra[extra_index];
-        const name = sema.code.nullTerminatedString(str_index);
+        const name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
+        const name = sema.code.nullTerminatedString(name_index);
         const name_ip = try mod.intern_pool.getOrPutString(gpa, name);
         _ = try mod.getErrorValue(name_ip);
         const result = names.getOrPutAssumeCapacity(name_ip);
@@ -5522,7 +5523,7 @@ fn zirIntBig(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
     const mod = sema.mod;
     const int = sema.code.instructions.items(.data)[@intFromEnum(inst)].str;
     const byte_count = int.len * @sizeOf(std.math.big.Limb);
-    const limb_bytes = sema.code.string_bytes[int.start..][0..byte_count];
+    const limb_bytes = sema.code.string_bytes[@intFromEnum(int.start)..][0..byte_count];
 
     // TODO: this allocation and copy is only needed because the limbs may be unaligned.
     // If ZIR is adjusted so that big int limbs are guaranteed to be aligned, these
@@ -7999,11 +8000,11 @@ fn instantiateGenericCall(
                 } },
             }));
             const param_name: Zir.NullTerminatedString = switch (param_tag) {
-                .param_anytype => @enumFromInt(fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].str_tok.start),
+                .param_anytype => fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].str_tok.start,
                 .param => name: {
                     const inst_data = fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].pl_tok;
                     const extra = fn_zir.extraData(Zir.Inst.Param, inst_data.payload_index);
-                    break :name @enumFromInt(extra.data.name);
+                    break :name extra.data.name;
                 },
                 else => unreachable,
             };
@@ -9616,7 +9617,7 @@ fn finishFunc(
                     .param_anytype => data[@intFromEnum(param_index)].str_tok.src(),
                     else => unreachable,
                 };
-                const name = sema.code.nullTerminatedString2(name_nts);
+                const name = sema.code.nullTerminatedString(name_nts);
                 if (name.len != 0) {
                     try sema.errNote(block, param_src, msg, "param '{s}' is required to be comptime", .{name});
                 } else {
@@ -9690,7 +9691,7 @@ fn zirParam(
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
     const src = inst_data.src();
     const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index);
-    const param_name: Zir.NullTerminatedString = @enumFromInt(extra.data.name);
+    const param_name: Zir.NullTerminatedString = extra.data.name;
     const body = sema.code.bodySlice(extra.end, extra.data.body_len);
 
     const param_ty = param_ty: {
@@ -9781,7 +9782,7 @@ fn zirParamAnytype(
     comptime_syntax: bool,
 ) CompileError!void {
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
-    const param_name: Zir.NullTerminatedString = @enumFromInt(inst_data.start);
+    const param_name: Zir.NullTerminatedString = inst_data.start;
 
     // We are evaluating a generic function without any comptime args provided.
 
@@ -16184,7 +16185,7 @@ fn zirAsm(
     const is_global_assembly = sema.func_index == .none;
 
     const asm_source: []const u8 = if (tmpl_is_expr) blk: {
-        const tmpl: Zir.Inst.Ref = @enumFromInt(extra.data.asm_source);
+        const tmpl: Zir.Inst.Ref = @enumFromInt(@intFromEnum(extra.data.asm_source));
         const s: []const u8 = try sema.resolveConstString(block, src, tmpl, .{
             .needed_comptime_reason = "assembly code must be comptime-known",
         });
@@ -16272,7 +16273,8 @@ fn zirAsm(
 
     const clobbers = try sema.arena.alloc([]const u8, clobbers_len);
     for (clobbers) |*name| {
-        name.* = sema.code.nullTerminatedString(sema.code.extra[extra_i]);
+        const name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_i]);
+        name.* = sema.code.nullTerminatedString(name_index);
         extra_i += 1;
 
         needed_capacity += name.*.len / 4 + 1;
@@ -24713,7 +24715,8 @@ fn zirVarExtended(
     var extra_index: usize = extra.end;
 
     const lib_name = if (small.has_lib_name) lib_name: {
-        const lib_name = sema.code.nullTerminatedString(sema.code.extra[extra_index]);
+        const lib_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
+        const lib_name = sema.code.nullTerminatedString(lib_name_index);
         extra_index += 1;
         try sema.handleExternLibName(block, ty_src, lib_name);
         break :lib_name lib_name;
@@ -24781,7 +24784,8 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
     var extra_index: usize = extra.end;
 
     const lib_name: ?[]const u8 = if (extra.data.bits.has_lib_name) blk: {
-        const lib_name = sema.code.nullTerminatedString(sema.code.extra[extra_index]);
+        const lib_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
+        const lib_name = sema.code.nullTerminatedString(lib_name_index);
         extra_index += 1;
         break :blk lib_name;
     } else null;
@@ -35841,7 +35845,7 @@ fn semaStructFields(
 
             var opt_field_name_zir: ?[:0]const u8 = null;
             if (!small.is_tuple) {
-                opt_field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
+                opt_field_name_zir = zir.nullTerminatedString(@enumFromInt(zir.extra[extra_index]));
                 extra_index += 1;
             }
             extra_index += 1; // doc_comment
@@ -36344,7 +36348,8 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Key.Un
         cur_bit_bag >>= 1;
         _ = unused;
 
-        const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
+        const field_name_index: Zir.NullTerminatedString = @enumFromInt(zir.extra[extra_index]);
+        const field_name_zir = zir.nullTerminatedString(field_name_index);
         extra_index += 1;
 
         // doc_comment
src/Zir.zig
@@ -93,6 +93,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) {
 
             Inst.Ref,
             Inst.Index,
+            NullTerminatedString,
             => @enumFromInt(code.extra[i]),
 
             i32,
@@ -112,18 +113,15 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) {
     };
 }
 
-/// TODO migrate to use this for type safety
 pub const NullTerminatedString = enum(u32) {
+    empty = 0,
+    unnamed_test_decl = 1,
+    decltest = 2,
     _,
 };
 
-/// TODO: migrate to nullTerminatedString2 for type safety
-pub fn nullTerminatedString(code: Zir, index: usize) [:0]const u8 {
-    return nullTerminatedString2(code, @enumFromInt(index));
-}
-
 /// Given an index into `string_bytes` returns the null-terminated string found there.
-pub fn nullTerminatedString2(code: Zir, index: NullTerminatedString) [:0]const u8 {
+pub fn nullTerminatedString(code: Zir, index: NullTerminatedString) [:0]const u8 {
     const start = @intFromEnum(index);
     var end: u32 = start;
     while (code.string_bytes[end] != 0) {
@@ -2298,17 +2296,17 @@ pub const Inst = struct {
         /// For strings which may contain null bytes.
         str: struct {
             /// Offset into `string_bytes`.
-            start: u32,
+            start: NullTerminatedString,
             /// Number of bytes in the string.
             len: u32,
 
             pub fn get(self: @This(), code: Zir) []const u8 {
-                return code.string_bytes[self.start..][0..self.len];
+                return code.string_bytes[@intFromEnum(self.start)..][0..self.len];
             }
         },
         str_tok: struct {
             /// Offset into `string_bytes`. Null-terminated.
-            start: u32,
+            start: NullTerminatedString,
             /// Offset from Decl AST token index.
             src_tok: u32,
 
@@ -2385,7 +2383,7 @@ pub const Inst = struct {
         },
         str_op: struct {
             /// Offset into `string_bytes`. Null-terminated.
-            str: u32,
+            str: NullTerminatedString,
             operand: Ref,
 
             pub fn getStr(self: @This(), zir: Zir) [:0]const u8 {
@@ -2466,11 +2464,11 @@ pub const Inst = struct {
     /// Trailing:
     /// 0. Output for every outputs_len
     /// 1. Input for every inputs_len
-    /// 2. clobber: u32 // index into string_bytes (null terminated) for every clobbers_len.
+    /// 2. clobber: NullTerminatedString // index into string_bytes (null terminated) for every clobbers_len.
     pub const Asm = struct {
         src_node: i32,
         // null-terminated string index
-        asm_source: u32,
+        asm_source: NullTerminatedString,
         /// 1 bit for each outputs_len: whether it uses `-> T` or not.
         ///   0b0 - operand is a pointer to where to store the output.
         ///   0b1 - operand is a type; asm expression has the output as the result.
@@ -2479,18 +2477,18 @@ pub const Inst = struct {
 
         pub const Output = struct {
             /// index into string_bytes (null terminated)
-            name: u32,
+            name: NullTerminatedString,
             /// index into string_bytes (null terminated)
-            constraint: u32,
+            constraint: NullTerminatedString,
             /// How to interpret this is determined by `output_type_bits`.
             operand: Ref,
         };
 
         pub const Input = struct {
             /// index into string_bytes (null terminated)
-            name: u32,
+            name: NullTerminatedString,
             /// index into string_bytes (null terminated)
-            constraint: u32,
+            constraint: NullTerminatedString,
             operand: Ref,
         };
     };
@@ -2524,7 +2522,7 @@ pub const Inst = struct {
     };
 
     /// Trailing:
-    /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set
+    /// 0. lib_name: NullTerminatedString, // null terminated string index, if has_lib_name is set
     /// if (has_align_ref and !has_align_body) {
     ///   1. align: Ref,
     /// }
@@ -2598,7 +2596,7 @@ pub const Inst = struct {
     };
 
     /// Trailing:
-    /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set
+    /// 0. lib_name: NullTerminatedString, // null terminated string index, if has_lib_name is set
     /// 1. align: Ref, // if has_align is set
     /// 2. init: Ref // if has_init is set
     /// The source node is obtained from the containing `block_inline`.
@@ -2672,7 +2670,7 @@ pub const Inst = struct {
         flags: Call.Flags,
         obj_ptr: Ref,
         /// Offset into `string_bytes`.
-        field_name_start: u32,
+        field_name_start: NullTerminatedString,
     };
 
     pub const TypeOfPeer = struct {
@@ -2871,7 +2869,7 @@ pub const Inst = struct {
     pub const Field = struct {
         lhs: Ref,
         /// Offset into `string_bytes`.
-        field_name_start: u32,
+        field_name_start: NullTerminatedString,
     };
 
     pub const FieldNamed = struct {
@@ -2900,7 +2898,7 @@ pub const Inst = struct {
     /// 7. decl: { // for every decls_len
     ///        src_hash: [4]u32, // hash of source bytes
     ///        line: u32, // line number of decl, relative to parent
-    ///        name: u32, // null terminated string index
+    ///        name: NullTerminatedString, // null terminated string index
     ///        - 0 means comptime or usingnamespace decl.
     ///          - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace
     ///        - 1 means test decl with no name.
@@ -2908,7 +2906,7 @@ pub const Inst = struct {
     ///        - if there is a 0 byte at the position `name` indexes, it indicates
     ///          this is a test decl, and the name starts at `name+1`.
     ///        value: Index,
-    ///        doc_comment: u32, 0 if no doc comment, if this is a decltest, doc_comment references the decl name in the string table
+    ///        doc_comment: u32, .empty if no doc comment, if this is a decltest, doc_comment references the decl name in the string table
     ///        align: Ref, // if corresponding bit is set
     ///        link_section_or_address_space: { // if corresponding bit is set.
     ///            link_section: Ref,
@@ -2923,7 +2921,7 @@ pub const Inst = struct {
     ///      0bX000: whether corresponding field has a type expression
     /// 9. fields: { // for every fields_len
     ///        field_name: u32, // if !is_tuple
-    ///        doc_comment: u32, // 0 if no doc comment
+    ///        doc_comment: NullTerminatedString, // .empty if no doc comment
     ///        field_type: Ref, // if corresponding bit is not set. none means anytype.
     ///        field_type_body_len: u32, // if corresponding bit is set
     ///        align_body_len: u32, // if corresponding bit is set
@@ -2996,14 +2994,14 @@ pub const Inst = struct {
     /// 6. decl: { // for every decls_len
     ///        src_hash: [4]u32, // hash of source bytes
     ///        line: u32, // line number of decl, relative to parent
-    ///        name: u32, // null terminated string index
+    ///        name: NullTerminatedString, // null terminated string index
     ///        - 0 means comptime or usingnamespace decl.
     ///          - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace
     ///        - 1 means test decl with no name.
     ///        - if there is a 0 byte at the position `name` indexes, it indicates
     ///          this is a test decl, and the name starts at `name+1`.
     ///        value: Index,
-    ///        doc_comment: u32, // 0 if no doc_comment
+    ///        doc_comment: u32, // .empty if no doc_comment
     ///        align: Ref, // if corresponding bit is set
     ///        link_section_or_address_space: { // if corresponding bit is set.
     ///            link_section: Ref,
@@ -3015,7 +3013,7 @@ pub const Inst = struct {
     ///    - the bit is whether corresponding field has an value expression
     /// 9. fields: { // for every fields_len
     ///        field_name: u32,
-    ///        doc_comment: u32, // 0 if no doc_comment
+    ///        doc_comment: u32, // .empty if no doc_comment
     ///        value: Ref, // if corresponding bit is set
     ///    }
     pub const EnumDecl = struct {
@@ -3046,14 +3044,14 @@ pub const Inst = struct {
     /// 6. decl: { // for every decls_len
     ///        src_hash: [4]u32, // hash of source bytes
     ///        line: u32, // line number of decl, relative to parent
-    ///        name: u32, // null terminated string index
+    ///        name: NullTerminatedString, // null terminated string index
     ///        - 0 means comptime or usingnamespace decl.
     ///          - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace
     ///        - 1 means test decl with no name.
     ///        - if there is a 0 byte at the position `name` indexes, it indicates
     ///          this is a test decl, and the name starts at `name+1`.
     ///        value: Index,
-    ///        doc_comment: u32, // 0 if no doc comment
+    ///        doc_comment: NullTerminatedString, // .empty if no doc comment
     ///        align: Ref, // if corresponding bit is set
     ///        link_section_or_address_space: { // if corresponding bit is set.
     ///            link_section: Ref,
@@ -3068,8 +3066,8 @@ pub const Inst = struct {
     ///      0b0X00: whether corresponding field has a tag value expression
     ///      0bX000: unused
     /// 9. fields: { // for every fields_len
-    ///        field_name: u32, // null terminated string index
-    ///        doc_comment: u32, // 0 if no doc comment
+    ///        field_name: NullTerminatedString, // null terminated string index
+    ///        doc_comment: NullTerminatedString, // .empty if no doc comment
     ///        field_type: Ref, // if corresponding bit is set
     ///        - if none, means `anytype`.
     ///        align: Ref, // if corresponding bit is set
@@ -3108,14 +3106,14 @@ pub const Inst = struct {
     /// 3. decl: { // for every decls_len
     ///        src_hash: [4]u32, // hash of source bytes
     ///        line: u32, // line number of decl, relative to parent
-    ///        name: u32, // null terminated string index
+    ///        name: NullTerminatedString, // null terminated string index
     ///        - 0 means comptime or usingnamespace decl.
     ///          - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace
     ///        - 1 means test decl with no name.
     ///        - if there is a 0 byte at the position `name` indexes, it indicates
     ///          this is a test decl, and the name starts at `name+1`.
     ///        value: Index,
-    ///        doc_comment: u32, // 0 if no doc comment,
+    ///        doc_comment: NullTerminatedString, // .empty if no doc comment,
     ///        align: Ref, // if corresponding bit is set
     ///        link_section_or_address_space: { // if corresponding bit is set.
     ///            link_section: Ref,
@@ -3133,8 +3131,8 @@ pub const Inst = struct {
 
     /// Trailing:
     /// { // for every fields_len
-    ///      field_name: u32 // null terminated string index
-    ///     doc_comment: u32 // null terminated string index
+    ///      field_name: NullTerminatedString // null terminated string index
+    ///     doc_comment: NullTerminatedString // null terminated string index
     /// }
     pub const ErrorSetDecl = struct {
         fields_len: u32,
@@ -3177,7 +3175,7 @@ pub const Inst = struct {
 
         pub const Item = struct {
             /// Null-terminated string table index.
-            field_name: u32,
+            field_name: NullTerminatedString,
             /// The field init expression to be used as the field value.
             init: Ref,
         };
@@ -3186,7 +3184,7 @@ pub const Inst = struct {
     pub const FieldType = struct {
         container_type: Ref,
         /// Offset into `string_bytes`, null terminated.
-        name_start: u32,
+        name_start: NullTerminatedString,
     };
 
     pub const FieldTypeRef = struct {
@@ -3266,9 +3264,9 @@ pub const Inst = struct {
     /// Trailing: inst: Index // for every body_len
     pub const Param = struct {
         /// Null-terminated string index.
-        name: u32,
-        /// 0 if no doc comment
-        doc_comment: u32,
+        name: NullTerminatedString,
+        /// Null-terminated string index.
+        doc_comment: NullTerminatedString,
         /// The body contains the type of the parameter.
         body_len: u32,
     };
@@ -3293,7 +3291,7 @@ pub const Inst = struct {
         /// If omitted, this is referring to a Decl via identifier, e.g. `a`.
         namespace: Ref,
         /// Null-terminated string index.
-        decl_name: u32,
+        decl_name: NullTerminatedString,
         options: Ref,
     };
 
@@ -3311,7 +3309,7 @@ pub const Inst = struct {
         /// It's a payload index of another `Item`.
         pub const Item = struct {
             /// null terminated string index
-            msg: u32,
+            msg: NullTerminatedString,
             node: Ast.Node.Index,
             /// If node is 0 then this will be populated.
             token: Ast.TokenIndex,
@@ -3335,7 +3333,7 @@ pub const Inst = struct {
 
         pub const Item = struct {
             /// null terminated string index
-            name: u32,
+            name: NullTerminatedString,
             /// points to the import name
             token: Ast.TokenIndex,
         };
@@ -3412,7 +3410,7 @@ pub const DeclIterator = struct {
 
         const sub_index: ExtraIndex = @enumFromInt(it.extra_index);
         it.extra_index += 5; // src_hash(4) + line(1)
-        const name = it.zir.nullTerminatedString(it.zir.extra[it.extra_index]);
+        const name = it.zir.nullTerminatedString(@enumFromInt(it.zir.extra[it.extra_index]));
         it.extra_index += 3; // name(1) + value(1) + doc_comment(1)
         it.extra_index += @as(u1, @truncate(flags >> 2)); // align
         it.extra_index += @as(u1, @truncate(flags >> 3)); // link_section