Merge pull request 'Sema: implement switch for packed structs/unions' (#31464) from justusk/zig:packed-switch into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31464
Reviewed-by: Andrew Kelley <andrew@ziglang.org>
This commit is contained in:
Andrew Kelley
2026-03-12 00:39:19 +01:00
12 changed files with 896 additions and 538 deletions
+20
View File
@@ -199,3 +199,23 @@ test "packed union with explicit backing integer" {
try U.check(.{ .raw = -2 });
try comptime U.check(.{ .raw = -2 });
}
test "packed union equality" {
const Foo = packed union {
a: u4,
b: i4,
};
const S = struct {
fn doTest(x: Foo, y: Foo) !void {
try expect(x == y);
try expect(!(x != y));
}
};
const x: Foo = .{ .a = 3 };
const y: Foo = .{ .b = 3 };
try S.doTest(x, y);
comptime try S.doTest(x, y);
}
+132
View File
@@ -1327,3 +1327,135 @@ test "single range switch prong capture" {
try S.doTheTest(2);
try comptime S.doTheTest(2);
}
test "switch on packed struct" {
const P = packed struct {
a: u1,
b: u1,
fn doTheTest(p: @This()) !void {
switch (p) {
.{ .a = 0, .b = 1 } => {},
else => return error.TestFailed,
}
switch (p) {
.{ .a = 0, .b = 1 } => {},
.{ .a = 0, .b = 0 },
.{ .a = 1, .b = 0 },
.{ .a = 1, .b = 1 },
=> return error.TestFailed,
}
switch (p) {
inline else => |val| {
if (val != @This(){ .a = 0, .b = 1 }) return error.TestFailed;
},
}
}
};
try P.doTheTest(.{ .a = 0, .b = 1 });
try comptime P.doTheTest(.{ .a = 0, .b = 1 });
}
test "switch on packed union" {
const P = packed union(u2) {
a: u2,
b: i2,
c: packed struct(u2) { x: u1, y: i1 },
fn doTheTest(p: @This()) !void {
switch (p) {
.{ .a = 1 } => {},
else => return error.TestFailed,
}
switch (p) {
.{ .a = 1 } => {},
.{ .a = 0 },
.{ .a = 2 },
.{ .a = 3 },
=> return error.TestFailed,
}
switch (p) {
.{ .a = 1 } => {},
.{ .a = 0 },
.{ .b = -2 },
.{ .b = -1 },
=> return error.TestFailed,
}
switch (p) {
.{ .c = .{ .x = 1, .y = 0 } } => {},
.{ .b = 0 },
.{ .a = 2 },
.{ .c = .{ .x = 1, .y = -1 } },
=> return error.TestFailed,
}
switch (p) {
inline else => |val| {
if (val != @This(){ .c = .{ .x = 1, .y = 0 } }) return error.TestFailed;
},
}
}
};
try P.doTheTest(.{ .a = 1 });
try comptime P.doTheTest(.{ .a = 1 });
}
test "switch on nested packed containers" {
if (builtin.object_format == .c) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/31467
const P = packed struct {
iu: u17,
is: i31,
b: bool,
e: enum(u5) { a = 5, b = 3, c = 12 },
un: packed union {
a: i9,
b: u9,
c: packed struct(u9) { a: i5, b: u4 },
},
p: packed struct(u9) { a: u3, b: u6 },
fn doTheTest(p: @This()) !void {
switch (p) {
.{
.iu = 72,
.is = 124,
.b = false,
.e = .c,
.un = .{ .b = 13 },
.p = .{ .a = 0, .b = 12 },
} => return error.TestFailed,
.{
.iu = 129,
.is = -162784612,
.b = true,
.e = .a,
.un = .{ .c = .{ .a = -3, .b = 9 } },
.p = .{ .a = 2, .b = 17 },
} => {},
else => return error.TestFailed,
}
}
};
try P.doTheTest(.{
.iu = 129,
.is = -162784612,
.b = true,
.e = .a,
.un = .{ .c = .{ .a = -3, .b = 9 } },
.p = .{ .a = 2, .b = 17 },
});
try comptime P.doTheTest(.{
.iu = 129,
.is = -162784612,
.b = true,
.e = .a,
.un = .{ .c = .{ .a = -3, .b = 9 } },
.p = .{ .a = 2, .b = 17 },
});
}
+55
View File
@@ -509,3 +509,58 @@ test "switch loop for error handling" {
try S.doTheTest();
try comptime S.doTheTest();
}
test "switch loop with packed structs" {
const P = packed struct {
a: u7,
b: u20,
fn doTheTest(p: @This()) !void {
const result = s: switch (p) {
.{ .a = 5, .b = 10 } => |x| x,
else => |x| continue :s .{ .a = x.a, .b = x.b + 1 },
};
try expect(result == @This(){ .a = 5, .b = 10 });
}
};
try P.doTheTest(.{ .a = 5, .b = 0 });
try comptime P.doTheTest(.{ .a = 5, .b = 0 });
}
test "switch loop with packed unions" {
const P = packed union {
a: u7,
b: i7,
fn doTheTest(p: @This()) !void {
const result = s: switch (p) {
.{ .a = 10 } => |x| x,
else => |x| continue :s .{ .b = @intCast(x.a + 1) },
};
try expect(result == @This(){ .b = 10 });
}
};
try P.doTheTest(.{ .a = 5 });
try comptime P.doTheTest(.{ .a = 5 });
}
test "switch loop with packed unions with OPV" {
const P = packed union {
a: u0,
b: i0,
fn doTheTest(p: @This()) !void {
var looped = false;
s: switch (p) {
.{ .b = 0 } => |x| {
comptime assert(x.a == 0);
if (looped) break :s;
looped = true;
continue :s .{ .a = 0 };
},
}
}
};
try P.doTheTest(.{ .a = 0 });
try comptime P.doTheTest(.{ .a = 0 });
}
@@ -0,0 +1,25 @@
export fn entry1() void {
switch (@as(anyerror, error.MyError)) {
error.MyError, error.MyOtherError => |*err| _ = err,
else => {},
}
}
export fn entry2() void {
switch (@as(anyerror, error.MyError)) {
inline error.MyError, error.MyOtherError => |*err| _ = err,
else => {},
}
}
export fn entry3() void {
switch (@as(anyerror, error.MyError)) {
else => |*err| _ = err,
}
}
// error
//
// :3:47: error: error set cannot be captured by reference
// :10:54: error: error set cannot be captured by reference
// :17:18: error: error set cannot be captured by reference
@@ -0,0 +1,15 @@
const P = packed union(u8) {
a: u8,
b: i8,
};
export fn foo(p: P) void {
switch (p) {
.{ .a = 123 } => |_, tag| _ = tag,
else => {},
}
}
// error
//
// :8:30: error: cannot capture tag of packed union
@@ -0,0 +1,25 @@
const Auto = struct {
a: u8,
};
export fn entry1(a: u8) void {
const s: Auto = .{ .a = a };
switch (s) {
else => {},
}
}
const Extern = extern struct {
a: u8,
};
export fn entry2(s: Extern) void {
switch (s) {
else => {},
}
}
// error
//
// :6:13: error: switch on struct with auto layout
// :1:14: note: consider 'packed struct' here
// :15:13: error: switch on struct with extern layout
// :11:23: note: consider 'packed struct' here
@@ -0,0 +1,41 @@
const S = packed struct(u2) {
a: u2,
};
export fn entry1(x: u8) void {
const s: S = .{ .a = @intCast(x) };
switch (s) {
.{ .a = 0b00 }, .{ .a = 0b01 }, .{ .a = 0b10 }, .{ .a = 0b11 } => {},
else => {},
}
}
export fn entry2(x: u8) void {
const s: S = .{ .a = @intCast(x) };
switch (s) {
.{ .a = 0b00 }, .{ .a = 0b01 }, .{ .a = 0b11 } => {},
}
}
const U = packed union(u2) {
a: u2,
b: i2,
};
export fn entry3(x: u8) void {
const u: U = .{ .a = @intCast(x) };
switch (u) {
.{ .a = 0b00 }, .{ .a = 0b01 }, .{ .a = 0b10 }, .{ .a = 0b11 } => {},
else => {},
}
}
export fn entry4(x: u8) void {
const u: U = .{ .a = @intCast(x) };
switch (u) {
.{ .a = 0b00 }, .{ .a = 0b01 }, .{ .a = 0b11 } => {},
}
}
// error
//
// :8:14: error: unreachable else prong; all cases already handled
// :13:5: error: switch must handle all possibilities
// :26:14: error: unreachable else prong; all cases already handled
// :31:5: error: switch must handle all possibilities