mirror of
https://github.com/ziglang/zig.git
synced 2026-02-21 08:45:52 +00:00
Sema: more union fixes
* `Module.Union.getLayout`: fixes to support components of the union being 0 bits. * Implement `@typeInfo` for unions. * Add missing calls to `resolveTypeFields`. * Fix explicitly-provided union tag types passing a `Zir.Inst.Ref` where an `Air.Inst.Ref` was expected. We don't have any type safety for this; these typess are aliases. * Fix explicitly-provided `union(enum)` tag Values allocated to the wrong arena.
This commit is contained in:
parent
91619cdf57
commit
efb7148a45
@ -1104,9 +1104,9 @@ pub const Union = struct {
|
||||
|
||||
pub fn getLayout(u: Union, target: Target, have_tag: bool) Layout {
|
||||
assert(u.status == .have_layout);
|
||||
var most_aligned_field: usize = undefined;
|
||||
var most_aligned_field: u32 = undefined;
|
||||
var most_aligned_field_size: u64 = undefined;
|
||||
var biggest_field: usize = undefined;
|
||||
var biggest_field: u32 = undefined;
|
||||
var payload_size: u64 = 0;
|
||||
var payload_align: u32 = 0;
|
||||
for (u.fields.values()) |field, i| {
|
||||
@ -1122,20 +1122,21 @@ pub const Union = struct {
|
||||
const field_size = field.ty.abiSize(target);
|
||||
if (field_size > payload_size) {
|
||||
payload_size = field_size;
|
||||
biggest_field = i;
|
||||
biggest_field = @intCast(u32, i);
|
||||
}
|
||||
if (field_align > payload_align) {
|
||||
payload_align = field_align;
|
||||
most_aligned_field = i;
|
||||
most_aligned_field = @intCast(u32, i);
|
||||
most_aligned_field_size = field_size;
|
||||
}
|
||||
}
|
||||
payload_align = @maximum(payload_align, 1);
|
||||
if (!have_tag) return .{
|
||||
.abi_size = std.mem.alignForwardGeneric(u64, payload_size, payload_align),
|
||||
.abi_align = payload_align,
|
||||
.most_aligned_field = @intCast(u32, most_aligned_field),
|
||||
.most_aligned_field = most_aligned_field,
|
||||
.most_aligned_field_size = most_aligned_field_size,
|
||||
.biggest_field = @intCast(u32, biggest_field),
|
||||
.biggest_field = biggest_field,
|
||||
.payload_size = payload_size,
|
||||
.payload_align = payload_align,
|
||||
.tag_align = 0,
|
||||
@ -1144,7 +1145,7 @@ pub const Union = struct {
|
||||
// Put the tag before or after the payload depending on which one's
|
||||
// alignment is greater.
|
||||
const tag_size = u.tag_ty.abiSize(target);
|
||||
const tag_align = u.tag_ty.abiAlignment(target);
|
||||
const tag_align = @maximum(1, u.tag_ty.abiAlignment(target));
|
||||
var size: u64 = 0;
|
||||
if (tag_align >= payload_align) {
|
||||
// {Tag, Payload}
|
||||
@ -1162,9 +1163,9 @@ pub const Union = struct {
|
||||
return .{
|
||||
.abi_size = size,
|
||||
.abi_align = @maximum(tag_align, payload_align),
|
||||
.most_aligned_field = @intCast(u32, most_aligned_field),
|
||||
.most_aligned_field = most_aligned_field,
|
||||
.most_aligned_field_size = most_aligned_field_size,
|
||||
.biggest_field = @intCast(u32, biggest_field),
|
||||
.biggest_field = biggest_field,
|
||||
.payload_size = payload_size,
|
||||
.payload_align = payload_align,
|
||||
.tag_align = tag_align,
|
||||
|
||||
135
src/Sema.zig
135
src/Sema.zig
@ -5799,8 +5799,12 @@ fn zirSwitchCond(
|
||||
) CompileError!Air.Inst.Ref {
|
||||
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
|
||||
const src = inst_data.src();
|
||||
const operand_src = src; // TODO make this point at the switch operand
|
||||
const operand_ptr = sema.resolveInst(inst_data.operand);
|
||||
const operand = if (is_ref) try sema.analyzeLoad(block, src, operand_ptr, src) else operand_ptr;
|
||||
const operand = if (is_ref)
|
||||
try sema.analyzeLoad(block, src, operand_ptr, operand_src)
|
||||
else
|
||||
operand_ptr;
|
||||
const operand_ty = sema.typeOf(operand);
|
||||
|
||||
switch (operand_ty.zigTypeTag()) {
|
||||
@ -5817,18 +5821,19 @@ fn zirSwitchCond(
|
||||
.ErrorSet,
|
||||
.Enum,
|
||||
=> {
|
||||
if ((try sema.typeHasOnePossibleValue(block, src, operand_ty))) |opv| {
|
||||
if ((try sema.typeHasOnePossibleValue(block, operand_src, operand_ty))) |opv| {
|
||||
return sema.addConstant(operand_ty, opv);
|
||||
}
|
||||
return operand;
|
||||
},
|
||||
|
||||
.Union => {
|
||||
const enum_ty = operand_ty.unionTagType() orelse {
|
||||
const union_ty = try sema.resolveTypeFields(block, operand_src, operand_ty);
|
||||
const enum_ty = union_ty.unionTagType() orelse {
|
||||
const msg = msg: {
|
||||
const msg = try sema.errMsg(block, src, "switch on untagged union", .{});
|
||||
errdefer msg.destroy(sema.gpa);
|
||||
try sema.addDeclaredHereNote(msg, operand_ty);
|
||||
try sema.addDeclaredHereNote(msg, union_ty);
|
||||
break :msg msg;
|
||||
};
|
||||
return sema.failWithOwnedErrorMsg(msg);
|
||||
@ -9154,9 +9159,107 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
|
||||
}),
|
||||
);
|
||||
},
|
||||
.Union => {
|
||||
// TODO: look into memoizing this result.
|
||||
|
||||
var fields_anon_decl = try block.startAnonDecl();
|
||||
defer fields_anon_decl.deinit();
|
||||
|
||||
const union_field_ty = t: {
|
||||
const union_field_ty_decl = (try sema.namespaceLookup(
|
||||
block,
|
||||
src,
|
||||
type_info_ty.getNamespace().?,
|
||||
"UnionField",
|
||||
)).?;
|
||||
try sema.mod.declareDeclDependency(sema.owner_decl, union_field_ty_decl);
|
||||
try sema.ensureDeclAnalyzed(union_field_ty_decl);
|
||||
var buffer: Value.ToTypeBuffer = undefined;
|
||||
break :t try union_field_ty_decl.val.toType(&buffer).copy(fields_anon_decl.arena());
|
||||
};
|
||||
|
||||
const union_ty = try sema.resolveTypeFields(block, src, ty);
|
||||
const union_fields = union_ty.unionFields();
|
||||
const union_field_vals = try fields_anon_decl.arena().alloc(Value, union_fields.count());
|
||||
|
||||
for (union_field_vals) |*field_val, i| {
|
||||
const field = union_fields.values()[i];
|
||||
const name = union_fields.keys()[i];
|
||||
const name_val = v: {
|
||||
var anon_decl = try block.startAnonDecl();
|
||||
defer anon_decl.deinit();
|
||||
const bytes = try anon_decl.arena().dupeZ(u8, name);
|
||||
const new_decl = try anon_decl.finish(
|
||||
try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len),
|
||||
try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
|
||||
);
|
||||
break :v try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl);
|
||||
};
|
||||
|
||||
const union_field_fields = try fields_anon_decl.arena().create([3]Value);
|
||||
union_field_fields.* = .{
|
||||
// name: []const u8,
|
||||
name_val,
|
||||
// field_type: type,
|
||||
try Value.Tag.ty.create(fields_anon_decl.arena(), field.ty),
|
||||
// alignment: comptime_int,
|
||||
try field.abi_align.copy(fields_anon_decl.arena()),
|
||||
};
|
||||
field_val.* = try Value.Tag.@"struct".create(fields_anon_decl.arena(), union_field_fields);
|
||||
}
|
||||
|
||||
const fields_val = v: {
|
||||
const new_decl = try fields_anon_decl.finish(
|
||||
try Type.Tag.array.create(fields_anon_decl.arena(), .{
|
||||
.len = union_field_vals.len,
|
||||
.elem_type = union_field_ty,
|
||||
}),
|
||||
try Value.Tag.array.create(
|
||||
fields_anon_decl.arena(),
|
||||
try fields_anon_decl.arena().dupe(Value, union_field_vals),
|
||||
),
|
||||
);
|
||||
break :v try Value.Tag.decl_ref.create(sema.arena, new_decl);
|
||||
};
|
||||
|
||||
if (ty.getNamespace()) |namespace| {
|
||||
if (namespace.decls.count() != 0) {
|
||||
return sema.fail(block, src, "TODO: implement zirTypeInfo for Union which has declarations", .{});
|
||||
}
|
||||
}
|
||||
const decls_val = Value.initTag(.empty_array);
|
||||
|
||||
const enum_tag_ty_val = if (union_ty.unionTagType()) |tag_ty| v: {
|
||||
const ty_val = try Value.Tag.ty.create(sema.arena, tag_ty);
|
||||
break :v try Value.Tag.opt_payload.create(sema.arena, ty_val);
|
||||
} else Value.@"null";
|
||||
|
||||
const field_values = try sema.arena.create([4]Value);
|
||||
field_values.* = .{
|
||||
// layout: ContainerLayout,
|
||||
try Value.Tag.enum_field_index.create(
|
||||
sema.arena,
|
||||
@enumToInt(std.builtin.TypeInfo.ContainerLayout.Auto),
|
||||
),
|
||||
|
||||
// tag_type: ?type,
|
||||
enum_tag_ty_val,
|
||||
// fields: []const UnionField,
|
||||
fields_val,
|
||||
// decls: []const Declaration,
|
||||
decls_val,
|
||||
};
|
||||
|
||||
return sema.addConstant(
|
||||
type_info_ty,
|
||||
try Value.Tag.@"union".create(sema.arena, .{
|
||||
.tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Union)),
|
||||
.val = try Value.Tag.@"struct".create(sema.arena, field_values),
|
||||
}),
|
||||
);
|
||||
},
|
||||
.Struct => return sema.fail(block, src, "TODO: implement zirTypeInfo for Struct", .{}),
|
||||
.ErrorSet => return sema.fail(block, src, "TODO: implement zirTypeInfo for ErrorSet", .{}),
|
||||
.Union => return sema.fail(block, src, "TODO: implement zirTypeInfo for Union", .{}),
|
||||
.BoundFn => @panic("TODO remove this type from the language and compiler"),
|
||||
.Opaque => return sema.fail(block, src, "TODO: implement zirTypeInfo for Opaque", .{}),
|
||||
.Frame => return sema.fail(block, src, "TODO: implement zirTypeInfo for Frame", .{}),
|
||||
@ -11847,12 +11950,14 @@ fn fieldVal(
|
||||
);
|
||||
},
|
||||
.Union => {
|
||||
if (child_type.getNamespace()) |namespace| {
|
||||
const union_ty = try sema.resolveTypeFields(block, src, child_type);
|
||||
|
||||
if (union_ty.getNamespace()) |namespace| {
|
||||
if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| {
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
if (child_type.unionTagType()) |enum_ty| {
|
||||
if (union_ty.unionTagType()) |enum_ty| {
|
||||
if (enum_ty.enumFieldIndex(field_name)) |field_index_usize| {
|
||||
const field_index = @intCast(u32, field_index_usize);
|
||||
return sema.addConstant(
|
||||
@ -11861,7 +11966,7 @@ fn fieldVal(
|
||||
);
|
||||
}
|
||||
}
|
||||
return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
|
||||
return sema.failWithBadMemberAccess(block, union_ty, field_name_src, field_name);
|
||||
},
|
||||
.Enum => {
|
||||
if (child_type.getNamespace()) |namespace| {
|
||||
@ -15185,8 +15290,9 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
|
||||
// The provided type is an integer type and we must construct the enum tag type here.
|
||||
int_tag_ty = provided_ty;
|
||||
union_obj.tag_ty = try sema.generateUnionTagTypeNumbered(&block_scope, fields_len, provided_ty);
|
||||
enum_field_names = &union_obj.tag_ty.castTag(.enum_numbered).?.data.fields;
|
||||
enum_value_map = &union_obj.tag_ty.castTag(.enum_numbered).?.data.values;
|
||||
const enum_obj = union_obj.tag_ty.castTag(.enum_numbered).?.data;
|
||||
enum_field_names = &enum_obj.fields;
|
||||
enum_value_map = &enum_obj.values;
|
||||
} else {
|
||||
// The provided type is the enum tag type.
|
||||
union_obj.tag_ty = try provided_ty.copy(decl_arena_allocator);
|
||||
@ -15239,14 +15345,19 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
|
||||
const tag_ref: Zir.Inst.Ref = if (has_tag) blk: {
|
||||
const tag_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
|
||||
extra_index += 1;
|
||||
break :blk tag_ref;
|
||||
break :blk sema.resolveInst(tag_ref);
|
||||
} else .none;
|
||||
|
||||
if (enum_value_map) |map| {
|
||||
const tag_src = src; // TODO better source location
|
||||
const coerced = try sema.coerce(&block_scope, int_tag_ty, tag_ref, tag_src);
|
||||
const val = try sema.resolveConstValue(&block_scope, tag_src, coerced);
|
||||
map.putAssumeCapacityContext(val, {}, .{ .ty = int_tag_ty });
|
||||
|
||||
// This puts the memory into the union arena, not the enum arena, but
|
||||
// it is OK since they share the same lifetime.
|
||||
const copied_val = try val.copy(decl_arena_allocator);
|
||||
|
||||
map.putAssumeCapacityContext(copied_val, {}, .{ .ty = int_tag_ty });
|
||||
}
|
||||
|
||||
// This string needs to outlive the ZIR code.
|
||||
|
||||
@ -2868,7 +2868,11 @@ pub const Type = extern union {
|
||||
/// Otherwise, returns `null`.
|
||||
pub fn unionTagType(ty: Type) ?Type {
|
||||
return switch (ty.tag()) {
|
||||
.union_tagged => ty.castTag(.union_tagged).?.data.tag_ty,
|
||||
.union_tagged => {
|
||||
const union_obj = ty.castTag(.union_tagged).?.data;
|
||||
assert(union_obj.haveFieldTypes());
|
||||
return union_obj.tag_ty;
|
||||
},
|
||||
|
||||
.atomic_order,
|
||||
.atomic_rmw_op,
|
||||
|
||||
@ -313,3 +313,16 @@ fn returnsFalse() bool {
|
||||
test "switch on const enum with var" {
|
||||
try expect(!returnsFalse());
|
||||
}
|
||||
|
||||
test "anon enum literal used in switch on union enum" {
|
||||
const Foo = union(enum) {
|
||||
a: i32,
|
||||
};
|
||||
|
||||
var foo = Foo{ .a = 1234 };
|
||||
switch (foo) {
|
||||
.a => |x| {
|
||||
try expect(x == 1234);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,19 +35,6 @@ test "capture value of switch with all unreachable prongs" {
|
||||
try expect(x == 1);
|
||||
}
|
||||
|
||||
test "anon enum literal used in switch on union enum" {
|
||||
const Foo = union(enum) {
|
||||
a: i32,
|
||||
};
|
||||
|
||||
var foo = Foo{ .a = 1234 };
|
||||
switch (foo) {
|
||||
.a => |x| {
|
||||
try expect(x == 1234);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test "else prong of switch on error set excludes other cases" {
|
||||
const S = struct {
|
||||
fn doTheTest() !void {
|
||||
|
||||
@ -221,6 +221,12 @@ fn testCastUnionToTag() !void {
|
||||
try expect(@as(TheTag, u) == TheTag.B);
|
||||
}
|
||||
|
||||
test "union field access gives the enum values" {
|
||||
try expect(TheUnion.A == TheTag.A);
|
||||
try expect(TheUnion.B == TheTag.B);
|
||||
try expect(TheUnion.C == TheTag.C);
|
||||
}
|
||||
|
||||
test "cast tag type of union to union" {
|
||||
var x: Value2 = Letter2.B;
|
||||
try expect(@as(Letter2, x) == Letter2.B);
|
||||
@ -255,3 +261,202 @@ test "constant packed union" {
|
||||
fn testConstPackedUnion(expected_tokens: []const PackThis) !void {
|
||||
try expect(expected_tokens[0].StringLiteral == 1);
|
||||
}
|
||||
|
||||
const MultipleChoice = union(enum(u32)) {
|
||||
A = 20,
|
||||
B = 40,
|
||||
C = 60,
|
||||
D = 1000,
|
||||
};
|
||||
test "simple union(enum(u32))" {
|
||||
var x = MultipleChoice.C;
|
||||
try expect(x == MultipleChoice.C);
|
||||
try expect(@enumToInt(@as(Tag(MultipleChoice), x)) == 60);
|
||||
}
|
||||
|
||||
const PackedPtrOrInt = packed union {
|
||||
ptr: *u8,
|
||||
int: u64,
|
||||
};
|
||||
test "packed union size" {
|
||||
comptime try expect(@sizeOf(PackedPtrOrInt) == 8);
|
||||
}
|
||||
|
||||
const ZeroBits = union {
|
||||
OnlyField: void,
|
||||
};
|
||||
test "union with only 1 field which is void should be zero bits" {
|
||||
comptime try expect(@sizeOf(ZeroBits) == 0);
|
||||
}
|
||||
|
||||
test "tagged union initialization with runtime void" {
|
||||
try expect(testTaggedUnionInit({}));
|
||||
}
|
||||
|
||||
const TaggedUnionWithAVoid = union(enum) {
|
||||
A,
|
||||
B: i32,
|
||||
};
|
||||
|
||||
fn testTaggedUnionInit(x: anytype) bool {
|
||||
const y = TaggedUnionWithAVoid{ .A = x };
|
||||
return @as(Tag(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A;
|
||||
}
|
||||
|
||||
pub const UnionEnumNoPayloads = union(enum) { A, B };
|
||||
|
||||
test "tagged union with no payloads" {
|
||||
const a = UnionEnumNoPayloads{ .B = {} };
|
||||
switch (a) {
|
||||
Tag(UnionEnumNoPayloads).A => @panic("wrong"),
|
||||
Tag(UnionEnumNoPayloads).B => {},
|
||||
}
|
||||
}
|
||||
|
||||
test "union with only 1 field casted to its enum type" {
|
||||
const Literal = union(enum) {
|
||||
Number: f64,
|
||||
Bool: bool,
|
||||
};
|
||||
|
||||
const Expr = union(enum) {
|
||||
Literal: Literal,
|
||||
};
|
||||
|
||||
var e = Expr{ .Literal = Literal{ .Bool = true } };
|
||||
const ExprTag = Tag(Expr);
|
||||
comptime try expect(Tag(ExprTag) == u0);
|
||||
var t = @as(ExprTag, e);
|
||||
try expect(t == Expr.Literal);
|
||||
}
|
||||
|
||||
test "union with one member defaults to u0 tag type" {
|
||||
const U0 = union(enum) {
|
||||
X: u32,
|
||||
};
|
||||
comptime try expect(Tag(Tag(U0)) == u0);
|
||||
}
|
||||
|
||||
const Foo1 = union(enum) {
|
||||
f: struct {
|
||||
x: usize,
|
||||
},
|
||||
};
|
||||
var glbl: Foo1 = undefined;
|
||||
|
||||
test "global union with single field is correctly initialized" {
|
||||
glbl = Foo1{
|
||||
.f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 },
|
||||
};
|
||||
try expect(glbl.f.x == 123);
|
||||
}
|
||||
|
||||
pub const FooUnion = union(enum) {
|
||||
U0: usize,
|
||||
U1: u8,
|
||||
};
|
||||
|
||||
var glbl_array: [2]FooUnion = undefined;
|
||||
|
||||
test "initialize global array of union" {
|
||||
glbl_array[1] = FooUnion{ .U1 = 2 };
|
||||
glbl_array[0] = FooUnion{ .U0 = 1 };
|
||||
try expect(glbl_array[0].U0 == 1);
|
||||
try expect(glbl_array[1].U1 == 2);
|
||||
}
|
||||
|
||||
test "update the tag value for zero-sized unions" {
|
||||
const S = union(enum) {
|
||||
U0: void,
|
||||
U1: void,
|
||||
};
|
||||
var x = S{ .U0 = {} };
|
||||
try expect(x == .U0);
|
||||
x = S{ .U1 = {} };
|
||||
try expect(x == .U1);
|
||||
}
|
||||
|
||||
test "union initializer generates padding only if needed" {
|
||||
const U = union(enum) {
|
||||
A: u24,
|
||||
};
|
||||
|
||||
var v = U{ .A = 532 };
|
||||
try expect(v.A == 532);
|
||||
}
|
||||
|
||||
test "runtime tag name with single field" {
|
||||
const U = union(enum) {
|
||||
A: i32,
|
||||
};
|
||||
|
||||
var v = U{ .A = 42 };
|
||||
try expect(std.mem.eql(u8, @tagName(v), "A"));
|
||||
}
|
||||
|
||||
test "method call on an empty union" {
|
||||
const S = struct {
|
||||
const MyUnion = union(MyUnionTag) {
|
||||
pub const MyUnionTag = enum { X1, X2 };
|
||||
X1: [0]u8,
|
||||
X2: [0]u8,
|
||||
|
||||
pub fn useIt(self: *@This()) bool {
|
||||
_ = self;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
fn doTheTest() !void {
|
||||
var u = MyUnion{ .X1 = [0]u8{} };
|
||||
try expect(u.useIt());
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
const Point = struct {
|
||||
x: u64,
|
||||
y: u64,
|
||||
};
|
||||
const TaggedFoo = union(enum) {
|
||||
One: i32,
|
||||
Two: Point,
|
||||
Three: void,
|
||||
};
|
||||
const FooNoVoid = union(enum) {
|
||||
One: i32,
|
||||
Two: Point,
|
||||
};
|
||||
const Baz = enum { A, B, C, D };
|
||||
|
||||
test "tagged union type" {
|
||||
const foo1 = TaggedFoo{ .One = 13 };
|
||||
const foo2 = TaggedFoo{
|
||||
.Two = Point{
|
||||
.x = 1234,
|
||||
.y = 5678,
|
||||
},
|
||||
};
|
||||
try expect(foo1.One == 13);
|
||||
try expect(foo2.Two.x == 1234 and foo2.Two.y == 5678);
|
||||
const baz = Baz.B;
|
||||
|
||||
try expect(baz == Baz.B);
|
||||
try expect(@typeInfo(TaggedFoo).Union.fields.len == 3);
|
||||
try expect(@typeInfo(Baz).Enum.fields.len == 4);
|
||||
try expect(@sizeOf(TaggedFoo) == @sizeOf(FooNoVoid));
|
||||
try expect(@sizeOf(Baz) == 1);
|
||||
}
|
||||
|
||||
test "tagged union as return value" {
|
||||
switch (returnAnInt(13)) {
|
||||
TaggedFoo.One => |value| try expect(value == 13),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn returnAnInt(x: i32) TaggedFoo {
|
||||
return TaggedFoo{ .One = x };
|
||||
}
|
||||
|
||||
@ -3,18 +3,6 @@ const expect = std.testing.expect;
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const Tag = std.meta.Tag;
|
||||
|
||||
const MultipleChoice = union(enum(u32)) {
|
||||
A = 20,
|
||||
B = 40,
|
||||
C = 60,
|
||||
D = 1000,
|
||||
};
|
||||
test "simple union(enum(u32))" {
|
||||
var x = MultipleChoice.C;
|
||||
try expect(x == MultipleChoice.C);
|
||||
try expect(@enumToInt(@as(Tag(MultipleChoice), x)) == 60);
|
||||
}
|
||||
|
||||
const MultipleChoice2 = union(enum(u32)) {
|
||||
Unspecified1: i32,
|
||||
A: f32 = 20,
|
||||
@ -48,33 +36,6 @@ fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
|
||||
});
|
||||
}
|
||||
|
||||
const PackedPtrOrInt = packed union {
|
||||
ptr: *u8,
|
||||
int: u64,
|
||||
};
|
||||
test "packed union size" {
|
||||
comptime try expect(@sizeOf(PackedPtrOrInt) == 8);
|
||||
}
|
||||
|
||||
const ZeroBits = union {
|
||||
OnlyField: void,
|
||||
};
|
||||
test "union with only 1 field which is void should be zero bits" {
|
||||
comptime try expect(@sizeOf(ZeroBits) == 0);
|
||||
}
|
||||
|
||||
const TheTag = enum { A, B, C };
|
||||
const TheUnion = union(TheTag) {
|
||||
A: i32,
|
||||
B: i32,
|
||||
C: i32,
|
||||
};
|
||||
test "union field access gives the enum values" {
|
||||
try expect(TheUnion.A == TheTag.A);
|
||||
try expect(TheUnion.B == TheTag.B);
|
||||
try expect(TheUnion.C == TheTag.C);
|
||||
}
|
||||
|
||||
test "switch on union with only 1 field" {
|
||||
var r: PartialInst = undefined;
|
||||
r = PartialInst.Compiled;
|
||||
@ -101,47 +62,6 @@ const PartialInstWithPayload = union(enum) {
|
||||
Compiled: i32,
|
||||
};
|
||||
|
||||
test "tagged union initialization with runtime void" {
|
||||
try expect(testTaggedUnionInit({}));
|
||||
}
|
||||
|
||||
const TaggedUnionWithAVoid = union(enum) {
|
||||
A,
|
||||
B: i32,
|
||||
};
|
||||
|
||||
fn testTaggedUnionInit(x: anytype) bool {
|
||||
const y = TaggedUnionWithAVoid{ .A = x };
|
||||
return @as(Tag(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A;
|
||||
}
|
||||
|
||||
pub const UnionEnumNoPayloads = union(enum) { A, B };
|
||||
|
||||
test "tagged union with no payloads" {
|
||||
const a = UnionEnumNoPayloads{ .B = {} };
|
||||
switch (a) {
|
||||
Tag(UnionEnumNoPayloads).A => @panic("wrong"),
|
||||
Tag(UnionEnumNoPayloads).B => {},
|
||||
}
|
||||
}
|
||||
|
||||
test "union with only 1 field casted to its enum type" {
|
||||
const Literal = union(enum) {
|
||||
Number: f64,
|
||||
Bool: bool,
|
||||
};
|
||||
|
||||
const Expr = union(enum) {
|
||||
Literal: Literal,
|
||||
};
|
||||
|
||||
var e = Expr{ .Literal = Literal{ .Bool = true } };
|
||||
const ExprTag = Tag(Expr);
|
||||
comptime try expect(Tag(ExprTag) == u0);
|
||||
var t = @as(ExprTag, e);
|
||||
try expect(t == Expr.Literal);
|
||||
}
|
||||
|
||||
test "union with only 1 field casted to its enum type which has enum value specified" {
|
||||
const Literal = union(enum) {
|
||||
Number: f64,
|
||||
@ -285,13 +205,6 @@ test "union no tag with struct member" {
|
||||
u.foo();
|
||||
}
|
||||
|
||||
test "union with one member defaults to u0 tag type" {
|
||||
const U0 = union(enum) {
|
||||
X: u32,
|
||||
};
|
||||
comptime try expect(Tag(Tag(U0)) == u0);
|
||||
}
|
||||
|
||||
test "union with comptime_int tag" {
|
||||
const Union = union(enum(comptime_int)) {
|
||||
X: u32,
|
||||
@ -311,34 +224,6 @@ test "extern union doesn't trigger field check at comptime" {
|
||||
comptime try expect(x.y == 0x55);
|
||||
}
|
||||
|
||||
const Foo1 = union(enum) {
|
||||
f: struct {
|
||||
x: usize,
|
||||
},
|
||||
};
|
||||
var glbl: Foo1 = undefined;
|
||||
|
||||
test "global union with single field is correctly initialized" {
|
||||
glbl = Foo1{
|
||||
.f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 },
|
||||
};
|
||||
try expect(glbl.f.x == 123);
|
||||
}
|
||||
|
||||
pub const FooUnion = union(enum) {
|
||||
U0: usize,
|
||||
U1: u8,
|
||||
};
|
||||
|
||||
var glbl_array: [2]FooUnion = undefined;
|
||||
|
||||
test "initialize global array of union" {
|
||||
glbl_array[1] = FooUnion{ .U1 = 2 };
|
||||
glbl_array[0] = FooUnion{ .U0 = 1 };
|
||||
try expect(glbl_array[0].U0 == 1);
|
||||
try expect(glbl_array[1].U1 == 2);
|
||||
}
|
||||
|
||||
test "anonymous union literal syntax" {
|
||||
const S = struct {
|
||||
const Number = union {
|
||||
@ -361,17 +246,6 @@ test "anonymous union literal syntax" {
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "update the tag value for zero-sized unions" {
|
||||
const S = union(enum) {
|
||||
U0: void,
|
||||
U1: void,
|
||||
};
|
||||
var x = S{ .U0 = {} };
|
||||
try expect(x == .U0);
|
||||
x = S{ .U1 = {} };
|
||||
try expect(x == .U1);
|
||||
}
|
||||
|
||||
test "function call result coerces from tagged union to the tag" {
|
||||
const S = struct {
|
||||
const Arch = union(enum) {
|
||||
@ -401,24 +275,6 @@ test "function call result coerces from tagged union to the tag" {
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "union initializer generates padding only if needed" {
|
||||
const U = union(enum) {
|
||||
A: u24,
|
||||
};
|
||||
|
||||
var v = U{ .A = 532 };
|
||||
try expect(v.A == 532);
|
||||
}
|
||||
|
||||
test "runtime tag name with single field" {
|
||||
const U = union(enum) {
|
||||
A: i32,
|
||||
};
|
||||
|
||||
var v = U{ .A = 42 };
|
||||
try expect(std.mem.eql(u8, @tagName(v), "A"));
|
||||
}
|
||||
|
||||
test "cast from anonymous struct to union" {
|
||||
const S = struct {
|
||||
const U = union(enum) {
|
||||
@ -473,28 +329,6 @@ test "cast from pointer to anonymous struct to pointer to union" {
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "method call on an empty union" {
|
||||
const S = struct {
|
||||
const MyUnion = union(MyUnionTag) {
|
||||
pub const MyUnionTag = enum { X1, X2 };
|
||||
X1: [0]u8,
|
||||
X2: [0]u8,
|
||||
|
||||
pub fn useIt(self: *@This()) bool {
|
||||
_ = self;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
fn doTheTest() !void {
|
||||
var u = MyUnion{ .X1 = [0]u8{} };
|
||||
try expect(u.useIt());
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "switching on non exhaustive union" {
|
||||
const S = struct {
|
||||
const E = enum(u8) {
|
||||
@ -590,48 +424,3 @@ test "anytype union field: issue #9233" {
|
||||
const Quux = union(enum) { bar: anytype };
|
||||
_ = Quux;
|
||||
}
|
||||
|
||||
const Point = struct {
|
||||
x: u64,
|
||||
y: u64,
|
||||
};
|
||||
const TaggedFoo = union(enum) {
|
||||
One: i32,
|
||||
Two: Point,
|
||||
Three: void,
|
||||
};
|
||||
const FooNoVoid = union(enum) {
|
||||
One: i32,
|
||||
Two: Point,
|
||||
};
|
||||
const Baz = enum { A, B, C, D };
|
||||
|
||||
test "tagged union type" {
|
||||
const foo1 = TaggedFoo{ .One = 13 };
|
||||
const foo2 = TaggedFoo{
|
||||
.Two = Point{
|
||||
.x = 1234,
|
||||
.y = 5678,
|
||||
},
|
||||
};
|
||||
try expect(foo1.One == 13);
|
||||
try expect(foo2.Two.x == 1234 and foo2.Two.y == 5678);
|
||||
const baz = Baz.B;
|
||||
|
||||
try expect(baz == Baz.B);
|
||||
try expect(@typeInfo(TaggedFoo).Union.fields.len == 3);
|
||||
try expect(@typeInfo(Baz).Enum.fields.len == 4);
|
||||
try expect(@sizeOf(TaggedFoo) == @sizeOf(FooNoVoid));
|
||||
try expect(@sizeOf(Baz) == 1);
|
||||
}
|
||||
|
||||
test "tagged union as return value" {
|
||||
switch (returnAnInt(13)) {
|
||||
TaggedFoo.One => |value| try expect(value == 13),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn returnAnInt(x: i32) TaggedFoo {
|
||||
return TaggedFoo{ .One = x };
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user