Commit f84a4953d2

mlugg <mlugg@mlugg.co.uk>
2024-07-16 12:37:53
Value: eliminate static recursion loop from value printing
1 parent 67cd14d
src/arch/x86_64/CodeGen.zig
@@ -17975,8 +17975,8 @@ fn airShuffle(self: *Self, inst: Air.Inst.Index) !void {
 
         break :result null;
     }) orelse return self.fail("TODO implement airShuffle from {} and {} to {} with {}", .{
-        lhs_ty.fmt(pt),                                    rhs_ty.fmt(pt), dst_ty.fmt(pt),
-        Value.fromInterned(extra.mask).fmtValue(pt, null),
+        lhs_ty.fmt(pt),                              rhs_ty.fmt(pt), dst_ty.fmt(pt),
+        Value.fromInterned(extra.mask).fmtValue(pt),
     });
     return self.finishAir(inst, result, .{ extra.a, extra.b, .none });
 }
src/codegen/spirv.zig
@@ -892,7 +892,7 @@ const DeclGen = struct {
         const result_ty_id = try self.resolveType(ty, repr);
         const ip = &mod.intern_pool;
 
-        log.debug("lowering constant: ty = {}, val = {}", .{ ty.fmt(pt), val.fmtValue(pt, null) });
+        log.debug("lowering constant: ty = {}, val = {}", .{ ty.fmt(pt), val.fmtValue(pt) });
         if (val.isUndefDeep(mod)) {
             return self.spv.constUndef(result_ty_id);
         }
src/codegen.zig
@@ -188,7 +188,7 @@ pub fn generateSymbol(
     const target = mod.getTarget();
     const endian = target.cpu.arch.endian();
 
-    log.debug("generateSymbol: val = {}", .{val.fmtValue(pt, null)});
+    log.debug("generateSymbol: val = {}", .{val.fmtValue(pt)});
 
     if (val.isUndefDeep(mod)) {
         const abi_size = math.cast(usize, ty.abiSize(pt)) orelse return error.Overflow;
@@ -838,7 +838,7 @@ fn genDeclRef(
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
     const ty = val.typeOf(zcu);
-    log.debug("genDeclRef: val = {}", .{val.fmtValue(pt, null)});
+    log.debug("genDeclRef: val = {}", .{val.fmtValue(pt)});
 
     const ptr_decl = zcu.declPtr(ptr_decl_index);
     const namespace = zcu.namespacePtr(ptr_decl.src_namespace);
@@ -943,7 +943,7 @@ fn genUnnamedConst(
     owner_decl_index: InternPool.DeclIndex,
 ) CodeGenError!GenResult {
     const gpa = lf.comp.gpa;
-    log.debug("genUnnamedConst: val = {}", .{val.fmtValue(pt, null)});
+    log.debug("genUnnamedConst: val = {}", .{val.fmtValue(pt)});
 
     const local_sym_index = lf.lowerUnnamedConst(pt, val, owner_decl_index) catch |err| {
         return GenResult.fail(gpa, src_loc, "lowering unnamed constant failed: {s}", .{@errorName(err)});
@@ -985,7 +985,7 @@ pub fn genTypedValue(
     const ip = &zcu.intern_pool;
     const ty = val.typeOf(zcu);
 
-    log.debug("genTypedValue: val = {}", .{val.fmtValue(pt, null)});
+    log.debug("genTypedValue: val = {}", .{val.fmtValue(pt)});
 
     if (val.isUndef(zcu))
         return GenResult.mcv(.undef);
src/print_air.zig
@@ -957,7 +957,7 @@ const Writer = struct {
             const ty = Type.fromInterned(pt.zcu.intern_pool.indexToKey(ip_index).typeOf());
             try s.print("<{}, {}>", .{
                 ty.fmt(pt),
-                Value.fromInterned(ip_index).fmtValue(pt, null),
+                Value.fromInterned(ip_index).fmtValue(pt),
             });
         } else {
             return w.writeInstIndex(s, operand.toIndex().?, dies);
src/print_value.zig
@@ -20,18 +20,35 @@ pub const FormatContext = struct {
     depth: u8,
 };
 
-pub fn format(
+pub fn formatSema(
     ctx: FormatContext,
     comptime fmt: []const u8,
     options: std.fmt.FormatOptions,
     writer: anytype,
 ) !void {
     _ = options;
+    const sema = ctx.opt_sema.?;
     comptime std.debug.assert(fmt.len == 0);
-    return print(ctx.val, writer, ctx.depth, ctx.pt, ctx.opt_sema) catch |err| switch (err) {
+    return print(ctx.val, writer, ctx.depth, ctx.pt, true, sema) catch |err| switch (err) {
         error.OutOfMemory => @panic("OOM"), // We're not allowed to return this from a format function
         error.ComptimeBreak, error.ComptimeReturn => unreachable,
-        error.AnalysisFail => unreachable, // TODO: re-evaluate when we use `opt_sema` more fully
+        error.AnalysisFail => unreachable, // TODO: re-evaluate when we use `sema` more fully
+        else => |e| return e,
+    };
+}
+
+pub fn format(
+    ctx: FormatContext,
+    comptime fmt: []const u8,
+    options: std.fmt.FormatOptions,
+    writer: anytype,
+) !void {
+    _ = options;
+    std.debug.assert(ctx.opt_sema == null);
+    comptime std.debug.assert(fmt.len == 0);
+    return print(ctx.val, writer, ctx.depth, ctx.pt, false, {}) catch |err| switch (err) {
+        error.OutOfMemory => @panic("OOM"), // We're not allowed to return this from a format function
+        error.ComptimeBreak, error.ComptimeReturn, error.AnalysisFail => unreachable,
         else => |e| return e,
     };
 }
@@ -42,7 +59,8 @@ pub fn print(
     level: u8,
     pt: Zcu.PerThread,
     /// If this `Sema` is provided, we will recurse through pointers where possible to provide friendly output.
-    opt_sema: ?*Sema,
+    comptime have_sema: bool,
+    sema: if (have_sema) *Sema else void,
 ) (@TypeOf(writer).Error || Zcu.CompileError)!void {
     const mod = pt.zcu;
     const ip = &mod.intern_pool;
@@ -80,11 +98,11 @@ pub fn print(
         }),
         .int => |int| switch (int.storage) {
             inline .u64, .i64, .big_int => |x| try writer.print("{}", .{x}),
-            .lazy_align => |ty| if (opt_sema != null) {
+            .lazy_align => |ty| if (have_sema) {
                 const a = (try Type.fromInterned(ty).abiAlignmentAdvanced(pt, .sema)).scalar;
                 try writer.print("{}", .{a.toByteUnits() orelse 0});
             } else try writer.print("@alignOf({})", .{Type.fromInterned(ty).fmt(pt)}),
-            .lazy_size => |ty| if (opt_sema != null) {
+            .lazy_size => |ty| if (have_sema) {
                 const s = (try Type.fromInterned(ty).abiSizeAdvanced(pt, .sema)).scalar;
                 try writer.print("{}", .{s});
             } else try writer.print("@sizeOf({})", .{Type.fromInterned(ty).fmt(pt)}),
@@ -96,7 +114,7 @@ pub fn print(
             .err_name => |err_name| try writer.print("error.{}", .{
                 err_name.fmt(ip),
             }),
-            .payload => |payload| try print(Value.fromInterned(payload), writer, level, pt, opt_sema),
+            .payload => |payload| try print(Value.fromInterned(payload), writer, level, pt, have_sema, sema),
         },
         .enum_literal => |enum_literal| try writer.print(".{}", .{
             enum_literal.fmt(ip),
@@ -110,7 +128,7 @@ pub fn print(
                 return writer.writeAll("@enumFromInt(...)");
             }
             try writer.writeAll("@enumFromInt(");
-            try print(Value.fromInterned(enum_tag.int), writer, level - 1, pt, opt_sema);
+            try print(Value.fromInterned(enum_tag.int), writer, level - 1, pt, have_sema, sema);
             try writer.writeAll(")");
         },
         .empty_enum_value => try writer.writeAll("(empty enum value)"),
@@ -124,15 +142,15 @@ pub fn print(
                 .decl, .int => false,
             };
             if (print_contents) {
-                // TODO: eventually we want to load the slice as an array with `opt_sema`, but that's
+                // TODO: eventually we want to load the slice as an array with `sema`, but that's
                 // currently not possible without e.g. triggering compile errors.
             }
-            try printPtr(Value.fromInterned(slice.ptr), writer, level, pt, opt_sema);
+            try printPtr(Value.fromInterned(slice.ptr), writer, level, pt, have_sema, sema);
             try writer.writeAll("[0..");
             if (level == 0) {
                 try writer.writeAll("(...)");
             } else {
-                try print(Value.fromInterned(slice.len), writer, level - 1, pt, opt_sema);
+                try print(Value.fromInterned(slice.len), writer, level - 1, pt, have_sema, sema);
             }
             try writer.writeAll("]");
         },
@@ -143,16 +161,16 @@ pub fn print(
                 .decl, .int => false,
             };
             if (print_contents) {
-                // TODO: eventually we want to load the pointer with `opt_sema`, but that's
+                // TODO: eventually we want to load the pointer with `sema`, but that's
                 // currently not possible without e.g. triggering compile errors.
             }
-            try printPtr(val, writer, level, pt, opt_sema);
+            try printPtr(val, writer, level, pt, have_sema, sema);
         },
         .opt => |opt| switch (opt.val) {
             .none => try writer.writeAll("null"),
-            else => |payload| try print(Value.fromInterned(payload), writer, level, pt, opt_sema),
+            else => |payload| try print(Value.fromInterned(payload), writer, level, pt, have_sema, sema),
         },
-        .aggregate => |aggregate| try printAggregate(val, aggregate, false, writer, level, pt, opt_sema),
+        .aggregate => |aggregate| try printAggregate(val, aggregate, false, writer, level, pt, have_sema, sema),
         .un => |un| {
             if (level == 0) {
                 try writer.writeAll(".{ ... }");
@@ -161,13 +179,13 @@ pub fn print(
             if (un.tag == .none) {
                 const backing_ty = try val.typeOf(mod).unionBackingType(pt);
                 try writer.print("@bitCast(@as({}, ", .{backing_ty.fmt(pt)});
-                try print(Value.fromInterned(un.val), writer, level - 1, pt, opt_sema);
+                try print(Value.fromInterned(un.val), writer, level - 1, pt, have_sema, sema);
                 try writer.writeAll("))");
             } else {
                 try writer.writeAll(".{ ");
-                try print(Value.fromInterned(un.tag), writer, level - 1, pt, opt_sema);
+                try print(Value.fromInterned(un.tag), writer, level - 1, pt, have_sema, sema);
                 try writer.writeAll(" = ");
-                try print(Value.fromInterned(un.val), writer, level - 1, pt, opt_sema);
+                try print(Value.fromInterned(un.val), writer, level - 1, pt, have_sema, sema);
                 try writer.writeAll(" }");
             }
         },
@@ -182,7 +200,8 @@ fn printAggregate(
     writer: anytype,
     level: u8,
     pt: Zcu.PerThread,
-    opt_sema: ?*Sema,
+    comptime have_sema: bool,
+    sema: if (have_sema) *Sema else void,
 ) (@TypeOf(writer).Error || Zcu.CompileError)!void {
     if (level == 0) {
         if (is_ref) try writer.writeByte('&');
@@ -203,7 +222,7 @@ fn printAggregate(
                 if (i != 0) try writer.writeAll(", ");
                 const field_name = ty.structFieldName(@intCast(i), zcu).unwrap().?;
                 try writer.print(".{i} = ", .{field_name.fmt(ip)});
-                try print(try val.fieldValue(pt, i), writer, level - 1, pt, opt_sema);
+                try print(try val.fieldValue(pt, i), writer, level - 1, pt, have_sema, sema);
             }
             try writer.writeAll(" }");
             return;
@@ -253,7 +272,7 @@ fn printAggregate(
     const max_len = @min(len, max_aggregate_items);
     for (0..max_len) |i| {
         if (i != 0) try writer.writeAll(", ");
-        try print(try val.fieldValue(pt, i), writer, level - 1, pt, opt_sema);
+        try print(try val.fieldValue(pt, i), writer, level - 1, pt, have_sema, sema);
     }
     if (len > max_aggregate_items) {
         try writer.writeAll(", ...");
@@ -261,7 +280,14 @@ fn printAggregate(
     return writer.writeAll(" }");
 }
 
-fn printPtr(ptr_val: Value, writer: anytype, level: u8, pt: Zcu.PerThread, opt_sema: ?*Sema) (@TypeOf(writer).Error || Zcu.CompileError)!void {
+fn printPtr(
+    ptr_val: Value,
+    writer: anytype,
+    level: u8,
+    pt: Zcu.PerThread,
+    comptime have_sema: bool,
+    sema: if (have_sema) *Sema else void,
+) (@TypeOf(writer).Error || Zcu.CompileError)!void {
     const ptr = switch (pt.zcu.intern_pool.indexToKey(ptr_val.toIntern())) {
         .undef => return writer.writeAll("undefined"),
         .ptr => |ptr| ptr,
@@ -278,7 +304,8 @@ fn printPtr(ptr_val: Value, writer: anytype, level: u8, pt: Zcu.PerThread, opt_s
                 writer,
                 level,
                 pt,
-                opt_sema,
+                have_sema,
+                sema,
             ),
             else => {},
         }
@@ -286,12 +313,19 @@ fn printPtr(ptr_val: Value, writer: anytype, level: u8, pt: Zcu.PerThread, opt_s
 
     var arena = std.heap.ArenaAllocator.init(pt.zcu.gpa);
     defer arena.deinit();
-    const derivation = try ptr_val.pointerDerivationAdvanced(arena.allocator(), pt, opt_sema);
-    try printPtrDerivation(derivation, writer, level, pt, opt_sema);
+    const derivation = try ptr_val.pointerDerivationAdvanced(arena.allocator(), pt, have_sema, sema);
+    try printPtrDerivation(derivation, writer, level, pt, have_sema, sema);
 }
 
 /// Print `derivation` as an lvalue, i.e. such that writing `&` before this gives the pointer value.
-fn printPtrDerivation(derivation: Value.PointerDeriveStep, writer: anytype, level: u8, pt: Zcu.PerThread, opt_sema: ?*Sema) (@TypeOf(writer).Error || Zcu.CompileError)!void {
+fn printPtrDerivation(
+    derivation: Value.PointerDeriveStep,
+    writer: anytype,
+    level: u8,
+    pt: Zcu.PerThread,
+    comptime have_sema: bool,
+    sema: if (have_sema) *Sema else void,
+) (@TypeOf(writer).Error || Zcu.CompileError)!void {
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
     switch (derivation) {
@@ -305,31 +339,31 @@ fn printPtrDerivation(derivation: Value.PointerDeriveStep, writer: anytype, leve
         .anon_decl_ptr => |anon| {
             const ty = Value.fromInterned(anon.val).typeOf(zcu);
             try writer.print("@as({}, ", .{ty.fmt(pt)});
-            try print(Value.fromInterned(anon.val), writer, level - 1, pt, opt_sema);
+            try print(Value.fromInterned(anon.val), writer, level - 1, pt, have_sema, sema);
             try writer.writeByte(')');
         },
         .comptime_alloc_ptr => |info| {
             try writer.print("@as({}, ", .{info.val.typeOf(zcu).fmt(pt)});
-            try print(info.val, writer, level - 1, pt, opt_sema);
+            try print(info.val, writer, level - 1, pt, have_sema, sema);
             try writer.writeByte(')');
         },
         .comptime_field_ptr => |val| {
             const ty = val.typeOf(zcu);
             try writer.print("@as({}, ", .{ty.fmt(pt)});
-            try print(val, writer, level - 1, pt, opt_sema);
+            try print(val, writer, level - 1, pt, have_sema, sema);
             try writer.writeByte(')');
         },
         .eu_payload_ptr => |info| {
             try writer.writeByte('(');
-            try printPtrDerivation(info.parent.*, writer, level, pt, opt_sema);
+            try printPtrDerivation(info.parent.*, writer, level, pt, have_sema, sema);
             try writer.writeAll(" catch unreachable)");
         },
         .opt_payload_ptr => |info| {
-            try printPtrDerivation(info.parent.*, writer, level, pt, opt_sema);
+            try printPtrDerivation(info.parent.*, writer, level, pt, have_sema, sema);
             try writer.writeAll(".?");
         },
         .field_ptr => |field| {
-            try printPtrDerivation(field.parent.*, writer, level, pt, opt_sema);
+            try printPtrDerivation(field.parent.*, writer, level, pt, have_sema, sema);
             const agg_ty = (try field.parent.ptrType(pt)).childType(zcu);
             switch (agg_ty.zigTypeTag(zcu)) {
                 .Struct => if (agg_ty.structFieldName(field.field_idx, zcu).unwrap()) |field_name| {
@@ -351,16 +385,16 @@ fn printPtrDerivation(derivation: Value.PointerDeriveStep, writer: anytype, leve
             }
         },
         .elem_ptr => |elem| {
-            try printPtrDerivation(elem.parent.*, writer, level, pt, opt_sema);
+            try printPtrDerivation(elem.parent.*, writer, level, pt, have_sema, sema);
             try writer.print("[{d}]", .{elem.elem_idx});
         },
         .offset_and_cast => |oac| if (oac.byte_offset == 0) {
             try writer.print("@as({}, @ptrCast(", .{oac.new_ptr_ty.fmt(pt)});
-            try printPtrDerivation(oac.parent.*, writer, level, pt, opt_sema);
+            try printPtrDerivation(oac.parent.*, writer, level, pt, have_sema, sema);
             try writer.writeAll("))");
         } else {
             try writer.print("@as({}, @ptrFromInt(@intFromPtr(", .{oac.new_ptr_ty.fmt(pt)});
-            try printPtrDerivation(oac.parent.*, writer, level, pt, opt_sema);
+            try printPtrDerivation(oac.parent.*, writer, level, pt, have_sema, sema);
             try writer.print(") + {d}))", .{oac.byte_offset});
         },
     }
src/Sema.zig
@@ -2318,7 +2318,7 @@ fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty:
     if (int_ty.zigTypeTag(zcu) == .Vector) {
         const msg = msg: {
             const msg = try sema.errMsg(src, "overflow of vector type '{}' with value '{}'", .{
-                int_ty.fmt(pt), val.fmtValue(pt, sema),
+                int_ty.fmt(pt), val.fmtValueSema(pt, sema),
             });
             errdefer msg.destroy(sema.gpa);
             try sema.errNote(src, msg, "when computing vector element at index '{d}'", .{vector_index});
@@ -2327,7 +2327,7 @@ fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty:
         return sema.failWithOwnedErrorMsg(block, msg);
     }
     return sema.fail(block, src, "overflow of integer type '{}' with value '{}'", .{
-        int_ty.fmt(pt), val.fmtValue(pt, sema),
+        int_ty.fmt(pt), val.fmtValueSema(pt, sema),
     });
 }
 
@@ -2912,7 +2912,7 @@ fn createAnonymousDeclTypeNamed(
                     // in turn helps to avoid unreasonably long symbol names for namespaced
                     // symbols. Such names should ideally be human-readable, and additionally,
                     // some tooling may not support very long symbol names.
-                    try writer.print("{}", .{Value.fmtValueFull(.{
+                    try writer.print("{}", .{Value.fmtValueSemaFull(.{
                         .val = arg_val,
                         .pt = pt,
                         .opt_sema = sema,
@@ -3202,7 +3202,7 @@ fn zirEnumDecl(
                     .offset = .{ .container_field_value = conflict.prev_field_idx },
                 };
                 const msg = msg: {
-                    const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValue(pt, sema)});
+                    const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, sema)});
                     errdefer msg.destroy(gpa);
                     try sema.errNote(other_field_src, msg, "other occurrence here", .{});
                     break :msg msg;
@@ -3224,7 +3224,7 @@ fn zirEnumDecl(
                     .offset = .{ .container_field_value = conflict.prev_field_idx },
                 };
                 const msg = msg: {
-                    const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValue(pt, sema)});
+                    const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, sema)});
                     errdefer msg.destroy(gpa);
                     try sema.errNote(other_field_src, msg, "other occurrence here", .{});
                     break :msg msg;
@@ -3242,7 +3242,7 @@ fn zirEnumDecl(
 
         if (tag_overflow) {
             const msg = try sema.errMsg(value_src, "enumeration value '{}' too large for type '{}'", .{
-                last_tag_val.?.fmtValue(pt, sema), int_tag_ty.fmt(pt),
+                last_tag_val.?.fmtValueSema(pt, sema), int_tag_ty.fmt(pt),
             });
             return sema.failWithOwnedErrorMsg(block, msg);
         }
@@ -4430,10 +4430,10 @@ fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
                             .input_index = len_idx,
                         } });
                         try sema.errNote(a_src, msg, "length {} here", .{
-                            v.fmtValue(pt, sema),
+                            v.fmtValueSema(pt, sema),
                         });
                         try sema.errNote(arg_src, msg, "length {} here", .{
-                            arg_val.fmtValue(pt, sema),
+                            arg_val.fmtValueSema(pt, sema),
                         });
                         break :msg msg;
                     };
@@ -5853,7 +5853,7 @@ fn zirCompileLog(
         const arg_ty = sema.typeOf(arg);
         if (try sema.resolveValueResolveLazy(arg)) |val| {
             try writer.print("@as({}, {})", .{
-                arg_ty.fmt(pt), val.fmtValue(pt, sema),
+                arg_ty.fmt(pt), val.fmtValueSema(pt, sema),
             });
         } else {
             try writer.print("@as({}, [runtime value])", .{arg_ty.fmt(pt)});
@@ -8949,7 +8949,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 return Air.internedToRef((try pt.getCoerced(int_val, dest_ty)).toIntern());
             }
             return sema.fail(block, src, "int value '{}' out of range of non-exhaustive enum '{}'", .{
-                int_val.fmtValue(pt, sema), dest_ty.fmt(pt),
+                int_val.fmtValueSema(pt, sema), dest_ty.fmt(pt),
             });
         }
         if (int_val.isUndef(mod)) {
@@ -8957,7 +8957,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
         }
         if (!(try sema.enumHasInt(dest_ty, int_val))) {
             return sema.fail(block, src, "enum '{}' has no tag with value '{}'", .{
-                dest_ty.fmt(pt), int_val.fmtValue(pt, sema),
+                dest_ty.fmt(pt), int_val.fmtValueSema(pt, sema),
             });
         }
         return Air.internedToRef((try pt.getCoerced(int_val, dest_ty)).toIntern());
@@ -14067,7 +14067,7 @@ fn zirShl(
                     const rhs_elem = try rhs_val.elemValue(pt, i);
                     if (rhs_elem.compareHetero(.gte, bit_value, pt)) {
                         return sema.fail(block, rhs_src, "shift amount '{}' at index '{d}' is too large for operand type '{}'", .{
-                            rhs_elem.fmtValue(pt, sema),
+                            rhs_elem.fmtValueSema(pt, sema),
                             i,
                             scalar_ty.fmt(pt),
                         });
@@ -14075,7 +14075,7 @@ fn zirShl(
                 }
             } else if (rhs_val.compareHetero(.gte, bit_value, pt)) {
                 return sema.fail(block, rhs_src, "shift amount '{}' is too large for operand type '{}'", .{
-                    rhs_val.fmtValue(pt, sema),
+                    rhs_val.fmtValueSema(pt, sema),
                     scalar_ty.fmt(pt),
                 });
             }
@@ -14086,14 +14086,14 @@ fn zirShl(
                 const rhs_elem = try rhs_val.elemValue(pt, i);
                 if (rhs_elem.compareHetero(.lt, try pt.intValue(scalar_rhs_ty, 0), pt)) {
                     return sema.fail(block, rhs_src, "shift by negative amount '{}' at index '{d}'", .{
-                        rhs_elem.fmtValue(pt, sema),
+                        rhs_elem.fmtValueSema(pt, sema),
                         i,
                     });
                 }
             }
         } else if (rhs_val.compareHetero(.lt, try pt.intValue(rhs_ty, 0), pt)) {
             return sema.fail(block, rhs_src, "shift by negative amount '{}'", .{
-                rhs_val.fmtValue(pt, sema),
+                rhs_val.fmtValueSema(pt, sema),
             });
         }
     }
@@ -14233,7 +14233,7 @@ fn zirShr(
                     const rhs_elem = try rhs_val.elemValue(pt, i);
                     if (rhs_elem.compareHetero(.gte, bit_value, pt)) {
                         return sema.fail(block, rhs_src, "shift amount '{}' at index '{d}' is too large for operand type '{}'", .{
-                            rhs_elem.fmtValue(pt, sema),
+                            rhs_elem.fmtValueSema(pt, sema),
                             i,
                             scalar_ty.fmt(pt),
                         });
@@ -14241,7 +14241,7 @@ fn zirShr(
                 }
             } else if (rhs_val.compareHetero(.gte, bit_value, pt)) {
                 return sema.fail(block, rhs_src, "shift amount '{}' is too large for operand type '{}'", .{
-                    rhs_val.fmtValue(pt, sema),
+                    rhs_val.fmtValueSema(pt, sema),
                     scalar_ty.fmt(pt),
                 });
             }
@@ -14252,14 +14252,14 @@ fn zirShr(
                 const rhs_elem = try rhs_val.elemValue(pt, i);
                 if (rhs_elem.compareHetero(.lt, try pt.intValue(rhs_ty.childType(mod), 0), pt)) {
                     return sema.fail(block, rhs_src, "shift by negative amount '{}' at index '{d}'", .{
-                        rhs_elem.fmtValue(pt, sema),
+                        rhs_elem.fmtValueSema(pt, sema),
                         i,
                     });
                 }
             }
         } else if (rhs_val.compareHetero(.lt, try pt.intValue(rhs_ty, 0), pt)) {
             return sema.fail(block, rhs_src, "shift by negative amount '{}'", .{
-                rhs_val.fmtValue(pt, sema),
+                rhs_val.fmtValueSema(pt, sema),
             });
         }
         if (maybe_lhs_val) |lhs_val| {
@@ -15190,7 +15190,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
                 block,
                 src,
                 "ambiguous coercion of division operands '{}' and '{}'; non-zero remainder '{}'",
-                .{ lhs_ty.fmt(pt), rhs_ty.fmt(pt), rem.fmtValue(pt, sema) },
+                .{ lhs_ty.fmt(pt), rhs_ty.fmt(pt), rem.fmtValueSema(pt, sema) },
             );
         }
     }
@@ -21359,7 +21359,7 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
         const field_index = enum_ty.enumTagFieldIndex(val, mod) orelse {
             const msg = msg: {
                 const msg = try sema.errMsg(src, "no field with value '{}' in enum '{}'", .{
-                    val.fmtValue(pt, sema), mod.declPtr(enum_decl_index).name.fmt(ip),
+                    val.fmtValueSema(pt, sema), mod.declPtr(enum_decl_index).name.fmt(ip),
                 });
                 errdefer msg.destroy(sema.gpa);
                 try sema.errNote(enum_ty.srcLoc(mod), msg, "declared here", .{});
@@ -22005,7 +22005,7 @@ fn reifyEnum(
             // TODO: better source location
             return sema.fail(block, src, "field '{}' with enumeration value '{}' is too large for backing int type '{}'", .{
                 field_name.fmt(ip),
-                field_value_val.fmtValue(pt, sema),
+                field_value_val.fmtValueSema(pt, sema),
                 tag_ty.fmt(pt),
             });
         }
@@ -22021,7 +22021,7 @@ fn reifyEnum(
                     break :msg msg;
                 },
                 .value => msg: {
-                    const msg = try sema.errMsg(src, "enum tag value {} already taken", .{field_value_val.fmtValue(pt, sema)});
+                    const msg = try sema.errMsg(src, "enum tag value {} already taken", .{field_value_val.fmtValueSema(pt, sema)});
                     errdefer msg.destroy(gpa);
                     _ = conflict.prev_field_idx; // TODO: this note is incorrect
                     try sema.errNote(src, msg, "other enum tag value here", .{});
@@ -23193,12 +23193,12 @@ fn ptrCastFull(
             return sema.failWithOwnedErrorMsg(block, msg: {
                 const msg = if (src_info.sentinel == .none) blk: {
                     break :blk try sema.errMsg(src, "destination pointer requires '{}' sentinel", .{
-                        Value.fromInterned(dest_info.sentinel).fmtValue(pt, sema),
+                        Value.fromInterned(dest_info.sentinel).fmtValueSema(pt, sema),
                     });
                 } else blk: {
                     break :blk try sema.errMsg(src, "pointer sentinel '{}' cannot coerce into pointer sentinel '{}'", .{
-                        Value.fromInterned(src_info.sentinel).fmtValue(pt, sema),
-                        Value.fromInterned(dest_info.sentinel).fmtValue(pt, sema),
+                        Value.fromInterned(src_info.sentinel).fmtValueSema(pt, sema),
+                        Value.fromInterned(dest_info.sentinel).fmtValueSema(pt, sema),
                     });
                 };
                 errdefer msg.destroy(sema.gpa);
@@ -25691,10 +25691,10 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
                         const msg = try sema.errMsg(src, "non-matching @memcpy lengths", .{});
                         errdefer msg.destroy(sema.gpa);
                         try sema.errNote(dest_src, msg, "length {} here", .{
-                            dest_len_val.fmtValue(pt, sema),
+                            dest_len_val.fmtValueSema(pt, sema),
                         });
                         try sema.errNote(src_src, msg, "length {} here", .{
-                            src_len_val.fmtValue(pt, sema),
+                            src_len_val.fmtValueSema(pt, sema),
                         });
                         break :msg msg;
                     };
@@ -29560,7 +29560,7 @@ fn coerceExtra(
                     // comptime-known integer to other number
                     if (!(try sema.intFitsInType(val, dest_ty, null))) {
                         if (!opts.report_err) return error.NotCoercible;
-                        return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(pt), val.fmtValue(pt, sema) });
+                        return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(pt), val.fmtValueSema(pt, sema) });
                     }
                     return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
                         .undef => try pt.undefRef(dest_ty),
@@ -29605,7 +29605,7 @@ fn coerceExtra(
                             block,
                             inst_src,
                             "type '{}' cannot represent float value '{}'",
-                            .{ dest_ty.fmt(pt), val.fmtValue(pt, sema) },
+                            .{ dest_ty.fmt(pt), val.fmtValueSema(pt, sema) },
                         );
                     }
                     return Air.internedToRef(result_val.toIntern());
@@ -30003,11 +30003,11 @@ const InMemoryCoercionResult = union(enum) {
             .array_sentinel => |sentinel| {
                 if (sentinel.actual.toIntern() != .unreachable_value) {
                     try sema.errNote(src, msg, "array sentinel '{}' cannot cast into array sentinel '{}'", .{
-                        sentinel.actual.fmtValue(pt, sema), sentinel.wanted.fmtValue(pt, sema),
+                        sentinel.actual.fmtValueSema(pt, sema), sentinel.wanted.fmtValueSema(pt, sema),
                     });
                 } else {
                     try sema.errNote(src, msg, "destination array requires '{}' sentinel", .{
-                        sentinel.wanted.fmtValue(pt, sema),
+                        sentinel.wanted.fmtValueSema(pt, sema),
                     });
                 }
                 break;
@@ -30129,11 +30129,11 @@ const InMemoryCoercionResult = union(enum) {
             .ptr_sentinel => |sentinel| {
                 if (sentinel.actual.toIntern() != .unreachable_value) {
                     try sema.errNote(src, msg, "pointer sentinel '{}' cannot cast into pointer sentinel '{}'", .{
-                        sentinel.actual.fmtValue(pt, sema), sentinel.wanted.fmtValue(pt, sema),
+                        sentinel.actual.fmtValueSema(pt, sema), sentinel.wanted.fmtValueSema(pt, sema),
                     });
                 } else {
                     try sema.errNote(src, msg, "destination pointer requires '{}' sentinel", .{
-                        sentinel.wanted.fmtValue(pt, sema),
+                        sentinel.wanted.fmtValueSema(pt, sema),
                     });
                 }
                 break;
@@ -31379,7 +31379,7 @@ fn coerceEnumToUnion(
     if (try sema.resolveDefinedValue(block, inst_src, enum_tag)) |val| {
         const field_index = union_ty.unionTagFieldIndex(val, pt.zcu) orelse {
             return sema.fail(block, inst_src, "union '{}' has no tag with value '{}'", .{
-                union_ty.fmt(pt), val.fmtValue(pt, sema),
+                union_ty.fmt(pt), val.fmtValueSema(pt, sema),
             });
         };
 
@@ -32611,8 +32611,8 @@ fn analyzeSlice(
                                     msg,
                                     "expected '{}', found '{}'",
                                     .{
-                                        Value.zero_comptime_int.fmtValue(pt, sema),
-                                        start_value.fmtValue(pt, sema),
+                                        Value.zero_comptime_int.fmtValueSema(pt, sema),
+                                        start_value.fmtValueSema(pt, sema),
                                     },
                                 );
                                 break :msg msg;
@@ -32627,8 +32627,8 @@ fn analyzeSlice(
                                     msg,
                                     "expected '{}', found '{}'",
                                     .{
-                                        Value.one_comptime_int.fmtValue(pt, sema),
-                                        end_value.fmtValue(pt, sema),
+                                        Value.one_comptime_int.fmtValueSema(pt, sema),
+                                        end_value.fmtValueSema(pt, sema),
                                     },
                                 );
                                 break :msg msg;
@@ -32641,7 +32641,7 @@ fn analyzeSlice(
                                 block,
                                 end_src,
                                 "end index {} out of bounds for slice of single-item pointer",
-                                .{end_value.fmtValue(pt, sema)},
+                                .{end_value.fmtValueSema(pt, sema)},
                             );
                         }
                     }
@@ -32736,8 +32736,8 @@ fn analyzeSlice(
                             end_src,
                             "end index {} out of bounds for array of length {}{s}",
                             .{
-                                end_val.fmtValue(pt, sema),
-                                len_val.fmtValue(pt, sema),
+                                end_val.fmtValueSema(pt, sema),
+                                len_val.fmtValueSema(pt, sema),
                                 sentinel_label,
                             },
                         );
@@ -32781,7 +32781,7 @@ fn analyzeSlice(
                                 end_src,
                                 "end index {} out of bounds for slice of length {d}{s}",
                                 .{
-                                    end_val.fmtValue(pt, sema),
+                                    end_val.fmtValueSema(pt, sema),
                                     try slice_val.sliceLen(pt),
                                     sentinel_label,
                                 },
@@ -32841,8 +32841,8 @@ fn analyzeSlice(
                     start_src,
                     "start index {} is larger than end index {}",
                     .{
-                        start_val.fmtValue(pt, sema),
-                        end_val.fmtValue(pt, sema),
+                        start_val.fmtValueSema(pt, sema),
+                        end_val.fmtValueSema(pt, sema),
                     },
                 );
             }
@@ -32879,8 +32879,8 @@ fn analyzeSlice(
                         const msg = try sema.errMsg(src, "value in memory does not match slice sentinel", .{});
                         errdefer msg.destroy(sema.gpa);
                         try sema.errNote(src, msg, "expected '{}', found '{}'", .{
-                            expected_sentinel.fmtValue(pt, sema),
-                            actual_sentinel.fmtValue(pt, sema),
+                            expected_sentinel.fmtValueSema(pt, sema),
+                            actual_sentinel.fmtValueSema(pt, sema),
                         });
 
                         break :msg msg;
@@ -36623,7 +36623,7 @@ fn semaUnionFields(pt: Zcu.PerThread, arena: Allocator, union_type: InternPool.L
                     .offset = .{ .container_field_value = @intCast(gop.index) },
                 };
                 const msg = msg: {
-                    const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{enum_tag_val.fmtValue(pt, &sema)});
+                    const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{enum_tag_val.fmtValueSema(pt, &sema)});
                     errdefer msg.destroy(gpa);
                     try sema.errNote(other_value_src, msg, "other occurrence here", .{});
                     break :msg msg;
@@ -37855,7 +37855,7 @@ fn intFromFloatScalar(
         block,
         src,
         "fractional component prevents float value '{}' from coercion to type '{}'",
-        .{ val.fmtValue(pt, sema), int_ty.fmt(pt) },
+        .{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
     );
 
     const float = val.toFloat(f128, pt);
@@ -37877,7 +37877,7 @@ fn intFromFloatScalar(
 
     if (!(try sema.intFitsInType(cti_result, int_ty, null))) {
         return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{
-            val.fmtValue(pt, sema), int_ty.fmt(pt),
+            val.fmtValueSema(pt, sema), int_ty.fmt(pt),
         });
     }
     return pt.getCoerced(cti_result, int_ty);
src/Type.zig
@@ -194,8 +194,8 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error
 
             if (info.sentinel != .none) switch (info.flags.size) {
                 .One, .C => unreachable,
-                .Many => try writer.print("[*:{}]", .{Value.fromInterned(info.sentinel).fmtValue(pt, null)}),
-                .Slice => try writer.print("[:{}]", .{Value.fromInterned(info.sentinel).fmtValue(pt, null)}),
+                .Many => try writer.print("[*:{}]", .{Value.fromInterned(info.sentinel).fmtValue(pt)}),
+                .Slice => try writer.print("[:{}]", .{Value.fromInterned(info.sentinel).fmtValue(pt)}),
             } else switch (info.flags.size) {
                 .One => try writer.writeAll("*"),
                 .Many => try writer.writeAll("[*]"),
@@ -241,7 +241,7 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error
             } else {
                 try writer.print("[{d}:{}]", .{
                     array_type.len,
-                    Value.fromInterned(array_type.sentinel).fmtValue(pt, null),
+                    Value.fromInterned(array_type.sentinel).fmtValue(pt),
                 });
                 try print(Type.fromInterned(array_type.child), writer, pt);
             }
@@ -359,7 +359,7 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error
                 try print(Type.fromInterned(field_ty), writer, pt);
 
                 if (val != .none) {
-                    try writer.print(" = {}", .{Value.fromInterned(val).fmtValue(pt, null)});
+                    try writer.print(" = {}", .{Value.fromInterned(val).fmtValue(pt)});
                 }
             }
             try writer.writeAll("}");
src/Value.zig
@@ -40,16 +40,25 @@ pub fn fmtDebug(val: Value) std.fmt.Formatter(dump) {
     return .{ .data = val };
 }
 
-pub fn fmtValue(val: Value, pt: Zcu.PerThread, opt_sema: ?*Sema) std.fmt.Formatter(print_value.format) {
+pub fn fmtValue(val: Value, pt: Zcu.PerThread) std.fmt.Formatter(print_value.format) {
     return .{ .data = .{
         .val = val,
         .pt = pt,
-        .opt_sema = opt_sema,
+        .opt_sema = null,
         .depth = 3,
     } };
 }
 
-pub fn fmtValueFull(ctx: print_value.FormatContext) std.fmt.Formatter(print_value.format) {
+pub fn fmtValueSema(val: Value, pt: Zcu.PerThread, sema: *Sema) std.fmt.Formatter(print_value.formatSema) {
+    return .{ .data = .{
+        .val = val,
+        .pt = pt,
+        .opt_sema = sema,
+        .depth = 3,
+    } };
+}
+
+pub fn fmtValueSemaFull(ctx: print_value.FormatContext) std.fmt.Formatter(print_value.formatSema) {
     return .{ .data = ctx };
 }
 
@@ -4071,7 +4080,7 @@ pub const PointerDeriveStep = union(enum) {
 };
 
 pub fn pointerDerivation(ptr_val: Value, arena: Allocator, pt: Zcu.PerThread) Allocator.Error!PointerDeriveStep {
-    return ptr_val.pointerDerivationAdvanced(arena, pt, null) catch |err| switch (err) {
+    return ptr_val.pointerDerivationAdvanced(arena, pt, false, {}) catch |err| switch (err) {
         error.OutOfMemory => |e| return e,
         error.AnalysisFail => unreachable,
     };
@@ -4081,7 +4090,7 @@ pub fn pointerDerivation(ptr_val: Value, arena: Allocator, pt: Zcu.PerThread) Al
 /// only field and element pointers with no casts. This can be used by codegen backends
 /// which prefer field/elem accesses when lowering constant pointer values.
 /// It is also used by the Value printing logic for pointers.
-pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, pt: Zcu.PerThread, opt_sema: ?*Sema) !PointerDeriveStep {
+pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, pt: Zcu.PerThread, comptime have_sema: bool, sema: if (have_sema) *Sema else void) !PointerDeriveStep {
     const zcu = pt.zcu;
     const ptr = zcu.intern_pool.indexToKey(ptr_val.toIntern()).ptr;
     const base_derive: PointerDeriveStep = switch (ptr.base_addr) {
@@ -4104,8 +4113,9 @@ pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, pt: Zcu.PerTh
             } };
         },
         .comptime_alloc => |idx| base: {
-            const alloc = opt_sema.?.getComptimeAlloc(idx);
-            const val = try alloc.val.intern(pt, opt_sema.?.arena);
+            if (!have_sema) unreachable;
+            const alloc = sema.getComptimeAlloc(idx);
+            const val = try alloc.val.intern(pt, sema.arena);
             const ty = val.typeOf(zcu);
             break :base .{ .comptime_alloc_ptr = .{
                 .val = val,
@@ -4122,7 +4132,7 @@ pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, pt: Zcu.PerTh
             const base_ptr = Value.fromInterned(eu_ptr);
             const base_ptr_ty = base_ptr.typeOf(zcu);
             const parent_step = try arena.create(PointerDeriveStep);
-            parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(eu_ptr), arena, pt, opt_sema);
+            parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(eu_ptr), arena, pt, have_sema, sema);
             break :base .{ .eu_payload_ptr = .{
                 .parent = parent_step,
                 .result_ptr_ty = try pt.adjustPtrTypeChild(base_ptr_ty, base_ptr_ty.childType(zcu).errorUnionPayload(zcu)),
@@ -4132,7 +4142,7 @@ pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, pt: Zcu.PerTh
             const base_ptr = Value.fromInterned(opt_ptr);
             const base_ptr_ty = base_ptr.typeOf(zcu);
             const parent_step = try arena.create(PointerDeriveStep);
-            parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(opt_ptr), arena, pt, opt_sema);
+            parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(opt_ptr), arena, pt, have_sema, sema);
             break :base .{ .opt_payload_ptr = .{
                 .parent = parent_step,
                 .result_ptr_ty = try pt.adjustPtrTypeChild(base_ptr_ty, base_ptr_ty.childType(zcu).optionalChild(zcu)),
@@ -4143,8 +4153,8 @@ pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, pt: Zcu.PerTh
             const base_ptr_ty = base_ptr.typeOf(zcu);
             const agg_ty = base_ptr_ty.childType(zcu);
             const field_ty, const field_align = switch (agg_ty.zigTypeTag(zcu)) {
-                .Struct => .{ agg_ty.structFieldType(@intCast(field.index), zcu), try agg_ty.structFieldAlignAdvanced(@intCast(field.index), pt, .sema) },
-                .Union => .{ agg_ty.unionFieldTypeByIndex(@intCast(field.index), zcu), try agg_ty.structFieldAlignAdvanced(@intCast(field.index), pt, .sema) },
+                .Struct => .{ agg_ty.structFieldType(@intCast(field.index), zcu), try agg_ty.structFieldAlignAdvanced(@intCast(field.index), pt, if (have_sema) .sema else .normal) },
+                .Union => .{ agg_ty.unionFieldTypeByIndex(@intCast(field.index), zcu), try agg_ty.structFieldAlignAdvanced(@intCast(field.index), pt, if (have_sema) .sema else .normal) },
                 .Pointer => .{ switch (field.index) {
                     Value.slice_ptr_index => agg_ty.slicePtrFieldType(zcu),
                     Value.slice_len_index => Type.usize,
@@ -4167,7 +4177,7 @@ pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, pt: Zcu.PerTh
                 },
             });
             const parent_step = try arena.create(PointerDeriveStep);
-            parent_step.* = try pointerDerivationAdvanced(base_ptr, arena, pt, opt_sema);
+            parent_step.* = try pointerDerivationAdvanced(base_ptr, arena, pt, have_sema, sema);
             break :base .{ .field_ptr = .{
                 .parent = parent_step,
                 .field_idx = @intCast(field.index),
@@ -4176,7 +4186,7 @@ pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, pt: Zcu.PerTh
         },
         .arr_elem => |arr_elem| base: {
             const parent_step = try arena.create(PointerDeriveStep);
-            parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(arr_elem.base), arena, pt, opt_sema);
+            parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(arr_elem.base), arena, pt, have_sema, sema);
             const parent_ptr_info = (try parent_step.ptrType(pt)).ptrInfo(zcu);
             const result_ptr_ty = try pt.ptrType(.{
                 .child = parent_ptr_info.child,