const std = @import("std");
const Type = @import("type.zig").Type;
const log2 = std.math.log2;
const assert = std.debug.assert;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
const Allocator = std.mem.Allocator;
const Module = @import("Module.zig");

/// This is the raw data, with no bookkeeping, no memory awareness,
/// no de-duplication, and no type system awareness.
/// It's important for this type to be small.
/// This union takes advantage of the fact that the first page of memory
/// is unmapped, giving us 4096 possible enum tags that have no payload.
pub const Value = extern union {
    /// If the tag value is less than Tag.no_payload_count, then no pointer
    /// dereference is needed.
    tag_if_small_enough: usize,
    ptr_otherwise: *Payload,

    pub const Tag = enum {
        // The first section of this enum are tags that require no payload.
        u8_type,
        i8_type,
        u16_type,
        i16_type,
        u32_type,
        i32_type,
        u64_type,
        i64_type,
        usize_type,
        isize_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,
        f128_type,
        c_void_type,
        bool_type,
        void_type,
        type_type,
        anyerror_type,
        comptime_int_type,
        comptime_float_type,
        noreturn_type,
        null_type,
        undefined_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,
        enum_literal_type,
        anyframe_type,

        undef,
        zero,
        one,
        void_value,
        unreachable_value,
        empty_array,
        null_value,
        bool_true,
        bool_false, // See last_no_payload_tag below.
        // After this, the tag requires a payload.

        ty,
        int_type,
        int_u64,
        int_i64,
        int_big_positive,
        int_big_negative,
        function,
        variable,
        ref_val,
        decl_ref,
        elem_ptr,
        bytes,
        repeated, // the value is a value repeated some number of times
        float_16,
        float_32,
        float_64,
        float_128,
        enum_literal,
        error_set,
        @"error",

        pub const last_no_payload_tag = Tag.bool_false;
        pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
    };

    pub fn initTag(small_tag: Tag) Value {
        assert(@enumToInt(small_tag) < Tag.no_payload_count);
        return .{ .tag_if_small_enough = @enumToInt(small_tag) };
    }

    pub fn initPayload(payload: *Payload) Value {
        assert(@enumToInt(payload.tag) >= Tag.no_payload_count);
        return .{ .ptr_otherwise = payload };
    }

    pub fn tag(self: Value) Tag {
        if (self.tag_if_small_enough < Tag.no_payload_count) {
            return @intToEnum(Tag, @intCast(@TagType(Tag), self.tag_if_small_enough));
        } else {
            return self.ptr_otherwise.tag;
        }
    }

    pub fn cast(self: Value, comptime T: type) ?*T {
        if (self.tag_if_small_enough < Tag.no_payload_count)
            return null;

        const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag;
        if (self.ptr_otherwise.tag != expected_tag)
            return null;

        return @fieldParentPtr(T, "base", self.ptr_otherwise);
    }

    pub fn copy(self: Value, allocator: *Allocator) error{OutOfMemory}!Value {
        if (self.tag_if_small_enough < Tag.no_payload_count) {
            return Value{ .tag_if_small_enough = self.tag_if_small_enough };
        } else switch (self.ptr_otherwise.tag) {
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .undef,
            .zero,
            .one,
            .void_value,
            .unreachable_value,
            .empty_array,
            .null_value,
            .bool_true,
            .bool_false,
            => unreachable,

            .ty => {
                const payload = @fieldParentPtr(Payload.Ty, "base", self.ptr_otherwise);
                const new_payload = try allocator.create(Payload.Ty);
                new_payload.* = .{
                    .base = payload.base,
                    .ty = try payload.ty.copy(allocator),
                };
                return Value{ .ptr_otherwise = &new_payload.base };
            },
            .int_type => return self.copyPayloadShallow(allocator, Payload.IntType),
            .int_u64 => return self.copyPayloadShallow(allocator, Payload.Int_u64),
            .int_i64 => return self.copyPayloadShallow(allocator, Payload.Int_i64),
            .int_big_positive => {
                @panic("TODO implement copying of big ints");
            },
            .int_big_negative => {
                @panic("TODO implement copying of big ints");
            },
            .function => return self.copyPayloadShallow(allocator, Payload.Function),
            .variable => return self.copyPayloadShallow(allocator, Payload.Variable),
            .ref_val => {
                const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise);
                const new_payload = try allocator.create(Payload.RefVal);
                new_payload.* = .{
                    .base = payload.base,
                    .val = try payload.val.copy(allocator),
                };
                return Value{ .ptr_otherwise = &new_payload.base };
            },
            .decl_ref => return self.copyPayloadShallow(allocator, Payload.DeclRef),
            .elem_ptr => {
                const payload = @fieldParentPtr(Payload.ElemPtr, "base", self.ptr_otherwise);
                const new_payload = try allocator.create(Payload.ElemPtr);
                new_payload.* = .{
                    .base = payload.base,
                    .array_ptr = try payload.array_ptr.copy(allocator),
                    .index = payload.index,
                };
                return Value{ .ptr_otherwise = &new_payload.base };
            },
            .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes),
            .repeated => {
                const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise);
                const new_payload = try allocator.create(Payload.Repeated);
                new_payload.* = .{
                    .base = payload.base,
                    .val = try payload.val.copy(allocator),
                };
                return Value{ .ptr_otherwise = &new_payload.base };
            },
            .float_16 => return self.copyPayloadShallow(allocator, Payload.Float_16),
            .float_32 => return self.copyPayloadShallow(allocator, Payload.Float_32),
            .float_64 => return self.copyPayloadShallow(allocator, Payload.Float_64),
            .float_128 => return self.copyPayloadShallow(allocator, Payload.Float_128),
            .enum_literal => {
                const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise);
                const new_payload = try allocator.create(Payload.Bytes);
                new_payload.* = .{
                    .base = payload.base,
                    .data = try allocator.dupe(u8, payload.data),
                };
                return Value{ .ptr_otherwise = &new_payload.base };
            },
            .@"error" => return self.copyPayloadShallow(allocator, Payload.Error),

            // memory is managed by the declaration
            .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet),
        }
    }

    fn copyPayloadShallow(self: Value, allocator: *Allocator, comptime T: type) error{OutOfMemory}!Value {
        const payload = @fieldParentPtr(T, "base", self.ptr_otherwise);
        const new_payload = try allocator.create(T);
        new_payload.* = payload.*;
        return Value{ .ptr_otherwise = &new_payload.base };
    }

    pub fn format(
        self: Value,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        out_stream: anytype,
    ) !void {
        comptime assert(fmt.len == 0);
        var val = self;
        while (true) switch (val.tag()) {
            .u8_type => return out_stream.writeAll("u8"),
            .i8_type => return out_stream.writeAll("i8"),
            .u16_type => return out_stream.writeAll("u16"),
            .i16_type => return out_stream.writeAll("i16"),
            .u32_type => return out_stream.writeAll("u32"),
            .i32_type => return out_stream.writeAll("i32"),
            .u64_type => return out_stream.writeAll("u64"),
            .i64_type => return out_stream.writeAll("i64"),
            .isize_type => return out_stream.writeAll("isize"),
            .usize_type => return out_stream.writeAll("usize"),
            .c_short_type => return out_stream.writeAll("c_short"),
            .c_ushort_type => return out_stream.writeAll("c_ushort"),
            .c_int_type => return out_stream.writeAll("c_int"),
            .c_uint_type => return out_stream.writeAll("c_uint"),
            .c_long_type => return out_stream.writeAll("c_long"),
            .c_ulong_type => return out_stream.writeAll("c_ulong"),
            .c_longlong_type => return out_stream.writeAll("c_longlong"),
            .c_ulonglong_type => return out_stream.writeAll("c_ulonglong"),
            .c_longdouble_type => return out_stream.writeAll("c_longdouble"),
            .f16_type => return out_stream.writeAll("f16"),
            .f32_type => return out_stream.writeAll("f32"),
            .f64_type => return out_stream.writeAll("f64"),
            .f128_type => return out_stream.writeAll("f128"),
            .c_void_type => return out_stream.writeAll("c_void"),
            .bool_type => return out_stream.writeAll("bool"),
            .void_type => return out_stream.writeAll("void"),
            .type_type => return out_stream.writeAll("type"),
            .anyerror_type => return out_stream.writeAll("anyerror"),
            .comptime_int_type => return out_stream.writeAll("comptime_int"),
            .comptime_float_type => return out_stream.writeAll("comptime_float"),
            .noreturn_type => return out_stream.writeAll("noreturn"),
            .null_type => return out_stream.writeAll("@Type(.Null)"),
            .undefined_type => return out_stream.writeAll("@Type(.Undefined)"),
            .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"),
            .fn_void_no_args_type => return out_stream.writeAll("fn() void"),
            .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
            .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"),
            .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
            .const_slice_u8_type => return out_stream.writeAll("[]const u8"),
            .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"),
            .anyframe_type => return out_stream.writeAll("anyframe"),

            .null_value => return out_stream.writeAll("null"),
            .undef => return out_stream.writeAll("undefined"),
            .zero => return out_stream.writeAll("0"),
            .one => return out_stream.writeAll("1"),
            .void_value => return out_stream.writeAll("{}"),
            .unreachable_value => return out_stream.writeAll("unreachable"),
            .bool_true => return out_stream.writeAll("true"),
            .bool_false => return out_stream.writeAll("false"),
            .ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
            .int_type => {
                const int_type = val.cast(Payload.IntType).?;
                return out_stream.print("{}{}", .{
                    if (int_type.signed) "s" else "u",
                    int_type.bits,
                });
            },
            .int_u64 => return std.fmt.formatIntValue(val.cast(Payload.Int_u64).?.int, "", options, out_stream),
            .int_i64 => return std.fmt.formatIntValue(val.cast(Payload.Int_i64).?.int, "", options, out_stream),
            .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}),
            .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}),
            .function => return out_stream.writeAll("(function)"),
            .variable => return out_stream.writeAll("(variable)"),
            .ref_val => {
                const ref_val = val.cast(Payload.RefVal).?;
                try out_stream.writeAll("&const ");
                val = ref_val.val;
            },
            .decl_ref => return out_stream.writeAll("(decl ref)"),
            .elem_ptr => {
                const elem_ptr = val.cast(Payload.ElemPtr).?;
                try out_stream.print("&[{}] ", .{elem_ptr.index});
                val = elem_ptr.array_ptr;
            },
            .empty_array => return out_stream.writeAll(".{}"),
            .enum_literal, .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
            .repeated => {
                try out_stream.writeAll("(repeated) ");
                val = val.cast(Payload.Repeated).?.val;
            },
            .float_16 => return out_stream.print("{}", .{val.cast(Payload.Float_16).?.val}),
            .float_32 => return out_stream.print("{}", .{val.cast(Payload.Float_32).?.val}),
            .float_64 => return out_stream.print("{}", .{val.cast(Payload.Float_64).?.val}),
            .float_128 => return out_stream.print("{}", .{val.cast(Payload.Float_128).?.val}),
            .error_set => {
                const error_set = val.cast(Payload.ErrorSet).?;
                try out_stream.writeAll("error{");
                var it = error_set.fields.iterator();
                while (it.next()) |entry| {
                    try out_stream.print("{},", .{entry.value});
                }
                return out_stream.writeAll("}");
            },
            .@"error" => return out_stream.print("error.{}", .{val.cast(Payload.Error).?.name}),
        };
    }

    /// Asserts that the value is representable as an array of bytes.
    /// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
    pub fn toAllocatedBytes(self: Value, allocator: *Allocator) ![]u8 {
        if (self.cast(Payload.Bytes)) |bytes| {
            return std.mem.dupe(allocator, u8, bytes.data);
        }
        if (self.cast(Payload.Repeated)) |repeated| {
            @panic("TODO implement toAllocatedBytes for this Value tag");
        }
        if (self.cast(Payload.DeclRef)) |declref| {
            const val = try declref.decl.value();
            return val.toAllocatedBytes(allocator);
        }
        unreachable;
    }

    /// Asserts that the value is representable as a type.
    pub fn toType(self: Value, allocator: *Allocator) !Type {
        return switch (self.tag()) {
            .ty => self.cast(Payload.Ty).?.ty,
            .u8_type => Type.initTag(.u8),
            .i8_type => Type.initTag(.i8),
            .u16_type => Type.initTag(.u16),
            .i16_type => Type.initTag(.i16),
            .u32_type => Type.initTag(.u32),
            .i32_type => Type.initTag(.i32),
            .u64_type => Type.initTag(.u64),
            .i64_type => Type.initTag(.i64),
            .usize_type => Type.initTag(.usize),
            .isize_type => Type.initTag(.isize),
            .c_short_type => Type.initTag(.c_short),
            .c_ushort_type => Type.initTag(.c_ushort),
            .c_int_type => Type.initTag(.c_int),
            .c_uint_type => Type.initTag(.c_uint),
            .c_long_type => Type.initTag(.c_long),
            .c_ulong_type => Type.initTag(.c_ulong),
            .c_longlong_type => Type.initTag(.c_longlong),
            .c_ulonglong_type => Type.initTag(.c_ulonglong),
            .c_longdouble_type => Type.initTag(.c_longdouble),
            .f16_type => Type.initTag(.f16),
            .f32_type => Type.initTag(.f32),
            .f64_type => Type.initTag(.f64),
            .f128_type => Type.initTag(.f128),
            .c_void_type => Type.initTag(.c_void),
            .bool_type => Type.initTag(.bool),
            .void_type => Type.initTag(.void),
            .type_type => Type.initTag(.type),
            .anyerror_type => Type.initTag(.anyerror),
            .comptime_int_type => Type.initTag(.comptime_int),
            .comptime_float_type => Type.initTag(.comptime_float),
            .noreturn_type => Type.initTag(.noreturn),
            .null_type => Type.initTag(.@"null"),
            .undefined_type => Type.initTag(.@"undefined"),
            .fn_noreturn_no_args_type => Type.initTag(.fn_noreturn_no_args),
            .fn_void_no_args_type => Type.initTag(.fn_void_no_args),
            .fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args),
            .fn_ccc_void_no_args_type => Type.initTag(.fn_ccc_void_no_args),
            .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int),
            .const_slice_u8_type => Type.initTag(.const_slice_u8),
            .enum_literal_type => Type.initTag(.enum_literal),
            .anyframe_type => Type.initTag(.@"anyframe"),

            .int_type => {
                const payload = self.cast(Payload.IntType).?;
                if (payload.signed) {
                    const new = try allocator.create(Type.Payload.IntSigned);
                    new.* = .{ .bits = payload.bits };
                    return Type.initPayload(&new.base);
                } else {
                    const new = try allocator.create(Type.Payload.IntUnsigned);
                    new.* = .{ .bits = payload.bits };
                    return Type.initPayload(&new.base);
                }
            },
            .error_set => {
                const payload = self.cast(Payload.ErrorSet).?;
                const new = try allocator.create(Type.Payload.ErrorSet);
                new.* = .{ .decl = payload.decl };
                return Type.initPayload(&new.base);
            },

            .undef,
            .zero,
            .one,
            .void_value,
            .unreachable_value,
            .empty_array,
            .bool_true,
            .bool_false,
            .null_value,
            .int_u64,
            .int_i64,
            .int_big_positive,
            .int_big_negative,
            .function,
            .variable,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .repeated,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .enum_literal,
            .@"error",
            => unreachable,
        };
    }

    /// Asserts the value is an integer.
    pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst {
        switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .null_value,
            .function,
            .variable,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .repeated,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .void_value,
            .unreachable_value,
            .empty_array,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .undef => unreachable,

            .zero,
            .bool_false,
            => return BigIntMutable.init(&space.limbs, 0).toConst(),

            .one,
            .bool_true,
            => return BigIntMutable.init(&space.limbs, 1).toConst(),

            .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(),
            .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(),
            .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(),
            .int_big_negative => return self.cast(Payload.IntBigPositive).?.asBigInt(),
        }
    }

    /// Asserts the value is an integer and it fits in a u64
    pub fn toUnsignedInt(self: Value) u64 {
        switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .null_value,
            .function,
            .variable,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .repeated,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .void_value,
            .unreachable_value,
            .empty_array,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .undef => unreachable,

            .zero,
            .bool_false,
            => return 0,

            .one,
            .bool_true,
            => return 1,

            .int_u64 => return self.cast(Payload.Int_u64).?.int,
            .int_i64 => return @intCast(u64, self.cast(Payload.Int_i64).?.int),
            .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(u64) catch unreachable,
            .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().to(u64) catch unreachable,
        }
    }

    /// Asserts the value is an integer and it fits in a i64
    pub fn toSignedInt(self: Value) i64 {
        switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .null_value,
            .function,
            .variable,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .repeated,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .void_value,
            .unreachable_value,
            .empty_array,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .undef => unreachable,

            .zero,
            .bool_false,
            => return 0,

            .one,
            .bool_true,
            => return 1,

            .int_u64 => return @intCast(i64, self.cast(Payload.Int_u64).?.int),
            .int_i64 => return self.cast(Payload.Int_i64).?.int,
            .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(i64) catch unreachable,
            .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().to(i64) catch unreachable,
        }
    }

    pub fn toBool(self: Value) bool {
        return switch (self.tag()) {
            .bool_true => true,
            .bool_false, .zero => false,
            else => unreachable,
        };
    }

    /// Asserts that the value is a float or an integer.
    pub fn toFloat(self: Value, comptime T: type) T {
        return switch (self.tag()) {
            .float_16 => @panic("TODO soft float"),
            .float_32 => @floatCast(T, self.cast(Payload.Float_32).?.val),
            .float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val),
            .float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val),

            .zero => 0,
            .one => 1,
            .int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int),
            .int_i64 => @intToFloat(T, self.cast(Payload.Int_i64).?.int),

            .int_big_positive, .int_big_negative => @panic("big int to f128"),
            else => unreachable,
        };
    }

    /// Asserts the value is an integer and not undefined.
    /// Returns the number of bits the value requires to represent stored in twos complement form.
    pub fn intBitCountTwosComp(self: Value) usize {
        switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .null_value,
            .function,
            .variable,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .undef,
            .repeated,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .void_value,
            .unreachable_value,
            .empty_array,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .zero,
            .bool_false,
            => return 0,

            .one,
            .bool_true,
            => return 1,

            .int_u64 => {
                const x = self.cast(Payload.Int_u64).?.int;
                if (x == 0) return 0;
                return std.math.log2(x) + 1;
            },
            .int_i64 => {
                @panic("TODO implement i64 intBitCountTwosComp");
            },
            .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().bitCountTwosComp(),
            .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().bitCountTwosComp(),
        }
    }

    /// Asserts the value is an integer, and the destination type is ComptimeInt or Int.
    pub fn intFitsInType(self: Value, ty: Type, target: Target) bool {
        switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .null_value,
            .function,
            .variable,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .repeated,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .void_value,
            .unreachable_value,
            .empty_array,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .zero,
            .undef,
            .bool_false,
            => return true,

            .one,
            .bool_true,
            => {
                const info = ty.intInfo(target);
                if (info.signed) {
                    return info.bits >= 2;
                } else {
                    return info.bits >= 1;
                }
            },

            .int_u64 => switch (ty.zigTypeTag()) {
                .Int => {
                    const x = self.cast(Payload.Int_u64).?.int;
                    if (x == 0) return true;
                    const info = ty.intInfo(target);
                    const needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signed);
                    return info.bits >= needed_bits;
                },
                .ComptimeInt => return true,
                else => unreachable,
            },
            .int_i64 => switch (ty.zigTypeTag()) {
                .Int => {
                    const x = self.cast(Payload.Int_i64).?.int;
                    if (x == 0) return true;
                    const info = ty.intInfo(target);
                    if (!info.signed and x < 0)
                        return false;
                    @panic("TODO implement i64 intFitsInType");
                },
                .ComptimeInt => return true,
                else => unreachable,
            },
            .int_big_positive => switch (ty.zigTypeTag()) {
                .Int => {
                    const info = ty.intInfo(target);
                    return self.cast(Payload.IntBigPositive).?.asBigInt().fitsInTwosComp(info.signed, info.bits);
                },
                .ComptimeInt => return true,
                else => unreachable,
            },
            .int_big_negative => switch (ty.zigTypeTag()) {
                .Int => {
                    const info = ty.intInfo(target);
                    return self.cast(Payload.IntBigNegative).?.asBigInt().fitsInTwosComp(info.signed, info.bits);
                },
                .ComptimeInt => return true,
                else => unreachable,
            },
        }
    }

    /// Converts an integer or a float to a float.
    /// Returns `error.Overflow` if the value does not fit in the new type.
    pub fn floatCast(self: Value, allocator: *Allocator, ty: Type, target: Target) !Value {
        const dest_bit_count = switch (ty.tag()) {
            .comptime_float => 128,
            else => ty.floatBits(target),
        };
        switch (dest_bit_count) {
            16, 32, 64, 128 => {},
            else => std.debug.panic("TODO float cast bit count {}\n", .{dest_bit_count}),
        }
        if (ty.isInt()) {
            @panic("TODO int to float");
        }

        switch (dest_bit_count) {
            16 => {
                @panic("TODO soft float");
                // var res_payload = Value.Payload.Float_16{.val = self.toFloat(f16)};
                // if (!self.eql(Value.initPayload(&res_payload.base)))
                //     return error.Overflow;
                // return Value.initPayload(&res_payload.base).copy(allocator);
            },
            32 => {
                var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) };
                if (!self.eql(Value.initPayload(&res_payload.base)))
                    return error.Overflow;
                return Value.initPayload(&res_payload.base).copy(allocator);
            },
            64 => {
                var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) };
                if (!self.eql(Value.initPayload(&res_payload.base)))
                    return error.Overflow;
                return Value.initPayload(&res_payload.base).copy(allocator);
            },
            128 => {
                const float_payload = try allocator.create(Value.Payload.Float_128);
                float_payload.* = .{ .val = self.toFloat(f128) };
                return Value.initPayload(&float_payload.base);
            },
            else => unreachable,
        }
    }

    /// Asserts the value is a float
    pub fn floatHasFraction(self: Value) bool {
        return switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .bool_true,
            .bool_false,
            .null_value,
            .function,
            .variable,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .repeated,
            .undef,
            .int_u64,
            .int_i64,
            .int_big_positive,
            .int_big_negative,
            .empty_array,
            .void_value,
            .unreachable_value,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .zero,
            .one,
            => false,

            .float_16 => @rem(self.cast(Payload.Float_16).?.val, 1) != 0,
            .float_32 => @rem(self.cast(Payload.Float_32).?.val, 1) != 0,
            .float_64 => @rem(self.cast(Payload.Float_64).?.val, 1) != 0,
            // .float_128 => @rem(self.cast(Payload.Float_128).?.val, 1) != 0,
            .float_128 => @panic("TODO lld: error: undefined symbol: fmodl"),
        };
    }

    pub fn orderAgainstZero(lhs: Value) std.math.Order {
        return switch (lhs.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .null_value,
            .function,
            .variable,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .repeated,
            .undef,
            .void_value,
            .unreachable_value,
            .empty_array,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .zero,
            .bool_false,
            => .eq,

            .one,
            .bool_true,
            => .gt,

            .int_u64 => std.math.order(lhs.cast(Payload.Int_u64).?.int, 0),
            .int_i64 => std.math.order(lhs.cast(Payload.Int_i64).?.int, 0),
            .int_big_positive => lhs.cast(Payload.IntBigPositive).?.asBigInt().orderAgainstScalar(0),
            .int_big_negative => lhs.cast(Payload.IntBigNegative).?.asBigInt().orderAgainstScalar(0),

            .float_16 => std.math.order(lhs.cast(Payload.Float_16).?.val, 0),
            .float_32 => std.math.order(lhs.cast(Payload.Float_32).?.val, 0),
            .float_64 => std.math.order(lhs.cast(Payload.Float_64).?.val, 0),
            .float_128 => std.math.order(lhs.cast(Payload.Float_128).?.val, 0),
        };
    }

    /// Asserts the value is comparable.
    pub fn order(lhs: Value, rhs: Value) std.math.Order {
        const lhs_tag = lhs.tag();
        const rhs_tag = rhs.tag();
        const lhs_is_zero = lhs_tag == .zero;
        const rhs_is_zero = rhs_tag == .zero;
        if (lhs_is_zero) return rhs.orderAgainstZero().invert();
        if (rhs_is_zero) return lhs.orderAgainstZero();

        const lhs_float = lhs.isFloat();
        const rhs_float = rhs.isFloat();
        if (lhs_float and rhs_float) {
            if (lhs_tag == rhs_tag) {
                return switch (lhs.tag()) {
                    .float_16 => return std.math.order(lhs.cast(Payload.Float_16).?.val, rhs.cast(Payload.Float_16).?.val),
                    .float_32 => return std.math.order(lhs.cast(Payload.Float_32).?.val, rhs.cast(Payload.Float_32).?.val),
                    .float_64 => return std.math.order(lhs.cast(Payload.Float_64).?.val, rhs.cast(Payload.Float_64).?.val),
                    .float_128 => return std.math.order(lhs.cast(Payload.Float_128).?.val, rhs.cast(Payload.Float_128).?.val),
                    else => unreachable,
                };
            }
        }
        if (lhs_float or rhs_float) {
            const lhs_f128 = lhs.toFloat(f128);
            const rhs_f128 = rhs.toFloat(f128);
            return std.math.order(lhs_f128, rhs_f128);
        }

        var lhs_bigint_space: BigIntSpace = undefined;
        var rhs_bigint_space: BigIntSpace = undefined;
        const lhs_bigint = lhs.toBigInt(&lhs_bigint_space);
        const rhs_bigint = rhs.toBigInt(&rhs_bigint_space);
        return lhs_bigint.order(rhs_bigint);
    }

    /// Asserts the value is comparable.
    pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value) bool {
        return order(lhs, rhs).compare(op);
    }

    /// Asserts the value is comparable.
    pub fn compareWithZero(lhs: Value, op: std.math.CompareOperator) bool {
        return orderAgainstZero(lhs).compare(op);
    }

    pub fn eql(a: Value, b: Value) bool {
        if (a.tag() == b.tag() and a.tag() == .enum_literal) {
            const a_name = @fieldParentPtr(Payload.Bytes, "base", a.ptr_otherwise).data;
            const b_name = @fieldParentPtr(Payload.Bytes, "base", b.ptr_otherwise).data;
            return std.mem.eql(u8, a_name, b_name);
        }
        // TODO non numerical comparisons
        return compare(a, .eq, b);
    }

    /// Asserts the value is a pointer and dereferences it.
    /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
    pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
        return switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .zero,
            .one,
            .bool_true,
            .bool_false,
            .null_value,
            .function,
            .variable,
            .int_u64,
            .int_i64,
            .int_big_positive,
            .int_big_negative,
            .bytes,
            .undef,
            .repeated,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .void_value,
            .unreachable_value,
            .empty_array,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .ref_val => self.cast(Payload.RefVal).?.val,
            .decl_ref => self.cast(Payload.DeclRef).?.decl.value(),
            .elem_ptr => {
                const elem_ptr = self.cast(Payload.ElemPtr).?;
                const array_val = try elem_ptr.array_ptr.pointerDeref(allocator);
                return array_val.elemValue(allocator, elem_ptr.index);
            },
        };
    }

    /// Asserts the value is a single-item pointer to an array, or an array,
    /// or an unknown-length pointer, and returns the element value at the index.
    pub fn elemValue(self: Value, allocator: *Allocator, index: usize) error{OutOfMemory}!Value {
        switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .zero,
            .one,
            .bool_true,
            .bool_false,
            .null_value,
            .function,
            .variable,
            .int_u64,
            .int_i64,
            .int_big_positive,
            .int_big_negative,
            .undef,
            .elem_ptr,
            .ref_val,
            .decl_ref,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .void_value,
            .unreachable_value,
            .enum_literal,
            .error_set,
            .@"error",
            => unreachable,

            .empty_array => unreachable, // out of bounds array index

            .bytes => {
                const int_payload = try allocator.create(Payload.Int_u64);
                int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
                return Value.initPayload(&int_payload.base);
            },

            // No matter the index; all the elements are the same!
            .repeated => return self.cast(Payload.Repeated).?.val,
        }
    }

    /// Returns a pointer to the element value at the index.
    pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value {
        const payload = try allocator.create(Payload.ElemPtr);
        if (self.cast(Payload.ElemPtr)) |elem_ptr| {
            payload.* = .{ .array_ptr = elem_ptr.array_ptr, .index = elem_ptr.index + index };
        } else {
            payload.* = .{ .array_ptr = self, .index = index };
        }
        return Value.initPayload(&payload.base);
    }

    pub fn isUndef(self: Value) bool {
        return self.tag() == .undef;
    }

    /// Valid for all types. Asserts the value is not undefined and not unreachable.
    pub fn isNull(self: Value) bool {
        return switch (self.tag()) {
            .ty,
            .int_type,
            .u8_type,
            .i8_type,
            .u16_type,
            .i16_type,
            .u32_type,
            .i32_type,
            .u64_type,
            .i64_type,
            .usize_type,
            .isize_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,
            .f128_type,
            .c_void_type,
            .bool_type,
            .void_type,
            .type_type,
            .anyerror_type,
            .comptime_int_type,
            .comptime_float_type,
            .noreturn_type,
            .null_type,
            .undefined_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,
            .enum_literal_type,
            .anyframe_type,
            .zero,
            .one,
            .empty_array,
            .bool_true,
            .bool_false,
            .function,
            .variable,
            .int_u64,
            .int_i64,
            .int_big_positive,
            .int_big_negative,
            .ref_val,
            .decl_ref,
            .elem_ptr,
            .bytes,
            .repeated,
            .float_16,
            .float_32,
            .float_64,
            .float_128,
            .void_value,
            .enum_literal,
            .error_set,
            .@"error",
            => false,

            .undef => unreachable,
            .unreachable_value => unreachable,
            .null_value => true,
        };
    }

    /// Valid for all types. Asserts the value is not undefined.
    pub fn isFloat(self: Value) bool {
        return switch (self.tag()) {
            .undef => unreachable,

            .float_16,
            .float_32,
            .float_64,
            .float_128,
            => true,
            else => false,
        };
    }

    /// This type is not copyable since it may contain pointers to its inner data.
    pub const Payload = struct {
        tag: Tag,

        pub const Int_u64 = struct {
            base: Payload = Payload{ .tag = .int_u64 },
            int: u64,
        };

        pub const Int_i64 = struct {
            base: Payload = Payload{ .tag = .int_i64 },
            int: i64,
        };

        pub const IntBigPositive = struct {
            base: Payload = Payload{ .tag = .int_big_positive },
            limbs: []const std.math.big.Limb,

            pub fn asBigInt(self: IntBigPositive) BigIntConst {
                return BigIntConst{ .limbs = self.limbs, .positive = true };
            }
        };

        pub const IntBigNegative = struct {
            base: Payload = Payload{ .tag = .int_big_negative },
            limbs: []const std.math.big.Limb,

            pub fn asBigInt(self: IntBigNegative) BigIntConst {
                return BigIntConst{ .limbs = self.limbs, .positive = false };
            }
        };

        pub const Function = struct {
            base: Payload = Payload{ .tag = .function },
            func: *Module.Fn,
        };

        pub const Variable = struct {
            base: Payload = Payload{ .tag = .variable },
            variable: *Module.Var,
        };

        pub const ArraySentinel0_u8_Type = struct {
            base: Payload = Payload{ .tag = .array_sentinel_0_u8_type },
            len: u64,
        };

        /// Represents a pointer to another immutable value.
        pub const RefVal = struct {
            base: Payload = Payload{ .tag = .ref_val },
            val: Value,
        };

        /// Represents a pointer to a decl, not the value of the decl.
        pub const DeclRef = struct {
            base: Payload = Payload{ .tag = .decl_ref },
            decl: *Module.Decl,
        };

        pub const ElemPtr = struct {
            base: Payload = Payload{ .tag = .elem_ptr },
            array_ptr: Value,
            index: usize,
        };

        pub const Bytes = struct {
            base: Payload = Payload{ .tag = .bytes },
            data: []const u8,
        };

        pub const Ty = struct {
            base: Payload = Payload{ .tag = .ty },
            ty: Type,
        };

        pub const IntType = struct {
            base: Payload = Payload{ .tag = .int_type },
            bits: u16,
            signed: bool,
        };

        pub const Repeated = struct {
            base: Payload = Payload{ .tag = .ty },
            /// This value is repeated some number of times. The amount of times to repeat
            /// is stored externally.
            val: Value,
        };

        pub const Float_16 = struct {
            base: Payload = .{ .tag = .float_16 },
            val: f16,
        };

        pub const Float_32 = struct {
            base: Payload = .{ .tag = .float_32 },
            val: f32,
        };

        pub const Float_64 = struct {
            base: Payload = .{ .tag = .float_64 },
            val: f64,
        };

        pub const Float_128 = struct {
            base: Payload = .{ .tag = .float_128 },
            val: f128,
        };

        pub const ErrorSet = struct {
            base: Payload = .{ .tag = .error_set },

            // TODO revisit this when we have the concept of the error tag type
            fields: std.StringHashMapUnmanaged(u16),
            decl: *Module.Decl,
        };

        pub const Error = struct {
            base: Payload = .{ .tag = .@"error" },

            // TODO revisit this when we have the concept of the error tag type
            /// `name` is owned by `Module` and will be valid for the entire
            /// duration of the compilation.
            name: []const u8,
            value: u16,
        };
    };

    /// Big enough to fit any non-BigInt value
    pub const BigIntSpace = struct {
        /// The +1 is headroom so that operations such as incrementing once or decrementing once
        /// are possible without using an allocator.
        limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,
    };
};
