mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
Alignment and fill options only apply to numbers. Rework the implementation to mainly branch on the format string rather than the type information. This is more straightforward to maintain and more straightforward for comptime evaluation. Enums support being printed as decimal, hexadecimal, octal, and binary. `formatInteger` is another possible format method that is unconditionally called when the value type is struct and one of the integer-printing format specifiers are used.
465 lines
18 KiB
Zig
465 lines
18 KiB
Zig
//! This type exists only for legacy purposes, and will be removed in the future.
|
|
//! It is a thin wrapper around a `Value` which also, redundantly, stores its `Type`.
|
|
|
|
const std = @import("std");
|
|
const Type = @import("Type.zig");
|
|
const Value = @import("Value.zig");
|
|
const Zcu = @import("Zcu.zig");
|
|
const Sema = @import("Sema.zig");
|
|
const InternPool = @import("InternPool.zig");
|
|
const Allocator = std.mem.Allocator;
|
|
const Target = std.Target;
|
|
|
|
const max_aggregate_items = 100;
|
|
const max_string_len = 256;
|
|
|
|
pub const FormatContext = struct {
|
|
val: Value,
|
|
pt: Zcu.PerThread,
|
|
opt_sema: ?*Sema,
|
|
depth: u8,
|
|
};
|
|
|
|
pub fn formatSema(ctx: FormatContext, writer: *std.io.Writer) std.io.Writer.Error!void {
|
|
const sema = ctx.opt_sema.?;
|
|
return print(ctx.val, writer, ctx.depth, ctx.pt, 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 `sema` more fully
|
|
else => |e| return e,
|
|
};
|
|
}
|
|
|
|
pub fn format(ctx: FormatContext, writer: *std.io.Writer) std.io.Writer.Error!void {
|
|
std.debug.assert(ctx.opt_sema == null);
|
|
return print(ctx.val, writer, ctx.depth, ctx.pt, null) 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,
|
|
};
|
|
}
|
|
|
|
pub fn print(
|
|
val: Value,
|
|
writer: *std.io.Writer,
|
|
level: u8,
|
|
pt: Zcu.PerThread,
|
|
opt_sema: ?*Sema,
|
|
) (std.io.Writer.Error || Zcu.CompileError)!void {
|
|
const zcu = pt.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
switch (ip.indexToKey(val.toIntern())) {
|
|
.int_type,
|
|
.ptr_type,
|
|
.array_type,
|
|
.vector_type,
|
|
.opt_type,
|
|
.anyframe_type,
|
|
.error_union_type,
|
|
.simple_type,
|
|
.struct_type,
|
|
.tuple_type,
|
|
.union_type,
|
|
.opaque_type,
|
|
.enum_type,
|
|
.func_type,
|
|
.error_set_type,
|
|
.inferred_error_set_type,
|
|
=> try Type.print(val.toType(), writer, pt),
|
|
.undef => try writer.writeAll("undefined"),
|
|
.simple_value => |simple_value| switch (simple_value) {
|
|
.void => try writer.writeAll("{}"),
|
|
.empty_tuple => try writer.writeAll(".{}"),
|
|
else => try writer.writeAll(@tagName(simple_value)),
|
|
},
|
|
.variable => try writer.writeAll("(variable)"),
|
|
.@"extern" => |e| try writer.print("(extern '{f}')", .{e.name.fmt(ip)}),
|
|
.func => |func| try writer.print("(function '{f}')", .{ip.getNav(func.owner_nav).name.fmt(ip)}),
|
|
.int => |int| switch (int.storage) {
|
|
inline .u64, .i64 => |x| try writer.print("{d}", .{x}),
|
|
.big_int => |x| try writer.print("{d}", .{x}),
|
|
.lazy_align => |ty| if (opt_sema != null) {
|
|
const a = try Type.fromInterned(ty).abiAlignmentSema(pt);
|
|
try writer.print("{d}", .{a.toByteUnits() orelse 0});
|
|
} else try writer.print("@alignOf({f})", .{Type.fromInterned(ty).fmt(pt)}),
|
|
.lazy_size => |ty| if (opt_sema != null) {
|
|
const s = try Type.fromInterned(ty).abiSizeSema(pt);
|
|
try writer.print("{d}", .{s});
|
|
} else try writer.print("@sizeOf({f})", .{Type.fromInterned(ty).fmt(pt)}),
|
|
},
|
|
.err => |err| try writer.print("error.{f}", .{
|
|
err.name.fmt(ip),
|
|
}),
|
|
.error_union => |error_union| switch (error_union.val) {
|
|
.err_name => |err_name| try writer.print("error.{f}", .{
|
|
err_name.fmt(ip),
|
|
}),
|
|
.payload => |payload| try print(Value.fromInterned(payload), writer, level, pt, opt_sema),
|
|
},
|
|
.enum_literal => |enum_literal| try writer.print(".{f}", .{
|
|
enum_literal.fmt(ip),
|
|
}),
|
|
.enum_tag => |enum_tag| {
|
|
const enum_type = ip.loadEnumType(val.typeOf(zcu).toIntern());
|
|
if (enum_type.tagValueIndex(ip, val.toIntern())) |tag_index| {
|
|
return writer.print(".{f}", .{enum_type.names.get(ip)[tag_index].fmt(ip)});
|
|
}
|
|
if (level == 0) {
|
|
return writer.writeAll("@enumFromInt(...)");
|
|
}
|
|
try writer.writeAll("@enumFromInt(");
|
|
try print(Value.fromInterned(enum_tag.int), writer, level - 1, pt, opt_sema);
|
|
try writer.writeAll(")");
|
|
},
|
|
.empty_enum_value => try writer.writeAll("(empty enum value)"),
|
|
.float => |float| switch (float.storage) {
|
|
inline else => |x| try writer.print("{d}", .{@as(f64, @floatCast(x))}),
|
|
},
|
|
.slice => |slice| {
|
|
if (ip.isUndef(slice.ptr)) {
|
|
if (slice.len == .zero_usize) {
|
|
return writer.writeAll("&.{}");
|
|
}
|
|
try print(.fromInterned(slice.ptr), writer, level - 1, pt, opt_sema);
|
|
} else {
|
|
const print_contents = switch (ip.getBackingAddrTag(slice.ptr).?) {
|
|
.field, .arr_elem, .eu_payload, .opt_payload => unreachable,
|
|
.uav, .comptime_alloc, .comptime_field => true,
|
|
.nav, .int => false,
|
|
};
|
|
if (print_contents) {
|
|
// 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), null, writer, level, pt, opt_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 writer.writeAll("]");
|
|
},
|
|
.ptr => {
|
|
const print_contents = switch (ip.getBackingAddrTag(val.toIntern()).?) {
|
|
.field, .arr_elem, .eu_payload, .opt_payload => unreachable,
|
|
.uav, .comptime_alloc, .comptime_field => true,
|
|
.nav, .int => false,
|
|
};
|
|
if (print_contents) {
|
|
// 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, .rvalue, writer, level, pt, opt_sema);
|
|
},
|
|
.opt => |opt| switch (opt.val) {
|
|
.none => try writer.writeAll("null"),
|
|
else => |payload| try print(Value.fromInterned(payload), writer, level, pt, opt_sema),
|
|
},
|
|
.aggregate => |aggregate| try printAggregate(val, aggregate, false, writer, level, pt, opt_sema),
|
|
.un => |un| {
|
|
if (level == 0) {
|
|
try writer.writeAll(".{ ... }");
|
|
return;
|
|
}
|
|
if (un.tag == .none) {
|
|
const backing_ty = try val.typeOf(zcu).unionBackingType(pt);
|
|
try writer.print("@bitCast(@as({f}, ", .{backing_ty.fmt(pt)});
|
|
try print(Value.fromInterned(un.val), writer, level - 1, pt, opt_sema);
|
|
try writer.writeAll("))");
|
|
} else {
|
|
try writer.writeAll(".{ ");
|
|
try print(Value.fromInterned(un.tag), writer, level - 1, pt, opt_sema);
|
|
try writer.writeAll(" = ");
|
|
try print(Value.fromInterned(un.val), writer, level - 1, pt, opt_sema);
|
|
try writer.writeAll(" }");
|
|
}
|
|
},
|
|
.memoized_call => unreachable,
|
|
}
|
|
}
|
|
|
|
fn printAggregate(
|
|
val: Value,
|
|
aggregate: InternPool.Key.Aggregate,
|
|
is_ref: bool,
|
|
writer: *std.io.Writer,
|
|
level: u8,
|
|
pt: Zcu.PerThread,
|
|
opt_sema: ?*Sema,
|
|
) (std.io.Writer.Error || Zcu.CompileError)!void {
|
|
if (level == 0) {
|
|
if (is_ref) try writer.writeByte('&');
|
|
return writer.writeAll(".{ ... }");
|
|
}
|
|
const zcu = pt.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const ty = Type.fromInterned(aggregate.ty);
|
|
switch (ty.zigTypeTag(zcu)) {
|
|
.@"struct" => if (!ty.isTuple(zcu)) {
|
|
if (is_ref) try writer.writeByte('&');
|
|
if (ty.structFieldCount(zcu) == 0) {
|
|
return writer.writeAll(".{}");
|
|
}
|
|
try writer.writeAll(".{ ");
|
|
const max_len = @min(ty.structFieldCount(zcu), max_aggregate_items);
|
|
for (0..max_len) |i| {
|
|
if (i != 0) try writer.writeAll(", ");
|
|
const field_name = ty.structFieldName(@intCast(i), zcu).unwrap().?;
|
|
try writer.print(".{f} = ", .{field_name.fmt(ip)});
|
|
try print(try val.fieldValue(pt, i), writer, level - 1, pt, opt_sema);
|
|
}
|
|
try writer.writeAll(" }");
|
|
return;
|
|
},
|
|
.array => {
|
|
switch (aggregate.storage) {
|
|
.bytes => |bytes| string: {
|
|
const len = ty.arrayLenIncludingSentinel(zcu);
|
|
if (len == 0) break :string;
|
|
const slice = bytes.toSlice(if (bytes.at(len - 1, ip) == 0) len - 1 else len, ip);
|
|
try writer.print("\"{f}\"", .{std.zig.fmtString(slice)});
|
|
if (!is_ref) try writer.writeAll(".*");
|
|
return;
|
|
},
|
|
.elems, .repeated_elem => {},
|
|
}
|
|
switch (ty.arrayLen(zcu)) {
|
|
0 => {
|
|
if (is_ref) try writer.writeByte('&');
|
|
return writer.writeAll(".{}");
|
|
},
|
|
1 => one_byte_str: {
|
|
// The repr isn't `bytes`, but we might still be able to print this as a string
|
|
if (ty.childType(zcu).toIntern() != .u8_type) break :one_byte_str;
|
|
const elem_val = Value.fromInterned(aggregate.storage.values()[0]);
|
|
if (elem_val.isUndef(zcu)) break :one_byte_str;
|
|
const byte = elem_val.toUnsignedInt(zcu);
|
|
try writer.print("\"{f}\"", .{std.zig.fmtString(&.{@intCast(byte)})});
|
|
if (!is_ref) try writer.writeAll(".*");
|
|
return;
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
.vector => if (ty.arrayLen(zcu) == 0) {
|
|
if (is_ref) try writer.writeByte('&');
|
|
return writer.writeAll(".{}");
|
|
},
|
|
else => unreachable,
|
|
}
|
|
|
|
const len = ty.arrayLen(zcu);
|
|
|
|
if (is_ref) try writer.writeByte('&');
|
|
try writer.writeAll(".{ ");
|
|
|
|
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);
|
|
}
|
|
if (len > max_aggregate_items) {
|
|
try writer.writeAll(", ...");
|
|
}
|
|
return writer.writeAll(" }");
|
|
}
|
|
|
|
fn printPtr(
|
|
ptr_val: Value,
|
|
/// Whether to print `derivation` as an lvalue or rvalue. If `null`, the more concise option is chosen.
|
|
want_kind: ?PrintPtrKind,
|
|
writer: *std.io.Writer,
|
|
level: u8,
|
|
pt: Zcu.PerThread,
|
|
opt_sema: ?*Sema,
|
|
) (std.io.Writer.Error || Zcu.CompileError)!void {
|
|
const ptr = switch (pt.zcu.intern_pool.indexToKey(ptr_val.toIntern())) {
|
|
.undef => return writer.writeAll("undefined"),
|
|
.ptr => |ptr| ptr,
|
|
else => unreachable,
|
|
};
|
|
|
|
if (ptr.base_addr == .uav) {
|
|
// If the value is an aggregate, we can potentially print it more nicely.
|
|
switch (pt.zcu.intern_pool.indexToKey(ptr.base_addr.uav.val)) {
|
|
.aggregate => |agg| return printAggregate(
|
|
Value.fromInterned(ptr.base_addr.uav.val),
|
|
agg,
|
|
true,
|
|
writer,
|
|
level,
|
|
pt,
|
|
opt_sema,
|
|
),
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
var arena = std.heap.ArenaAllocator.init(pt.zcu.gpa);
|
|
defer arena.deinit();
|
|
const derivation = if (opt_sema) |sema|
|
|
try ptr_val.pointerDerivationAdvanced(arena.allocator(), pt, true, sema)
|
|
else
|
|
try ptr_val.pointerDerivationAdvanced(arena.allocator(), pt, false, null);
|
|
|
|
_ = try printPtrDerivation(derivation, writer, pt, want_kind, .{ .print_val = .{
|
|
.level = level,
|
|
.opt_sema = opt_sema,
|
|
} }, 20);
|
|
}
|
|
|
|
const PrintPtrKind = enum { lvalue, rvalue };
|
|
|
|
/// Print the pointer defined by `derivation` as an lvalue or an rvalue.
|
|
/// Returns the root derivation, which may be ignored.
|
|
pub fn printPtrDerivation(
|
|
derivation: Value.PointerDeriveStep,
|
|
writer: *std.io.Writer,
|
|
pt: Zcu.PerThread,
|
|
/// Whether to print `derivation` as an lvalue or rvalue. If `null`, the more concise option is chosen.
|
|
/// If this is `.rvalue`, the result may look like `&foo`, so it's not necessarily valid to treat it as
|
|
/// an atom -- e.g. `&foo.*` is distinct from `(&foo).*`.
|
|
want_kind: ?PrintPtrKind,
|
|
/// How to print the "root" of the derivation. `.print_val` will recursively print other values if needed,
|
|
/// e.g. for UAV refs. `.str` will just write the root as the given string.
|
|
root_strat: union(enum) {
|
|
str: []const u8,
|
|
print_val: struct {
|
|
level: u8,
|
|
opt_sema: ?*Sema,
|
|
},
|
|
},
|
|
/// The maximum recursion depth. We can never recurse infinitely here, but the depth can be arbitrary,
|
|
/// so at this depth we just write "..." to prevent stack overflow.
|
|
ptr_depth: u8,
|
|
) !Value.PointerDeriveStep {
|
|
const zcu = pt.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
|
|
if (ptr_depth == 0) {
|
|
const root_step = root: switch (derivation) {
|
|
inline .eu_payload_ptr,
|
|
.opt_payload_ptr,
|
|
.field_ptr,
|
|
.elem_ptr,
|
|
.offset_and_cast,
|
|
=> |step| continue :root step.parent.*,
|
|
else => |step| break :root step,
|
|
};
|
|
try writer.writeAll("...");
|
|
return root_step;
|
|
}
|
|
|
|
const result_kind: PrintPtrKind = switch (derivation) {
|
|
.nav_ptr,
|
|
.uav_ptr,
|
|
.comptime_alloc_ptr,
|
|
.comptime_field_ptr,
|
|
.eu_payload_ptr,
|
|
.opt_payload_ptr,
|
|
.field_ptr,
|
|
.elem_ptr,
|
|
=> .lvalue,
|
|
|
|
.offset_and_cast,
|
|
.int,
|
|
=> .rvalue,
|
|
};
|
|
|
|
const need_kind = want_kind orelse result_kind;
|
|
|
|
if (need_kind == .rvalue and result_kind == .lvalue) {
|
|
try writer.writeByte('&');
|
|
}
|
|
|
|
// null if `derivation` is the root.
|
|
const root_or_null: ?Value.PointerDeriveStep = switch (derivation) {
|
|
.eu_payload_ptr => |info| root: {
|
|
try writer.writeByte('(');
|
|
const root = try printPtrDerivation(info.parent.*, writer, pt, .lvalue, root_strat, ptr_depth - 1);
|
|
try writer.writeAll(" catch unreachable)");
|
|
break :root root;
|
|
},
|
|
.opt_payload_ptr => |info| root: {
|
|
const root = try printPtrDerivation(info.parent.*, writer, pt, .lvalue, root_strat, ptr_depth - 1);
|
|
try writer.writeAll(".?");
|
|
break :root root;
|
|
},
|
|
.field_ptr => |field| root: {
|
|
const root = try printPtrDerivation(field.parent.*, writer, pt, null, root_strat, ptr_depth - 1);
|
|
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| {
|
|
try writer.print(".{f}", .{field_name.fmt(ip)});
|
|
} else {
|
|
try writer.print("[{d}]", .{field.field_idx});
|
|
},
|
|
.@"union" => {
|
|
const tag_ty = agg_ty.unionTagTypeHypothetical(zcu);
|
|
const field_name = tag_ty.enumFieldName(field.field_idx, zcu);
|
|
try writer.print(".{f}", .{field_name.fmt(ip)});
|
|
},
|
|
.pointer => switch (field.field_idx) {
|
|
Value.slice_ptr_index => try writer.writeAll(".ptr"),
|
|
Value.slice_len_index => try writer.writeAll(".len"),
|
|
else => unreachable,
|
|
},
|
|
else => unreachable,
|
|
}
|
|
break :root root;
|
|
},
|
|
.elem_ptr => |elem| root: {
|
|
const root = try printPtrDerivation(elem.parent.*, writer, pt, null, root_strat, ptr_depth - 1);
|
|
try writer.print("[{d}]", .{elem.elem_idx});
|
|
break :root root;
|
|
},
|
|
|
|
.offset_and_cast => |oac| if (oac.byte_offset == 0) root: {
|
|
try writer.print("@as({f}, @ptrCast(", .{oac.new_ptr_ty.fmt(pt)});
|
|
const root = try printPtrDerivation(oac.parent.*, writer, pt, .rvalue, root_strat, ptr_depth - 1);
|
|
try writer.writeAll("))");
|
|
break :root root;
|
|
} else root: {
|
|
try writer.print("@as({f}, @ptrFromInt(@intFromPtr(", .{oac.new_ptr_ty.fmt(pt)});
|
|
const root = try printPtrDerivation(oac.parent.*, writer, pt, .rvalue, root_strat, ptr_depth - 1);
|
|
try writer.print(") + {d}))", .{oac.byte_offset});
|
|
break :root root;
|
|
},
|
|
|
|
.int, .nav_ptr, .uav_ptr, .comptime_alloc_ptr, .comptime_field_ptr => null,
|
|
};
|
|
|
|
if (root_or_null == null) switch (root_strat) {
|
|
.str => |x| try writer.writeAll(x),
|
|
.print_val => |x| switch (derivation) {
|
|
.int => |int| try writer.print("@as({f}, @ptrFromInt(0x{x}))", .{ int.ptr_ty.fmt(pt), int.addr }),
|
|
.nav_ptr => |nav| try writer.print("{f}", .{ip.getNav(nav).fqn.fmt(ip)}),
|
|
.uav_ptr => |uav| {
|
|
const ty = Value.fromInterned(uav.val).typeOf(zcu);
|
|
try writer.print("@as({f}, ", .{ty.fmt(pt)});
|
|
try print(Value.fromInterned(uav.val), writer, x.level - 1, pt, x.opt_sema);
|
|
try writer.writeByte(')');
|
|
},
|
|
.comptime_alloc_ptr => |info| {
|
|
try writer.print("@as({f}, ", .{info.val.typeOf(zcu).fmt(pt)});
|
|
try print(info.val, writer, x.level - 1, pt, x.opt_sema);
|
|
try writer.writeByte(')');
|
|
},
|
|
.comptime_field_ptr => |val| {
|
|
const ty = val.typeOf(zcu);
|
|
try writer.print("@as({f}, ", .{ty.fmt(pt)});
|
|
try print(val, writer, x.level - 1, pt, x.opt_sema);
|
|
try writer.writeByte(')');
|
|
},
|
|
else => unreachable,
|
|
},
|
|
};
|
|
|
|
if (need_kind == .lvalue and result_kind == .rvalue) {
|
|
try writer.writeAll(".*");
|
|
}
|
|
|
|
return root_or_null orelse derivation;
|
|
}
|