mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
stage2: LLVM backend: make unnamed struct globals
LLVM union globals have to be lowered as unnamed structs if the non-most-aligned field is the active tag. In this case it bubbles up so that structs containing unions have the same restriction. This fix needs to be applied to optionals and other callsites of createNamedStruct. The bug fixed in this commit was revealed in searching for the cause of #10837.
This commit is contained in:
parent
57357c43e3
commit
c10fdde5a6
4 changed files with 514 additions and 433 deletions
|
|
@ -809,7 +809,16 @@ pub const DeclGen = struct {
|
|||
};
|
||||
}
|
||||
|
||||
fn llvmType(dg: *DeclGen, t: Type) Error!*const llvm.Type {
|
||||
fn isUnnamedType(dg: *DeclGen, ty: Type, val: *const llvm.Value) bool {
|
||||
// Once `llvmType` succeeds, successive calls to it with the same Zig type
|
||||
// are guaranteed to succeed. So if a call to `llvmType` fails here it means
|
||||
// it is the first time lowering the type, which means the value can't possible
|
||||
// have that type.
|
||||
const llvm_ty = dg.llvmType(ty) catch return true;
|
||||
return val.typeOf() != llvm_ty;
|
||||
}
|
||||
|
||||
fn llvmType(dg: *DeclGen, t: Type) Allocator.Error!*const llvm.Type {
|
||||
const gpa = dg.gpa;
|
||||
switch (t.zigTypeTag()) {
|
||||
.Void, .NoReturn => return dg.context.voidType(),
|
||||
|
|
@ -1168,9 +1177,8 @@ pub const DeclGen = struct {
|
|||
|
||||
.BoundFn => @panic("TODO remove BoundFn from the language"),
|
||||
|
||||
.Frame,
|
||||
.AnyFrame,
|
||||
=> return dg.todo("implement llvmType for type '{}'", .{t}),
|
||||
.Frame => @panic("TODO implement llvmType for Frame types"),
|
||||
.AnyFrame => @panic("TODO implement llvmType for AnyFrame types"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1299,7 +1307,8 @@ pub const DeclGen = struct {
|
|||
llvm_u32.constInt(0, .False),
|
||||
llvm_u32.constInt(field_ptr.field_index, .False),
|
||||
};
|
||||
return parent_ptr.constInBoundsGEP(&indices, indices.len);
|
||||
const uncasted = parent_ptr.constInBoundsGEP(&indices, indices.len);
|
||||
return uncasted.constBitCast(try dg.llvmType(tv.ty));
|
||||
},
|
||||
.elem_ptr => {
|
||||
const elem_ptr = tv.val.castTag(.elem_ptr).?.data;
|
||||
|
|
@ -1463,6 +1472,7 @@ pub const DeclGen = struct {
|
|||
var llvm_fields = try std.ArrayListUnmanaged(*const llvm.Value).initCapacity(gpa, llvm_field_count);
|
||||
defer llvm_fields.deinit(gpa);
|
||||
|
||||
var make_unnamed_struct = false;
|
||||
const struct_obj = tv.ty.castTag(.@"struct").?.data;
|
||||
if (struct_obj.layout == .Packed) {
|
||||
const target = dg.module.getTarget();
|
||||
|
|
@ -1558,17 +1568,30 @@ pub const DeclGen = struct {
|
|||
const field_ty = tv.ty.structFieldType(i);
|
||||
if (!field_ty.hasRuntimeBits()) continue;
|
||||
|
||||
llvm_fields.appendAssumeCapacity(try dg.genTypedValue(.{
|
||||
const field_llvm_val = try dg.genTypedValue(.{
|
||||
.ty = field_ty,
|
||||
.val = field_val,
|
||||
}));
|
||||
});
|
||||
|
||||
make_unnamed_struct = make_unnamed_struct or
|
||||
dg.isUnnamedType(field_ty, field_llvm_val);
|
||||
|
||||
llvm_fields.appendAssumeCapacity(field_llvm_val);
|
||||
}
|
||||
}
|
||||
|
||||
return llvm_struct_ty.constNamedStruct(
|
||||
llvm_fields.items.ptr,
|
||||
@intCast(c_uint, llvm_fields.items.len),
|
||||
);
|
||||
if (make_unnamed_struct) {
|
||||
return dg.context.constStruct(
|
||||
llvm_fields.items.ptr,
|
||||
@intCast(c_uint, llvm_fields.items.len),
|
||||
.False,
|
||||
);
|
||||
} else {
|
||||
return llvm_struct_ty.constNamedStruct(
|
||||
llvm_fields.items.ptr,
|
||||
@intCast(c_uint, llvm_fields.items.len),
|
||||
);
|
||||
}
|
||||
},
|
||||
.Union => {
|
||||
const llvm_union_ty = try dg.llvmType(tv.ty);
|
||||
|
|
|
|||
|
|
@ -166,7 +166,6 @@ test {
|
|||
_ = @import("behavior/tuple.zig");
|
||||
_ = @import("behavior/type_stage1.zig");
|
||||
_ = @import("behavior/typename.zig");
|
||||
_ = @import("behavior/union_stage1.zig");
|
||||
_ = @import("behavior/union_with_members.zig");
|
||||
_ = @import("behavior/var_args.zig");
|
||||
_ = @import("behavior/vector.zig");
|
||||
|
|
|
|||
|
|
@ -490,3 +490,483 @@ test "tagged union with all void fields but a meaningful tag" {
|
|||
// TODO enable the test at comptime too
|
||||
//comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "union(enum(u32)) with specified and unspecified tag values" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
comptime try expect(Tag(Tag(MultipleChoice2)) == u32);
|
||||
try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
|
||||
comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
|
||||
}
|
||||
|
||||
const MultipleChoice2 = union(enum(u32)) {
|
||||
Unspecified1: i32,
|
||||
A: f32 = 20,
|
||||
Unspecified2: void,
|
||||
B: bool = 40,
|
||||
Unspecified3: i32,
|
||||
C: i8 = 60,
|
||||
Unspecified4: void,
|
||||
D: void = 1000,
|
||||
Unspecified5: i32,
|
||||
};
|
||||
|
||||
fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
|
||||
try expect(@enumToInt(@as(Tag(MultipleChoice2), x)) == 60);
|
||||
try expect(1123 == switch (x) {
|
||||
MultipleChoice2.A => 1,
|
||||
MultipleChoice2.B => 2,
|
||||
MultipleChoice2.C => |v| @as(i32, 1000) + v,
|
||||
MultipleChoice2.D => 4,
|
||||
MultipleChoice2.Unspecified1 => 5,
|
||||
MultipleChoice2.Unspecified2 => 6,
|
||||
MultipleChoice2.Unspecified3 => 7,
|
||||
MultipleChoice2.Unspecified4 => 8,
|
||||
MultipleChoice2.Unspecified5 => 9,
|
||||
});
|
||||
}
|
||||
|
||||
test "switch on union with only 1 field" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
var r: PartialInst = undefined;
|
||||
r = PartialInst.Compiled;
|
||||
switch (r) {
|
||||
PartialInst.Compiled => {
|
||||
var z: PartialInstWithPayload = undefined;
|
||||
z = PartialInstWithPayload{ .Compiled = 1234 };
|
||||
switch (z) {
|
||||
PartialInstWithPayload.Compiled => |x| {
|
||||
try expect(x == 1234);
|
||||
return;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
const PartialInst = union(enum) {
|
||||
Compiled,
|
||||
};
|
||||
|
||||
const PartialInstWithPayload = union(enum) {
|
||||
Compiled: i32,
|
||||
};
|
||||
|
||||
test "union with only 1 field casted to its enum type which has enum value specified" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const Literal = union(enum) {
|
||||
Number: f64,
|
||||
Bool: bool,
|
||||
};
|
||||
|
||||
const ExprTag = enum(comptime_int) {
|
||||
Literal = 33,
|
||||
};
|
||||
|
||||
const Expr = union(ExprTag) {
|
||||
Literal: Literal,
|
||||
};
|
||||
|
||||
var e = Expr{ .Literal = Literal{ .Bool = true } };
|
||||
comptime try expect(Tag(ExprTag) == comptime_int);
|
||||
var t = @as(ExprTag, e);
|
||||
try expect(t == Expr.Literal);
|
||||
try expect(@enumToInt(t) == 33);
|
||||
comptime try expect(@enumToInt(t) == 33);
|
||||
}
|
||||
|
||||
test "@enumToInt works on unions" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const Bar = union(enum) {
|
||||
A: bool,
|
||||
B: u8,
|
||||
C,
|
||||
};
|
||||
|
||||
const a = Bar{ .A = true };
|
||||
var b = Bar{ .B = undefined };
|
||||
var c = Bar.C;
|
||||
try expect(@enumToInt(a) == 0);
|
||||
try expect(@enumToInt(b) == 1);
|
||||
try expect(@enumToInt(c) == 2);
|
||||
}
|
||||
|
||||
test "comptime union field value equality" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const a0 = Setter(Attribute{ .A = false });
|
||||
const a1 = Setter(Attribute{ .A = true });
|
||||
const a2 = Setter(Attribute{ .A = false });
|
||||
|
||||
const b0 = Setter(Attribute{ .B = 5 });
|
||||
const b1 = Setter(Attribute{ .B = 9 });
|
||||
const b2 = Setter(Attribute{ .B = 5 });
|
||||
|
||||
try expect(a0 == a0);
|
||||
try expect(a1 == a1);
|
||||
try expect(a0 == a2);
|
||||
|
||||
try expect(b0 == b0);
|
||||
try expect(b1 == b1);
|
||||
try expect(b0 == b2);
|
||||
|
||||
try expect(a0 != b0);
|
||||
try expect(a0 != a1);
|
||||
try expect(b0 != b1);
|
||||
}
|
||||
|
||||
const Attribute = union(enum) {
|
||||
A: bool,
|
||||
B: u8,
|
||||
};
|
||||
|
||||
fn setAttribute(attr: Attribute) void {
|
||||
_ = attr;
|
||||
}
|
||||
|
||||
fn Setter(attr: Attribute) type {
|
||||
return struct {
|
||||
fn set() void {
|
||||
setAttribute(attr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "return union init with void payload" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
fn entry() !void {
|
||||
try expect(func().state == State.one);
|
||||
}
|
||||
const Outer = union(enum) {
|
||||
state: State,
|
||||
};
|
||||
const State = union(enum) {
|
||||
one: void,
|
||||
two: u32,
|
||||
};
|
||||
fn func() Outer {
|
||||
return Outer{ .state = State{ .one = {} } };
|
||||
}
|
||||
};
|
||||
try S.entry();
|
||||
comptime try S.entry();
|
||||
}
|
||||
|
||||
test "@unionInit can modify a union type" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const UnionInitEnum = union(enum) {
|
||||
Boolean: bool,
|
||||
Byte: u8,
|
||||
};
|
||||
|
||||
var value: UnionInitEnum = undefined;
|
||||
|
||||
value = @unionInit(UnionInitEnum, "Boolean", true);
|
||||
try expect(value.Boolean == true);
|
||||
value.Boolean = false;
|
||||
try expect(value.Boolean == false);
|
||||
|
||||
value = @unionInit(UnionInitEnum, "Byte", 2);
|
||||
try expect(value.Byte == 2);
|
||||
value.Byte = 3;
|
||||
try expect(value.Byte == 3);
|
||||
}
|
||||
|
||||
test "@unionInit can modify a pointer value" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const UnionInitEnum = union(enum) {
|
||||
Boolean: bool,
|
||||
Byte: u8,
|
||||
};
|
||||
|
||||
var value: UnionInitEnum = undefined;
|
||||
var value_ptr = &value;
|
||||
|
||||
value_ptr.* = @unionInit(UnionInitEnum, "Boolean", true);
|
||||
try expect(value.Boolean == true);
|
||||
|
||||
value_ptr.* = @unionInit(UnionInitEnum, "Byte", 2);
|
||||
try expect(value.Byte == 2);
|
||||
}
|
||||
|
||||
test "union no tag with struct member" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const Struct = struct {};
|
||||
const Union = union {
|
||||
s: Struct,
|
||||
pub fn foo(self: *@This()) void {
|
||||
_ = self;
|
||||
}
|
||||
};
|
||||
var u = Union{ .s = Struct{} };
|
||||
u.foo();
|
||||
}
|
||||
|
||||
test "union with comptime_int tag" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const Union = union(enum(comptime_int)) {
|
||||
X: u32,
|
||||
Y: u16,
|
||||
Z: u8,
|
||||
};
|
||||
comptime try expect(Tag(Tag(Union)) == comptime_int);
|
||||
}
|
||||
|
||||
test "extern union doesn't trigger field check at comptime" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const U = extern union {
|
||||
x: u32,
|
||||
y: u8,
|
||||
};
|
||||
|
||||
const x = U{ .x = 0x55AAAA55 };
|
||||
comptime try expect(x.y == 0x55);
|
||||
}
|
||||
|
||||
test "anonymous union literal syntax" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
const Number = union {
|
||||
int: i32,
|
||||
float: f64,
|
||||
};
|
||||
|
||||
fn doTheTest() !void {
|
||||
var i: Number = .{ .int = 42 };
|
||||
var f = makeNumber();
|
||||
try expect(i.int == 42);
|
||||
try expect(f.float == 12.34);
|
||||
}
|
||||
|
||||
fn makeNumber() Number {
|
||||
return .{ .float = 12.34 };
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "function call result coerces from tagged union to the tag" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
const Arch = union(enum) {
|
||||
One,
|
||||
Two: usize,
|
||||
};
|
||||
|
||||
const ArchTag = Tag(Arch);
|
||||
|
||||
fn doTheTest() !void {
|
||||
var x: ArchTag = getArch1();
|
||||
try expect(x == .One);
|
||||
|
||||
var y: ArchTag = getArch2();
|
||||
try expect(y == .Two);
|
||||
}
|
||||
|
||||
pub fn getArch1() Arch {
|
||||
return .One;
|
||||
}
|
||||
|
||||
pub fn getArch2() Arch {
|
||||
return .{ .Two = 99 };
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "cast from anonymous struct to union" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
const U = union(enum) {
|
||||
A: u32,
|
||||
B: []const u8,
|
||||
C: void,
|
||||
};
|
||||
fn doTheTest() !void {
|
||||
var y: u32 = 42;
|
||||
const t0 = .{ .A = 123 };
|
||||
const t1 = .{ .B = "foo" };
|
||||
const t2 = .{ .C = {} };
|
||||
const t3 = .{ .A = y };
|
||||
const x0: U = t0;
|
||||
var x1: U = t1;
|
||||
const x2: U = t2;
|
||||
var x3: U = t3;
|
||||
try expect(x0.A == 123);
|
||||
try expect(std.mem.eql(u8, x1.B, "foo"));
|
||||
try expect(x2 == .C);
|
||||
try expect(x3.A == y);
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "cast from pointer to anonymous struct to pointer to union" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
const U = union(enum) {
|
||||
A: u32,
|
||||
B: []const u8,
|
||||
C: void,
|
||||
};
|
||||
fn doTheTest() !void {
|
||||
var y: u32 = 42;
|
||||
const t0 = &.{ .A = 123 };
|
||||
const t1 = &.{ .B = "foo" };
|
||||
const t2 = &.{ .C = {} };
|
||||
const t3 = &.{ .A = y };
|
||||
const x0: *const U = t0;
|
||||
var x1: *const U = t1;
|
||||
const x2: *const U = t2;
|
||||
var x3: *const U = t3;
|
||||
try expect(x0.A == 123);
|
||||
try expect(std.mem.eql(u8, x1.B, "foo"));
|
||||
try expect(x2.* == .C);
|
||||
try expect(x3.A == y);
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "switching on non exhaustive union" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
const E = enum(u8) {
|
||||
a,
|
||||
b,
|
||||
_,
|
||||
};
|
||||
const U = union(E) {
|
||||
a: i32,
|
||||
b: u32,
|
||||
};
|
||||
fn doTheTest() !void {
|
||||
var a = U{ .a = 2 };
|
||||
switch (a) {
|
||||
.a => |val| try expect(val == 2),
|
||||
.b => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "containers with single-field enums" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
const A = union(enum) { f1 };
|
||||
const B = union(enum) { f1: void };
|
||||
const C = struct { a: A };
|
||||
const D = struct { a: B };
|
||||
|
||||
fn doTheTest() !void {
|
||||
var array1 = [1]A{A{ .f1 = {} }};
|
||||
var array2 = [1]B{B{ .f1 = {} }};
|
||||
try expect(array1[0] == .f1);
|
||||
try expect(array2[0] == .f1);
|
||||
|
||||
var struct1 = C{ .a = A{ .f1 = {} } };
|
||||
var struct2 = D{ .a = B{ .f1 = {} } };
|
||||
try expect(struct1.a == .f1);
|
||||
try expect(struct2.a == .f1);
|
||||
}
|
||||
};
|
||||
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "@unionInit on union w/ tag but no fields" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
const Type = enum(u8) { no_op = 105 };
|
||||
|
||||
const Data = union(Type) {
|
||||
no_op: void,
|
||||
|
||||
pub fn decode(buf: []const u8) Data {
|
||||
_ = buf;
|
||||
return @unionInit(Data, "no_op", {});
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
std.debug.assert(@sizeOf(Data) != 0);
|
||||
}
|
||||
|
||||
fn doTheTest() !void {
|
||||
var data: Data = .{ .no_op = .{} };
|
||||
_ = data;
|
||||
var o = Data.decode(&[_]u8{});
|
||||
try expectEqual(Type.no_op, o);
|
||||
}
|
||||
};
|
||||
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "union enum type gets a separate scope" {
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
const U = union(enum) {
|
||||
a: u8,
|
||||
const foo = 1;
|
||||
};
|
||||
|
||||
fn doTheTest() !void {
|
||||
try expect(!@hasDecl(Tag(U), "foo"));
|
||||
}
|
||||
};
|
||||
|
||||
try S.doTheTest();
|
||||
}
|
||||
|
||||
test "global variable struct contains union initialized to non-most-aligned field" {
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
||||
|
||||
const T = struct {
|
||||
const U = union(enum) {
|
||||
a: i32,
|
||||
b: f64,
|
||||
};
|
||||
|
||||
const S = struct {
|
||||
u: U,
|
||||
};
|
||||
|
||||
var s: S = .{
|
||||
.u = .{
|
||||
.a = 3,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
T.s.u.a += 1;
|
||||
try expect(T.s.u.a == 4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,421 +0,0 @@
|
|||
const std = @import("std");
|
||||
const expect = std.testing.expect;
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const Tag = std.meta.Tag;
|
||||
|
||||
const MultipleChoice2 = union(enum(u32)) {
|
||||
Unspecified1: i32,
|
||||
A: f32 = 20,
|
||||
Unspecified2: void,
|
||||
B: bool = 40,
|
||||
Unspecified3: i32,
|
||||
C: i8 = 60,
|
||||
Unspecified4: void,
|
||||
D: void = 1000,
|
||||
Unspecified5: i32,
|
||||
};
|
||||
|
||||
test "union(enum(u32)) with specified and unspecified tag values" {
|
||||
comptime try expect(Tag(Tag(MultipleChoice2)) == u32);
|
||||
try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
|
||||
comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
|
||||
}
|
||||
|
||||
fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
|
||||
try expect(@enumToInt(@as(Tag(MultipleChoice2), x)) == 60);
|
||||
try expect(1123 == switch (x) {
|
||||
MultipleChoice2.A => 1,
|
||||
MultipleChoice2.B => 2,
|
||||
MultipleChoice2.C => |v| @as(i32, 1000) + v,
|
||||
MultipleChoice2.D => 4,
|
||||
MultipleChoice2.Unspecified1 => 5,
|
||||
MultipleChoice2.Unspecified2 => 6,
|
||||
MultipleChoice2.Unspecified3 => 7,
|
||||
MultipleChoice2.Unspecified4 => 8,
|
||||
MultipleChoice2.Unspecified5 => 9,
|
||||
});
|
||||
}
|
||||
|
||||
test "switch on union with only 1 field" {
|
||||
var r: PartialInst = undefined;
|
||||
r = PartialInst.Compiled;
|
||||
switch (r) {
|
||||
PartialInst.Compiled => {
|
||||
var z: PartialInstWithPayload = undefined;
|
||||
z = PartialInstWithPayload{ .Compiled = 1234 };
|
||||
switch (z) {
|
||||
PartialInstWithPayload.Compiled => |x| {
|
||||
try expect(x == 1234);
|
||||
return;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
const PartialInst = union(enum) {
|
||||
Compiled,
|
||||
};
|
||||
|
||||
const PartialInstWithPayload = union(enum) {
|
||||
Compiled: i32,
|
||||
};
|
||||
|
||||
test "union with only 1 field casted to its enum type which has enum value specified" {
|
||||
const Literal = union(enum) {
|
||||
Number: f64,
|
||||
Bool: bool,
|
||||
};
|
||||
|
||||
const ExprTag = enum(comptime_int) {
|
||||
Literal = 33,
|
||||
};
|
||||
|
||||
const Expr = union(ExprTag) {
|
||||
Literal: Literal,
|
||||
};
|
||||
|
||||
var e = Expr{ .Literal = Literal{ .Bool = true } };
|
||||
comptime try expect(Tag(ExprTag) == comptime_int);
|
||||
var t = @as(ExprTag, e);
|
||||
try expect(t == Expr.Literal);
|
||||
try expect(@enumToInt(t) == 33);
|
||||
comptime try expect(@enumToInt(t) == 33);
|
||||
}
|
||||
|
||||
test "@enumToInt works on unions" {
|
||||
const Bar = union(enum) {
|
||||
A: bool,
|
||||
B: u8,
|
||||
C,
|
||||
};
|
||||
|
||||
const a = Bar{ .A = true };
|
||||
var b = Bar{ .B = undefined };
|
||||
var c = Bar.C;
|
||||
try expect(@enumToInt(a) == 0);
|
||||
try expect(@enumToInt(b) == 1);
|
||||
try expect(@enumToInt(c) == 2);
|
||||
}
|
||||
|
||||
const Attribute = union(enum) {
|
||||
A: bool,
|
||||
B: u8,
|
||||
};
|
||||
|
||||
fn setAttribute(attr: Attribute) void {
|
||||
_ = attr;
|
||||
}
|
||||
|
||||
fn Setter(attr: Attribute) type {
|
||||
return struct {
|
||||
fn set() void {
|
||||
setAttribute(attr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "comptime union field value equality" {
|
||||
const a0 = Setter(Attribute{ .A = false });
|
||||
const a1 = Setter(Attribute{ .A = true });
|
||||
const a2 = Setter(Attribute{ .A = false });
|
||||
|
||||
const b0 = Setter(Attribute{ .B = 5 });
|
||||
const b1 = Setter(Attribute{ .B = 9 });
|
||||
const b2 = Setter(Attribute{ .B = 5 });
|
||||
|
||||
try expect(a0 == a0);
|
||||
try expect(a1 == a1);
|
||||
try expect(a0 == a2);
|
||||
|
||||
try expect(b0 == b0);
|
||||
try expect(b1 == b1);
|
||||
try expect(b0 == b2);
|
||||
|
||||
try expect(a0 != b0);
|
||||
try expect(a0 != a1);
|
||||
try expect(b0 != b1);
|
||||
}
|
||||
|
||||
test "return union init with void payload" {
|
||||
const S = struct {
|
||||
fn entry() !void {
|
||||
try expect(func().state == State.one);
|
||||
}
|
||||
const Outer = union(enum) {
|
||||
state: State,
|
||||
};
|
||||
const State = union(enum) {
|
||||
one: void,
|
||||
two: u32,
|
||||
};
|
||||
fn func() Outer {
|
||||
return Outer{ .state = State{ .one = {} } };
|
||||
}
|
||||
};
|
||||
try S.entry();
|
||||
comptime try S.entry();
|
||||
}
|
||||
|
||||
test "@unionInit can modify a union type" {
|
||||
const UnionInitEnum = union(enum) {
|
||||
Boolean: bool,
|
||||
Byte: u8,
|
||||
};
|
||||
|
||||
var value: UnionInitEnum = undefined;
|
||||
|
||||
value = @unionInit(UnionInitEnum, "Boolean", true);
|
||||
try expect(value.Boolean == true);
|
||||
value.Boolean = false;
|
||||
try expect(value.Boolean == false);
|
||||
|
||||
value = @unionInit(UnionInitEnum, "Byte", 2);
|
||||
try expect(value.Byte == 2);
|
||||
value.Byte = 3;
|
||||
try expect(value.Byte == 3);
|
||||
}
|
||||
|
||||
test "@unionInit can modify a pointer value" {
|
||||
const UnionInitEnum = union(enum) {
|
||||
Boolean: bool,
|
||||
Byte: u8,
|
||||
};
|
||||
|
||||
var value: UnionInitEnum = undefined;
|
||||
var value_ptr = &value;
|
||||
|
||||
value_ptr.* = @unionInit(UnionInitEnum, "Boolean", true);
|
||||
try expect(value.Boolean == true);
|
||||
|
||||
value_ptr.* = @unionInit(UnionInitEnum, "Byte", 2);
|
||||
try expect(value.Byte == 2);
|
||||
}
|
||||
|
||||
test "union no tag with struct member" {
|
||||
const Struct = struct {};
|
||||
const Union = union {
|
||||
s: Struct,
|
||||
pub fn foo(self: *@This()) void {
|
||||
_ = self;
|
||||
}
|
||||
};
|
||||
var u = Union{ .s = Struct{} };
|
||||
u.foo();
|
||||
}
|
||||
|
||||
test "union with comptime_int tag" {
|
||||
const Union = union(enum(comptime_int)) {
|
||||
X: u32,
|
||||
Y: u16,
|
||||
Z: u8,
|
||||
};
|
||||
comptime try expect(Tag(Tag(Union)) == comptime_int);
|
||||
}
|
||||
|
||||
test "extern union doesn't trigger field check at comptime" {
|
||||
const U = extern union {
|
||||
x: u32,
|
||||
y: u8,
|
||||
};
|
||||
|
||||
const x = U{ .x = 0x55AAAA55 };
|
||||
comptime try expect(x.y == 0x55);
|
||||
}
|
||||
|
||||
test "anonymous union literal syntax" {
|
||||
const S = struct {
|
||||
const Number = union {
|
||||
int: i32,
|
||||
float: f64,
|
||||
};
|
||||
|
||||
fn doTheTest() !void {
|
||||
var i: Number = .{ .int = 42 };
|
||||
var f = makeNumber();
|
||||
try expect(i.int == 42);
|
||||
try expect(f.float == 12.34);
|
||||
}
|
||||
|
||||
fn makeNumber() Number {
|
||||
return .{ .float = 12.34 };
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "function call result coerces from tagged union to the tag" {
|
||||
const S = struct {
|
||||
const Arch = union(enum) {
|
||||
One,
|
||||
Two: usize,
|
||||
};
|
||||
|
||||
const ArchTag = Tag(Arch);
|
||||
|
||||
fn doTheTest() !void {
|
||||
var x: ArchTag = getArch1();
|
||||
try expect(x == .One);
|
||||
|
||||
var y: ArchTag = getArch2();
|
||||
try expect(y == .Two);
|
||||
}
|
||||
|
||||
pub fn getArch1() Arch {
|
||||
return .One;
|
||||
}
|
||||
|
||||
pub fn getArch2() Arch {
|
||||
return .{ .Two = 99 };
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "cast from anonymous struct to union" {
|
||||
const S = struct {
|
||||
const U = union(enum) {
|
||||
A: u32,
|
||||
B: []const u8,
|
||||
C: void,
|
||||
};
|
||||
fn doTheTest() !void {
|
||||
var y: u32 = 42;
|
||||
const t0 = .{ .A = 123 };
|
||||
const t1 = .{ .B = "foo" };
|
||||
const t2 = .{ .C = {} };
|
||||
const t3 = .{ .A = y };
|
||||
const x0: U = t0;
|
||||
var x1: U = t1;
|
||||
const x2: U = t2;
|
||||
var x3: U = t3;
|
||||
try expect(x0.A == 123);
|
||||
try expect(std.mem.eql(u8, x1.B, "foo"));
|
||||
try expect(x2 == .C);
|
||||
try expect(x3.A == y);
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "cast from pointer to anonymous struct to pointer to union" {
|
||||
const S = struct {
|
||||
const U = union(enum) {
|
||||
A: u32,
|
||||
B: []const u8,
|
||||
C: void,
|
||||
};
|
||||
fn doTheTest() !void {
|
||||
var y: u32 = 42;
|
||||
const t0 = &.{ .A = 123 };
|
||||
const t1 = &.{ .B = "foo" };
|
||||
const t2 = &.{ .C = {} };
|
||||
const t3 = &.{ .A = y };
|
||||
const x0: *const U = t0;
|
||||
var x1: *const U = t1;
|
||||
const x2: *const U = t2;
|
||||
var x3: *const U = t3;
|
||||
try expect(x0.A == 123);
|
||||
try expect(std.mem.eql(u8, x1.B, "foo"));
|
||||
try expect(x2.* == .C);
|
||||
try expect(x3.A == y);
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "switching on non exhaustive union" {
|
||||
const S = struct {
|
||||
const E = enum(u8) {
|
||||
a,
|
||||
b,
|
||||
_,
|
||||
};
|
||||
const U = union(E) {
|
||||
a: i32,
|
||||
b: u32,
|
||||
};
|
||||
fn doTheTest() !void {
|
||||
var a = U{ .a = 2 };
|
||||
switch (a) {
|
||||
.a => |val| try expect(val == 2),
|
||||
.b => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "containers with single-field enums" {
|
||||
const S = struct {
|
||||
const A = union(enum) { f1 };
|
||||
const B = union(enum) { f1: void };
|
||||
const C = struct { a: A };
|
||||
const D = struct { a: B };
|
||||
|
||||
fn doTheTest() !void {
|
||||
var array1 = [1]A{A{ .f1 = {} }};
|
||||
var array2 = [1]B{B{ .f1 = {} }};
|
||||
try expect(array1[0] == .f1);
|
||||
try expect(array2[0] == .f1);
|
||||
|
||||
var struct1 = C{ .a = A{ .f1 = {} } };
|
||||
var struct2 = D{ .a = B{ .f1 = {} } };
|
||||
try expect(struct1.a == .f1);
|
||||
try expect(struct2.a == .f1);
|
||||
}
|
||||
};
|
||||
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "@unionInit on union w/ tag but no fields" {
|
||||
const S = struct {
|
||||
const Type = enum(u8) { no_op = 105 };
|
||||
|
||||
const Data = union(Type) {
|
||||
no_op: void,
|
||||
|
||||
pub fn decode(buf: []const u8) Data {
|
||||
_ = buf;
|
||||
return @unionInit(Data, "no_op", {});
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
std.debug.assert(@sizeOf(Data) != 0);
|
||||
}
|
||||
|
||||
fn doTheTest() !void {
|
||||
var data: Data = .{ .no_op = .{} };
|
||||
_ = data;
|
||||
var o = Data.decode(&[_]u8{});
|
||||
try expectEqual(Type.no_op, o);
|
||||
}
|
||||
};
|
||||
|
||||
try S.doTheTest();
|
||||
comptime try S.doTheTest();
|
||||
}
|
||||
|
||||
test "union enum type gets a separate scope" {
|
||||
const S = struct {
|
||||
const U = union(enum) {
|
||||
a: u8,
|
||||
const foo = 1;
|
||||
};
|
||||
|
||||
fn doTheTest() !void {
|
||||
try expect(!@hasDecl(Tag(U), "foo"));
|
||||
}
|
||||
};
|
||||
|
||||
try S.doTheTest();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue