Commit a31fe0ff12

Veikka Tuominen <git@vexu.eu>
2022-03-21 13:48:47
stage2: add way to print values with types
1 parent 3d8d6c0
src/arch/aarch64/CodeGen.zig
@@ -3873,7 +3873,7 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl: *Module.Decl) InnerError!MCVa
 }
 
 fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue {
-    log.debug("lowerUnnamedConst: ty = {}, val = {}", .{ tv.ty, tv.val });
+    log.debug("lowerUnnamedConst: ty = {}, val = {}", .{ tv.ty, tv.val.fmtDebug() });
     const local_sym_index = self.bin_file.lowerUnnamedConst(tv, self.mod_fn.owner_decl) catch |err| {
         return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)});
     };
src/arch/x86_64/CodeGen.zig
@@ -5735,7 +5735,7 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl: *Module.Decl) InnerError!MCVa
 }
 
 fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue {
-    log.debug("lowerUnnamedConst: ty = {}, val = {}", .{ tv.ty, tv.val });
+    log.debug("lowerUnnamedConst: ty = {}, val = {}", .{ tv.ty, tv.val.fmtDebug() });
     const local_sym_index = self.bin_file.lowerUnnamedConst(tv, self.mod_fn.owner_decl) catch |err| {
         return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)});
     };
