zls/src/analyser/InternPool.zig
2023-04-17 22:14:41 +02:00

3683 lines
139 KiB
Zig

/// 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;
}