mirror of
https://github.com/ziglang/zig.git
synced 2026-02-02 12:43:40 +00:00
InternPool: add ptr-to-int value
Also modify coercion in Sema to be InternPool-aware by calling getCoerced. The unnecessary comptime logic in mod.intValue is deleted too
This commit is contained in:
parent
fd674d95be
commit
68b95a39b1
@ -55,6 +55,7 @@ pub const Key = union(enum) {
|
||||
lib_name: u32,
|
||||
},
|
||||
int: Key.Int,
|
||||
ptr: Key.Ptr,
|
||||
enum_tag: struct {
|
||||
ty: Index,
|
||||
tag: BigIntConst,
|
||||
@ -140,6 +141,16 @@ pub const Key = union(enum) {
|
||||
};
|
||||
};
|
||||
|
||||
pub const Ptr = struct {
|
||||
ty: Index,
|
||||
addr: Addr,
|
||||
|
||||
pub const Addr = union(enum) {
|
||||
decl: DeclIndex,
|
||||
int: Index,
|
||||
};
|
||||
};
|
||||
|
||||
pub fn hash32(key: Key) u32 {
|
||||
return @truncate(u32, key.hash64());
|
||||
}
|
||||
@ -176,6 +187,16 @@ pub const Key = union(enum) {
|
||||
for (big_int.limbs) |limb| std.hash.autoHash(hasher, limb);
|
||||
},
|
||||
|
||||
.ptr => |ptr| {
|
||||
std.hash.autoHash(hasher, ptr.ty);
|
||||
// Int-to-ptr pointers are hashed separately than decl-referencing pointers.
|
||||
// This is sound due to pointer province rules.
|
||||
switch (ptr.addr) {
|
||||
.int => |int| std.hash.autoHash(hasher, int),
|
||||
.decl => @panic("TODO"),
|
||||
}
|
||||
},
|
||||
|
||||
.enum_tag => |enum_tag| {
|
||||
std.hash.autoHash(hasher, enum_tag.ty);
|
||||
std.hash.autoHash(hasher, enum_tag.tag.positive);
|
||||
@ -237,8 +258,30 @@ pub const Key = union(enum) {
|
||||
return std.meta.eql(a_info, b_info);
|
||||
},
|
||||
|
||||
.ptr => |a_info| {
|
||||
const b_info = b.ptr;
|
||||
|
||||
if (a_info.ty != b_info.ty)
|
||||
return false;
|
||||
|
||||
return switch (a_info.addr) {
|
||||
.int => |a_int| switch (b_info.addr) {
|
||||
.int => |b_int| a_int == b_int,
|
||||
.decl => false,
|
||||
},
|
||||
.decl => |a_decl| switch (b_info.addr) {
|
||||
.int => false,
|
||||
.decl => |b_decl| a_decl == b_decl,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
.int => |a_info| {
|
||||
const b_info = b.int;
|
||||
|
||||
if (a_info.ty != b_info.ty)
|
||||
return false;
|
||||
|
||||
return switch (a_info.storage) {
|
||||
.u64 => |aa| switch (b_info.storage) {
|
||||
.u64 => |bb| aa == bb,
|
||||
@ -298,9 +341,11 @@ pub const Key = union(enum) {
|
||||
.union_type,
|
||||
=> return .type_type,
|
||||
|
||||
.int => |x| return x.ty,
|
||||
.extern_func => |x| return x.ty,
|
||||
.enum_tag => |x| return x.ty,
|
||||
inline .ptr,
|
||||
.int,
|
||||
.extern_func,
|
||||
.enum_tag,
|
||||
=> |x| return x.ty,
|
||||
|
||||
.simple_value => |s| switch (s) {
|
||||
.undefined => return .undefined_type,
|
||||
@ -724,6 +769,9 @@ pub const Tag = enum(u8) {
|
||||
/// only an enum tag, but will be presented via the API with a different Key.
|
||||
/// data is SimpleInternal enum value.
|
||||
simple_internal,
|
||||
/// A pointer to an integer value.
|
||||
/// data is extra index of PtrInt, which contains the type and address.
|
||||
ptr_int,
|
||||
/// Type: u8
|
||||
/// data is integer value
|
||||
int_u8,
|
||||
@ -897,16 +945,13 @@ pub const Array = struct {
|
||||
child: Index,
|
||||
sentinel: Index,
|
||||
|
||||
pub const Length = packed struct(u64) {
|
||||
len0: u32,
|
||||
len1: u32,
|
||||
};
|
||||
pub const Length = PackedU64;
|
||||
|
||||
pub fn getLength(a: Array) u64 {
|
||||
return @bitCast(u64, Length{
|
||||
.len0 = a.len0,
|
||||
.len1 = a.len1,
|
||||
});
|
||||
return (PackedU64{
|
||||
.a = a.len0,
|
||||
.b = a.len1,
|
||||
}).get();
|
||||
}
|
||||
};
|
||||
|
||||
@ -929,6 +974,24 @@ pub const EnumSimple = struct {
|
||||
fields_len: u32,
|
||||
};
|
||||
|
||||
pub const PackedU64 = packed struct(u64) {
|
||||
a: u32,
|
||||
b: u32,
|
||||
|
||||
pub fn get(x: PackedU64) u64 {
|
||||
return @bitCast(u64, x);
|
||||
}
|
||||
|
||||
pub fn init(x: u64) PackedU64 {
|
||||
return @bitCast(PackedU64, x);
|
||||
}
|
||||
};
|
||||
|
||||
pub const PtrInt = struct {
|
||||
ty: Index,
|
||||
addr: Index,
|
||||
};
|
||||
|
||||
/// Trailing: Limb for every limbs_len
|
||||
pub const Int = struct {
|
||||
ty: Index,
|
||||
@ -1066,6 +1129,13 @@ pub fn indexToKey(ip: InternPool, index: Index) Key {
|
||||
.fields_len = 0,
|
||||
} },
|
||||
},
|
||||
.ptr_int => {
|
||||
const info = ip.extraData(PtrInt, data);
|
||||
return .{ .ptr = .{
|
||||
.ty = info.ty,
|
||||
.addr = .{ .int = info.addr },
|
||||
} };
|
||||
},
|
||||
.int_u8 => .{ .int = .{
|
||||
.ty = .u8_type,
|
||||
.storage = .{ .u64 = data },
|
||||
@ -1188,12 +1258,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
|
||||
}
|
||||
}
|
||||
|
||||
const length = @bitCast(Array.Length, array_type.len);
|
||||
const length = Array.Length.init(array_type.len);
|
||||
ip.items.appendAssumeCapacity(.{
|
||||
.tag = .type_array_big,
|
||||
.data = try ip.addExtra(gpa, Array{
|
||||
.len0 = length.len0,
|
||||
.len1 = length.len1,
|
||||
.len0 = length.a,
|
||||
.len1 = length.b,
|
||||
.child = array_type.child,
|
||||
.sentinel = array_type.sentinel,
|
||||
}),
|
||||
@ -1237,6 +1307,20 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
|
||||
},
|
||||
.extern_func => @panic("TODO"),
|
||||
|
||||
.ptr => |ptr| switch (ptr.addr) {
|
||||
.decl => @panic("TODO"),
|
||||
.int => |int| {
|
||||
assert(ptr.ty != .none);
|
||||
ip.items.appendAssumeCapacity(.{
|
||||
.tag = .ptr_int,
|
||||
.data = try ip.addExtra(gpa, PtrInt{
|
||||
.ty = ptr.ty,
|
||||
.addr = int,
|
||||
}),
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
.int => |int| b: {
|
||||
switch (int.ty) {
|
||||
.none => unreachable,
|
||||
@ -1620,38 +1704,43 @@ pub fn slicePtrType(ip: InternPool, i: Index) Index {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an existing integer value, returns the same numerical value but with
|
||||
/// the supplied type.
|
||||
pub fn getCoercedInt(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Allocator.Error!Index {
|
||||
const key = ip.indexToKey(val);
|
||||
// The key cannot be passed directly to `get`, otherwise in the case of
|
||||
// big_int storage, the limbs would be invalidated before they are read.
|
||||
// Here we pre-reserve the limbs to ensure that the logic in `addInt` will
|
||||
// not use an invalidated limbs pointer.
|
||||
switch (key.int.storage) {
|
||||
.u64 => |x| return ip.get(gpa, .{ .int = .{
|
||||
.ty = new_ty,
|
||||
.storage = .{ .u64 = x },
|
||||
} }),
|
||||
.i64 => |x| return ip.get(gpa, .{ .int = .{
|
||||
.ty = new_ty,
|
||||
.storage = .{ .i64 = x },
|
||||
} }),
|
||||
/// Given an existing value, returns the same value but with the supplied type.
|
||||
/// Only some combinations are allowed:
|
||||
/// * int to int
|
||||
pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Allocator.Error!Index {
|
||||
switch (ip.indexToKey(val)) {
|
||||
.int => |int| {
|
||||
// The key cannot be passed directly to `get`, otherwise in the case of
|
||||
// big_int storage, the limbs would be invalidated before they are read.
|
||||
// Here we pre-reserve the limbs to ensure that the logic in `addInt` will
|
||||
// not use an invalidated limbs pointer.
|
||||
switch (int.storage) {
|
||||
.u64 => |x| return ip.get(gpa, .{ .int = .{
|
||||
.ty = new_ty,
|
||||
.storage = .{ .u64 = x },
|
||||
} }),
|
||||
.i64 => |x| return ip.get(gpa, .{ .int = .{
|
||||
.ty = new_ty,
|
||||
.storage = .{ .i64 = x },
|
||||
} }),
|
||||
|
||||
.big_int => |big_int| {
|
||||
const positive = big_int.positive;
|
||||
const limbs = ip.limbsSliceToIndex(big_int.limbs);
|
||||
// This line invalidates the limbs slice, but the indexes computed in the
|
||||
// previous line are still correct.
|
||||
try reserveLimbs(ip, gpa, @typeInfo(Int).Struct.fields.len + big_int.limbs.len);
|
||||
return ip.get(gpa, .{ .int = .{
|
||||
.ty = new_ty,
|
||||
.storage = .{ .big_int = .{
|
||||
.limbs = ip.limbsIndexToSlice(limbs),
|
||||
.positive = positive,
|
||||
} },
|
||||
} });
|
||||
.big_int => |big_int| {
|
||||
const positive = big_int.positive;
|
||||
const limbs = ip.limbsSliceToIndex(big_int.limbs);
|
||||
// This line invalidates the limbs slice, but the indexes computed in the
|
||||
// previous line are still correct.
|
||||
try reserveLimbs(ip, gpa, @typeInfo(Int).Struct.fields.len + big_int.limbs.len);
|
||||
return ip.get(gpa, .{ .int = .{
|
||||
.ty = new_ty,
|
||||
.storage = .{ .big_int = .{
|
||||
.limbs = ip.limbsIndexToSlice(limbs),
|
||||
.positive = positive,
|
||||
} },
|
||||
} });
|
||||
},
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1708,6 +1797,7 @@ fn dumpFallible(ip: InternPool, arena: Allocator) anyerror!void {
|
||||
.simple_type => 0,
|
||||
.simple_value => 0,
|
||||
.simple_internal => 0,
|
||||
.ptr_int => @sizeOf(PtrInt),
|
||||
.int_u8 => 0,
|
||||
.int_u16 => 0,
|
||||
.int_u32 => 0,
|
||||
|
||||
@ -6887,17 +6887,23 @@ pub fn singleConstPtrType(mod: *Module, child_type: Type) Allocator.Error!Type {
|
||||
return ptrType(mod, .{ .elem_type = child_type.ip_index, .is_const = true });
|
||||
}
|
||||
|
||||
pub fn ptrIntValue(mod: *Module, ty: Type, x: u64) Allocator.Error!Value {
|
||||
assert(ty.zigTypeTag(mod) == .Pointer);
|
||||
const i = try intern(mod, .{ .ptr = .{
|
||||
.ty = ty.ip_index,
|
||||
.addr = .{ .int = try intern(mod, .{ .int = .{
|
||||
.ty = ty.ip_index,
|
||||
.storage = .{ .u64 = x },
|
||||
} }) },
|
||||
} });
|
||||
return i.toValue();
|
||||
}
|
||||
|
||||
pub fn intValue(mod: *Module, ty: Type, x: anytype) Allocator.Error!Value {
|
||||
if (std.debug.runtime_safety) {
|
||||
// TODO: decide if this also works for ABI int types like enums
|
||||
const tag = ty.zigTypeTag(mod);
|
||||
assert(tag == .Int or tag == .ComptimeInt);
|
||||
}
|
||||
if (@TypeOf(x) == comptime_int) {
|
||||
if (comptime std.math.cast(u64, x)) |casted| return intValue_u64(mod, ty, casted);
|
||||
if (comptime std.math.cast(i64, x)) |casted| return intValue_i64(mod, ty, casted);
|
||||
@compileError("Out-of-range comptime_int passed to Module.intValue");
|
||||
}
|
||||
if (std.math.cast(u64, x)) |casted| return intValue_u64(mod, ty, casted);
|
||||
if (std.math.cast(i64, x)) |casted| return intValue_i64(mod, ty, casted);
|
||||
var limbs_buffer: [4]usize = undefined;
|
||||
|
||||
39
src/Sema.zig
39
src/Sema.zig
@ -15096,7 +15096,7 @@ fn analyzePtrArithmetic(
|
||||
.ptr_sub => addr - elem_size * offset_int,
|
||||
else => unreachable,
|
||||
};
|
||||
const new_ptr_val = try mod.intValue(new_ptr_ty, new_addr);
|
||||
const new_ptr_val = try mod.ptrIntValue(new_ptr_ty, new_addr);
|
||||
return sema.addConstant(new_ptr_ty, new_ptr_val);
|
||||
}
|
||||
if (air_tag == .ptr_sub) {
|
||||
@ -19931,7 +19931,7 @@ fn zirIntToPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
|
||||
if (addr != 0 and ptr_align != 0 and addr % ptr_align != 0)
|
||||
return sema.fail(block, operand_src, "pointer type '{}' requires aligned address", .{ptr_ty.fmt(sema.mod)});
|
||||
|
||||
return sema.addConstant(ptr_ty, try mod.intValue(ptr_ty, addr));
|
||||
return sema.addConstant(ptr_ty, try mod.ptrIntValue(ptr_ty, addr));
|
||||
}
|
||||
|
||||
try sema.requireRuntimeBlock(block, src, operand_src);
|
||||
@ -25640,8 +25640,13 @@ fn coerceExtra(
|
||||
var in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src);
|
||||
if (in_memory_result == .ok) {
|
||||
if (maybe_inst_val) |val| {
|
||||
// Keep the comptime Value representation; take the new type.
|
||||
return sema.addConstant(dest_ty, val);
|
||||
if (val.ip_index == .none or val.ip_index == .null_value) {
|
||||
// Keep the comptime Value representation; take the new type.
|
||||
return sema.addConstant(dest_ty, val);
|
||||
} else {
|
||||
const new_val = try mod.intern_pool.getCoerced(mod.gpa, val.ip_index, dest_ty.ip_index);
|
||||
return sema.addConstant(dest_ty, new_val.toValue());
|
||||
}
|
||||
}
|
||||
try sema.requireRuntimeBlock(block, inst_src, null);
|
||||
return block.addBitCast(dest_ty, inst);
|
||||
@ -26014,7 +26019,7 @@ fn coerceExtra(
|
||||
if (!opts.report_err) return error.NotCoercible;
|
||||
return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) });
|
||||
}
|
||||
const new_val = try mod.intern_pool.getCoercedInt(sema.gpa, val.ip_index, dest_ty.ip_index);
|
||||
const new_val = try mod.intern_pool.getCoerced(sema.gpa, val.ip_index, dest_ty.ip_index);
|
||||
return try sema.addConstant(dest_ty, new_val.toValue());
|
||||
}
|
||||
if (dest_ty.zigTypeTag(mod) == .ComptimeInt) {
|
||||
@ -31673,10 +31678,13 @@ pub fn resolveTypeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -33193,10 +33201,13 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -33253,7 +33264,14 @@ pub fn addConstant(sema: *Sema, ty: Type, val: Value) SemaError!Air.Inst.Ref {
|
||||
const result = Air.indexToRef(@intCast(u32, sema.air_instructions.len - 1));
|
||||
// This assertion can be removed when the `ty` parameter is removed from
|
||||
// this function thanks to the InternPool transition being complete.
|
||||
assert(Type.eql(sema.typeOf(result), ty, sema.mod));
|
||||
if (std.debug.runtime_safety) {
|
||||
const val_ty = sema.typeOf(result);
|
||||
if (!Type.eql(val_ty, ty, sema.mod)) {
|
||||
std.debug.panic("addConstant type mismatch: '{}' vs '{}'\n", .{
|
||||
ty.fmt(sema.mod), val_ty.fmt(sema.mod),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const ty_inst = try sema.addType(ty);
|
||||
@ -33752,10 +33770,13 @@ pub fn typeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
46
src/type.zig
46
src/type.zig
@ -142,11 +142,12 @@ pub const Type = struct {
|
||||
.var_args_param => unreachable,
|
||||
},
|
||||
|
||||
.extern_func,
|
||||
.int,
|
||||
.enum_tag,
|
||||
.simple_value,
|
||||
=> unreachable, // it's a value, not a type
|
||||
// values, not types
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
.simple_value => unreachable,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1576,6 +1577,7 @@ pub const Type = struct {
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
}
|
||||
@ -1842,10 +1844,13 @@ pub const Type = struct {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1950,10 +1955,13 @@ pub const Type = struct {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -2348,10 +2356,13 @@ pub const Type = struct {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -2759,10 +2770,13 @@ pub const Type = struct {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -2926,10 +2940,13 @@ pub const Type = struct {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
};
|
||||
|
||||
const strat: AbiAlignmentAdvancedStrat = if (opt_sema) |sema| .{ .sema = sema } else .eager;
|
||||
@ -3780,9 +3797,12 @@ pub const Type = struct {
|
||||
.simple_type => unreachable, // handled via Index enum tag above
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => unreachable,
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
};
|
||||
@ -4152,10 +4172,13 @@ pub const Type = struct {
|
||||
},
|
||||
.struct_type => @panic("TODO"),
|
||||
.union_type => @panic("TODO"),
|
||||
|
||||
// values, not types
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -4319,6 +4342,7 @@ pub const Type = struct {
|
||||
.simple_value => unreachable,
|
||||
.extern_func => unreachable,
|
||||
.int => unreachable,
|
||||
.ptr => unreachable,
|
||||
.enum_tag => unreachable, // it's a value, not a type
|
||||
},
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user