/// Based on src/InternPool.zig from the zig codebase /// https://github.com/ziglang/zig/blob/master/src/InternPool.zig map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, items: std.MultiArrayList(Item) = .{}, extra: std.ArrayListUnmanaged(u8) = .{}, decls: std.SegmentedList(InternPool.Decl, 0) = .{}, structs: std.SegmentedList(InternPool.Struct, 0) = .{}, enums: std.SegmentedList(InternPool.Enum, 0) = .{}, unions: std.SegmentedList(InternPool.Union, 0) = .{}, const InternPool = @This(); const std = @import("std"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const expect = std.testing.expect; const expectFmt = std.testing.expectFmt; const encoding = @import("encoding.zig"); pub const Int = packed struct { signedness: std.builtin.Signedness, bits: u16, }; pub const Pointer = packed struct { elem_type: Index, sentinel: Index = .none, size: std.builtin.Type.Pointer.Size, alignment: u16 = 0, bit_offset: u16 = 0, host_size: u16 = 0, is_const: bool = false, is_volatile: bool = false, is_allowzero: bool = false, address_space: std.builtin.AddressSpace = .generic, }; pub const Array = packed struct { len: u64, child: Index, sentinel: Index = .none, }; pub const FieldStatus = enum { none, field_types_wip, have_field_types, layout_wip, have_layout, fully_resolved_wip, fully_resolved, }; pub const StructIndex = enum(u32) { _ }; pub const Struct = struct { fields: std.StringArrayHashMapUnmanaged(Field), namespace: NamespaceIndex, layout: std.builtin.Type.ContainerLayout = .Auto, backing_int_ty: Index, status: FieldStatus, pub const Field = packed struct { ty: Index, default_value: Index = .none, alignment: u16 = 0, is_comptime: bool = false, }; }; pub const Optional = packed struct { payload_type: Index, }; pub const ErrorUnion = packed struct { error_set_type: Index, payload_type: Index, }; pub const ErrorSet = struct { /// every element is guaranteed to be .bytes names: []const Index, }; pub const EnumIndex = enum(u32) { _ }; pub const Enum = struct { tag_type: Index, fields: std.StringArrayHashMapUnmanaged(void), values: std.AutoArrayHashMapUnmanaged(Index, void), namespace: NamespaceIndex, tag_type_inferred: bool, }; pub const Function = struct { args: []const Index, /// zig only lets the first 32 arguments be `comptime` args_is_comptime: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), /// zig only lets the first 32 arguments be generic args_is_generic: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), /// zig only lets the first 32 arguments be `noalias` args_is_noalias: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), return_type: Index, alignment: u16 = 0, calling_convention: std.builtin.CallingConvention = .Unspecified, is_generic: bool = false, is_var_args: bool = false, }; pub const UnionIndex = enum(u32) { _ }; pub const Union = struct { tag_type: Index, fields: std.StringArrayHashMapUnmanaged(Field), namespace: NamespaceIndex, layout: std.builtin.Type.ContainerLayout = .Auto, status: FieldStatus, pub const Field = packed struct { ty: Index, alignment: u16, }; }; pub const Tuple = struct { types: []const Index, /// Index.none elements are used to indicate runtime-known. values: []const Index, }; pub const Vector = packed struct { len: u32, child: Index, }; pub const AnyFrame = packed struct { child: Index, }; const U64Value = packed struct { ty: Index, int: u64, }; const I64Value = packed struct { ty: Index, int: i64, }; pub const BigInt = struct { ty: Index, int: std.math.big.int.Const, }; pub const Bytes = []const u8; pub const OptionalValue = packed struct { ty: Index, val: Index, }; pub const Slice = packed struct { ty: Index, ptr: Index, len: Index, }; pub const Aggregate = struct { ty: Index, values: []const Index, }; pub const UnionValue = packed struct { ty: Index, field_index: u32, val: Index, }; pub const UnknownValue = packed struct { ty: Index, }; pub const DeclIndex = enum(u32) { _ }; pub const Decl = struct { name: []const u8, node_idx: u32, /// this stores both the type and the value index: Index, alignment: u16, address_space: std.builtin.AddressSpace, is_pub: bool, is_exported: bool, }; const BigIntInternal = struct { ty: Index, limbs: []const std.math.big.Limb, }; pub const Key = union(enum) { simple_type: SimpleType, simple_value: SimpleValue, int_type: Int, pointer_type: Pointer, array_type: Array, /// TODO consider *Struct instead of StructIndex struct_type: StructIndex, optional_type: Optional, error_union_type: ErrorUnion, error_set_type: ErrorSet, /// TODO consider *Enum instead of EnumIndex enum_type: EnumIndex, function_type: Function, /// TODO consider *Union instead of UnionIndex union_type: UnionIndex, tuple_type: Tuple, vector_type: Vector, anyframe_type: AnyFrame, int_u64_value: U64Value, int_i64_value: I64Value, int_big_value: BigInt, float_16_value: f16, float_32_value: f32, float_64_value: f64, float_80_value: f80, float_128_value: f128, float_comptime_value: f128, bytes: Bytes, optional_value: OptionalValue, slice: Slice, aggregate: Aggregate, union_value: UnionValue, unknown_value: UnknownValue, // error // error union pub fn eql(a: Key, b: Key) bool { return deepEql(a, b); } pub fn hash(a: Key) u32 { var hasher = std.hash.Wyhash.init(0); deepHash(&hasher, a); return @truncate(u32, hasher.final()); } pub fn tag(key: Key) Tag { return switch (key) { .simple_type => .simple_type, .simple_value => .simple_value, .int_type => |int_info| switch (int_info.signedness) { .signed => .type_int_signed, .unsigned => .type_int_unsigned, }, .pointer_type => .type_pointer, .array_type => .type_array, .struct_type => .type_struct, .optional_type => .type_optional, .error_union_type => .type_error_union, .error_set_type => .type_error_set, .enum_type => .type_enum, .function_type => .type_function, .union_type => .type_union, .tuple_type => .type_tuple, .vector_type => .type_vector, .anyframe_type => .type_anyframe, .int_u64_value => .int_u64, .int_i64_value => .int_i64, .int_big_value => |big_int| if (big_int.int.positive) .int_big_positive else .int_big_negative, .float_16_value => .float_f16, .float_32_value => .float_f32, .float_64_value => .float_f64, .float_80_value => .float_f80, .float_128_value => .float_f128, .float_comptime_value => .float_comptime, .bytes => .bytes, .optional_value => .optional_value, .slice => .slice, .aggregate => .aggregate, .union_value => .union_value, .unknown_value => .unknown_value, }; } pub fn zigTypeTag(key: Key) std.builtin.TypeId { return switch (key) { .simple_type => |simple| switch (simple) { .f16, .f32, .f64, .f80, .f128, .c_longdouble, => .Float, .usize, .isize, .c_char, .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong, => .Int, .comptime_int => .ComptimeInt, .comptime_float => .ComptimeFloat, .anyopaque => .Opaque, .bool => .Bool, .void => .Void, .type => .Type, .anyerror => .ErrorSet, .noreturn => .NoReturn, .anyframe_type => .AnyFrame, .null_type => .Null, .undefined_type => .Undefined, .enum_literal_type => .EnumLiteral, .atomic_order => .Enum, .atomic_rmw_op => .Enum, .calling_convention => .Enum, .address_space => .Enum, .float_mode => .Enum, .reduce_op => .Enum, .modifier => .Enum, .prefetch_options => .Struct, .export_options => .Struct, .extern_options => .Struct, .type_info => .Union, .unknown => unreachable, .generic_poison => unreachable, }, .int_type => .Int, .pointer_type => .Pointer, .array_type => .Array, .struct_type => .Struct, .optional_type => .Optional, .error_union_type => .ErrorUnion, .error_set_type => .ErrorSet, .enum_type => .Enum, .function_type => .Fn, .union_type => .Union, .tuple_type => .Struct, .vector_type => .Vector, .anyframe_type => .AnyFrame, .simple_value, .int_u64_value, .int_i64_value, .int_big_value, .float_16_value, .float_32_value, .float_64_value, .float_80_value, .float_128_value, .float_comptime_value, => unreachable, .bytes, .optional_value, .slice, .aggregate, .union_value, .unknown_value, => unreachable, }; } pub fn typeOf(key: Key) Index { return switch (key) { .simple_type => .type_type, .simple_value => |simple| switch (simple) { .undefined_value => .undefined_type, .void_value => .void_type, .unreachable_value => .noreturn_type, .null_value => .null_type, .bool_true => .bool_type, .bool_false => .bool_type, .the_only_possible_value => unreachable, .generic_poison => .generic_poison_type, }, .int_type, .pointer_type, .array_type, .struct_type, .optional_type, .error_union_type, .error_set_type, .enum_type, .function_type, .union_type, .tuple_type, .vector_type, .anyframe_type, => .type_type, .int_u64_value => |int| int.ty, .int_i64_value => |int| int.ty, .int_big_value => |int| int.ty, .float_16_value => .f16_type, .float_32_value => .f32_type, .float_64_value => .f64_type, .float_80_value => .f80_type, .float_128_value => .f128_type, .float_comptime_value => .comptime_float_type, .bytes => .unknown_type, // TODO .optional_value => |optional_info| optional_info.ty, .slice => |slice_info| slice_info.ty, .aggregate => |aggregate_info| aggregate_info.ty, .union_value => |union_info| union_info.ty, .unknown_value => |unknown_info| unknown_info.ty, }; } /// Asserts the type is an integer, enum, error set, packed struct, or vector of one of them. pub fn intInfo(ty: Key, target: std.Target, ip: InternPool) Int { var key: Key = ty; while (true) switch (key) { .simple_type => |simple| switch (simple) { .usize => return .{ .signedness = .signed, .bits = target.cpu.arch.ptrBitWidth() }, .isize => return .{ .signedness = .unsigned, .bits = target.cpu.arch.ptrBitWidth() }, .c_char => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.char) }, .c_short => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.short) }, .c_ushort => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ushort) }, .c_int => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.int) }, .c_uint => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.uint) }, .c_long => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.long) }, .c_ulong => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ulong) }, .c_longlong => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.longlong) }, .c_ulonglong => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ulonglong) }, .c_longdouble => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.longdouble) }, // TODO revisit this when error sets support custom int types (comment taken from zig codebase) .anyerror => return .{ .signedness = .unsigned, .bits = 16 }, else => unreachable, }, .int_type => |int_info| return int_info, .enum_type => |enum_index| { const enum_info = ip.getEnum(enum_index); key = ip.indexToKey(enum_info.tag_type); }, .struct_type => |struct_index| { const struct_info = ip.getStruct(struct_index); assert(struct_info.layout == .Packed); key = ip.indexToKey(struct_info.backing_int_ty); }, // TODO revisit this when error sets support custom int types (comment taken from zig codebase) .error_set_type => return .{ .signedness = .unsigned, .bits = 16 }, .vector_type => |vector_info| { assert(vector_info.len == 1); key = ip.indexToKey(vector_info.child); }, else => unreachable, }; } /// Asserts the type is a fixed-size float or comptime_float. /// Returns 128 for comptime_float types. pub fn floatBits(ty: Key, target: std.Target) u16 { return switch (ty.simple_type) { .f16 => 16, .f32 => 32, .f64 => 64, .f80 => 80, .f128, .comptime_float => 128, .c_longdouble => target.c_type_bit_size(.longdouble), else => unreachable, }; } pub fn isSinglePointer(ty: Key) bool { return switch (ty) { .pointer_type => |pointer_info| pointer_info.size == .One, else => false, }; } pub fn isCPtr(ty: Key) bool { return switch (ty) { .pointer_type => |pointer_info| pointer_info.size == .C, else => false, }; } pub fn isConstPtr(ty: Key) bool { return switch (ty) { .pointer_type => |pointer_info| pointer_info.is_const, else => false, }; } /// For pointer-like optionals, returns true, otherwise returns the allowzero property /// of pointers. pub fn ptrAllowsZero(ty: Key, ip: *const InternPool) bool { if (ty.pointer_type.is_allowzero) return true; return ty.isPtrLikeOptional(ip); } /// Returns true if the type is optional and would be lowered to a single pointer /// address value, using 0 for null. Note that this returns true for C pointers. pub fn isPtrLikeOptional(ty: Key, ip: *const InternPool) bool { switch (ty) { .optional_type => |optional_info| { const child_ty = optional_info.payload_type; const child_key = ip.indexToKey(child_ty); if (child_key != .pointer_type) return false; const info = child_key.pointer_type; switch (info.size) { .Slice, .C => return false, .Many, .One => return !info.is_allowzero, } }, .pointer_type => |pointer_info| return pointer_info.size == .C, else => return false, } } pub fn isPtrAtRuntime(ty: Key, ip: *const InternPool) bool { return switch (ty) { .pointer_type => |pointer_info| pointer_info.size != .Slice, .optional_type => |optional_info| { const child_type = ip.indexToKey(optional_info.payload_type); switch (child_type) { .pointer_type => |pointer_info| switch (pointer_info.size) { .Slice, .C => return false, .Many, .One => return !pointer_info.is_allowzero, }, else => return false, } }, else => false, }; } pub fn elemType2(ty: Key) Index { return switch (ty) { .simple_type => |simple| switch (simple) { .anyframe_type => Index.void_type, else => unreachable, }, .pointer_type => |pointer_info| pointer_info.elem_type, .array_type => |array_info| array_info.child, .optional_type => |optional_info| optional_info.payload_type, .vector_type => |vector_info| vector_info.child, .anyframe_type => |anyframe_info| anyframe_info.child, else => unreachable, }; } /// Asserts the type is an array, pointer or vector. pub fn sentinel(ty: Key) Index { return switch (ty) { .pointer_type => |pointer_info| pointer_info.sentinel, .array_type => |array_info| array_info.sentinel, .vector_type => Index.none, else => unreachable, }; } pub fn getNamespace(ty: Key, ip: InternPool) NamespaceIndex { return switch (ty) { .struct_type => |struct_index| ip.getStruct(struct_index).namespace, .enum_type => |enum_index| ip.getEnum(enum_index).namespace, .union_type => |union_index| ip.getUnion(union_index).namespace, else => .none, }; } pub fn onePossibleValue(ty: Key, ip: InternPool) Index { return switch (ty) { .simple_type => |simple| switch (simple) { .f16, .f32, .f64, .f80, .f128, .usize, .isize, .c_char, .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong, .c_longdouble, .anyopaque, .bool, .type, .anyerror, .comptime_int, .comptime_float, .anyframe_type, .enum_literal_type, => Index.none, .void => Index.void_value, .noreturn => Index.unreachable_value, .null_type => Index.null_value, .undefined_type => Index.undefined_value, .atomic_order, .atomic_rmw_op, .calling_convention, .address_space, .float_mode, .reduce_op, .modifier, .prefetch_options, .export_options, .extern_options, .type_info, => Index.none, .unknown => Index.unknown_unknown, .generic_poison => unreachable, }, .int_type => |int_info| { if (int_info.bits == 0) { switch (int_info.signedness) { .unsigned => return Index.zero, .signed => return Index.zero, // do we need a signed zero? } } return Index.none; }, .pointer_type => Index.none, .array_type => |array_info| { if (array_info.len == 0) { return Index.empty_aggregate; } else if (ip.indexToKey(array_info.child).onePossibleValue(ip) != Index.none) { return Index.the_only_possible_value; } return Index.none; }, .struct_type => |struct_index| { const struct_info = ip.getStruct(struct_index); var field_it = struct_info.fields.iterator(); while (field_it.next()) |entry| { if (entry.value_ptr.is_comptime) continue; if (ip.indexToKey(entry.value_ptr.ty).onePossibleValue(ip) != Index.none) continue; return Index.none; } return Index.empty_aggregate; }, .optional_type => |optional_info| { if (optional_info.payload_type == Index.noreturn_type) { return Index.null_value; } return Index.none; }, .error_union_type => Index.none, .error_set_type => Index.none, .enum_type => |enum_index| { const enum_info = ip.getEnum(enum_index); return switch (enum_info.fields.count()) { 0 => Index.unreachable_value, 1 => enum_info.values.keys()[0], else => Index.none, }; }, .function_type => Index.none, .union_type => panicOrElse("TODO", Index.none), .tuple_type => panicOrElse("TODO", Index.none), .vector_type => |vector_info| { if (vector_info.len == 0) { return panicOrElse("TODO return empty array value", Index.none); } return ip.indexToKey(vector_info.child).onePossibleValue(ip); }, .anyframe_type => Index.none, .simple_value, .int_u64_value, .int_i64_value, .int_big_value, .float_16_value, .float_32_value, .float_64_value, .float_80_value, .float_128_value, .float_comptime_value, => unreachable, .bytes, .optional_value, .slice, .aggregate, .union_value, .unknown_value, => unreachable, }; } pub fn isNull(val: Key) bool { return switch (val) { .simple_value => |simple| switch (simple) { .null_value => true, .bool_true => false, .bool_false => true, .the_only_possible_value => true, else => unreachable, }, .int_u64_value => |int_value| int_value.int == 0, .int_i64_value => |int_value| int_value.int == 0, .int_big_value => |int_value| int_value.int.orderAgainstScalar(0).compare(.eq), .optional_value => false, .unknown_value => unreachable, else => unreachable, }; } pub const FormatContext = struct { key: Key, options: FormatOptions = .{}, ip: InternPool, }; // TODO add options for controling how types show be formatted pub const FormatOptions = struct {}; fn format( ctx: FormatContext, comptime fmt_str: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { _ = options; if (fmt_str.len != 0) std.fmt.invalidFmtError(fmt_str, Key); try print(ctx.key, ctx.ip, writer); } pub fn print(key: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!void { var k = key; while (try printInternal(k, ip, writer)) |index| { k = ip.indexToKey(index); } } fn printInternal(ty: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!?Index { switch (ty) { .simple_type => |simple| switch (simple) { .f16, .f32, .f64, .f80, .f128, .usize, .isize, .c_char, .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong, .c_longdouble, .anyopaque, .bool, .void, .type, .anyerror, .comptime_int, .comptime_float, .noreturn, .anyframe_type, => try writer.writeAll(@tagName(simple)), .null_type => try writer.writeAll("@TypeOf(null)"), .undefined_type => try writer.writeAll("@TypeOf(undefined)"), .enum_literal_type => try writer.writeAll("@TypeOf(.enum_literal)"), .atomic_order => try writer.writeAll("std.builtin.AtomicOrder"), .atomic_rmw_op => try writer.writeAll("std.builtin.AtomicRmwOp"), .calling_convention => try writer.writeAll("std.builtin.CallingConvention"), .address_space => try writer.writeAll("std.builtin.AddressSpace"), .float_mode => try writer.writeAll("std.builtin.FloatMode"), .reduce_op => try writer.writeAll("std.builtin.ReduceOp"), .modifier => try writer.writeAll("std.builtin.CallModifier"), .prefetch_options => try writer.writeAll("std.builtin.PrefetchOptions"), .export_options => try writer.writeAll("std.builtin.ExportOptions"), .extern_options => try writer.writeAll("std.builtin.ExternOptions"), .type_info => try writer.writeAll("std.builtin.Type"), .unknown => try writer.writeAll("(unknown type)"), .generic_poison => unreachable, }, .int_type => |int_info| switch (int_info.signedness) { .signed => try writer.print("i{}", .{int_info.bits}), .unsigned => try writer.print("u{}", .{int_info.bits}), }, .pointer_type => |pointer_info| { if (pointer_info.sentinel != Index.none) { switch (pointer_info.size) { .One, .C => unreachable, .Many => try writer.print("[*:{}]", .{pointer_info.sentinel.fmt(ip)}), .Slice => try writer.print("[:{}]", .{pointer_info.sentinel.fmt(ip)}), } } else switch (pointer_info.size) { .One => try writer.writeAll("*"), .Many => try writer.writeAll("[*]"), .C => try writer.writeAll("[*c]"), .Slice => try writer.writeAll("[]"), } if (pointer_info.alignment != 0) { try writer.print("align({d}", .{pointer_info.alignment}); if (pointer_info.bit_offset != 0 or pointer_info.host_size != 0) { try writer.print(":{d}:{d}", .{ pointer_info.bit_offset, pointer_info.host_size }); } try writer.writeAll(") "); } if (pointer_info.address_space != .generic) { try writer.print("addrspace(.{s}) ", .{@tagName(pointer_info.address_space)}); } if (pointer_info.is_const) try writer.writeAll("const "); if (pointer_info.is_volatile) try writer.writeAll("volatile "); if (pointer_info.is_allowzero and pointer_info.size != .C) try writer.writeAll("allowzero "); return pointer_info.elem_type; }, .array_type => |array_info| { try writer.print("[{d}", .{array_info.len}); if (array_info.sentinel != Index.none) { try writer.print(":{}", .{array_info.sentinel.fmt(ip)}); } try writer.writeByte(']'); return array_info.child; }, .struct_type => return panicOrElse("TODO", null), .optional_type => |optional_info| { try writer.writeByte('?'); return optional_info.payload_type; }, .error_union_type => |error_union_info| { try print(ip.indexToKey(error_union_info.error_set_type), ip, writer); try writer.writeByte('!'); return error_union_info.payload_type; }, .error_set_type => |error_set_info| { const names = error_set_info.names; try writer.writeAll("error{"); for (names, 0..) |name, i| { if (i != 0) try writer.writeByte(','); try writer.writeAll(ip.indexToKey(name).bytes); } try writer.writeByte('}'); }, .enum_type => return panicOrElse("TODO", null), .function_type => |function_info| { try writer.writeAll("fn("); for (function_info.args, 0..) |arg_ty, i| { if (i != 0) try writer.writeAll(", "); if (i < 32) { if (function_info.args_is_comptime.isSet(i)) { try writer.writeAll("comptime "); } if (function_info.args_is_noalias.isSet(i)) { try writer.writeAll("noalias "); } } try print(ip.indexToKey(arg_ty), ip, writer); } if (function_info.is_var_args) { if (function_info.args.len != 0) { try writer.writeAll(", "); } try writer.writeAll("..."); } try writer.writeAll(") "); if (function_info.alignment != 0) { try writer.print("align({d}) ", .{function_info.alignment}); } if (function_info.calling_convention != .Unspecified) { try writer.print("callconv(.{s}) ", .{@tagName(function_info.calling_convention)}); } return function_info.return_type; }, .union_type => return panicOrElse("TODO", null), .tuple_type => |tuple_info| { try writer.writeAll("tuple{"); for (tuple_info.types, 0..) |field_ty, i| { if (i != 0) try writer.writeAll(", "); const val = tuple_info.values[i]; if (val != Index.none) { try writer.writeAll("comptime "); } try print(ip.indexToKey(field_ty), ip, writer); if (val != Index.none) { try writer.print(" = {}", .{val.fmt(ip)}); } } try writer.writeByte('}'); }, .vector_type => |vector_info| { try writer.print("@Vector({d},{})", .{ vector_info.len, vector_info.child.fmt(ip), }); }, .anyframe_type => |anyframe_info| { try writer.writeAll("anyframe->"); return anyframe_info.child; }, .simple_value => |simple| switch (simple) { .undefined_value => try writer.writeAll("undefined"), .void_value => try writer.writeAll("{}"), .unreachable_value => try writer.writeAll("unreachable"), .null_value => try writer.writeAll("null"), .bool_true => try writer.writeAll("true"), .bool_false => try writer.writeAll("false"), .the_only_possible_value => try writer.writeAll("(the only possible value)"), .generic_poison => try writer.writeAll("(generic poison)"), }, .int_u64_value => |i| try std.fmt.formatIntValue(i.int, "", .{}, writer), .int_i64_value => |i| try std.fmt.formatIntValue(i.int, "", .{}, writer), .int_big_value => |i| try i.int.format("", .{}, writer), .float_16_value => |float| try writer.print("{d}", .{float}), .float_32_value => |float| try writer.print("{d}", .{float}), .float_64_value => |float| try writer.print("{d}", .{float}), .float_80_value => |float| try writer.print("{d}", .{@floatCast(f64, float)}), .float_128_value, .float_comptime_value, => |float| try writer.print("{d}", .{@floatCast(f64, float)}), .bytes => |bytes| try writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)}), .optional_value => |optional| { return optional.val; }, .slice => |slice_value| { _ = slice_value; try writer.writeAll(".{"); try writer.writeAll(" TODO "); // TODO try writer.writeByte('}'); }, .aggregate => |aggregate| { if (aggregate.values.len == 0) { try writer.writeAll(".{}"); return null; } const struct_info = ip.getStruct(ip.indexToKey(aggregate.ty).struct_type); assert(aggregate.values.len == struct_info.fields.count()); try writer.writeAll(".{"); var i: u32 = 0; while (i < aggregate.values.len) : (i += 1) { if (i != 0) try writer.writeAll(", "); const field_name = struct_info.fields.keys()[i]; try writer.print(".{s} = {}", .{ field_name, aggregate.values[i].fmt(ip) }); } try writer.writeByte('}'); }, .union_value => |union_value| { const union_info = ip.getUnion(ip.indexToKey(union_value.ty).union_type); const name = union_info.fields.keys()[union_value.field_index]; try writer.print(".{{ .{} = {} }}", .{ std.zig.fmtId(name), union_value.val.fmt(ip), }); }, .unknown_value => try writer.print("(unknown value)", .{}), } return null; } pub fn fmt(key: Key, ip: InternPool) std.fmt.Formatter(format) { return .{ .data = .{ .key = key, .ip = ip, } }; } }; pub const Item = struct { tag: Tag, /// The doc comments on the respective Tag explain how to interpret this. data: u32, }; /// Represents an index into `map`. It represents the canonical index /// of a `Value` within this `InternPool`. The values are typed. /// Two values which have the same type can be equality compared simply /// by checking if their indexes are equal, provided they are both in /// the same `InternPool`. pub const Index = enum(u32) { u1_type, u8_type, i8_type, u16_type, i16_type, u29_type, u32_type, i32_type, u64_type, i64_type, u128_type, i128_type, usize_type, isize_type, c_char_type, c_short_type, c_ushort_type, c_int_type, c_uint_type, c_long_type, c_ulong_type, c_longlong_type, c_ulonglong_type, c_longdouble_type, f16_type, f32_type, f64_type, f80_type, f128_type, anyopaque_type, bool_type, void_type, type_type, anyerror_type, comptime_int_type, comptime_float_type, noreturn_type, anyframe_type, null_type, undefined_type, enum_literal_type, atomic_order_type, atomic_rmw_op_type, calling_convention_type, address_space_type, float_mode_type, reduce_op_type, modifier_type, prefetch_options_type, export_options_type, extern_options_type, type_info_type, manyptr_u8_type, manyptr_const_u8_type, fn_noreturn_no_args_type, fn_void_no_args_type, fn_naked_noreturn_no_args_type, fn_ccc_void_no_args_type, single_const_pointer_to_comptime_int_type, const_slice_u8_type, anyerror_void_error_union_type, generic_poison_type, unknown_type, /// `undefined` (untyped) undefined_value, /// `0` (comptime_int) zero, /// `1` (comptime_int) one, /// `{}` void_value, /// `unreachable` (noreturn type) unreachable_value, /// `null` (untyped) null_value, /// `true` bool_true, /// `false` bool_false, /// `.{}` (untyped) empty_aggregate, /// `0` (usize) zero_usize, /// `1` (usize) one_usize, the_only_possible_value, generic_poison, // unknown value of unknown type unknown_unknown, none = std.math.maxInt(u32), _, pub fn fmt(index: Index, ip: InternPool) std.fmt.Formatter(Key.format) { return .{ .data = .{ .key = ip.indexToKey(index), .ip = ip, } }; } pub fn fmtDebug(index: Index, ip: InternPool) std.fmt.Formatter(formatDebug) { return .{ .data = .{ .index = index, .ip = ip, } }; } const FormatContext = struct { index: Index, ip: InternPool, }; fn formatDebug( ctx: FormatContext, comptime fmt_str: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (ctx.index == .none) { return writer.writeAll(".none"); } else { return Key.format(.{ .key = ctx.ip.indexToKey(ctx.index), .ip = ctx.ip, }, fmt_str, options, writer); } } }; comptime { const Zir = @import("../stage2/Zir.zig"); assert(@enumToInt(Zir.Inst.Ref.generic_poison_type) == @enumToInt(Index.generic_poison_type)); assert(@enumToInt(Zir.Inst.Ref.undef) == @enumToInt(Index.undefined_value)); assert(@enumToInt(Zir.Inst.Ref.one_usize) == @enumToInt(Index.one_usize)); } pub const NamespaceIndex = enum(u32) { none = std.math.maxInt(u32), _, }; pub const Tag = enum(u8) { /// A type that can be represented with only an enum tag. /// data is SimpleType enum value simple_type, /// A value that can be represented with only an enum tag. /// data is SimpleValue enum value simple_value, /// An integer type. /// data is number of bits type_int_signed, /// An integer type. /// data is number of bits type_int_unsigned, /// A pointer type. /// data is payload to Pointer. type_pointer, /// An array type. /// data is payload to Array. type_array, /// An struct type. /// data is payload to Struct. type_struct, /// An optional type. /// data is index to type type_optional, /// An error union type. /// data is payload to ErrorUnion. type_error_union, /// An error set type. /// data is payload to ErrorSet. type_error_set, /// An enum type. /// data is payload to Enum. type_enum, /// An function type. /// data is payload to Function. type_function, /// An union type. /// data is payload to Union. type_union, /// An tuple type. /// data is payload to Tuple. type_tuple, /// An vector type. /// data is payload to Vector. type_vector, /// An anyframe->T type. /// data is index to type type_anyframe, // TODO use a more efficient encoding for small integers /// An unsigned integer value that can be represented by u64. /// data is payload to u64 int_u64, /// An unsigned integer value that can be represented by u64. /// data is payload to i64 bitcasted to u64 int_i64, /// A positive integer value that does not fit in 64 bits. /// data is a extra index to BigInt limbs. int_big_positive, /// A negative integer value that does not fit in 64 bits. /// data is a extra index to BigInt limbs. int_big_negative, /// A float value that can be represented by f16. /// data is f16 bitcasted to u16 cast to u32. float_f16, /// A float value that can be represented by f32. /// data is f32 bitcasted to u32. float_f32, /// A float value that can be represented by f64. /// data is payload to f64. float_f64, /// A float value that can be represented by f80. /// data is payload to f80. float_f80, /// A float value that can be represented by f128. /// data is payload to f128. float_f128, /// A comptime float value. /// data is payload to f128. float_comptime, /// A byte sequence value. /// data is payload to data begin and length. bytes, /// A optional value that is not null. /// data is index to OptionalValue. optional_value, /// A aggregate (struct) value. /// data is index to Aggregate. aggregate, /// A slice value. /// data is index to Slice. slice, /// A union value. /// data is index to UnionValue. union_value, /// A unknown value. /// data is index to type which may also be unknown. unknown_value, }; pub const SimpleType = enum(u32) { f16, f32, f64, f80, f128, usize, isize, c_char, c_short, c_ushort, c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong, c_longdouble, anyopaque, bool, void, type, anyerror, comptime_int, comptime_float, noreturn, anyframe_type, null_type, undefined_type, enum_literal_type, atomic_order, atomic_rmw_op, calling_convention, address_space, float_mode, reduce_op, modifier, prefetch_options, export_options, extern_options, type_info, unknown, generic_poison, }; pub const SimpleValue = enum(u32) { undefined_value, void_value, unreachable_value, null_value, bool_true, bool_false, the_only_possible_value, generic_poison, }; comptime { assert(@sizeOf(SimpleType) == @sizeOf(SimpleValue)); } pub fn init(gpa: Allocator) Allocator.Error!InternPool { var ip: InternPool = .{}; const items = [_]struct { index: Index, key: Key }{ .{ .index = .u1_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 1 } } }, .{ .index = .u8_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 8 } } }, .{ .index = .i8_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 8 } } }, .{ .index = .u16_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 16 } } }, .{ .index = .i16_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 16 } } }, .{ .index = .u29_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 29 } } }, .{ .index = .u32_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 32 } } }, .{ .index = .i32_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 32 } } }, .{ .index = .u64_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 64 } } }, .{ .index = .i64_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 64 } } }, .{ .index = .u128_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 128 } } }, .{ .index = .i128_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 128 } } }, .{ .index = .usize_type, .key = .{ .simple_type = .usize } }, .{ .index = .isize_type, .key = .{ .simple_type = .isize } }, .{ .index = .c_char_type, .key = .{ .simple_type = .c_char } }, .{ .index = .c_short_type, .key = .{ .simple_type = .c_short } }, .{ .index = .c_ushort_type, .key = .{ .simple_type = .c_ushort } }, .{ .index = .c_int_type, .key = .{ .simple_type = .c_int } }, .{ .index = .c_uint_type, .key = .{ .simple_type = .c_uint } }, .{ .index = .c_long_type, .key = .{ .simple_type = .c_long } }, .{ .index = .c_ulong_type, .key = .{ .simple_type = .c_ulong } }, .{ .index = .c_longlong_type, .key = .{ .simple_type = .c_longlong } }, .{ .index = .c_ulonglong_type, .key = .{ .simple_type = .c_ulonglong } }, .{ .index = .c_longdouble_type, .key = .{ .simple_type = .c_longdouble } }, .{ .index = .f16_type, .key = .{ .simple_type = .f16 } }, .{ .index = .f32_type, .key = .{ .simple_type = .f32 } }, .{ .index = .f64_type, .key = .{ .simple_type = .f64 } }, .{ .index = .f80_type, .key = .{ .simple_type = .f80 } }, .{ .index = .f128_type, .key = .{ .simple_type = .f128 } }, .{ .index = .anyopaque_type, .key = .{ .simple_type = .anyopaque } }, .{ .index = .bool_type, .key = .{ .simple_type = .bool } }, .{ .index = .void_type, .key = .{ .simple_type = .void } }, .{ .index = .type_type, .key = .{ .simple_type = .type } }, .{ .index = .anyerror_type, .key = .{ .simple_type = .anyerror } }, .{ .index = .comptime_int_type, .key = .{ .simple_type = .comptime_int } }, .{ .index = .comptime_float_type, .key = .{ .simple_type = .comptime_float } }, .{ .index = .noreturn_type, .key = .{ .simple_type = .noreturn } }, .{ .index = .anyframe_type, .key = .{ .simple_type = .anyframe_type } }, .{ .index = .null_type, .key = .{ .simple_type = .null_type } }, .{ .index = .undefined_type, .key = .{ .simple_type = .undefined_type } }, .{ .index = .enum_literal_type, .key = .{ .simple_type = .enum_literal_type } }, .{ .index = .atomic_order_type, .key = .{ .simple_type = .atomic_order } }, .{ .index = .atomic_rmw_op_type, .key = .{ .simple_type = .atomic_rmw_op } }, .{ .index = .calling_convention_type, .key = .{ .simple_type = .calling_convention } }, .{ .index = .address_space_type, .key = .{ .simple_type = .address_space } }, .{ .index = .float_mode_type, .key = .{ .simple_type = .float_mode } }, .{ .index = .reduce_op_type, .key = .{ .simple_type = .reduce_op } }, .{ .index = .modifier_type, .key = .{ .simple_type = .modifier } }, .{ .index = .prefetch_options_type, .key = .{ .simple_type = .prefetch_options } }, .{ .index = .export_options_type, .key = .{ .simple_type = .export_options } }, .{ .index = .extern_options_type, .key = .{ .simple_type = .extern_options } }, .{ .index = .type_info_type, .key = .{ .simple_type = .type_info } }, .{ .index = .manyptr_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Many } } }, .{ .index = .manyptr_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Many, .is_const = true } } }, .{ .index = .fn_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .noreturn_type } } }, .{ .index = .fn_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type } } }, .{ .index = .fn_naked_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .calling_convention = .Naked } } }, .{ .index = .fn_ccc_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .calling_convention = .C } } }, .{ .index = .single_const_pointer_to_comptime_int_type, .key = .{ .pointer_type = .{ .elem_type = .comptime_int_type, .size = .One, .is_const = true } } }, .{ .index = .const_slice_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Slice, .is_const = true } } }, .{ .index = .anyerror_void_error_union_type, .key = .{ .error_union_type = .{ .error_set_type = .anyerror_type, .payload_type = .void_type } } }, .{ .index = .generic_poison_type, .key = .{ .simple_type = .generic_poison } }, .{ .index = .unknown_type, .key = .{ .simple_type = .unknown } }, .{ .index = .undefined_value, .key = .{ .simple_value = .undefined_value } }, .{ .index = .zero, .key = .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 0 } } }, .{ .index = .one, .key = .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 1 } } }, .{ .index = .void_value, .key = .{ .simple_value = .void_value } }, .{ .index = .unreachable_value, .key = .{ .simple_value = .unreachable_value } }, .{ .index = .null_value, .key = .{ .simple_value = .null_value } }, .{ .index = .bool_true, .key = .{ .simple_value = .bool_true } }, .{ .index = .bool_false, .key = .{ .simple_value = .bool_false } }, .{ .index = .empty_aggregate, .key = .{ .aggregate = .{ .ty = .none, .values = &.{} } } }, .{ .index = .zero_usize, .key = .{ .int_u64_value = .{ .ty = .usize_type, .int = 0 } } }, .{ .index = .one_usize, .key = .{ .int_u64_value = .{ .ty = .usize_type, .int = 1 } } }, .{ .index = .the_only_possible_value, .key = .{ .simple_value = .the_only_possible_value } }, .{ .index = .generic_poison, .key = .{ .simple_value = .generic_poison } }, .{ .index = .unknown_unknown, .key = .{ .unknown_value = .{ .ty = .unknown_type } } }, }; const extra_count = 4 * @sizeOf(Pointer) + @sizeOf(ErrorUnion) + 4 * @sizeOf(Function) + 4 * @sizeOf(InternPool.U64Value); try ip.map.ensureTotalCapacity(gpa, items.len); try ip.items.ensureTotalCapacity(gpa, items.len); try ip.extra.ensureTotalCapacity(gpa, extra_count); for (items) |item| { if (builtin.is_test or builtin.mode == .Debug) { var failing_allocator = std.testing.FailingAllocator.init(undefined, 0); std.testing.expectEqual(item.index, ip.get(failing_allocator.allocator(), item.key) catch unreachable) catch unreachable; } else { assert(item.index == ip.get(undefined, item.key) catch unreachable); } } return ip; } pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.map.deinit(gpa); ip.items.deinit(gpa); ip.extra.deinit(gpa); var struct_it = ip.structs.iterator(0); while (struct_it.next()) |item| { item.fields.deinit(gpa); } var enum_it = ip.enums.iterator(0); while (enum_it.next()) |item| { item.fields.deinit(gpa); item.values.deinit(gpa); } var union_it = ip.unions.iterator(0); while (union_it.next()) |item| { item.fields.deinit(gpa); } ip.decls.deinit(gpa); ip.structs.deinit(gpa); ip.enums.deinit(gpa); ip.unions.deinit(gpa); } pub fn indexToKey(ip: InternPool, index: Index) Key { assert(index != .none); const item = ip.items.get(@enumToInt(index)); const data = item.data; return switch (item.tag) { .simple_type => .{ .simple_type = @intToEnum(SimpleType, data) }, .simple_value => .{ .simple_value = @intToEnum(SimpleValue, data) }, .type_int_signed => .{ .int_type = .{ .signedness = .signed, .bits = @intCast(u16, data), } }, .type_int_unsigned => .{ .int_type = .{ .signedness = .unsigned, .bits = @intCast(u16, data), } }, .type_pointer => .{ .pointer_type = ip.extraData(Pointer, data) }, .type_array => .{ .array_type = ip.extraData(Array, data) }, .type_optional => .{ .optional_type = .{ .payload_type = @intToEnum(Index, data) } }, .type_anyframe => .{ .anyframe_type = .{ .child = @intToEnum(Index, data) } }, .type_error_union => .{ .error_union_type = ip.extraData(ErrorUnion, data) }, .type_error_set => .{ .error_set_type = ip.extraData(ErrorSet, data) }, .type_function => .{ .function_type = ip.extraData(Function, data) }, .type_tuple => .{ .tuple_type = ip.extraData(Tuple, data) }, .type_vector => .{ .vector_type = ip.extraData(Vector, data) }, .type_struct => .{ .struct_type = @intToEnum(StructIndex, data) }, .type_enum => .{ .enum_type = @intToEnum(EnumIndex, data) }, .type_union => .{ .union_type = @intToEnum(UnionIndex, data) }, .int_u64 => .{ .int_u64_value = ip.extraData(U64Value, data) }, .int_i64 => .{ .int_i64_value = ip.extraData(I64Value, data) }, .int_big_positive, .int_big_negative, => .{ .int_big_value = blk: { const big_int = ip.extraData(BigIntInternal, data); break :blk .{ .ty = big_int.ty, .int = .{ .positive = item.tag == .int_big_positive, .limbs = big_int.limbs, }, }; } }, .float_f16 => .{ .float_16_value = @bitCast(f16, @intCast(u16, data)) }, .float_f32 => .{ .float_32_value = @bitCast(f32, data) }, .float_f64 => .{ .float_64_value = ip.extraData(f64, data) }, .float_f80 => .{ .float_80_value = ip.extraData(f80, data) }, .float_f128 => .{ .float_128_value = ip.extraData(f128, data) }, .float_comptime => .{ .float_comptime_value = ip.extraData(f128, data) }, .bytes => .{ .bytes = ip.extraData([]const u8, data) }, .optional_value => .{ .optional_value = ip.extraData(OptionalValue, data) }, .slice => .{ .slice = ip.extraData(Slice, data) }, .aggregate => .{ .aggregate = ip.extraData(Aggregate, data) }, .union_value => .{ .union_value = ip.extraData(UnionValue, data) }, .unknown_value => .{ .unknown_value = .{ .ty = @intToEnum(Index, data) } }, }; } pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { const adapter: KeyAdapter = .{ .ip = ip }; const gop = try ip.map.getOrPutAdapted(gpa, key, adapter); if (gop.found_existing) return @intToEnum(Index, gop.index); const tag: Tag = key.tag(); const data: u32 = switch (key) { .simple_type => |simple| @enumToInt(simple), .simple_value => |simple| @enumToInt(simple), .int_type => |int_ty| int_ty.bits, .optional_type => |optional_ty| @enumToInt(optional_ty.payload_type), .anyframe_type => |anyframe_ty| @enumToInt(anyframe_ty.child), .struct_type => |struct_index| @enumToInt(struct_index), .enum_type => |enum_index| @enumToInt(enum_index), .union_type => |union_index| @enumToInt(union_index), .int_u64_value => |int_val| try ip.addExtra(gpa, int_val), .int_i64_value => |int_val| try ip.addExtra(gpa, int_val), .int_big_value => |big_int_val| try ip.addExtra(gpa, BigIntInternal{ .ty = big_int_val.ty, .limbs = big_int_val.int.limbs, }), .float_16_value => |float_val| @bitCast(u16, float_val), .float_32_value => |float_val| @bitCast(u32, float_val), .unknown_value => |unknown_val| @enumToInt(unknown_val.ty), inline else => |data| try ip.addExtra(gpa, data), // TODO sad stage1 noises :( }; try ip.items.append(gpa, .{ .tag = tag, .data = data, }); return @intToEnum(Index, ip.items.len - 1); } pub fn contains(ip: InternPool, key: Key) ?Index { const adapter: KeyAdapter = .{ .ip = &ip }; const index = ip.map.getIndexAdapted(key, adapter) orelse return null; return @intToEnum(Index, index); } pub fn getDecl(ip: InternPool, index: InternPool.DeclIndex) *InternPool.Decl { var decls = ip.decls; return decls.at(@enumToInt(index)); } pub fn getStruct(ip: InternPool, index: InternPool.StructIndex) *InternPool.Struct { var structs = ip.structs; return structs.at(@enumToInt(index)); } pub fn getEnum(ip: InternPool, index: InternPool.EnumIndex) *InternPool.Enum { var enums = ip.enums; return enums.at(@enumToInt(index)); } pub fn getUnion(ip: InternPool, index: InternPool.UnionIndex) *InternPool.Union { var unions = ip.unions; return unions.at(@enumToInt(index)); } pub fn createDecl(ip: *InternPool, gpa: Allocator, decl: InternPool.Decl) Allocator.Error!InternPool.DeclIndex { try ip.decls.append(gpa, decl); return @intToEnum(InternPool.DeclIndex, ip.decls.count() - 1); } pub fn createStruct(ip: *InternPool, gpa: Allocator, struct_info: InternPool.Struct) Allocator.Error!InternPool.StructIndex { try ip.structs.append(gpa, struct_info); return @intToEnum(InternPool.StructIndex, ip.structs.count() - 1); } pub fn createEnum(ip: *InternPool, gpa: Allocator, enum_info: InternPool.Enum) Allocator.Error!InternPool.EnumIndex { try ip.enums.append(gpa, enum_info); return @intToEnum(InternPool.EnumIndex, ip.enums.count() - 1); } pub fn createUnion(ip: *InternPool, gpa: Allocator, union_info: InternPool.Union) Allocator.Error!InternPool.UnionIndex { try ip.unions.append(gpa, union_info); return @intToEnum(InternPool.UnionIndex, ip.unions.count() - 1); } fn addExtra(ip: *InternPool, gpa: Allocator, extra: anytype) Allocator.Error!u32 { const T = @TypeOf(extra); comptime if (@sizeOf(T) <= 4) { @compileError(@typeName(T) ++ " fits into a u32! Consider directly storing this extra in Item's data field"); }; const result = @intCast(u32, ip.extra.items.len); var managed = ip.extra.toManaged(gpa); defer ip.extra = managed.moveToUnmanaged(); try encoding.encode(&managed, T, extra); return result; } fn extraData(ip: InternPool, comptime T: type, index: usize) T { var bytes: []const u8 = ip.extra.items[index..]; return encoding.decode(&bytes, T); } const KeyAdapter = struct { ip: *const InternPool, pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool { _ = b_void; return a.eql(ctx.ip.indexToKey(@intToEnum(Index, b_map_index))); } pub fn hash(ctx: @This(), a: Key) u32 { _ = ctx; return a.hash(); } }; fn deepEql(a: anytype, b: @TypeOf(a)) bool { const T = @TypeOf(a); switch (@typeInfo(T)) { .Struct => |info| { if (info.layout == .Packed and comptime std.meta.trait.hasUniqueRepresentation(T)) { return std.mem.eql(u8, std.mem.asBytes(&a), std.mem.asBytes(&b)); } inline for (info.fields) |field_info| { if (!deepEql(@field(a, field_info.name), @field(b, field_info.name))) return false; } return true; }, .Union => |info| { const UnionTag = info.tag_type.?; const tag_a = std.meta.activeTag(a); const tag_b = std.meta.activeTag(b); if (tag_a != tag_b) return false; inline for (info.fields) |field_info| { if (@field(UnionTag, field_info.name) == tag_a) { return deepEql(@field(a, field_info.name), @field(b, field_info.name)); } } return false; }, .Pointer => |info| switch (info.size) { .One => return deepEql(a.*, b.*), .Slice => { if (a.len != b.len) return false; var i: usize = 0; while (i < a.len) : (i += 1) { if (!deepEql(a[i], b[i])) return false; } return true; }, .Many, .C, => @compileError("Unable to equality compare pointer " ++ @typeName(T)), }, .Float => { const I = std.meta.Int(.unsigned, @bitSizeOf(T)); return @bitCast(I, a) == @bitCast(I, b); }, .Bool, .Int, .Enum, => return a == b, else => @compileError("Unable to equality compare type " ++ @typeName(T)), } } fn deepHash(hasher: anytype, key: anytype) void { const T = @TypeOf(key); switch (@typeInfo(T)) { .Int => { if (comptime std.meta.trait.hasUniqueRepresentation(Tuple)) { hasher.update(std.mem.asBytes(&key)); } else { const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(T), 8) catch unreachable; hasher.update(std.mem.asBytes(&key)[0..byte_size]); } }, .Bool => deepHash(hasher, @boolToInt(key)), .Enum => deepHash(hasher, @enumToInt(key)), .Float => |info| deepHash(hasher, switch (info.bits) { 16 => @bitCast(u16, key), 32 => @bitCast(u32, key), 64 => @bitCast(u64, key), 80 => @bitCast(u80, key), 128 => @bitCast(u128, key), else => unreachable, }), .Pointer => |info| switch (info.size) { .One => { deepHash(hasher, key.*); }, .Slice => { if (info.child == u8) { hasher.update(key); } else { for (key) |item| { deepHash(hasher, item); } } }, .Many, .C, => @compileError("Unable to hash pointer " ++ @typeName(T)), }, .Struct => |info| { if (info.layout == .Packed and comptime std.meta.trait.hasUniqueRepresentation(T)) { hasher.update(std.mem.asBytes(&key)); } else { inline for (info.fields) |field| { deepHash(hasher, @field(key, field.name)); } } }, .Union => |info| { const TagType = info.tag_type.?; const tag = std.meta.activeTag(key); deepHash(hasher, tag); inline for (info.fields) |field| { if (@field(TagType, field.name) == tag) { deepHash(hasher, @field(key, field.name)); break; } } }, else => @compileError("Unable to hash type " ++ @typeName(T)), } } // --------------------------------------------- // UTILITY // --------------------------------------------- pub fn cast(ip: *InternPool, gpa: Allocator, destination_ty: Index, source_ty: Index, target: std.Target) Allocator.Error!Index { return resolvePeerTypes(ip, gpa, &.{ destination_ty, source_ty }, target); } pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, target: std.Target) Allocator.Error!Index { switch (types.len) { 0 => return Index.noreturn_type, 1 => return types[0], else => {}, } var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); var arena = arena_allocator.allocator(); var chosen = types[0]; // If this is non-null then it does the following thing, depending on the chosen zigTypeTag(). // * ErrorSet: this is an override // * ErrorUnion: this is an override of the error set only // * other: at the end we make an ErrorUnion with the other thing and this var err_set_ty: Index = Index.none; var any_are_null = false; var seen_const = false; var convert_to_slice = false; var chosen_i: usize = 0; for (types[1..], 1..) |candidate, candidate_i| { if (candidate == chosen) continue; const candidate_key: Key = ip.indexToKey(candidate); const chosen_key = ip.indexToKey(chosen); // If the candidate can coerce into our chosen type, we're done. // If the chosen type can coerce into the candidate, use that. if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen, candidate, true, target)) == .ok) { continue; } if ((try ip.coerceInMemoryAllowed(gpa, arena, candidate, chosen, true, target)) == .ok) { chosen = candidate; chosen_i = candidate_i; continue; } switch (candidate_key) { .simple_type => |candidate_simple| switch (candidate_simple) { .f16, .f32, .f64, .f80, .f128 => switch (chosen_key) { .simple_type => |chosen_simple| switch (chosen_simple) { .f16, .f32, .f64, .f80, .f128 => { if (chosen_key.floatBits(target) < candidate_key.floatBits(target)) { chosen = candidate; chosen_i = candidate_i; } continue; }, .comptime_int, .comptime_float => { chosen = candidate; chosen_i = candidate_i; continue; }, else => {}, }, else => {}, }, .usize, .isize, .c_char, .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong, .c_longdouble, => switch (chosen_key) { .simple_type => |chosen_simple| switch (chosen_simple) { .usize, .isize, .c_char, .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong, .c_longdouble, => { const chosen_bits = chosen_key.intInfo(target, ip.*).bits; const candidate_bits = candidate_key.intInfo(target, ip.*).bits; if (chosen_bits < candidate_bits) { chosen = candidate; chosen_i = candidate_i; } continue; }, .comptime_int => { chosen = candidate; chosen_i = candidate_i; continue; }, else => {}, }, .int_type => |chosen_info| { if (chosen_info.bits < candidate_key.intInfo(target, ip.*).bits) { chosen = candidate; chosen_i = candidate_i; } continue; }, .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, else => {}, }, .noreturn, .undefined_type => continue, .comptime_int => switch (chosen_key) { .simple_type => |chosen_simple| switch (chosen_simple) { .f16, .f32, .f64, .f80, .f128, .usize, .isize, .c_char, .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong, .c_longdouble, .comptime_float, => continue, .comptime_int => unreachable, else => {}, }, .int_type => continue, .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, else => {}, }, .comptime_float => switch (chosen_key) { .simple_type => |chosen_simple| switch (chosen_simple) { .f16, .f32, .f64, .f80, .f128 => continue, .comptime_int => { chosen = candidate; chosen_i = candidate_i; continue; }, .comptime_float => unreachable, else => {}, }, else => {}, }, .null_type => { any_are_null = true; continue; }, else => {}, }, .int_type => |candidate_info| switch (chosen_key) { .simple_type => |chosen_simple| switch (chosen_simple) { .usize, .isize, .c_char, .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong, .c_longdouble, => { const chosen_bits = chosen_key.intInfo(target, ip.*).bits; const candidate_bits = candidate_key.intInfo(target, ip.*).bits; if (chosen_bits < candidate_bits) { chosen = candidate; chosen_i = candidate_i; } continue; }, .comptime_int => { chosen = candidate; chosen_i = candidate_i; continue; }, else => {}, }, .int_type => |chosen_info| { if (chosen_info.bits < candidate_info.bits) { chosen = candidate; chosen_i = candidate_i; } continue; }, .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, else => {}, }, .pointer_type => |candidate_info| switch (chosen_key) { .simple_type => |chosen_simple| switch (chosen_simple) { .comptime_int => { if (candidate_info.size == .C) { chosen = candidate; chosen_i = candidate_i; continue; } }, else => {}, }, .pointer_type => |chosen_info| { seen_const = seen_const or chosen_info.is_const or candidate_info.is_const; const candidate_elem_info = ip.indexToKey(candidate_info.elem_type); const chosen_elem_info = ip.indexToKey(chosen_info.elem_type); // *[N]T to [*]T // *[N]T to []T if ((candidate_info.size == .Many or candidate_info.size == .Slice) and chosen_info.size == .One and chosen_elem_info == .array_type) { // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` convert_to_slice = false; chosen = candidate; chosen_i = candidate_i; continue; } if (candidate_info.size == .One and candidate_elem_info == .array_type and (chosen_info.size == .Many or chosen_info.size == .Slice)) { // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` convert_to_slice = false; continue; } // *[N]T and *[M]T // Verify both are single-pointers to arrays. // Keep the one whose element type can be coerced into. if (chosen_info.size == .One and candidate_info.size == .One and chosen_elem_info == .array_type and candidate_elem_info == .array_type) { const chosen_elem_ty = chosen_elem_info.array_type.child; const cand_elem_ty = candidate_elem_info.array_type.child; const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_elem_ty, cand_elem_ty, chosen_info.is_const, target); if (chosen_ok) { convert_to_slice = true; continue; } const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, cand_elem_ty, chosen_elem_ty, candidate_info.is_const, target); if (cand_ok) { convert_to_slice = true; chosen = candidate; chosen_i = candidate_i; continue; } // They're both bad. Report error. // In the future we probably want to use the // coerceInMemoryAllowed error reporting mechanism, // however, for now we just fall through for the // "incompatible types" error below. } // [*c]T and any other pointer size // Whichever element type can coerce to the other one, is // the one we will keep. If they're both OK then we keep the // C pointer since it matches both single and many pointers. if (candidate_info.size == .C or chosen_info.size == .C) { const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, candidate_info.elem_type, chosen_info.elem_type, candidate_info.is_const, target); const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.elem_type, candidate_info.elem_type, chosen_info.is_const, target); if (cand_ok) { if (!chosen_ok or chosen_info.size != .C) { chosen = candidate; chosen_i = candidate_i; } continue; } else { if (chosen_ok) continue; // They're both bad. Report error. // In the future we probably want to use the // coerceInMemoryAllowed error reporting mechanism, // however, for now we just fall through for the // "incompatible types" error below. } } }, .int_type => { if (candidate_info.size == .C) { chosen = candidate; chosen_i = candidate_i; continue; } }, .optional_type => |chosen_info| switch (ip.indexToKey(chosen_info.payload_type)) { .pointer_type => |chosen_ptr_info| { seen_const = seen_const or chosen_ptr_info.is_const or candidate_info.is_const; // *[N]T to ?![*]T // *[N]T to ?![]T if (candidate_info.size == .One and ip.indexToKey(candidate_info.elem_type) == .array_type and (chosen_ptr_info.size == .Many or chosen_ptr_info.size == .Slice)) { continue; } }, else => {}, }, .error_union_type => |chosen_info| { const chosen_ptr_key = ip.indexToKey(chosen_info.payload_type); if (chosen_ptr_key == .pointer_type) { const chosen_ptr_info = chosen_ptr_key.pointer_type; seen_const = seen_const or chosen_ptr_info.is_const or candidate_info.is_const; // *[N]T to E![*]T // *[N]T to E![]T if (candidate_info.size == .One and (chosen_ptr_info.size == .Many or chosen_ptr_info.size == .Slice) and ip.indexToKey(candidate_info.elem_type) == .array_type) { continue; } } }, .function_type => |chosen_info| { if (candidate_info.is_const) { const candidate_elem_key = ip.indexToKey(candidate_info.elem_type); if (candidate_elem_key == .function_type and .ok == try ip.coerceInMemoryAllowedFns(gpa, arena, chosen_info, candidate_elem_key.function_type, target)) { chosen = candidate; chosen_i = candidate_i; continue; } } }, else => {}, }, .array_type => switch (chosen_key) { .vector_type => continue, else => {}, }, .optional_type => |candidate_info| { if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen, candidate_info.payload_type, true, target)) == .ok) { seen_const = seen_const or ip.indexToKey(candidate_info.payload_type).isConstPtr(); any_are_null = true; continue; } seen_const = seen_const or chosen_key.isConstPtr(); any_are_null = false; chosen = candidate; chosen_i = candidate_i; continue; }, .vector_type => switch (chosen_key) { .array_type => { chosen = candidate; chosen_i = candidate_i; continue; }, else => {}, }, else => {}, } switch (chosen_key) { .simple_type => |simple| switch (simple) { .noreturn, .undefined_type, => { chosen = candidate; chosen_i = candidate_i; continue; }, .null_type => { any_are_null = true; chosen = candidate; chosen_i = candidate_i; continue; }, else => {}, }, .optional_type => |chosen_info| { if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.payload_type, candidate, true, target)) == .ok) { continue; } if ((try ip.coerceInMemoryAllowed(gpa, arena, candidate, chosen_info.payload_type, true, target)) == .ok) { any_are_null = true; chosen = candidate; chosen_i = candidate_i; continue; } }, .error_union_type => |chosen_info| { if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.payload_type, candidate, true, target)) == .ok) { continue; } }, else => {}, } return Index.none; } if (chosen == .none) return chosen; const chosen_key = ip.indexToKey(chosen); if (convert_to_slice) { // turn *[N]T => []T const chosen_elem_key = ip.indexToKey(chosen_key.pointer_type.elem_type); var info = chosen_key.pointer_type; info.sentinel = chosen_elem_key.sentinel(); info.size = .Slice; info.is_const = seen_const or chosen_elem_key.isConstPtr(); info.elem_type = chosen_elem_key.elemType2(); const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; const set_ty = if (err_set_ty != .none) err_set_ty else return opt_ptr_ty; return try ip.get(gpa, .{ .error_union_type = .{ .error_set_type = set_ty, .payload_type = opt_ptr_ty, } }); } if (seen_const) { // turn []T => []const T switch (chosen_key) { .error_union_type => |error_union_info| { var info: Pointer = ip.indexToKey(error_union_info.payload_type).pointer_type; info.is_const = true; const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; const set_ty = if (err_set_ty != .none) err_set_ty else error_union_info.error_set_type; return try ip.get(gpa, .{ .error_union_type = .{ .error_set_type = set_ty, .payload_type = opt_ptr_ty, } }); }, .pointer_type => |pointer_info| { var info = pointer_info; info.is_const = true; const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; const set_ty = if (err_set_ty != .none) err_set_ty else return opt_ptr_ty; return try ip.get(gpa, .{ .error_union_type = .{ .error_set_type = set_ty, .payload_type = opt_ptr_ty, } }); }, else => return chosen, } } if (any_are_null) { const opt_ty = switch (chosen_key) { .simple_type => |simple| switch (simple) { .null_type => chosen, else => try ip.get(gpa, .{ .optional_type = .{ .payload_type = chosen } }), }, .optional_type => chosen, else => try ip.get(gpa, .{ .optional_type = .{ .payload_type = chosen } }), }; const set_ty = if (err_set_ty != .none) err_set_ty else return opt_ty; return try ip.get(gpa, .{ .error_union_type = .{ .error_set_type = set_ty, .payload_type = opt_ty, } }); } return chosen; } const InMemoryCoercionResult = union(enum) { ok, no_match: Pair, int_not_coercible: IntMismatch, error_union_payload: PairAndChild, array_len: IntPair, array_sentinel: Sentinel, array_elem: PairAndChild, vector_len: IntPair, vector_elem: PairAndChild, optional_shape: Pair, optional_child: PairAndChild, from_anyerror, missing_error: []const Index, /// true if wanted is var args fn_var_args: bool, /// true if wanted is generic fn_generic: bool, fn_param_count: IntPair, fn_param_noalias: IntPair, fn_param_comptime: ComptimeParam, fn_param: Param, fn_cc: CC, fn_return_type: PairAndChild, ptr_child: PairAndChild, ptr_addrspace: AddressSpace, ptr_sentinel: Sentinel, ptr_size: Size, ptr_qualifiers: Qualifiers, ptr_allowzero: Pair, ptr_bit_range: BitRange, ptr_alignment: IntPair, double_ptr_to_anyopaque: Pair, const Pair = struct { actual: Index, // type wanted: Index, // type }; const PairAndChild = struct { child: *InMemoryCoercionResult, actual: Index, // type wanted: Index, // type }; const Param = struct { child: *InMemoryCoercionResult, actual: Index, // type wanted: Index, // type index: u64, }; const ComptimeParam = struct { index: u64, wanted: bool, }; const Sentinel = struct { // Index.none indicates no sentinel actual: Index, // value wanted: Index, // value ty: Index, }; const IntMismatch = struct { actual_signedness: std.builtin.Signedness, wanted_signedness: std.builtin.Signedness, actual_bits: u16, wanted_bits: u16, }; const IntPair = struct { actual: u64, wanted: u64, }; const Size = struct { actual: std.builtin.Type.Pointer.Size, wanted: std.builtin.Type.Pointer.Size, }; const Qualifiers = struct { actual_const: bool, wanted_const: bool, actual_volatile: bool, wanted_volatile: bool, }; const AddressSpace = struct { actual: std.builtin.AddressSpace, wanted: std.builtin.AddressSpace, }; const CC = struct { actual: std.builtin.CallingConvention, wanted: std.builtin.CallingConvention, }; const BitRange = struct { actual_host: u16, wanted_host: u16, actual_offset: u16, wanted_offset: u16, }; fn dupe(child: *const InMemoryCoercionResult, arena: Allocator) !*InMemoryCoercionResult { const res = try arena.create(InMemoryCoercionResult); res.* = child.*; return res; } }; /// If types have the same representation in runtime memory /// * int/float: same number of bits /// * pointer: see `coerceInMemoryAllowedPtrs` /// * error union: coerceable error set and payload /// * error set: sub-set to super-set /// * array: same shape and coerceable child fn coerceInMemoryAllowed( ip: *InternPool, gpa: Allocator, arena: Allocator, dest_ty: Index, src_ty: Index, dest_is_const: bool, target: std.Target, ) Allocator.Error!InMemoryCoercionResult { if (dest_ty == src_ty) return .ok; if (dest_ty == .unknown_type or src_ty == .unknown_type) return .ok; const dest_key = ip.indexToKey(dest_ty); const src_key = ip.indexToKey(src_ty); assert(dest_key.typeOf() == .type_type); assert(src_key.typeOf() == .type_type); const dest_tag = dest_key.zigTypeTag(); const src_tag = src_key.zigTypeTag(); if (dest_tag != src_tag) { return InMemoryCoercionResult{ .no_match = .{ .actual = dest_ty, .wanted = src_ty, } }; } switch (dest_tag) { .Int => { const dest_info = dest_key.intInfo(target, ip.*); const src_info = src_key.intInfo(target, ip.*); if (dest_info.signedness == src_info.signedness and dest_info.bits == src_info.bits) return .ok; if ((src_info.signedness == dest_info.signedness and dest_info.bits < src_info.bits) or // small enough unsigned ints can get casted to large enough signed ints (dest_info.signedness == .signed and (src_info.signedness == .unsigned or dest_info.bits <= src_info.bits)) or (dest_info.signedness == .unsigned and src_info.signedness == .signed)) { return InMemoryCoercionResult{ .int_not_coercible = .{ .actual_signedness = src_info.signedness, .wanted_signedness = dest_info.signedness, .actual_bits = src_info.bits, .wanted_bits = dest_info.bits, } }; } return .ok; }, .Float => { const dest_bits = dest_key.floatBits(target); const src_bits = src_key.floatBits(target); if (dest_bits == src_bits) return .ok; return InMemoryCoercionResult{ .no_match = .{ .actual = dest_ty, .wanted = src_ty, } }; }, .Pointer => { return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_key, src_key, dest_is_const, target); }, .Optional => { // Pointer-like Optionals const maybe_dest_ptr_ty = try ip.optionalPtrTy(dest_key); const maybe_src_ptr_ty = try ip.optionalPtrTy(src_key); if (maybe_dest_ptr_ty != .none and maybe_src_ptr_ty != .none) { const dest_ptr_info = ip.indexToKey(maybe_dest_ptr_ty); const src_ptr_info = ip.indexToKey(maybe_src_ptr_ty); return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_ptr_info, src_ptr_info, dest_is_const, target); } if (maybe_dest_ptr_ty != maybe_src_ptr_ty) { return InMemoryCoercionResult{ .optional_shape = .{ .actual = src_ty, .wanted = dest_ty, } }; } const dest_child_type = dest_key.optional_type.payload_type; const src_child_type = src_key.optional_type.payload_type; const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_child_type, src_child_type, dest_is_const, target); if (child != .ok) { return InMemoryCoercionResult{ .optional_child = .{ .child = try child.dupe(arena), .actual = src_child_type, .wanted = dest_child_type, } }; } return .ok; }, .Fn => { return try ip.coerceInMemoryAllowedFns(gpa, arena, dest_key.function_type, src_key.function_type, target); }, .ErrorUnion => { const dest_payload = dest_key.error_union_type.payload_type; const src_payload = src_key.error_union_type.payload_type; const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_payload, src_payload, dest_is_const, target); if (child != .ok) { return InMemoryCoercionResult{ .error_union_payload = .{ .child = try child.dupe(arena), .actual = src_payload, .wanted = dest_payload, } }; } const dest_set = dest_key.error_union_type.error_set_type; const src_set = src_key.error_union_type.error_set_type; return try ip.coerceInMemoryAllowed(gpa, arena, dest_set, src_set, dest_is_const, target); }, .ErrorSet => { return try ip.coerceInMemoryAllowedErrorSets(gpa, arena, dest_ty, src_ty); }, .Array => { const dest_info = dest_key.array_type; const src_info = src_key.array_type; if (dest_info.len != src_info.len) { return InMemoryCoercionResult{ .array_len = .{ .actual = src_info.len, .wanted = dest_info.len, } }; } const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.child, src_info.child, dest_is_const, target); if (child != .ok) { return InMemoryCoercionResult{ .array_elem = .{ .child = try child.dupe(arena), .actual = src_info.child, .wanted = dest_info.child, } }; } const ok_sent = dest_info.sentinel == Index.none or (src_info.sentinel != Index.none and dest_info.sentinel == src_info.sentinel // is this enough for a value equality check? ); if (!ok_sent) { return InMemoryCoercionResult{ .array_sentinel = .{ .actual = src_info.sentinel, .wanted = dest_info.sentinel, .ty = dest_info.child, } }; } return .ok; }, .Vector => { const dest_len = dest_key.vector_type.len; const src_len = src_key.vector_type.len; if (dest_len != src_len) { return InMemoryCoercionResult{ .vector_len = .{ .actual = src_len, .wanted = dest_len, } }; } const dest_elem_ty = dest_key.vector_type.child; const src_elem_ty = src_key.vector_type.child; const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_elem_ty, src_elem_ty, dest_is_const, target); if (child != .ok) { return InMemoryCoercionResult{ .vector_elem = .{ .child = try child.dupe(arena), .actual = src_elem_ty, .wanted = dest_elem_ty, } }; } return .ok; }, else => { return InMemoryCoercionResult{ .no_match = .{ .actual = dest_ty, .wanted = src_ty, } }; }, } } fn coerceInMemoryAllowedErrorSets( ip: *InternPool, gpa: Allocator, arena: Allocator, dest_ty: Index, src_ty: Index, ) !InMemoryCoercionResult { if (dest_ty == src_ty) return .ok; const dest_key = ip.indexToKey(dest_ty); assert(dest_key.zigTypeTag() == .ErrorSet); if (dest_ty == .anyerror_type) return .ok; const src_key = ip.indexToKey(src_ty); assert(src_key.zigTypeTag() == .ErrorSet); if (src_ty == .anyerror_type) return .from_anyerror; var missing_error_buf = std.ArrayListUnmanaged(Index){}; defer missing_error_buf.deinit(gpa); for (src_key.error_set_type.names) |name| { if (std.mem.indexOfScalar(Index, dest_key.error_set_type.names, name) == null) { try missing_error_buf.append(gpa, name); } } if (missing_error_buf.items.len == 0) return .ok; return InMemoryCoercionResult{ .missing_error = try arena.dupe(Index, missing_error_buf.items), }; } fn coerceInMemoryAllowedFns( ip: *InternPool, gpa: Allocator, arena: Allocator, dest_info: Function, src_info: Function, target: std.Target, ) Allocator.Error!InMemoryCoercionResult { if (dest_info.is_var_args != src_info.is_var_args) { return InMemoryCoercionResult{ .fn_var_args = dest_info.is_var_args }; } if (dest_info.is_generic != src_info.is_generic) { return InMemoryCoercionResult{ .fn_generic = dest_info.is_generic }; } if (dest_info.calling_convention != src_info.calling_convention) { return InMemoryCoercionResult{ .fn_cc = .{ .actual = src_info.calling_convention, .wanted = dest_info.calling_convention, } }; } if (src_info.return_type != Index.noreturn_type) { const rt = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.return_type, src_info.return_type, true, target); if (rt != .ok) { return InMemoryCoercionResult{ .fn_return_type = .{ .child = try rt.dupe(arena), .actual = src_info.return_type, .wanted = dest_info.return_type, } }; } } if (dest_info.args.len != src_info.args.len) { return InMemoryCoercionResult{ .fn_param_count = .{ .actual = src_info.args.len, .wanted = dest_info.args.len, } }; } if (!dest_info.args_is_noalias.eql(src_info.args_is_noalias)) { return InMemoryCoercionResult{ .fn_param_noalias = .{ .actual = src_info.args_is_noalias.mask, .wanted = dest_info.args_is_noalias.mask, } }; } if (!dest_info.args_is_comptime.eql(src_info.args_is_comptime)) { const index = dest_info.args_is_comptime.xorWith(src_info.args_is_comptime).findFirstSet().?; return InMemoryCoercionResult{ .fn_param_comptime = .{ .index = index, .wanted = dest_info.args_is_comptime.isSet(index), } }; } for (dest_info.args, src_info.args, 0..) |dest_arg_ty, src_arg_ty, i| { // Note: Cast direction is reversed here. const param = try ip.coerceInMemoryAllowed(gpa, arena, src_arg_ty, dest_arg_ty, true, target); if (param != .ok) { return InMemoryCoercionResult{ .fn_param = .{ .child = try param.dupe(arena), .actual = src_arg_ty, .wanted = dest_arg_ty, .index = i, } }; } } return .ok; } /// If pointers have the same representation in runtime memory /// * `const` attribute can be gained /// * `volatile` attribute can be gained /// * `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer) but only if dest_is_const /// * alignment can be decreased /// * bit offset attributes must match exactly /// * `*`/`[*]` must match exactly, but `[*c]` matches either one /// * sentinel-terminated pointers can coerce into `[*]` fn coerceInMemoryAllowedPtrs( ip: *InternPool, gpa: Allocator, arena: Allocator, dest_ty: Index, src_ty: Index, dest_ptr_info: Key, src_ptr_info: Key, dest_is_const: bool, target: std.Target, ) Allocator.Error!InMemoryCoercionResult { const dest_info = dest_ptr_info.pointer_type; const src_info = src_ptr_info.pointer_type; const ok_ptr_size = src_info.size == dest_info.size or src_info.size == .C or dest_info.size == .C; if (!ok_ptr_size) { return InMemoryCoercionResult{ .ptr_size = .{ .actual = src_info.size, .wanted = dest_info.size, } }; } const ok_cv_qualifiers = (!src_info.is_const or dest_info.is_const) and (!src_info.is_volatile or dest_info.is_volatile); if (!ok_cv_qualifiers) { return InMemoryCoercionResult{ .ptr_qualifiers = .{ .actual_const = src_info.is_const, .wanted_const = dest_info.is_const, .actual_volatile = src_info.is_volatile, .wanted_volatile = dest_info.is_volatile, } }; } if (dest_info.address_space != src_info.address_space) { return InMemoryCoercionResult{ .ptr_addrspace = .{ .actual = src_info.address_space, .wanted = dest_info.address_space, } }; } const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, src_info.elem_type, dest_info.is_const, target); if (child != .ok) { return InMemoryCoercionResult{ .ptr_child = .{ .child = try child.dupe(arena), .actual = src_info.elem_type, .wanted = dest_info.elem_type, } }; } const dest_allow_zero = dest_ptr_info.ptrAllowsZero(ip); const src_allow_zero = src_ptr_info.ptrAllowsZero(ip); const ok_allows_zero = (dest_allow_zero and (src_allow_zero or dest_is_const)) or (!dest_allow_zero and !src_allow_zero); if (!ok_allows_zero) { return InMemoryCoercionResult{ .ptr_allowzero = .{ .actual = src_ty, .wanted = dest_ty, } }; } if (src_info.host_size != dest_info.host_size or src_info.bit_offset != dest_info.bit_offset) { return InMemoryCoercionResult{ .ptr_bit_range = .{ .actual_host = src_info.host_size, .wanted_host = dest_info.host_size, .actual_offset = src_info.bit_offset, .wanted_offset = dest_info.bit_offset, } }; } const ok_sent = dest_info.sentinel == .none or src_info.size == .C or dest_info.sentinel == src_info.sentinel; // is this enough for a value equality check? if (!ok_sent) { return InMemoryCoercionResult{ .ptr_sentinel = .{ .actual = src_info.sentinel, .wanted = dest_info.sentinel, .ty = dest_info.elem_type, } }; } // If both pointers have alignment 0, it means they both want ABI alignment. // In this case, if they share the same child type, no need to resolve // pointee type alignment. Otherwise both pointee types must have their alignment // resolved and we compare the alignment numerically. alignment: { if (src_info.alignment == 0 and dest_info.alignment == 0 and dest_info.elem_type == src_info.elem_type // is this enough for a value equality check? ) { break :alignment; } // const src_align = if (src_info.alignment != 0) // src_info.alignment // else // src_info.elem_type.abiAlignment(target); // const dest_align = if (dest_info.alignment != 0) // dest_info.alignment // else // dest_info.elem_type.abiAlignment(target); // if (dest_align > src_align) { // return InMemoryCoercionResult{ .ptr_alignment = .{ // .actual = src_align, // .wanted = dest_align, // } }; // } break :alignment; } return .ok; } fn optionalPtrTy( ip: InternPool, ty: Key, ) !Index { switch (ty) { .optional_type => |optional_info| { const child_type = optional_info.payload_type; const child_key = ip.indexToKey(child_type); if (child_key != .pointer_type) return Index.none; const child_ptr_key = child_key.pointer_type; switch (child_ptr_key.size) { .Slice, .C => return Index.none, .Many, .One => { if (child_ptr_key.is_allowzero) return Index.none; // optionals of zero sized types behave like bools, not pointers if (child_key.onePossibleValue(ip) != Index.none) return Index.none; return child_type; }, } }, else => unreachable, } } /// will panic in during testing else will return `value` inline fn panicOrElse(message: []const u8, value: anytype) @TypeOf(value) { if (builtin.is_test) { @panic(message); } return value; } // --------------------------------------------- // TESTS // --------------------------------------------- test "simple types" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const null_type = try ip.get(gpa, .{ .simple_type = .null_type }); const undefined_type = try ip.get(gpa, .{ .simple_type = .undefined_type }); const enum_literal_type = try ip.get(gpa, .{ .simple_type = .enum_literal_type }); const undefined_value = try ip.get(gpa, .{ .simple_value = .undefined_value }); const void_value = try ip.get(gpa, .{ .simple_value = .void_value }); const unreachable_value = try ip.get(gpa, .{ .simple_value = .unreachable_value }); const null_value = try ip.get(gpa, .{ .simple_value = .null_value }); const bool_true = try ip.get(gpa, .{ .simple_value = .bool_true }); const bool_false = try ip.get(gpa, .{ .simple_value = .bool_false }); try expectFmt("@TypeOf(null)", "{}", .{null_type.fmt(ip)}); try expectFmt("@TypeOf(undefined)", "{}", .{undefined_type.fmt(ip)}); try expectFmt("@TypeOf(.enum_literal)", "{}", .{enum_literal_type.fmt(ip)}); try expectFmt("undefined", "{}", .{undefined_value.fmt(ip)}); try expectFmt("{}", "{}", .{void_value.fmt(ip)}); try expectFmt("unreachable", "{}", .{unreachable_value.fmt(ip)}); try expectFmt("null", "{}", .{null_value.fmt(ip)}); try expectFmt("true", "{}", .{bool_true.fmt(ip)}); try expectFmt("false", "{}", .{bool_false.fmt(ip)}); } test "int type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const i32_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .signed, .bits = 32 } }); const i16_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .signed, .bits = 16 } }); const u7_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .unsigned, .bits = 7 } }); const another_i32_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .signed, .bits = 32 } }); try expect(i32_type == another_i32_type); try expect(i32_type != u7_type); try expect(i16_type != another_i32_type); try expect(i16_type != u7_type); try expectFmt("i32", "{}", .{i32_type.fmt(ip)}); try expectFmt("i16", "{}", .{i16_type.fmt(ip)}); try expectFmt("u7", "{}", .{u7_type.fmt(ip)}); } test "int value" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const unsigned_zero_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = 0 } }); const unsigned_one_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = 1 } }); const signed_zero_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .i64_type, .int = 0 } }); const signed_one_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .i64_type, .int = 1 } }); const u64_max_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = std.math.maxInt(u64) } }); const i64_max_value = try ip.get(gpa, .{ .int_i64_value = .{ .ty = .i64_type, .int = std.math.maxInt(i64) } }); const i64_min_value = try ip.get(gpa, .{ .int_i64_value = .{ .ty = .i64_type, .int = std.math.minInt(i64) } }); try expect(unsigned_zero_value != unsigned_one_value); try expect(unsigned_one_value != signed_zero_value); try expect(signed_zero_value != signed_one_value); try expect(signed_one_value != u64_max_value); try expect(u64_max_value != i64_max_value); try expect(i64_max_value != i64_min_value); try expectFmt("0", "{}", .{unsigned_zero_value.fmt(ip)}); try expectFmt("1", "{}", .{unsigned_one_value.fmt(ip)}); try expectFmt("0", "{}", .{signed_zero_value.fmt(ip)}); try expectFmt("1", "{}", .{signed_one_value.fmt(ip)}); try expectFmt("18446744073709551615", "{}", .{u64_max_value.fmt(ip)}); try expectFmt("9223372036854775807", "{}", .{i64_max_value.fmt(ip)}); try expectFmt("-9223372036854775808", "{}", .{i64_min_value.fmt(ip)}); } test "big int value" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); var result = try std.math.big.int.Managed.init(gpa); defer result.deinit(); var a = try std.math.big.int.Managed.initSet(gpa, 2); defer a.deinit(); try result.pow(&a, 128); const positive_big_int_value = try ip.get(gpa, .{ .int_big_value = .{ .ty = .comptime_int_type, .int = result.toConst(), } }); const negative_big_int_value = try ip.get(gpa, .{ .int_big_value = .{ .ty = .comptime_int_type, .int = result.toConst().negate(), } }); try expectFmt("340282366920938463463374607431768211456", "{}", .{positive_big_int_value.fmt(ip)}); try expectFmt("-340282366920938463463374607431768211456", "{}", .{negative_big_int_value.fmt(ip)}); } test "float type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const f16_type = try ip.get(gpa, .{ .simple_type = .f16 }); const f32_type = try ip.get(gpa, .{ .simple_type = .f32 }); const f64_type = try ip.get(gpa, .{ .simple_type = .f64 }); const f80_type = try ip.get(gpa, .{ .simple_type = .f80 }); const f128_type = try ip.get(gpa, .{ .simple_type = .f128 }); const another_f32_type = try ip.get(gpa, .{ .simple_type = .f32 }); const another_f64_type = try ip.get(gpa, .{ .simple_type = .f64 }); try expect(f16_type != f32_type); try expect(f32_type != f64_type); try expect(f64_type != f80_type); try expect(f80_type != f128_type); try expect(f32_type == another_f32_type); try expect(f64_type == another_f64_type); try expectFmt("f16", "{}", .{f16_type.fmt(ip)}); try expectFmt("f32", "{}", .{f32_type.fmt(ip)}); try expectFmt("f64", "{}", .{f64_type.fmt(ip)}); try expectFmt("f80", "{}", .{f80_type.fmt(ip)}); try expectFmt("f128", "{}", .{f128_type.fmt(ip)}); } test "float value" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const f16_value = try ip.get(gpa, .{ .float_16_value = 0.25 }); const f32_value = try ip.get(gpa, .{ .float_32_value = 0.5 }); const f64_value = try ip.get(gpa, .{ .float_64_value = 1.0 }); const f80_value = try ip.get(gpa, .{ .float_80_value = 2.0 }); const f128_value = try ip.get(gpa, .{ .float_128_value = 2.75 }); const f32_nan_value = try ip.get(gpa, .{ .float_32_value = std.math.nan_f32 }); const f32_qnan_value = try ip.get(gpa, .{ .float_32_value = std.math.qnan_f32 }); const f32_inf_value = try ip.get(gpa, .{ .float_32_value = std.math.inf_f32 }); const f32_ninf_value = try ip.get(gpa, .{ .float_32_value = -std.math.inf_f32 }); const f32_zero_value = try ip.get(gpa, .{ .float_32_value = 0.0 }); const f32_nzero_value = try ip.get(gpa, .{ .float_32_value = -0.0 }); try expect(f16_value != f32_value); try expect(f32_value != f64_value); try expect(f64_value != f80_value); try expect(f80_value != f128_value); try expect(f32_nan_value != f32_qnan_value); try expect(f32_inf_value != f32_ninf_value); try expect(f32_zero_value != f32_nzero_value); try expect(!ip.indexToKey(f16_value).eql(ip.indexToKey(f32_value))); try expect(ip.indexToKey(f32_value).eql(ip.indexToKey(f32_value))); try expect(ip.indexToKey(f32_nan_value).eql(ip.indexToKey(f32_nan_value))); try expect(!ip.indexToKey(f32_nan_value).eql(ip.indexToKey(f32_qnan_value))); try expect(ip.indexToKey(f32_inf_value).eql(ip.indexToKey(f32_inf_value))); try expect(!ip.indexToKey(f32_inf_value).eql(ip.indexToKey(f32_ninf_value))); try expect(ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_zero_value))); try expect(!ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_nzero_value))); try expectFmt("0.25", "{}", .{f16_value.fmt(ip)}); try expectFmt("0.5", "{}", .{f32_value.fmt(ip)}); try expectFmt("1", "{}", .{f64_value.fmt(ip)}); try expectFmt("2", "{}", .{f80_value.fmt(ip)}); try expectFmt("2.75", "{}", .{f128_value.fmt(ip)}); try expectFmt("nan", "{}", .{f32_nan_value.fmt(ip)}); try expectFmt("nan", "{}", .{f32_qnan_value.fmt(ip)}); try expectFmt("inf", "{}", .{f32_inf_value.fmt(ip)}); try expectFmt("-inf", "{}", .{f32_ninf_value.fmt(ip)}); try expectFmt("0", "{}", .{f32_zero_value.fmt(ip)}); try expectFmt("-0", "{}", .{f32_nzero_value.fmt(ip)}); } test "pointer type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const @"*i32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .i32_type, .size = .One, } }); const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, } }); const @"*const volatile u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .is_const = true, .is_volatile = true, } }); const @"*align(4:2:3) u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .alignment = 4, .bit_offset = 2, .host_size = 3, } }); const @"*addrspace(.shared) const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .is_const = true, .address_space = .shared, } }); const @"[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Many, } }); const @"[*:0]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Many, .sentinel = .zero, } }); const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Slice, } }); const @"[:0]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Slice, .sentinel = .zero, } }); const @"[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .C, } }); try expect(@"*i32" != @"*u32"); try expect(@"*u32" != @"*const volatile u32"); try expect(@"*const volatile u32" != @"*align(4:2:3) u32"); try expect(@"*align(4:2:3) u32" != @"*addrspace(.shared) const u32"); try expect(@"[*]u32" != @"[*:0]u32"); try expect(@"[*:0]u32" != @"[]u32"); try expect(@"[*:0]u32" != @"[:0]u32"); try expect(@"[:0]u32" != @"[*c]u32"); try expectFmt("*i32", "{}", .{@"*i32".fmt(ip)}); try expectFmt("*u32", "{}", .{@"*u32".fmt(ip)}); try expectFmt("*const volatile u32", "{}", .{@"*const volatile u32".fmt(ip)}); try expectFmt("*align(4:2:3) u32", "{}", .{@"*align(4:2:3) u32".fmt(ip)}); try expectFmt("*addrspace(.shared) const u32", "{}", .{@"*addrspace(.shared) const u32".fmt(ip)}); try expectFmt("[*]u32", "{}", .{@"[*]u32".fmt(ip)}); try expectFmt("[*:0]u32", "{}", .{@"[*:0]u32".fmt(ip)}); try expectFmt("[]u32", "{}", .{@"[]u32".fmt(ip)}); try expectFmt("[:0]u32", "{}", .{@"[:0]u32".fmt(ip)}); try expectFmt("[*c]u32", "{}", .{@"[*c]u32".fmt(ip)}); } test "optional type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const i32_optional_type = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .i32_type } }); const u32_optional_type = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .u32_type } }); try expect(i32_optional_type != u32_optional_type); try expectFmt("?i32", "{}", .{i32_optional_type.fmt(ip)}); try expectFmt("?u32", "{}", .{u32_optional_type.fmt(ip)}); } test "optional value" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const u32_optional_type = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .u32_type } }); const u64_42_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = 42 } }); const optional_42_value = try ip.get(gpa, .{ .optional_value = .{ .ty = u32_optional_type, .val = u64_42_value } }); try expectFmt("42", "{}", .{optional_42_value.fmt(ip)}); } test "error set type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const foo_name = try ip.get(gpa, .{ .bytes = "foo" }); const bar_name = try ip.get(gpa, .{ .bytes = "bar" }); const baz_name = try ip.get(gpa, .{ .bytes = "baz" }); const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{ foo_name, bar_name, baz_name }, } }); const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{ foo_name, bar_name }, } }); try expect(empty_error_set != foo_bar_baz_set); try expect(foo_bar_baz_set != foo_bar_set); try expectFmt("error{}", "{}", .{empty_error_set.fmt(ip)}); try expectFmt("error{foo,bar,baz}", "{}", .{foo_bar_baz_set.fmt(ip)}); try expectFmt("error{foo,bar}", "{}", .{foo_bar_set.fmt(ip)}); } test "error union type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); const bool_type = try ip.get(gpa, .{ .simple_type = .bool }); const @"error{}!bool" = try ip.get(gpa, .{ .error_union_type = .{ .error_set_type = empty_error_set, .payload_type = bool_type, } }); try expectFmt("error{}!bool", "{}", .{@"error{}!bool".fmt(ip)}); } test "array type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const i32_3_array_type = try ip.get(gpa, .{ .array_type = .{ .len = 3, .child = .i32_type, } }); const u32_0_0_array_type = try ip.get(gpa, .{ .array_type = .{ .len = 3, .child = .u32_type, .sentinel = .zero, } }); try expect(i32_3_array_type != u32_0_0_array_type); try expectFmt("[3]i32", "{}", .{i32_3_array_type.fmt(ip)}); try expectFmt("[3:0]u32", "{}", .{u32_0_0_array_type.fmt(ip)}); } test "struct value" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const struct_index = try ip.createStruct(gpa, .{ .fields = .{}, .namespace = .none, .layout = .Auto, .backing_int_ty = .none, .status = .none, }); const struct_type = try ip.get(gpa, .{ .struct_type = struct_index }); const struct_info = ip.getStruct(struct_index); try struct_info.fields.put(gpa, "foo", .{ .ty = .usize_type }); try struct_info.fields.put(gpa, "bar", .{ .ty = .bool_type }); const aggregate_value = try ip.get(gpa, .{ .aggregate = .{ .ty = struct_type, .values = &.{ .one_usize, .bool_true }, } }); try expectFmt(".{.foo = 1, .bar = true}", "{}", .{aggregate_value.fmt(ip)}); } test "function type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const @"fn(i32) bool" = try ip.get(gpa, .{ .function_type = .{ .args = &.{.i32_type}, .return_type = .bool_type, } }); var args_is_comptime = std.StaticBitSet(32).initEmpty(); args_is_comptime.set(0); var args_is_noalias = std.StaticBitSet(32).initEmpty(); args_is_noalias.set(1); const @"fn(comptime type, noalias i32) type" = try ip.get(gpa, .{ .function_type = .{ .args = &.{ .type_type, .i32_type }, .args_is_comptime = args_is_comptime, .args_is_noalias = args_is_noalias, .return_type = .type_type, } }); const @"fn(i32, ...) type" = try ip.get(gpa, .{ .function_type = .{ .args = &.{.i32_type}, .return_type = .type_type, .is_var_args = true, } }); const @"fn() align(4) callconv(.C) type" = try ip.get(gpa, .{ .function_type = .{ .args = &.{}, .return_type = .type_type, .alignment = 4, .calling_convention = .C, } }); try expectFmt("fn(i32) bool", "{}", .{@"fn(i32) bool".fmt(ip)}); try expectFmt("fn(comptime type, noalias i32) type", "{}", .{@"fn(comptime type, noalias i32) type".fmt(ip)}); try expectFmt("fn(i32, ...) type", "{}", .{@"fn(i32, ...) type".fmt(ip)}); try expectFmt("fn() align(4) callconv(.C) type", "{}", .{@"fn() align(4) callconv(.C) type".fmt(ip)}); } test "union value" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const f16_value = try ip.get(gpa, .{ .float_16_value = 0.25 }); const union_index = try ip.createUnion(gpa, .{ .tag_type = .none, .fields = .{}, .namespace = .none, .layout = .Auto, .status = .none, }); const union_type = try ip.get(gpa, .{ .union_type = union_index }); const union_info = ip.getUnion(union_index); try union_info.fields.put(gpa, "int", .{ .ty = .usize_type, .alignment = 0 }); try union_info.fields.put(gpa, "float", .{ .ty = .f16_type, .alignment = 0 }); const union_value1 = try ip.get(gpa, .{ .union_value = .{ .ty = union_type, .field_index = 0, .val = .one_usize, } }); const union_value2 = try ip.get(gpa, .{ .union_value = .{ .ty = union_type, .field_index = 1, .val = f16_value, } }); try expectFmt(".{ .int = 1 }", "{}", .{union_value1.fmt(ip)}); try expectFmt(".{ .float = 0.25 }", "{}", .{union_value2.fmt(ip)}); } test "anyframe type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const @"anyframe->i32" = try ip.get(gpa, .{ .anyframe_type = .{ .child = .i32_type } }); const @"anyframe->bool" = try ip.get(gpa, .{ .anyframe_type = .{ .child = .bool_type } }); try expect(@"anyframe->i32" != @"anyframe->bool"); try expectFmt("anyframe->i32", "{}", .{@"anyframe->i32".fmt(ip)}); try expectFmt("anyframe->bool", "{}", .{@"anyframe->bool".fmt(ip)}); } test "vector type" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const @"@Vector(2,u32)" = try ip.get(gpa, .{ .vector_type = .{ .len = 2, .child = .u32_type, } }); const @"@Vector(2,bool)" = try ip.get(gpa, .{ .vector_type = .{ .len = 2, .child = .bool_type, } }); try expect(@"@Vector(2,u32)" != @"@Vector(2,bool)"); try expectFmt("@Vector(2,u32)", "{}", .{@"@Vector(2,u32)".fmt(ip)}); try expectFmt("@Vector(2,bool)", "{}", .{@"@Vector(2,bool)".fmt(ip)}); } test "bytes value" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); var str1: [43]u8 = "https://www.youtube.com/watch?v=dQw4w9WgXcQ".*; const bytes_value1 = try ip.get(gpa, .{ .bytes = &str1 }); @memset(&str1, 0, str1.len); var str2: [43]u8 = "https://www.youtube.com/watch?v=dQw4w9WgXcQ".*; const bytes_value2 = try ip.get(gpa, .{ .bytes = &str2 }); @memset(&str2, 0, str2.len); var str3: [26]u8 = "https://www.duckduckgo.com".*; const bytes_value3 = try ip.get(gpa, .{ .bytes = &str3 }); @memset(&str3, 0, str3.len); try expect(bytes_value1 == bytes_value2); try expect(bytes_value2 != bytes_value3); try expect(@ptrToInt(&str1) != @ptrToInt(ip.indexToKey(bytes_value1).bytes.ptr)); try expect(@ptrToInt(&str2) != @ptrToInt(ip.indexToKey(bytes_value2).bytes.ptr)); try expect(@ptrToInt(&str3) != @ptrToInt(ip.indexToKey(bytes_value3).bytes.ptr)); try std.testing.expectEqual(ip.indexToKey(bytes_value1).bytes.ptr, ip.indexToKey(bytes_value2).bytes.ptr); try std.testing.expectEqualStrings("https://www.youtube.com/watch?v=dQw4w9WgXcQ", ip.indexToKey(bytes_value1).bytes); try std.testing.expectEqualStrings("https://www.youtube.com/watch?v=dQw4w9WgXcQ", ip.indexToKey(bytes_value2).bytes); try std.testing.expectEqualStrings("https://www.duckduckgo.com", ip.indexToKey(bytes_value3).bytes); } test "coerceInMemoryAllowed integers and floats" { const gpa = std.testing.allocator; var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var ip = try InternPool.init(gpa); defer ip.deinit(gpa); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .u32_type, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .u16_type, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u16_type, .u32_type, true, builtin.target) == .int_not_coercible); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .i32_type, .u32_type, true, builtin.target) == .int_not_coercible); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .i32_type, true, builtin.target) == .int_not_coercible); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .i16_type, true, builtin.target) == .int_not_coercible); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .f32_type, .f32_type, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .f64_type, .f32_type, true, builtin.target) == .no_match); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .f32_type, .f64_type, true, builtin.target) == .no_match); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .f32_type, true, builtin.target) == .no_match); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .f32_type, .u32_type, true, builtin.target) == .no_match); } test "coerceInMemoryAllowed error set" { const gpa = std.testing.allocator; var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const foo_name = try ip.get(gpa, .{ .bytes = "foo" }); const bar_name = try ip.get(gpa, .{ .bytes = "bar" }); const baz_name = try ip.get(gpa, .{ .bytes = "baz" }); const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{ baz_name, bar_name, foo_name } } }); const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{ foo_name, bar_name } } }); const foo_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{foo_name} } }); const empty_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_bar_baz_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_bar_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, empty_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, .anyerror_type, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, .anyerror_type, true, builtin.target) == .from_anyerror); try expect(try ip.coerceInMemoryAllowed(gpa, arena, empty_set, .anyerror_type, true, builtin.target) == .from_anyerror); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, foo_bar_baz_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, foo_bar_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, foo_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, empty_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_set, foo_bar_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_set, foo_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_set, empty_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_set, foo_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_set, empty_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, empty_set, empty_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, empty_set, foo_set, true, builtin.target) == .missing_error); try expect(try ip.coerceInMemoryAllowed(gpa, arena, empty_set, foo_bar_baz_set, true, builtin.target) == .missing_error); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_set, foo_bar_set, true, builtin.target) == .missing_error); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_set, foo_bar_baz_set, true, builtin.target) == .missing_error); try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_set, foo_bar_baz_set, true, builtin.target) == .missing_error); } test "resolvePeerTypes" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); try expect(.noreturn_type == try ip.resolvePeerTypes(std.testing.allocator, &.{}, builtin.target)); try expect(.type_type == try ip.resolvePeerTypes(std.testing.allocator, &.{.type_type}, builtin.target)); try ip.testResolvePeerTypes(.none, .none, .none); try ip.testResolvePeerTypes(.bool_type, .bool_type, .bool_type); try ip.testResolvePeerTypes(.bool_type, .noreturn_type, .bool_type); try ip.testResolvePeerTypes(.bool_type, .undefined_type, .bool_type); try ip.testResolvePeerTypes(.type_type, .noreturn_type, .type_type); try ip.testResolvePeerTypes(.type_type, .undefined_type, .type_type); } test "resolvePeerTypes integers and floats" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); try ip.testResolvePeerTypes(.i16_type, .i16_type, .i16_type); try ip.testResolvePeerTypes(.i16_type, .i32_type, .i32_type); try ip.testResolvePeerTypes(.i32_type, .i64_type, .i64_type); try ip.testResolvePeerTypes(.u16_type, .u16_type, .u16_type); try ip.testResolvePeerTypes(.u16_type, .u32_type, .u32_type); try ip.testResolvePeerTypes(.u32_type, .u64_type, .u64_type); try ip.testResolvePeerTypesInOrder(.i16_type, .u16_type, .i16_type); try ip.testResolvePeerTypesInOrder(.u16_type, .i16_type, .u16_type); try ip.testResolvePeerTypesInOrder(.i32_type, .u32_type, .i32_type); try ip.testResolvePeerTypesInOrder(.u32_type, .i32_type, .u32_type); try ip.testResolvePeerTypesInOrder(.isize_type, .usize_type, .isize_type); try ip.testResolvePeerTypesInOrder(.usize_type, .isize_type, .usize_type); try ip.testResolvePeerTypes(.i16_type, .u32_type, .u32_type); try ip.testResolvePeerTypes(.u16_type, .i32_type, .i32_type); try ip.testResolvePeerTypes(.i32_type, .u64_type, .u64_type); try ip.testResolvePeerTypes(.u32_type, .i64_type, .i64_type); try ip.testResolvePeerTypes(.i16_type, .usize_type, .usize_type); try ip.testResolvePeerTypes(.i16_type, .isize_type, .isize_type); try ip.testResolvePeerTypes(.u16_type, .usize_type, .usize_type); try ip.testResolvePeerTypes(.u16_type, .isize_type, .isize_type); try ip.testResolvePeerTypes(.c_short_type, .usize_type, .usize_type); try ip.testResolvePeerTypes(.c_short_type, .isize_type, .isize_type); try ip.testResolvePeerTypes(.i16_type, .c_long_type, .c_long_type); try ip.testResolvePeerTypes(.i16_type, .c_long_type, .c_long_type); try ip.testResolvePeerTypes(.u16_type, .c_long_type, .c_long_type); try ip.testResolvePeerTypes(.u16_type, .c_long_type, .c_long_type); try ip.testResolvePeerTypes(.comptime_int_type, .i16_type, .i16_type); try ip.testResolvePeerTypes(.comptime_int_type, .u64_type, .u64_type); try ip.testResolvePeerTypes(.comptime_int_type, .isize_type, .isize_type); try ip.testResolvePeerTypes(.comptime_int_type, .usize_type, .usize_type); try ip.testResolvePeerTypes(.comptime_int_type, .c_short_type, .c_short_type); try ip.testResolvePeerTypes(.comptime_int_type, .c_int_type, .c_int_type); try ip.testResolvePeerTypes(.comptime_int_type, .c_long_type, .c_long_type); try ip.testResolvePeerTypes(.comptime_float_type, .i16_type, .none); try ip.testResolvePeerTypes(.comptime_float_type, .u64_type, .none); try ip.testResolvePeerTypes(.comptime_float_type, .isize_type, .none); try ip.testResolvePeerTypes(.comptime_float_type, .usize_type, .none); try ip.testResolvePeerTypes(.comptime_float_type, .c_short_type, .none); try ip.testResolvePeerTypes(.comptime_float_type, .c_int_type, .none); try ip.testResolvePeerTypes(.comptime_float_type, .c_long_type, .none); try ip.testResolvePeerTypes(.comptime_float_type, .comptime_int_type, .comptime_float_type); try ip.testResolvePeerTypes(.f16_type, .f32_type, .f32_type); try ip.testResolvePeerTypes(.f32_type, .f64_type, .f64_type); try ip.testResolvePeerTypes(.comptime_int_type, .f16_type, .f16_type); try ip.testResolvePeerTypes(.comptime_int_type, .f32_type, .f32_type); try ip.testResolvePeerTypes(.comptime_int_type, .f64_type, .f64_type); try ip.testResolvePeerTypes(.comptime_float_type, .f16_type, .f16_type); try ip.testResolvePeerTypes(.comptime_float_type, .f32_type, .f32_type); try ip.testResolvePeerTypes(.comptime_float_type, .f64_type, .f64_type); try ip.testResolvePeerTypes(.f16_type, .i16_type, .none); try ip.testResolvePeerTypes(.f32_type, .u64_type, .none); try ip.testResolvePeerTypes(.f64_type, .isize_type, .none); try ip.testResolvePeerTypes(.f16_type, .usize_type, .none); try ip.testResolvePeerTypes(.f32_type, .c_short_type, .none); try ip.testResolvePeerTypes(.f64_type, .c_int_type, .none); try ip.testResolvePeerTypes(.f64_type, .c_long_type, .none); try ip.testResolvePeerTypes(.bool_type, .i16_type, .none); try ip.testResolvePeerTypes(.bool_type, .u64_type, .none); try ip.testResolvePeerTypes(.bool_type, .usize_type, .none); try ip.testResolvePeerTypes(.bool_type, .c_int_type, .none); try ip.testResolvePeerTypes(.bool_type, .comptime_int_type, .none); try ip.testResolvePeerTypes(.bool_type, .comptime_float_type, .none); try ip.testResolvePeerTypes(.bool_type, .f32_type, .none); } test "resolvePeerTypes optionals" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const @"?u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .u32_type } }); const @"?bool" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .bool_type } }); try ip.testResolvePeerTypes(.u32_type, .null_type, @"?u32"); try ip.testResolvePeerTypes(.bool_type, .null_type, @"?bool"); } test "resolvePeerTypes pointers" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One } }); const @"[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Many } }); const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Slice } }); const @"[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .C } }); const @"?*u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*u32" } }); const @"?[*]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[*]u32" } }); const @"?[]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[]u32" } }); const @"**u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"*u32", .size = .One } }); const @"*[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[*]u32", .size = .One } }); const @"*[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[]u32", .size = .One } }); const @"*[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[*c]u32", .size = .One } }); const @"?*[*]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[*]u32" } }); const @"?*[]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[]u32" } }); const @"[1]u32" = try ip.get(gpa, .{ .array_type = .{ .len = 1, .child = .u32_type, .sentinel = .none } }); const @"[2]u32" = try ip.get(gpa, .{ .array_type = .{ .len = 2, .child = .u32_type, .sentinel = .none } }); const @"*[1]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[1]u32", .size = .One } }); const @"*[2]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[2]u32", .size = .One } }); const @"?*[1]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[1]u32" } }); const @"?*[2]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[2]u32" } }); const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .is_const = true } }); const @"[*]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Many, .is_const = true } }); const @"[]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Slice, .is_const = true } }); const @"[*c]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .C, .is_const = true } }); const @"?*const u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*const u32" } }); const @"?[*]const u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[*]const u32" } }); const @"?[]const u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[]const u32" } }); _ = @"**u32"; _ = @"*[*c]u32"; _ = @"?*[]u32"; _ = @"?*[2]u32"; _ = @"?[*]const u32"; _ = @"?[]const u32"; // gain const try ip.testResolvePeerTypes(@"*u32", @"*u32", @"*u32"); try ip.testResolvePeerTypes(@"*u32", @"*const u32", @"*const u32"); try ip.testResolvePeerTypes(@"[*]u32", @"[*]const u32", @"[*]const u32"); try ip.testResolvePeerTypes(@"[]u32", @"[]const u32", @"[]const u32"); try ip.testResolvePeerTypes(@"[*c]u32", @"[*c]const u32", @"[*c]const u32"); // array to slice try ip.testResolvePeerTypes(@"*[1]u32", @"*[2]u32", @"[]u32"); try ip.testResolvePeerTypes(@"[]u32", @"*[1]u32", @"[]u32"); // pointer like optionals try ip.testResolvePeerTypes(@"*u32", @"?*u32", @"?*u32"); try ip.testResolvePeerTypesInOrder(@"*u32", @"?[*]u32", @"?[*]u32"); try ip.testResolvePeerTypesInOrder(@"[*]u32", @"?*u32", @"?*u32"); try ip.testResolvePeerTypes(@"[*c]u32", .comptime_int_type, @"[*c]u32"); try ip.testResolvePeerTypes(@"[*c]u32", .u32_type, @"[*c]u32"); try ip.testResolvePeerTypes(@"[*c]u32", .comptime_float_type, .none); try ip.testResolvePeerTypes(@"[*c]u32", .bool_type, .none); try ip.testResolvePeerTypes(@"[*c]u32", @"*u32", @"[*c]u32"); try ip.testResolvePeerTypes(@"[*c]u32", @"[*]u32", @"[*c]u32"); try ip.testResolvePeerTypes(@"[*c]u32", @"[]u32", @"[*c]u32"); try ip.testResolvePeerTypes(@"[*c]u32", @"*[1]u32", .none); try ip.testResolvePeerTypesInOrder(@"[*c]u32", @"?*[1]u32", @"?*[1]u32"); try ip.testResolvePeerTypesInOrder(@"?*[1]u32", @"[*c]u32", .none); try ip.testResolvePeerTypes(@"[*c]u32", @"*[*]u32", .none); try ip.testResolvePeerTypesInOrder(@"[*c]u32", @"?*[*]u32", @"?*[*]u32"); try ip.testResolvePeerTypesInOrder(@"?*[*]u32", @"[*c]u32", .none); try ip.testResolvePeerTypes(@"[*c]u32", @"[]u32", @"[*c]u32"); // TODO try ip.testResolvePeerTypesInOrder(@"[*c]u32", @"?[]u32", @"?[]u32"); // TODO try ip.testResolvePeerTypesInOrder(@"?[]u32", @"[*c]u32", Index.none); // TODO try ip.testResolvePeerTypesInOrder(@"*u32", @"?[*]const u32", @"?[*]const u32"); try ip.testResolvePeerTypesInOrder(@"*const u32", @"?[*]u32", @"?[*]u32"); try ip.testResolvePeerTypesInOrder(@"[*]const u32", @"?*u32", @"?*u32"); try ip.testResolvePeerTypesInOrder(@"[*]u32", @"?*const u32", @"?*const u32"); try ip.testResolvePeerTypes(@"?[*]u32", @"*[2]u32", @"?[*]u32"); try ip.testResolvePeerTypes(@"?[]u32", @"*[2]u32", @"?[]u32"); try ip.testResolvePeerTypes(@"[*]u32", @"*[2]u32", @"[*]u32"); } test "resolvePeerTypes function pointers" { const gpa = std.testing.allocator; var ip = try InternPool.init(gpa); defer ip.deinit(gpa); const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One } }); const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .is_const = true } }); const @"fn(*u32) void" = try ip.get(gpa, .{ .function_type = .{ .args = &.{@"*u32"}, .return_type = .void_type, } }); const @"fn(*const u32) void" = try ip.get(gpa, .{ .function_type = .{ .args = &.{@"*const u32"}, .return_type = .void_type, } }); try ip.testResolvePeerTypes(@"fn(*u32) void", @"fn(*u32) void", @"fn(*u32) void"); try ip.testResolvePeerTypes(@"fn(*u32) void", @"fn(*const u32) void", @"fn(*u32) void"); } fn testResolvePeerTypes(ip: *InternPool, a: Index, b: Index, expected: Index) !void { try ip.testResolvePeerTypesInOrder(a, b, expected); try ip.testResolvePeerTypesInOrder(b, a, expected); } fn testResolvePeerTypesInOrder(ip: *InternPool, lhs: Index, rhs: Index, expected: Index) !void { const actual = try resolvePeerTypes(ip, std.testing.allocator, &.{ lhs, rhs }, builtin.target); try expectEqualTypes(ip.*, expected, actual); } fn expectEqualTypes(ip: InternPool, expected: Index, actual: Index) !void { if (expected == actual) return; const allocator = std.testing.allocator; const expected_type = if (expected == .none) @tagName(Index.none) else try std.fmt.allocPrint(allocator, "{}", .{expected.fmt(ip)}); defer if (expected != .none) allocator.free(expected_type); const actual_type = if (actual == .none) @tagName(Index.none) else try std.fmt.allocPrint(allocator, "{}", .{actual.fmt(ip)}); defer if (actual != .none) allocator.free(actual_type); std.debug.print("expected `{s}`, found `{s}`\n", .{ expected_type, actual_type }); return error.TestExpectedEqual; }