Commit e39cc0dff7

mlugg <mlugg@mlugg.co.uk>
2024-06-10 02:22:54
Zir: use absolute nodes for declarations and type declarations
The justification for using relative source nodes in ZIR is that it allows source locations -- which may be serialized across incremental updates -- to be relative to the source location of their containing declaration. However, having those "baseline" instructions themselves be relative to their own parent is counterproductive, since the source location updating problem is only being moved to `Decl`. Storing the absolute node here instead makes more sense, since it allows for this source location update logic to be elided entirely in the future by storing a `TrackedInst.Index` to resolve a source location relative to rather than a `Decl.Index`.
1 parent 82a934b
Changed files (5)
lib/std/zig/AstGen.zig
@@ -4011,7 +4011,7 @@ fn fnDecl(
 
     // We insert this at the beginning so that its instruction index marks the
     // start of the top level declaration.
-    const decl_inst = try gz.makeBlockInst(.declaration, fn_proto.ast.proto_node);
+    const decl_inst = try gz.makeDeclaration(fn_proto.ast.proto_node);
     astgen.advanceSourceCursorToNode(decl_node);
 
     var decl_gz: GenZir = .{
@@ -4393,7 +4393,7 @@ fn globalVarDecl(
     const is_mutable = token_tags[var_decl.ast.mut_token] == .keyword_var;
     // We do this at the beginning so that the instruction index marks the range start
     // of the top level declaration.
-    const decl_inst = try gz.makeBlockInst(.declaration, node);
+    const decl_inst = try gz.makeDeclaration(node);
 
     const name_token = var_decl.ast.mut_token + 1;
     astgen.advanceSourceCursorToNode(node);
@@ -4555,7 +4555,7 @@ fn comptimeDecl(
 
     // Up top so the ZIR instruction index marks the start range of this
     // top-level declaration.
-    const decl_inst = try gz.makeBlockInst(.declaration, node);
+    const decl_inst = try gz.makeDeclaration(node);
     wip_members.nextDecl(decl_inst);
     astgen.advanceSourceCursorToNode(node);
 
@@ -4607,7 +4607,7 @@ fn usingnamespaceDecl(
     };
     // Up top so the ZIR instruction index marks the start range of this
     // top-level declaration.
-    const decl_inst = try gz.makeBlockInst(.declaration, node);
+    const decl_inst = try gz.makeDeclaration(node);
     wip_members.nextDecl(decl_inst);
     astgen.advanceSourceCursorToNode(node);
 
@@ -4651,7 +4651,7 @@ fn testDecl(
 
     // Up top so the ZIR instruction index marks the start range of this
     // top-level declaration.
-    const decl_inst = try gz.makeBlockInst(.declaration, node);
+    const decl_inst = try gz.makeDeclaration(node);
 
     wip_members.nextDecl(decl_inst);
     astgen.advanceSourceCursorToNode(node);
@@ -13071,6 +13071,21 @@ const GenZir = struct {
         return new_index;
     }
 
+    /// Note that this returns a `Zir.Inst.Index` not a ref.
+    /// Does *not* append the block instruction to the scope.
+    /// Leaves the `payload_index` field undefined. Use `setDeclaration` to finalize.
+    fn makeDeclaration(gz: *GenZir, node: Ast.Node.Index) !Zir.Inst.Index {
+        const new_index: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len);
+        try gz.astgen.instructions.append(gz.astgen.gpa, .{
+            .tag = .declaration,
+            .data = .{ .declaration = .{
+                .src_node = node,
+                .payload_index = undefined,
+            } },
+        });
+        return new_index;
+    }
+
     /// Note that this returns a `Zir.Inst.Index` not a ref.
     /// Leaves the `payload_index` field undefined.
     fn addCondBr(gz: *GenZir, tag: Zir.Inst.Tag, node: Ast.Node.Index) !Zir.Inst.Index {
@@ -13117,7 +13132,7 @@ const GenZir = struct {
             .fields_hash_1 = fields_hash_arr[1],
             .fields_hash_2 = fields_hash_arr[2],
             .fields_hash_3 = fields_hash_arr[3],
-            .src_node = gz.nodeIndexToRelative(args.src_node),
+            .src_node = args.src_node,
         });
 
         if (args.captures_len != 0) {
@@ -13177,7 +13192,7 @@ const GenZir = struct {
             .fields_hash_1 = fields_hash_arr[1],
             .fields_hash_2 = fields_hash_arr[2],
             .fields_hash_3 = fields_hash_arr[3],
-            .src_node = gz.nodeIndexToRelative(args.src_node),
+            .src_node = args.src_node,
         });
 
         if (args.tag_type != .none) {
@@ -13238,7 +13253,7 @@ const GenZir = struct {
             .fields_hash_1 = fields_hash_arr[1],
             .fields_hash_2 = fields_hash_arr[2],
             .fields_hash_3 = fields_hash_arr[3],
-            .src_node = gz.nodeIndexToRelative(args.src_node),
+            .src_node = args.src_node,
         });
 
         if (args.tag_type != .none) {
@@ -13285,9 +13300,7 @@ const GenZir = struct {
         assert(args.src_node != 0);
 
         try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.OpaqueDecl).Struct.fields.len + 2);
-        const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.OpaqueDecl{
-            .src_node = gz.nodeIndexToRelative(args.src_node),
-        });
+        const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.OpaqueDecl{ .src_node = args.src_node });
 
         if (args.captures_len != 0) {
             astgen.extra.appendAssumeCapacity(args.captures_len);
@@ -13897,7 +13910,7 @@ fn setDeclaration(
             .has_align_linksection_addrspace = align_len != 0 or linksection_len != 0 or addrspace_len != 0,
         },
     };
-    astgen.instructions.items(.data)[@intFromEnum(decl_inst)].pl_node.payload_index = try astgen.addExtra(extra);
+    astgen.instructions.items(.data)[@intFromEnum(decl_inst)].declaration.payload_index = try astgen.addExtra(extra);
     if (extra.flags.has_doc_comment) {
         try astgen.extra.append(gpa, @intFromEnum(true_doc_comment));
     }
lib/std/zig/Zir.zig
@@ -287,7 +287,7 @@ pub const Inst = struct {
         /// namespace type, e.g. within a `struct_decl` instruction. It represents a
         /// single source declaration (`const`/`var`/`fn`), containing the name,
         /// attributes, type, and value of the declaration.
-        /// Uses the `pl_node` union field. Payload is `Declaration`.
+        /// Uses the `declaration` union field. Payload is `Declaration`.
         declaration,
         /// Implements `suspend {...}`.
         /// Uses the `pl_node` union field. Payload is `Block`.
@@ -1596,7 +1596,7 @@ pub const Inst = struct {
                 .block = .pl_node,
                 .block_comptime = .pl_node,
                 .block_inline = .pl_node,
-                .declaration = .pl_node,
+                .declaration = .declaration,
                 .suspend_block = .pl_node,
                 .bool_not = .un_node,
                 .bool_br_and = .pl_node,
@@ -2370,6 +2370,16 @@ pub const Inst = struct {
             /// The index being accessed.
             idx: u32,
         },
+        declaration: struct {
+            /// This node provides a new absolute baseline node for all instructions within this struct.
+            src_node: Ast.Node.Index,
+            /// index into extra to a `Declaration` payload.
+            payload_index: u32,
+
+            pub fn src(self: @This()) LazySrcLoc {
+                return .{ .node_abs = self.src_node };
+            }
+        },
 
         // Make sure we don't accidentally add a field to make this union
         // bigger than expected. Note that in Debug builds, Zig is allowed
@@ -2408,6 +2418,7 @@ pub const Inst = struct {
             defer_err_code,
             save_err_ret_index,
             elem_val_imm,
+            declaration,
         };
     };
 
@@ -3018,10 +3029,11 @@ pub const Inst = struct {
         fields_hash_1: u32,
         fields_hash_2: u32,
         fields_hash_3: u32,
-        src_node: i32,
+        /// This node provides a new absolute baseline node for all instructions within this struct.
+        src_node: Ast.Node.Index,
 
         pub fn src(self: StructDecl) LazySrcLoc {
-            return LazySrcLoc.nodeOffset(self.src_node);
+            return .{ .node_abs = self.src_node };
         }
 
         pub const Small = packed struct {
@@ -3150,10 +3162,11 @@ pub const Inst = struct {
         fields_hash_1: u32,
         fields_hash_2: u32,
         fields_hash_3: u32,
-        src_node: i32,
+        /// This node provides a new absolute baseline node for all instructions within this struct.
+        src_node: Ast.Node.Index,
 
         pub fn src(self: EnumDecl) LazySrcLoc {
-            return LazySrcLoc.nodeOffset(self.src_node);
+            return .{ .node_abs = self.src_node };
         }
 
         pub const Small = packed struct {
@@ -3198,10 +3211,11 @@ pub const Inst = struct {
         fields_hash_1: u32,
         fields_hash_2: u32,
         fields_hash_3: u32,
-        src_node: i32,
+        /// This node provides a new absolute baseline node for all instructions within this struct.
+        src_node: Ast.Node.Index,
 
         pub fn src(self: UnionDecl) LazySrcLoc {
-            return LazySrcLoc.nodeOffset(self.src_node);
+            return .{ .node_abs = self.src_node };
         }
 
         pub const Small = packed struct {
@@ -3230,10 +3244,11 @@ pub const Inst = struct {
     /// 2. capture: Capture, // for every captures_len
     /// 3. decl: Index, // for every decls_len; points to a `declaration` instruction
     pub const OpaqueDecl = struct {
-        src_node: i32,
+        /// This node provides a new absolute baseline node for all instructions within this struct.
+        src_node: Ast.Node.Index,
 
         pub fn src(self: OpaqueDecl) LazySrcLoc {
-            return LazySrcLoc.nodeOffset(self.src_node);
+            return .{ .node_abs = self.src_node };
         }
 
         pub const Small = packed struct {
@@ -4046,7 +4061,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
 
 pub fn getDeclaration(zir: Zir, inst: Zir.Inst.Index) struct { Inst.Declaration, u32 } {
     assert(zir.instructions.items(.tag)[@intFromEnum(inst)] == .declaration);
-    const pl_node = zir.instructions.items(.data)[@intFromEnum(inst)].pl_node;
+    const pl_node = zir.instructions.items(.data)[@intFromEnum(inst)].declaration;
     const extra = zir.extraData(Inst.Declaration, pl_node.payload_index);
     return .{
         extra.data,
src/Module.zig
@@ -413,8 +413,8 @@ pub const Decl = struct {
     pub fn zirBodies(decl: Decl, zcu: *Zcu) Zir.Inst.Declaration.Bodies {
         const zir = decl.getFileScope(zcu).zir;
         const zir_index = decl.zir_decl_index.unwrap().?.resolve(&zcu.intern_pool);
-        const pl_node = zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node;
-        const extra = zir.extraData(Zir.Inst.Declaration, pl_node.payload_index);
+        const declaration = zir.instructions.items(.data)[@intFromEnum(zir_index)].declaration;
+        const extra = zir.extraData(Zir.Inst.Declaration, declaration.payload_index);
         return extra.data.getBodies(@intCast(extra.end), zir);
     }
 
@@ -4255,12 +4255,11 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
     const zir = namespace.file_scope.zir;
     const ip = &zcu.intern_pool;
 
-    const pl_node = zir.instructions.items(.data)[@intFromEnum(decl_inst)].pl_node;
-    const extra = zir.extraData(Zir.Inst.Declaration, pl_node.payload_index);
+    const inst_data = zir.instructions.items(.data)[@intFromEnum(decl_inst)].declaration;
+    const extra = zir.extraData(Zir.Inst.Declaration, inst_data.payload_index);
     const declaration = extra.data;
 
     const line = iter.parent_decl.src_line + declaration.line_offset;
-    const decl_node = iter.parent_decl.relativeToNodeIndex(pl_node.src_node);
 
     // Every Decl needs a name.
     const decl_name: InternPool.NullTerminatedString, const kind: Decl.Kind, const is_named_test: bool = switch (declaration.name) {
@@ -4348,14 +4347,14 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
         const was_exported = decl.is_exported;
         assert(decl.kind == kind); // ZIR tracking should preserve this
         decl.name = decl_name;
-        decl.src_node = decl_node;
+        decl.src_node = inst_data.src_node;
         decl.src_line = line;
         decl.is_pub = declaration.flags.is_pub;
         decl.is_exported = declaration.flags.is_export;
         break :decl_index .{ was_exported, decl_index };
     } else decl_index: {
         // Create and set up a new Decl.
-        const new_decl_index = try zcu.allocateNewDecl(namespace_index, decl_node);
+        const new_decl_index = try zcu.allocateNewDecl(namespace_index, inst_data.src_node);
         const new_decl = zcu.declPtr(new_decl_index);
         new_decl.kind = kind;
         new_decl.name = decl_name;
src/print_zir.zig
@@ -1390,9 +1390,14 @@ const Writer = struct {
     }
 
     fn writeStructDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
-        const small = @as(Zir.Inst.StructDecl.Small, @bitCast(extended.small));
+        const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
 
         const extra = self.code.extraData(Zir.Inst.StructDecl, extended.operand);
+
+        const prev_parent_decl_node = self.parent_decl_node;
+        self.parent_decl_node = extra.data.src_node;
+        defer self.parent_decl_node = prev_parent_decl_node;
+
         const fields_hash: std.zig.SrcHash = @bitCast([4]u32{
             extra.data.fields_hash_0,
             extra.data.fields_hash_1,
@@ -1465,10 +1470,6 @@ const Writer = struct {
         if (decls_len == 0) {
             try stream.writeAll("{}, ");
         } else {
-            const prev_parent_decl_node = self.parent_decl_node;
-            self.parent_decl_node = self.relativeToNodeIndex(extra.data.src_node);
-            defer self.parent_decl_node = prev_parent_decl_node;
-
             try stream.writeAll("{\n");
             self.indent += 2;
             try self.writeBody(stream, self.code.bodySlice(extra_index, decls_len));
@@ -1546,8 +1547,6 @@ const Writer = struct {
                 }
             }
 
-            const prev_parent_decl_node = self.parent_decl_node;
-            self.parent_decl_node = self.relativeToNodeIndex(extra.data.src_node);
             try stream.writeAll("{\n");
             self.indent += 2;
 
@@ -1595,18 +1594,22 @@ const Writer = struct {
                 try stream.writeAll(",\n");
             }
 
-            self.parent_decl_node = prev_parent_decl_node;
             self.indent -= 2;
             try stream.writeByteNTimes(' ', self.indent);
             try stream.writeAll("})");
         }
-        try self.writeSrcNode(stream, extra.data.src_node);
+        try self.writeSrcNode(stream, 0);
     }
 
     fn writeUnionDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
         const small = @as(Zir.Inst.UnionDecl.Small, @bitCast(extended.small));
 
         const extra = self.code.extraData(Zir.Inst.UnionDecl, extended.operand);
+
+        const prev_parent_decl_node = self.parent_decl_node;
+        self.parent_decl_node = extra.data.src_node;
+        defer self.parent_decl_node = prev_parent_decl_node;
+
         const fields_hash: std.zig.SrcHash = @bitCast([4]u32{
             extra.data.fields_hash_0,
             extra.data.fields_hash_1,
@@ -1670,10 +1673,6 @@ const Writer = struct {
         if (decls_len == 0) {
             try stream.writeAll("{}");
         } else {
-            const prev_parent_decl_node = self.parent_decl_node;
-            self.parent_decl_node = self.relativeToNodeIndex(extra.data.src_node);
-            defer self.parent_decl_node = prev_parent_decl_node;
-
             try stream.writeAll("{\n");
             self.indent += 2;
             try self.writeBody(stream, self.code.bodySlice(extra_index, decls_len));
@@ -1690,7 +1689,7 @@ const Writer = struct {
 
         if (fields_len == 0) {
             try stream.writeAll("})");
-            try self.writeSrcNode(stream, extra.data.src_node);
+            try self.writeSrcNode(stream, 0);
             return;
         }
         try stream.writeAll(", ");
@@ -1698,8 +1697,6 @@ const Writer = struct {
         const body = self.code.bodySlice(extra_index, body_len);
         extra_index += body.len;
 
-        const prev_parent_decl_node = self.parent_decl_node;
-        self.parent_decl_node = self.relativeToNodeIndex(extra.data.src_node);
         try self.writeBracedDecl(stream, body);
         try stream.writeAll(", {\n");
 
@@ -1763,17 +1760,21 @@ const Writer = struct {
             try stream.writeAll(",\n");
         }
 
-        self.parent_decl_node = prev_parent_decl_node;
         self.indent -= 2;
         try stream.writeByteNTimes(' ', self.indent);
         try stream.writeAll("})");
-        try self.writeSrcNode(stream, extra.data.src_node);
+        try self.writeSrcNode(stream, 0);
     }
 
     fn writeEnumDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
         const small = @as(Zir.Inst.EnumDecl.Small, @bitCast(extended.small));
 
         const extra = self.code.extraData(Zir.Inst.EnumDecl, extended.operand);
+
+        const prev_parent_decl_node = self.parent_decl_node;
+        self.parent_decl_node = extra.data.src_node;
+        defer self.parent_decl_node = prev_parent_decl_node;
+
         const fields_hash: std.zig.SrcHash = @bitCast([4]u32{
             extra.data.fields_hash_0,
             extra.data.fields_hash_1,
@@ -1835,10 +1836,6 @@ const Writer = struct {
         if (decls_len == 0) {
             try stream.writeAll("{}, ");
         } else {
-            const prev_parent_decl_node = self.parent_decl_node;
-            self.parent_decl_node = self.relativeToNodeIndex(extra.data.src_node);
-            defer self.parent_decl_node = prev_parent_decl_node;
-
             try stream.writeAll("{\n");
             self.indent += 2;
             try self.writeBody(stream, self.code.bodySlice(extra_index, decls_len));
@@ -1856,12 +1853,9 @@ const Writer = struct {
         const body = self.code.bodySlice(extra_index, body_len);
         extra_index += body.len;
 
-        const prev_parent_decl_node = self.parent_decl_node;
-        self.parent_decl_node = self.relativeToNodeIndex(extra.data.src_node);
         try self.writeBracedDecl(stream, body);
         if (fields_len == 0) {
             try stream.writeAll(", {})");
-            self.parent_decl_node = prev_parent_decl_node;
         } else {
             try stream.writeAll(", {\n");
 
@@ -1900,12 +1894,11 @@ const Writer = struct {
                 }
                 try stream.writeAll(",\n");
             }
-            self.parent_decl_node = prev_parent_decl_node;
             self.indent -= 2;
             try stream.writeByteNTimes(' ', self.indent);
             try stream.writeAll("})");
         }
-        try self.writeSrcNode(stream, extra.data.src_node);
+        try self.writeSrcNode(stream, 0);
     }
 
     fn writeOpaqueDecl(
@@ -1915,6 +1908,11 @@ const Writer = struct {
     ) !void {
         const small = @as(Zir.Inst.OpaqueDecl.Small, @bitCast(extended.small));
         const extra = self.code.extraData(Zir.Inst.OpaqueDecl, extended.operand);
+
+        const prev_parent_decl_node = self.parent_decl_node;
+        self.parent_decl_node = extra.data.src_node;
+        defer self.parent_decl_node = prev_parent_decl_node;
+
         var extra_index: usize = extra.end;
 
         const captures_len = if (small.has_captures_len) blk: {
@@ -1948,10 +1946,6 @@ const Writer = struct {
         if (decls_len == 0) {
             try stream.writeAll("{})");
         } else {
-            const prev_parent_decl_node = self.parent_decl_node;
-            self.parent_decl_node = self.relativeToNodeIndex(extra.data.src_node);
-            defer self.parent_decl_node = prev_parent_decl_node;
-
             try stream.writeAll("{\n");
             self.indent += 2;
             try self.writeBody(stream, self.code.bodySlice(extra_index, decls_len));
@@ -1959,7 +1953,7 @@ const Writer = struct {
             try stream.writeByteNTimes(' ', self.indent);
             try stream.writeAll("})");
         }
-        try self.writeSrcNode(stream, extra.data.src_node);
+        try self.writeSrcNode(stream, 0);
     }
 
     fn writeErrorSetDecl(
@@ -2729,11 +2723,16 @@ const Writer = struct {
     }
 
     fn writeDeclaration(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
-        const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
+        const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].declaration;
         const extra = self.code.extraData(Zir.Inst.Declaration, inst_data.payload_index);
         const doc_comment: ?Zir.NullTerminatedString = if (extra.data.flags.has_doc_comment) dc: {
             break :dc @enumFromInt(self.code.extra[extra.end]);
         } else null;
+
+        const prev_parent_decl_node = self.parent_decl_node;
+        defer self.parent_decl_node = prev_parent_decl_node;
+        self.parent_decl_node = inst_data.src_node;
+
         if (extra.data.flags.is_pub) try stream.writeAll("pub ");
         if (extra.data.flags.is_export) try stream.writeAll("export ");
         switch (extra.data.name) {
@@ -2757,10 +2756,6 @@ const Writer = struct {
         try stream.print(" line(+{d}) hash({})", .{ extra.data.line_offset, std.fmt.fmtSliceHexLower(&src_hash_bytes) });
 
         {
-            const prev_parent_decl_node = self.parent_decl_node;
-            defer self.parent_decl_node = prev_parent_decl_node;
-            self.parent_decl_node = self.relativeToNodeIndex(inst_data.src_node);
-
             const bodies = extra.data.getBodies(@intCast(extra.end), self.code);
 
             try stream.writeAll(" value=");
@@ -2783,7 +2778,7 @@ const Writer = struct {
         }
 
         try stream.writeAll(") ");
-        try self.writeSrc(stream, inst_data.src());
+        try self.writeSrcNode(stream, 0);
     }
 
     fn writeClosureGet(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
src/Sema.zig
@@ -2835,7 +2835,7 @@ fn zirStructDecl(
 
     const new_decl_index = try sema.createAnonymousDeclTypeNamed(
         block,
-        src,
+        extra.data.src_node,
         Value.fromInterned(wip_ty.index),
         small.name_strategy,
         "struct",
@@ -2872,7 +2872,7 @@ fn zirStructDecl(
 fn createAnonymousDeclTypeNamed(
     sema: *Sema,
     block: *Block,
-    src: LazySrcLoc,
+    src_node: std.zig.Ast.Node.Index,
     val: Value,
     name_strategy: Zir.Inst.NameStrategy,
     anon_prefix: []const u8,
@@ -2883,31 +2883,17 @@ fn createAnonymousDeclTypeNamed(
     const gpa = sema.gpa;
     const namespace = block.namespace;
     const src_decl = zcu.declPtr(block.src_decl);
-    const src_node = src_decl.relativeToNodeIndex(src.node_offset.x);
     const new_decl_index = try zcu.allocateNewDecl(namespace, src_node);
     errdefer zcu.destroyDecl(new_decl_index);
 
     switch (name_strategy) {
-        .anon => {
-            // It would be neat to have "struct:line:column" but this name has
-            // to survive incremental updates, where it may have been shifted down
-            // or up to a different line, but unchanged, and thus not unnecessarily
-            // semantically analyzed.
-            // This name is also used as the key in the parent namespace so it cannot be
-            // renamed.
-
-            const name = ip.getOrPutStringFmt(gpa, "{}__{s}_{d}", .{
-                src_decl.name.fmt(ip), anon_prefix, @intFromEnum(new_decl_index),
-            }, .no_embedded_nulls) catch unreachable;
-            try zcu.initNewAnonDecl(new_decl_index, src_decl.src_line, val, name);
-            return new_decl_index;
-        },
+        .anon => {}, // handled after switch
         .parent => {
             const name = zcu.declPtr(block.src_decl).name;
             try zcu.initNewAnonDecl(new_decl_index, src_decl.src_line, val, name);
             return new_decl_index;
         },
-        .func => {
+        .func => func_strat: {
             const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip));
             const zir_tags = sema.code.instructions.items(.tag);
 
@@ -2927,7 +2913,7 @@ fn createAnonymousDeclTypeNamed(
                     // function and the name doesn't matter since it will later
                     // result in a compile error.
                     const arg_val = sema.resolveConstValue(block, .unneeded, arg, undefined) catch
-                        return sema.createAnonymousDeclTypeNamed(block, src, val, .anon, anon_prefix, null);
+                        break :func_strat; // fall through to anon strat
 
                     if (arg_i != 0) try writer.writeByte(',');
 
@@ -2969,9 +2955,24 @@ fn createAnonymousDeclTypeNamed(
                 },
                 else => {},
             };
-            return sema.createAnonymousDeclTypeNamed(block, src, val, .anon, anon_prefix, null);
+            // fall through to anon strat
         },
     }
+
+    // anon strat handling.
+
+    // It would be neat to have "struct:line:column" but this name has
+    // to survive incremental updates, where it may have been shifted down
+    // or up to a different line, but unchanged, and thus not unnecessarily
+    // semantically analyzed.
+    // This name is also used as the key in the parent namespace so it cannot be
+    // renamed.
+
+    const name = ip.getOrPutStringFmt(gpa, "{}__{s}_{d}", .{
+        src_decl.name.fmt(ip), anon_prefix, @intFromEnum(new_decl_index),
+    }, .no_embedded_nulls) catch unreachable;
+    try zcu.initNewAnonDecl(new_decl_index, src_decl.src_line, val, name);
+    return new_decl_index;
 }
 
 fn zirEnumDecl(
@@ -2991,7 +2992,6 @@ fn zirEnumDecl(
     var extra_index: usize = extra.end;
 
     const src = extra.data.src();
-    const tag_ty_src: LazySrcLoc = .{ .node_offset_container_tag = src.node_offset.x };
 
     const tag_type_ref = if (small.has_tag_type) blk: {
         const tag_type_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
@@ -3071,7 +3071,7 @@ fn zirEnumDecl(
 
     const new_decl_index = try sema.createAnonymousDeclTypeNamed(
         block,
-        src,
+        extra.data.src_node,
         Value.fromInterned(wip_ty.index),
         small.name_strategy,
         "enum",
@@ -3140,14 +3140,17 @@ fn zirEnumDecl(
         };
         defer enum_block.instructions.deinit(sema.gpa);
 
+        // This source location applies in the context of `enum_block`.
+        const tag_ty_src: LazySrcLoc = .{ .node_offset_container_tag = 0 };
+
         if (body.len != 0) {
             _ = try sema.analyzeInlineBody(&enum_block, body, inst);
         }
 
         if (tag_type_ref != .none) {
-            const ty = try sema.resolveType(block, tag_ty_src, tag_type_ref);
+            const ty = try sema.resolveType(&enum_block, tag_ty_src, tag_type_ref);
             if (ty.zigTypeTag(mod) != .Int and ty.zigTypeTag(mod) != .ComptimeInt) {
-                return sema.fail(block, tag_ty_src, "expected integer tag type, found '{}'", .{ty.fmt(sema.mod)});
+                return sema.fail(&enum_block, tag_ty_src, "expected integer tag type, found '{}'", .{ty.fmt(sema.mod)});
             }
             break :ty ty;
         } else if (fields_len == 0) {
@@ -3342,7 +3345,7 @@ fn zirUnionDecl(
 
     const new_decl_index = try sema.createAnonymousDeclTypeNamed(
         block,
-        src,
+        extra.data.src_node,
         Value.fromInterned(wip_ty.index),
         small.name_strategy,
         "union",
@@ -3430,7 +3433,7 @@ fn zirOpaqueDecl(
 
     const new_decl_index = try sema.createAnonymousDeclTypeNamed(
         block,
-        src,
+        extra.data.src_node,
         Value.fromInterned(wip_ty.index),
         small.name_strategy,
         "opaque",
@@ -21658,7 +21661,7 @@ fn zirReify(
 
             const new_decl_index = try sema.createAnonymousDeclTypeNamed(
                 block,
-                src,
+                mod.declPtr(block.src_decl).relativeToNodeIndex(src.node_offset.x),
                 Value.fromInterned(wip_ty.index),
                 name_strategy,
                 "opaque",
@@ -21858,7 +21861,7 @@ fn reifyEnum(
 
     const new_decl_index = try sema.createAnonymousDeclTypeNamed(
         block,
-        src,
+        mod.declPtr(block.src_decl).relativeToNodeIndex(src.node_offset.x),
         Value.fromInterned(wip_ty.index),
         name_strategy,
         "enum",
@@ -22005,7 +22008,7 @@ fn reifyUnion(
 
     const new_decl_index = try sema.createAnonymousDeclTypeNamed(
         block,
-        src,
+        mod.declPtr(block.src_decl).relativeToNodeIndex(src.node_offset.x),
         Value.fromInterned(wip_ty.index),
         name_strategy,
         "union",
@@ -22264,7 +22267,7 @@ fn reifyStruct(
 
     const new_decl_index = try sema.createAnonymousDeclTypeNamed(
         block,
-        src,
+        mod.declPtr(block.src_decl).relativeToNodeIndex(src.node_offset.x),
         Value.fromInterned(wip_ty.index),
         name_strategy,
         "struct",