src/codegen/llvm.zig
@@ -1676,7 +1676,7 @@ pub const DeclGen = struct {
         const decl = dg.decl;
         assert(decl.has_tv);
 
-        log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });
+        log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val.fmtDebug() });
 
         if (decl.val.castTag(.function)) |func_payload| {
             _ = func_payload;
src/codegen.zig
@@ -165,7 +165,7 @@ pub fn generateSymbol(
     const target = bin_file.options.target;
     const endian = target.cpu.arch.endian();
 
-    log.debug("generateSymbol: ty = {}, val = {}", .{ typed_value.ty, typed_value.val });
+    log.debug("generateSymbol: ty = {}, val = {}", .{ typed_value.ty, typed_value.val.fmtDebug() });
 
     if (typed_value.val.isUndefDeep()) {
         const abi_size = try math.cast(usize, typed_value.ty.abiSize(target));
src/print_air.zig
@@ -491,7 +491,7 @@ const Writer = struct {
     fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
         const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
         const val = w.air.values[ty_pl.payload];
-        try s.print("{}, {}", .{ w.air.getRefType(ty_pl.ty), val });
+        try s.print("{}, {}", .{ w.air.getRefType(ty_pl.ty), val.fmtDebug() });
     }
 
     fn writeAssembly(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
src/Sema.zig
@@ -1867,7 +1867,7 @@ fn createTypeName(
                     const arg_val = sema.resolveConstMaybeUndefVal(block, .unneeded, arg) catch unreachable;
 
                     if (arg_i != 0) try buf.appendSlice(",");
-                    try buf.writer().print("{}", .{arg_val});
+                    try buf.writer().print("{}", .{arg_val.fmtValue(sema.typeOf(arg))});
 
                     arg_i += 1;
                     continue;
@@ -3673,7 +3673,7 @@ fn zirCompileLog(
         const arg = sema.resolveInst(arg_ref);
         const arg_ty = sema.typeOf(arg);
         if (try sema.resolveMaybeUndefVal(block, src, arg)) |val| {
-            try writer.print("@as({}, {})", .{ arg_ty, val });
+            try writer.print("@as({}, {})", .{ arg_ty, val.fmtValue(arg_ty) });
         } else {
             try writer.print("@as({}, [runtime value])", .{arg_ty});
         }
@@ -5670,7 +5670,7 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
                     block,
                     src,
                     "enum '{}' has no tag with value {}",
-                    .{ dest_ty, int_val },
+                    .{ dest_ty, int_val.fmtValue(sema.typeOf(operand)) },
                 );
                 errdefer msg.destroy(sema.gpa);
                 try sema.mod.errNoteNonLazy(
@@ -7900,7 +7900,7 @@ fn validateSwitchItemEnum(
                 block,
                 src,
                 "enum '{}' has no tag with value '{}'",
-                .{ item_tv.ty, item_tv.val },
+                .{ item_tv.ty, item_tv.val.fmtValue(item_tv.ty) },
             );
             errdefer msg.destroy(sema.gpa);
             try sema.mod.errNoteNonLazy(
@@ -17507,7 +17507,7 @@ fn coerce(
                 const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :float;
 
                 if (val.floatHasFraction()) {
-                    return sema.fail(block, inst_src, "fractional component prevents float value {} from coercion to type '{}'", .{ val, dest_ty });
+                    return sema.fail(block, inst_src, "fractional component prevents float value {} from coercion to type '{}'", .{ val.fmtValue(inst_ty), dest_ty });
                 }
                 const result_val = val.floatToInt(sema.arena, dest_ty, target) catch |err| switch (err) {
                     error.FloatCannotFit => {
@@ -17521,7 +17521,7 @@ fn coerce(
                 if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| {
                     // comptime known integer to other number
                     if (!val.intFitsInType(dest_ty, target)) {
-                        return sema.fail(block, inst_src, "type {} cannot represent integer value {}", .{ dest_ty, val });
+                        return sema.fail(block, inst_src, "type {} cannot represent integer value {}", .{ dest_ty, val.fmtValue(inst_ty) });
                     }
                     return try sema.addConstant(dest_ty, val);
                 }
@@ -17556,7 +17556,7 @@ fn coerce(
                             block,
                             inst_src,
                             "type {} cannot represent float value {}",
-                            .{ dest_ty, val },
+                            .{ dest_ty, val.fmtValue(inst_ty) },
                         );
                     }
                     return try sema.addConstant(dest_ty, result_val);
@@ -18850,7 +18850,7 @@ fn coerceEnumToUnion(
         const field_index = union_obj.tag_ty.enumTagFieldIndex(val) orelse {
             const msg = msg: {
                 const msg = try sema.errMsg(block, inst_src, "union {} has no tag with value {}", .{
-                    union_ty, val,
+                    union_ty, val.fmtValue(tag_ty),
                 });
                 errdefer msg.destroy(sema.gpa);
                 try sema.addDeclaredHereNote(msg, union_ty);
src/type.zig
@@ -1633,7 +1633,7 @@ pub const Type = extern union {
                 },
                 .array_sentinel => {
                     const payload = ty.castTag(.array_sentinel).?.data;
-                    try writer.print("[{d}:{}]", .{ payload.len, payload.sentinel });
+                    try writer.print("[{d}:{}]", .{ payload.len, payload.sentinel.fmtValue(payload.elem_type) });
                     ty = payload.elem_type;
                     continue;
                 },
@@ -1648,8 +1648,7 @@ pub const Type = extern union {
                         }
                         try field_ty.format("", .{}, writer);
                         if (val.tag() != .unreachable_value) {
-                            try writer.writeAll(" = ");
-                            try val.format("", .{}, writer);
+                            try writer.print(" = {}", .{val.fmtValue(field_ty)});
                         }
                     }
                     try writer.writeAll("}");
@@ -1668,8 +1667,7 @@ pub const Type = extern union {
                         try writer.writeAll(": ");
                         try field_ty.format("", .{}, writer);
                         if (val.tag() != .unreachable_value) {
-                            try writer.writeAll(" = ");
-                            try val.format("", .{}, writer);
+                            try writer.print(" = {}", .{val.fmtValue(field_ty)});
                         }
                     }
                     try writer.writeAll("}");
@@ -1754,8 +1752,8 @@ pub const Type = extern union {
                     const payload = ty.castTag(.pointer).?.data;
                     if (payload.sentinel) |some| switch (payload.size) {
                         .One, .C => unreachable,
-                        .Many => try writer.print("[*:{}]", .{some}),
-                        .Slice => try writer.print("[:{}]", .{some}),
+                        .Many => try writer.print("[*:{}]", .{some.fmtValue(payload.pointee_type)}),
+                        .Slice => try writer.print("[:{}]", .{some.fmtValue(payload.pointee_type)}),
                     } else switch (payload.size) {
                         .One => try writer.writeAll("*"),
                         .Many => try writer.writeAll("[*]"),
src/TypedValue.zig
@@ -42,3 +42,284 @@ pub fn hash(tv: TypedValue, hasher: *std.hash.Wyhash) void {
 pub fn enumToInt(tv: TypedValue, buffer: *Value.Payload.U64) Value {
     return tv.val.enumToInt(tv.ty, buffer);
 }
+
+const max_aggregate_items = 100;
+
+pub fn format(
+    tv: TypedValue,
+    comptime fmt: []const u8,
+    options: std.fmt.FormatOptions,
+    writer: anytype,
+) !void {
+    comptime std.debug.assert(fmt.len == 0);
+    return tv.print(options, writer, 3);
+}
+
+pub fn print(
+    tv: TypedValue,
+    options: std.fmt.FormatOptions,
+    writer: anytype,
+    level: u8,
+) @TypeOf(writer).Error!void {
+    var val = tv.val;
+    var ty = tv.ty;
+    while (true) switch (val.tag()) {
+        .u1_type => return writer.writeAll("u1"),
+        .u8_type => return writer.writeAll("u8"),
+        .i8_type => return writer.writeAll("i8"),
+        .u16_type => return writer.writeAll("u16"),
+        .i16_type => return writer.writeAll("i16"),
+        .u32_type => return writer.writeAll("u32"),
+        .i32_type => return writer.writeAll("i32"),
+        .u64_type => return writer.writeAll("u64"),
+        .i64_type => return writer.writeAll("i64"),
+        .u128_type => return writer.writeAll("u128"),
+        .i128_type => return writer.writeAll("i128"),
+        .isize_type => return writer.writeAll("isize"),
+        .usize_type => return writer.writeAll("usize"),
+        .c_short_type => return writer.writeAll("c_short"),
+        .c_ushort_type => return writer.writeAll("c_ushort"),
+        .c_int_type => return writer.writeAll("c_int"),
+        .c_uint_type => return writer.writeAll("c_uint"),
+        .c_long_type => return writer.writeAll("c_long"),
+        .c_ulong_type => return writer.writeAll("c_ulong"),
+        .c_longlong_type => return writer.writeAll("c_longlong"),
+        .c_ulonglong_type => return writer.writeAll("c_ulonglong"),
+        .c_longdouble_type => return writer.writeAll("c_longdouble"),
+        .f16_type => return writer.writeAll("f16"),
+        .f32_type => return writer.writeAll("f32"),
+        .f64_type => return writer.writeAll("f64"),
+        .f80_type => return writer.writeAll("f80"),
+        .f128_type => return writer.writeAll("f128"),
+        .anyopaque_type => return writer.writeAll("anyopaque"),
+        .bool_type => return writer.writeAll("bool"),
+        .void_type => return writer.writeAll("void"),
+        .type_type => return writer.writeAll("type"),
+        .anyerror_type => return writer.writeAll("anyerror"),
+        .comptime_int_type => return writer.writeAll("comptime_int"),
+        .comptime_float_type => return writer.writeAll("comptime_float"),
+        .noreturn_type => return writer.writeAll("noreturn"),
+        .null_type => return writer.writeAll("@Type(.Null)"),
+        .undefined_type => return writer.writeAll("@Type(.Undefined)"),
+        .fn_noreturn_no_args_type => return writer.writeAll("fn() noreturn"),
+        .fn_void_no_args_type => return writer.writeAll("fn() void"),
+        .fn_naked_noreturn_no_args_type => return writer.writeAll("fn() callconv(.Naked) noreturn"),
+        .fn_ccc_void_no_args_type => return writer.writeAll("fn() callconv(.C) void"),
+        .single_const_pointer_to_comptime_int_type => return writer.writeAll("*const comptime_int"),
+        .anyframe_type => return writer.writeAll("anyframe"),
+        .const_slice_u8_type => return writer.writeAll("[]const u8"),
+        .const_slice_u8_sentinel_0_type => return writer.writeAll("[:0]const u8"),
+        .anyerror_void_error_union_type => return writer.writeAll("anyerror!void"),
+
+        .enum_literal_type => return writer.writeAll("@Type(.EnumLiteral)"),
+        .manyptr_u8_type => return writer.writeAll("[*]u8"),
+        .manyptr_const_u8_type => return writer.writeAll("[*]const u8"),
+        .manyptr_const_u8_sentinel_0_type => return writer.writeAll("[*:0]const u8"),
+        .atomic_order_type => return writer.writeAll("std.builtin.AtomicOrder"),
+        .atomic_rmw_op_type => return writer.writeAll("std.builtin.AtomicRmwOp"),
+        .calling_convention_type => return writer.writeAll("std.builtin.CallingConvention"),
+        .address_space_type => return writer.writeAll("std.builtin.AddressSpace"),
+        .float_mode_type => return writer.writeAll("std.builtin.FloatMode"),
+        .reduce_op_type => return writer.writeAll("std.builtin.ReduceOp"),
+        .call_options_type => return writer.writeAll("std.builtin.CallOptions"),
+        .prefetch_options_type => return writer.writeAll("std.builtin.PrefetchOptions"),
+        .export_options_type => return writer.writeAll("std.builtin.ExportOptions"),
+        .extern_options_type => return writer.writeAll("std.builtin.ExternOptions"),
+        .type_info_type => return writer.writeAll("std.builtin.Type"),
+
+        .empty_struct_value => return writer.writeAll(".{}"),
+        .aggregate => {
+            if (level == 0) {
+                return writer.writeAll(".{ ... }");
+            }
+            const vals = val.castTag(.aggregate).?.data;
+            if (ty.zigTypeTag() == .Struct) {
+                try writer.writeAll(".{ ");
+                const struct_fields = ty.structFields();
+                const max_len = std.math.min(struct_fields.count(), max_aggregate_items);
+
+                const field_names = struct_fields.keys();
+                const fields = struct_fields.values();
+
+                var i: u32 = 0;
+                while (i < max_len) : (i += 1) {
+                    if (i != 0) try writer.writeAll(", ");
+                    try writer.print(".{s} = ", .{field_names[i]});
+                    try print(.{
+                        .ty = fields[i].ty,
+                        .val = vals[i],
+                    }, options, writer, level - 1);
+                }
+                return writer.writeAll(" }");
+            } else {
+                try writer.writeAll(".{ ");
+                const elem_ty = ty.elemType2();
+                const max_len = std.math.min(ty.arrayLen(), max_aggregate_items);
+
+                var i: u32 = 0;
+                while (i < max_len) : (i += 1) {
+                    if (i != 0) try writer.writeAll(", ");
+                    try print(.{
+                        .ty = elem_ty,
+                        .val = vals[i],
+                    }, options, writer, level - 1);
+                }
+                return writer.writeAll(" }");
+            }
+        },
+        .@"union" => {
+            if (level == 0) {
+                return writer.writeAll(".{ ... }");
+            }
+            const union_val = val.castTag(.@"union").?.data;
+            try writer.writeAll(".{ ");
+
+            try print(.{
+                .ty = ty.unionTagType().?,
+                .val = union_val.tag,
+            }, options, writer, level - 1);
+            try writer.writeAll(" = ");
+            try print(.{
+                .ty = ty.unionFieldType(union_val.tag),
+                .val = union_val.val,
+            }, options, writer, level - 1);
+
+            return writer.writeAll(" }");
+        },
+        .null_value => return writer.writeAll("null"),
+        .undef => return writer.writeAll("undefined"),
+        .zero => return writer.writeAll("0"),
+        .one => return writer.writeAll("1"),
+        .void_value => return writer.writeAll("{}"),
+        .unreachable_value => return writer.writeAll("unreachable"),
+        .the_only_possible_value => {
+            val = ty.onePossibleValue().?;
+        },
+        .bool_true => return writer.writeAll("true"),
+        .bool_false => return writer.writeAll("false"),
+        .ty => return val.castTag(.ty).?.data.format("", options, writer),
+        .int_type => {
+            const int_type = val.castTag(.int_type).?.data;
+            return writer.print("{s}{d}", .{
+                if (int_type.signed) "s" else "u",
+                int_type.bits,
+            });
+        },
+        .int_u64 => return std.fmt.formatIntValue(val.castTag(.int_u64).?.data, "", options, writer),
+        .int_i64 => return std.fmt.formatIntValue(val.castTag(.int_i64).?.data, "", options, writer),
+        .int_big_positive => return writer.print("{}", .{val.castTag(.int_big_positive).?.asBigInt()}),
+        .int_big_negative => return writer.print("{}", .{val.castTag(.int_big_negative).?.asBigInt()}),
+        .function => return writer.print("(function '{s}')", .{val.castTag(.function).?.data.owner_decl.name}),
+        .extern_fn => return writer.writeAll("(extern function)"),
+        .variable => return writer.writeAll("(variable)"),
+        .decl_ref_mut => {
+            const decl = val.castTag(.decl_ref_mut).?.data.decl;
+            if (level == 0) {
+                return writer.print("(decl ref mut '{s}')", .{decl.name});
+            }
+            return print(.{
+                .ty = decl.ty,
+                .val = decl.val,
+            }, options, writer, level - 1);
+        },
+        .decl_ref => {
+            const decl = val.castTag(.decl_ref).?.data;
+            if (level == 0) {
+                return writer.print("(decl ref '{s}')", .{decl.name});
+            }
+            return print(.{
+                .ty = decl.ty,
+                .val = decl.val,
+            }, options, writer, level - 1);
+        },
+        .elem_ptr => {
+            const elem_ptr = val.castTag(.elem_ptr).?.data;
+            try writer.writeAll("&");
+            try print(.{
+                .ty = elem_ptr.elem_ty,
+                .val = elem_ptr.array_ptr,
+            }, options, writer, level - 1);
+            return writer.print("[{}]", .{elem_ptr.index});
+        },
+        .field_ptr => {
+            const field_ptr = val.castTag(.field_ptr).?.data;
+            try writer.writeAll("&");
+            try print(.{
+                .ty = field_ptr.container_ty,
+                .val = field_ptr.container_ptr,
+            }, options, writer, level - 1);
+
+            if (field_ptr.container_ty.zigTypeTag() == .Struct) {
+                const field_name = field_ptr.container_ty.structFields().keys()[field_ptr.field_index];
+                return writer.print(".{s}", .{field_name});
+            } else if (field_ptr.container_ty.zigTypeTag() == .Union) {
+                const field_name = field_ptr.container_ty.unionFields().keys()[field_ptr.field_index];
+                return writer.print(".{s}", .{field_name});
+            } else unreachable;
+        },
+        .empty_array => return writer.writeAll(".{}"),
+        .enum_literal => return writer.print(".{}", .{std.zig.fmtId(val.castTag(.enum_literal).?.data)}),
+        .enum_field_index => {
+            return writer.print(".{s}", .{ty.enumFieldName(val.castTag(.enum_field_index).?.data)});
+        },
+        .bytes => return writer.print("\"{}\"", .{std.zig.fmtEscapes(val.castTag(.bytes).?.data)}),
+        .repeated => {
+            if (level == 0) {
+                return writer.writeAll(".{ ... }");
+            }
+            var i: u32 = 0;
+            try writer.writeAll(".{ ");
+            const elem_tv = TypedValue{
+                .ty = ty.elemType2(),
+                .val = val.castTag(.repeated).?.data,
+            };
+            while (i < max_aggregate_items) : (i += 1) {
+                if (i != 0) try writer.writeAll(", ");
+                try print(elem_tv, options, writer, level - 1);
+            }
+            return writer.writeAll(" }");
+        },
+        .empty_array_sentinel => {
+            if (level == 0) {
+                return writer.writeAll(".{ (sentinel) }");
+            }
+            try writer.writeAll(".{ ");
+            try print(.{
+                .ty = ty.elemType2(),
+                .val = ty.sentinel().?,
+            }, options, writer, level - 1);
+            return writer.writeAll(" }");
+        },
+        .slice => return writer.writeAll("(slice)"),
+        .float_16 => return writer.print("{}", .{val.castTag(.float_16).?.data}),
+        .float_32 => return writer.print("{}", .{val.castTag(.float_32).?.data}),
+        .float_64 => return writer.print("{}", .{val.castTag(.float_64).?.data}),
+        .float_80 => return writer.print("{}", .{val.castTag(.float_80).?.data}),
+        .float_128 => return writer.print("{}", .{val.castTag(.float_128).?.data}),
+        .@"error" => return writer.print("error.{s}", .{val.castTag(.@"error").?.data.name}),
+        .eu_payload => {
+            val = val.castTag(.eu_payload).?.data;
+        },
+        .opt_payload => {
+            val = val.castTag(.opt_payload).?.data;
+        },
+        .eu_payload_ptr => {
+            try writer.writeAll("&");
+            val = val.castTag(.eu_payload_ptr).?.data.container_ptr;
+        },
+        .opt_payload_ptr => {
+            try writer.writeAll("&");
+            val = val.castTag(.opt_payload_ptr).?.data.container_ptr;
+        },
+
+        // TODO these should not appear in this function
+        .inferred_alloc => return writer.writeAll("(inferred allocation value)"),
+        .inferred_alloc_comptime => return writer.writeAll("(inferred comptime allocation value)"),
+        .bound_fn => {
+            const bound_func = val.castTag(.bound_fn).?.data;
+            return writer.print("(bound_fn %{}(%{})", .{ bound_func.func_inst, bound_func.arg0_inst });
+        },
+        .generic_poison_type => return writer.writeAll("(generic poison type)"),
+        .generic_poison => return writer.writeAll("(generic poison)"),
+    };
+}
src/value.zig
@@ -600,9 +600,17 @@ pub const Value = extern union {
         return Value{ .ptr_otherwise = &new_payload.base };
     }
 
+    pub fn format(val: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = val;
+        _ = fmt;
+        _ = options;
+        _ = writer;
+        @compileError("do not use format values directly; use either fmtDebug or fmtValue");
+    }
+
     /// TODO this should become a debug dump() function. In order to print values in a meaningful way
     /// we also need access to the type.
-    pub fn format(
+    pub fn dump(
         start_val: Value,
         comptime fmt: []const u8,
         options: std.fmt.FormatOptions,
@@ -766,6 +774,16 @@ pub const Value = extern union {
         };
     }
 
+    pub fn fmtDebug(val: Value) std.fmt.Formatter(dump) {
+        return .{ .data = val };
+    }
+
+    const TypedValue = @import("TypedValue.zig");
+
+    pub fn fmtValue(val: Value, ty: Type) std.fmt.Formatter(TypedValue.format) {
+        return .{ .data = .{ .ty = ty, .val = val } };
+    }
+
     /// Asserts that the value is representable as an array of bytes.
     /// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
     pub fn toAllocatedBytes(val: Value, ty: Type, allocator: Allocator) ![]u8 {