Files
zig/test/behavior/switch.zig
T
Justus Klausecker 047df44d71 cbe: fix switch statements on large types
`switch` statements on types >128bits are now lowered to conditionals.
This is necessary because Zig lowers integers with more than 128 bits to
bigints, which are not 'native' integers and thus cannot be used as case
values.
128-bit integers get special treatment because Zig will emit actual 128bit
ints if they are supported by the target. They still *may* be lowered to
bigints though, e.g. for 32-bit targets or MSVC. To solve this, this commit
adds a bunch of switch macros to `zig.h` which will either resolve to an
actual `switch` statement or to conditionals. The `if` statements this
approach can generate are not as optimal as they could be but I think this
is a good trade-off since the generated `switch` statements are still the
same as the ones generated for smaller integers. Also the macros result
in pretty readable code.
2026-03-18 15:47:18 +01:00

1492 lines
40 KiB
Zig

const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
const expect = std.testing.expect;
const expectError = std.testing.expectError;
const expectEqual = std.testing.expectEqual;
const minInt = std.math.minInt;
const maxInt = std.math.maxInt;
test "switch with numbers" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try testSwitchWithNumbers(13);
}
fn testSwitchWithNumbers(x: u32) !void {
const result = switch (x) {
1, 2, 3, 4...8 => false,
13 => true,
else => false,
};
try expect(result);
}
test "switch with all ranges" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
try expect(testSwitchWithAllRanges(50, 3) == 1);
try expect(testSwitchWithAllRanges(101, 0) == 2);
try expect(testSwitchWithAllRanges(300, 5) == 3);
try expect(testSwitchWithAllRanges(301, 6) == 6);
}
fn testSwitchWithAllRanges(x: u32, y: u32) u32 {
return switch (x) {
0...100 => 1,
101...200 => 2,
201...300 => 3,
else => y,
};
}
test "switch arbitrary int size" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
try expect(testSwitchArbInt(u64, 0) == 0);
try expect(testSwitchArbInt(u64, 12) == 1);
try expect(testSwitchArbInt(u64, maxInt(u64)) == 2);
try expect(testSwitchArbInt(u64, 5555) == 3);
try expect(testSwitchArbInt(i64, minInt(i64)) == 0);
try expect(testSwitchArbInt(i64, 12) == 1);
try expect(testSwitchArbInt(i64, maxInt(i64)) == 2);
try expect(testSwitchArbInt(i64, -1000) == 3);
try expect(testSwitchArbInt(u128, 0) == 0);
try expect(testSwitchArbInt(u128, 12) == 1);
try expect(testSwitchArbInt(u128, maxInt(u128)) == 2);
try expect(testSwitchArbInt(u128, 5555) == 3);
try expect(testSwitchArbInt(i128, minInt(i128)) == 0);
try expect(testSwitchArbInt(i128, 12) == 1);
try expect(testSwitchArbInt(i128, maxInt(i128)) == 2);
try expect(testSwitchArbInt(i128, -1000) == 3);
}
fn testSwitchArbInt(comptime T: type, x: T) u32 {
return switch (x) {
minInt(T) => 0,
10...15 => 1,
maxInt(T) => 2,
else => 3,
};
}
test "implicit comptime switch" {
const x = 3 + 4;
const result = switch (x) {
3 => 10,
4 => 11,
5, 6 => 12,
7, 8 => 13,
else => 14,
};
comptime {
try expect(result + 1 == 14);
}
}
test "switch on enum" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const fruit = Fruit.Orange;
try expect(nonConstSwitchOnEnum(fruit));
}
const Fruit = enum {
Apple,
Orange,
Banana,
};
fn nonConstSwitchOnEnum(fruit: Fruit) bool {
return switch (fruit) {
Fruit.Apple => false,
Fruit.Orange => true,
Fruit.Banana => false,
};
}
test "switch statement" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try nonConstSwitch(SwitchStatementFoo.C);
}
fn nonConstSwitch(foo: SwitchStatementFoo) !void {
const val = switch (foo) {
SwitchStatementFoo.A => @as(i32, 1),
SwitchStatementFoo.B => 2,
SwitchStatementFoo.C => 3,
SwitchStatementFoo.D => 4,
};
try expect(val == 3);
}
const SwitchStatementFoo = enum { A, B, C, D };
test "switch with multiple expressions" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const x = switch (returnsFive()) {
1, 2, 3 => 1,
4, 5, 6 => 2,
else => @as(i32, 3),
};
try expect(x == 2);
}
fn returnsFive() i32 {
return 5;
}
test "switch on type" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try expect(trueIfBoolFalseOtherwise(bool));
try expect(!trueIfBoolFalseOtherwise(i32));
}
fn trueIfBoolFalseOtherwise(comptime T: type) bool {
return switch (T) {
bool => true,
else => false,
};
}
test "switching on booleans" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try testSwitchOnBools();
try comptime testSwitchOnBools();
}
fn testSwitchOnBools() !void {
try expect(testSwitchOnBoolsTrueAndFalse(true) == false);
try expect(testSwitchOnBoolsTrueAndFalse(false) == true);
try expect(testSwitchOnBoolsTrueWithElse(true) == false);
try expect(testSwitchOnBoolsTrueWithElse(false) == true);
try expect(testSwitchOnBoolsFalseWithElse(true) == false);
try expect(testSwitchOnBoolsFalseWithElse(false) == true);
}
fn testSwitchOnBoolsTrueAndFalse(x: bool) bool {
return switch (x) {
true => false,
false => true,
};
}
fn testSwitchOnBoolsTrueWithElse(x: bool) bool {
return switch (x) {
true => false,
else => true,
};
}
fn testSwitchOnBoolsFalseWithElse(x: bool) bool {
return switch (x) {
false => true,
else => false,
};
}
test "u0" {
var val: u0 = 0;
_ = &val;
switch (val) {
0 => try expect(val == 0),
}
}
test "undefined.u0" {
var val: u0 = undefined;
_ = &val;
switch (val) {
0 => try expect(val == 0),
}
}
test "switch with disjoint range" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
var q: u8 = 0;
_ = &q;
switch (q) {
0...125 => {},
127...255 => {},
126...126 => {},
}
}
test "switch variable for range and multiple prongs" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
try doTheSwitch(16);
try doTheSwitch(42);
}
fn doTheSwitch(q: u8) !void {
switch (q) {
0...40 => |x| try expect(x == 16),
41, 42, 43 => |x| try expect(x == 42),
else => try expect(false),
}
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
var state: u32 = 0;
fn poll() void {
switch (state) {
0 => {
state = 1;
},
else => {
state += 1;
},
}
}
test "switch on global mutable var isn't constant-folded" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
while (state < 2) {
poll();
}
}
const SwitchProngWithVarEnum = union(enum) {
One: i32,
Two: f32,
Meh: void,
};
test "switch prong with variable" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });
try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });
try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} });
}
fn switchProngWithVarFn(a: SwitchProngWithVarEnum) !void {
switch (a) {
SwitchProngWithVarEnum.One => |x| {
try expect(x == 13);
},
SwitchProngWithVarEnum.Two => |x| {
try expect(x == 13.0);
},
SwitchProngWithVarEnum.Meh => |x| {
const v: void = x;
_ = v;
},
}
}
test "switch on enum using pointer capture" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try testSwitchEnumPtrCapture();
try comptime testSwitchEnumPtrCapture();
}
fn testSwitchEnumPtrCapture() !void {
var value = SwitchProngWithVarEnum{ .One = 1234 };
switch (value) {
SwitchProngWithVarEnum.One => |*x| x.* += 1,
else => unreachable,
}
switch (value) {
SwitchProngWithVarEnum.One => |x| try expect(x == 1235),
else => unreachable,
}
}
test "switch handles all cases of number" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try testSwitchHandleAllCases();
try comptime testSwitchHandleAllCases();
}
fn testSwitchHandleAllCases() !void {
try expect(testSwitchHandleAllCasesExhaustive(0) == 3);
try expect(testSwitchHandleAllCasesExhaustive(1) == 2);
try expect(testSwitchHandleAllCasesExhaustive(2) == 1);
try expect(testSwitchHandleAllCasesExhaustive(3) == 0);
try expect(testSwitchHandleAllCasesRange(100) == 0);
try expect(testSwitchHandleAllCasesRange(200) == 1);
try expect(testSwitchHandleAllCasesRange(201) == 2);
try expect(testSwitchHandleAllCasesRange(202) == 4);
try expect(testSwitchHandleAllCasesRange(230) == 3);
}
fn testSwitchHandleAllCasesExhaustive(x: u2) u2 {
return switch (x) {
0 => @as(u2, 3),
1 => 2,
2 => 1,
3 => 0,
};
}
fn testSwitchHandleAllCasesRange(x: u8) u8 {
return switch (x) {
0...100 => @as(u8, 0),
101...200 => 1,
201, 203 => 2,
202 => 4,
204...255 => 3,
};
}
test "switch on union with some prongs capturing" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const X = union(enum) {
a,
b: i32,
};
var x: X = X{ .b = 10 };
_ = &x;
const y: i32 = switch (x) {
.a => unreachable,
.b => |b| b + 1,
};
try expect(y == 11);
}
const Number = union(enum) {
One: u64,
Two: u8,
Three: f32,
};
const number = Number{ .Three = 1.23 };
fn returnsFalse() bool {
switch (number) {
Number.One => |x| return x > 1234,
Number.Two => |x| return x == 'a',
Number.Three => |x| return x > 12.34,
}
}
test "switch on const enum with var" {
try expect(!returnsFalse());
}
test "anon enum literal used in switch on union enum" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const Foo = union(enum) {
a: i32,
};
var foo = Foo{ .a = 1234 };
_ = &foo;
switch (foo) {
.a => |x| {
try expect(x == 1234);
},
}
}
test "switch all prongs unreachable" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try testAllProngsUnreachable();
try comptime testAllProngsUnreachable();
}
fn testAllProngsUnreachable() !void {
try expect(switchWithUnreachable(1) == 2);
try expect(switchWithUnreachable(2) == 10);
}
fn switchWithUnreachable(x: i32) i32 {
while (true) {
switch (x) {
1 => return 2,
2 => break,
else => continue,
}
}
return 10;
}
test "capture value of switch with all unreachable prongs" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const x = return_a_number() catch |err| switch (err) {
else => unreachable,
};
try expect(x == 1);
}
fn return_a_number() anyerror!i32 {
return 1;
}
test "switch on integer with else capturing expr" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var x: i32 = 5;
_ = &x;
switch (x + 10) {
14 => return error.TestFailed,
16 => return error.TestFailed,
else => |e| try expect(e == 15),
}
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "else prong of switch on error set excludes other cases" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const S = struct {
fn doTheTest() !void {
try expectError(error.C, bar());
}
const E = error{
A,
B,
} || E2;
const E2 = error{
C,
D,
};
fn foo() E!void {
return error.C;
}
fn bar() E2!void {
foo() catch |err| switch (err) {
error.A, error.B => {},
else => |e| return e,
};
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "switch prongs with error set cases make a new error set type for capture value" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const S = struct {
fn doTheTest() !void {
try expectError(error.B, bar());
}
const E = E1 || E2;
const E1 = error{
A,
B,
};
const E2 = error{
C,
D,
};
fn foo() E!void {
return error.B;
}
fn bar() E1!void {
foo() catch |err| switch (err) {
error.A, error.B => |e| return e,
else => {},
};
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "return result loc and then switch with range implicit casted to error union" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
try expect((func(0xb) catch unreachable) == 0xb);
}
fn func(d: u8) anyerror!u8 {
return switch (d) {
0xa...0xf => d,
else => unreachable,
};
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "switch with null and T peer types and inferred result location type" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest(c: u8) !void {
if (switch (c) {
0 => true,
else => null,
}) |v| {
_ = v;
return error.TestFailed;
}
}
};
try S.doTheTest(1);
try comptime S.doTheTest(1);
}
test "switch prongs with cases with identical payload types" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const Union = union(enum) {
A: usize,
B: isize,
C: usize,
};
const S = struct {
fn doTheTest() !void {
try doTheSwitch1(Union{ .A = 8 });
try doTheSwitch2(Union{ .B = -8 });
}
fn doTheSwitch1(u: Union) !void {
switch (u) {
.A, .C => |e| {
comptime assert(@TypeOf(e) == usize);
try expect(e == 8);
},
.B => |e| {
_ = e;
return error.TestFailed;
},
}
}
fn doTheSwitch2(u: Union) !void {
switch (u) {
.A, .C => |e| {
_ = e;
return error.TestFailed;
},
.B => |e| {
comptime assert(@TypeOf(e) == isize);
try expect(e == -8);
},
}
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "switch prong pointer capture alignment" {
const U = union(enum) {
a: u8 align(8),
b: u8 align(4),
c: u8,
};
const S = struct {
fn doTheTest() !void {
const u = U{ .a = 1 };
switch (u) {
.a => |*a| comptime assert(@TypeOf(a) == *align(8) const u8),
.b, .c => |*p| {
_ = p;
return error.TestFailed;
},
}
switch (u) {
.a, .b => |*p| comptime assert(@TypeOf(p) == *align(4) const u8),
.c => |*p| {
_ = p;
return error.TestFailed;
},
}
switch (u) {
.a, .c => |*p| comptime assert(@TypeOf(p) == *align(1) const u8),
.b => |*p| {
_ = p;
return error.TestFailed;
},
}
}
fn doTheTest2() !void {
const un1 = U{ .b = 1 };
switch (un1) {
.b => |*b| comptime assert(@TypeOf(b) == *align(4) const u8),
.a, .c => |*p| {
_ = p;
return error.TestFailed;
},
}
const un2 = U{ .c = 1 };
switch (un2) {
.c => |*c| comptime assert(@TypeOf(c) == *const u8),
.a, .b => |*p| {
_ = p;
return error.TestFailed;
},
}
}
};
try S.doTheTest();
try comptime S.doTheTest();
try S.doTheTest2();
try comptime S.doTheTest2();
}
test "switch on pointer type" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
const X = struct {
field: u32,
};
const P1 = @as(*X, @ptrFromInt(0x400));
const P2 = @as(*X, @ptrFromInt(0x800));
const P3 = @as(*X, @ptrFromInt(0xC00));
fn doTheTest(arg: *X) i32 {
switch (arg) {
P1 => return 1,
P2 => return 2,
else => return 3,
}
}
};
try expect(1 == S.doTheTest(S.P1));
try expect(2 == S.doTheTest(S.P2));
try expect(3 == S.doTheTest(S.P3));
comptime assert(1 == S.doTheTest(S.P1));
comptime assert(2 == S.doTheTest(S.P2));
comptime assert(3 == S.doTheTest(S.P3));
}
test "switch on error set with single else" {
const S = struct {
fn doTheTest() !void {
var some: error{Foo} = error.Foo;
_ = &some;
try expect(switch (some) {
else => blk: {
break :blk true;
},
});
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "switch capture copies its payload" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn doTheTest() !void {
var tmp: union(enum) {
A: u8,
B: u32,
} = .{ .A = 42 };
switch (tmp) {
.A => |value| {
// Modify the original union
tmp = .{ .B = 0x10101010 };
try expectEqual(@as(u8, 42), value);
},
else => unreachable,
}
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "capture of integer forwards the switch condition directly" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
const S = struct {
fn foo(x: u8) !void {
switch (x) {
40...45 => |capture| {
try expect(capture == 42);
},
else => |capture| {
try expect(capture == 100);
},
}
}
};
try S.foo(42);
try S.foo(100);
try comptime S.foo(42);
try comptime S.foo(100);
}
test "enum value without tag name used as switch item" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const E = enum(u32) {
a = 1,
b = 2,
_,
};
var e: E = @enumFromInt(0);
_ = &e;
switch (e) {
@as(E, @enumFromInt(0)) => {},
.a => return error.TestFailed,
.b => return error.TestFailed,
_ => return error.TestFailed,
}
}
test "switch item sizeof" {
const S = struct {
fn doTheTest() !void {
var a: usize = 0;
_ = &a;
switch (a) {
@sizeOf(struct {}) => {},
else => return error.TestFailed,
}
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "comptime inline switch" {
const U = union(enum) { a: type, b: type };
const value = comptime blk: {
var u: U = .{ .a = u32 };
_ = &u;
break :blk switch (u) {
inline .a, .b => |v| v,
};
};
try expectEqual(u32, value);
}
test "switch capture peer type resolution" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const U = union(enum) {
a: u32,
b: u64,
fn innerVal(u: @This()) u64 {
switch (u) {
.a, .b => |x| return x,
}
}
};
try expectEqual(@as(u64, 100), U.innerVal(.{ .a = 100 }));
try expectEqual(@as(u64, 200), U.innerVal(.{ .b = 200 }));
}
test "switch capture peer type resolution for in-memory coercible payloads" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
const T1 = c_int;
const t1_info = @typeInfo(T1).int;
const T2 = @Int(t1_info.signedness, t1_info.bits);
comptime assert(T1 != T2);
const U = union(enum) {
a: T1,
b: T2,
fn innerVal(u: @This()) c_int {
switch (u) {
.a, .b => |x| return x,
}
}
};
try expectEqual(@as(c_int, 100), U.innerVal(.{ .a = 100 }));
try expectEqual(@as(c_int, 200), U.innerVal(.{ .b = 200 }));
}
test "switch pointer capture peer type resolution" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
const T1 = c_int;
const t1_info = @typeInfo(T1).int;
const T2 = @Int(t1_info.signedness, t1_info.bits);
comptime assert(T1 != T2);
const U = union(enum) {
a: T1,
b: T2,
fn innerVal(u: *@This()) *c_int {
switch (u.*) {
.a, .b => |*ptr| return ptr,
}
}
};
var ua: U = .{ .a = 100 };
var ub: U = .{ .b = 200 };
ua.innerVal().* = 111;
ub.innerVal().* = 222;
try expectEqual(U{ .a = 111 }, ua);
try expectEqual(U{ .b = 222 }, ub);
}
test "inline switch range that includes the maximum value of the switched type" {
const inputs: [3]u8 = .{ 0, 254, 255 };
for (inputs) |input| {
switch (input) {
inline 254...255 => |val| try expectEqual(input, val),
else => |val| try expectEqual(input, val),
}
}
}
test "nested break ignores switch conditions and breaks instead" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn register_to_address(ident: []const u8) !u8 {
const reg: u8 = if (std.mem.eql(u8, ident, "zero")) 0x00 else blk: {
break :blk switch (ident[0]) {
0x61 => (try std.fmt.parseInt(u8, ident[1..], 0)) + 1,
0x66 => (try std.fmt.parseInt(u8, ident[1..], 0)) + 1,
else => {
break :blk 0xFF;
},
};
};
return reg;
}
};
// Originally reported at https://github.com/ziglang/zig/issues/10196
try expect(0x01 == try S.register_to_address("a0"));
}
test "peer type resolution on switch captures ignores unused payload bits" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const Foo = union(enum) {
a: u32,
b: u64,
};
var val: Foo = undefined;
@memset(std.mem.asBytes(&val), 0xFF);
// This is runtime-known so the following store isn't comptime-known.
var rt: u32 = 123;
_ = &rt;
val = .{ .a = rt }; // will not necessarily zero remaning payload memory
// Fields intentionally backwards here
const x = switch (val) {
.b, .a => |x| x,
};
try expect(x == 123);
}
test "switch prong captures range" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
const S = struct {
fn a(b: []u3, c: u3) void {
switch (c) {
0...1 => b[c] = c,
2...3 => b[c] = c,
4...7 => |d| b[d] = c,
}
}
};
var arr: [8]u3 = undefined;
S.a(&arr, 5);
try expect(arr[5] == 5);
}
test "prong with inline call to unreachable" {
const U = union(enum) {
void: void,
bool: bool,
inline fn unreach() noreturn {
unreachable;
}
};
var u: U = undefined;
u = .{ .bool = true };
switch (u) {
.void => U.unreach(),
.bool => |ok| try expect(ok),
}
}
test "block error return trace index is reset between prongs" {
const S = struct {
fn returnError() error{TestFailed} {
return error.TestFailed;
}
};
var x: u1 = 0;
_ = &x;
const result = switch (x) {
0 => {
const result: anyerror!i32 = blk: {
break :blk 1;
};
_ = &result;
},
1 => blk: {
const err = switch (x) {
0 => {},
1 => S.returnError(),
};
break :blk err;
},
};
try result;
}
test "labeled switch with break" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
var six: u32 = undefined;
six = 6;
const val = s: switch (six) {
0...4 => break :s false,
5 => break :s false,
6...7 => break :s true,
else => break :s false,
};
try expect(val);
// Make sure the switch is implicitly comptime!
const comptime_val = s: switch (@as(u32, 6)) {
0...4 => break :s false,
5 => break :s false,
6...7 => break :s true,
else => break :s false,
};
comptime assert(comptime_val);
}
test "unlabeled break ignores switch" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
const result = while (true) {
_ = s: switch (@as(u32, 1)) {
1 => continue :s 123,
else => |x| break x,
};
comptime unreachable; // control flow never breaks from the switch
};
try expect(result == 123);
}
test "switch on a signed value smaller than the smallest prong value" {
var v: i32 = undefined;
v = -1;
switch (v) {
inline 0...10 => return error.TestFailed,
else => {},
}
}
test "switch on 8-bit mod result" {
var x: u8 = undefined;
x = 16;
switch (x % 4) {
0 => {},
1, 2, 3 => return error.TestFailed,
else => unreachable,
}
}
test "switch on non-exhaustive enum" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
const E = enum(u4) {
a,
b,
c,
_,
fn doTheTest(e: @This()) !void {
switch (e) {
.a, .b => {},
else => return error.TestFailed,
}
switch (e) {
.a, .b => {},
.c => return error.TestFailed,
_ => return error.TestFailed,
}
switch (e) {
.a, .b => {},
.c, _ => return error.TestFailed,
}
switch (e) {
.a => {},
.b, .c, _ => return error.TestFailed,
}
switch (e) {
.b => return error.TestFailed,
else => {},
_ => return error.TestFailed,
}
switch (e) {
else => {},
_ => return error.TestFailed,
}
switch (e) {
inline else => {},
_ => return error.TestFailed,
}
}
};
try E.doTheTest(.a);
try comptime E.doTheTest(.a);
}
test "decl literals as switch cases" {
const E = enum(u8) {
bar = 3,
_,
const foo: @This() = @enumFromInt(0xa);
fn doTheTest(e: @This()) !void {
switch (e) {
.bar => return error.TestFailed,
.foo => {},
else => return error.TestFailed,
}
}
};
try E.doTheTest(.foo);
try comptime E.doTheTest(.foo);
}
// TODO audit after #15909 and/or #19855 are decided/implemented.
// When we do that, consider adding an 'error{}' case if possible.
test "switch with uninstantiable union fields" {
const U = union(enum) {
ok: void,
a: noreturn,
b: noreturn,
fn doTheTest(u: @This()) void {
switch (u) {
.ok => {},
.a => comptime unreachable,
.b => comptime unreachable,
}
switch (u) {
.ok => {},
.a, .b => comptime unreachable,
}
switch (u) {
.ok => {},
else => comptime unreachable,
}
switch (u) {
.a => comptime unreachable,
.ok, .b => {},
}
}
};
U.doTheTest(.ok);
comptime U.doTheTest(.ok);
}
test "switch with tag capture" {
const U = union(enum) {
a,
b: i32,
c: u8,
d: i32,
e: noreturn,
fn doTheTest() !void {
try doTheSwitch(.a);
try doTheSwitch(.{ .b = 123 });
try doTheSwitch(.{ .c = 0xFF });
}
fn doTheSwitch(u: @This()) !void {
switch (u) {
.a => |nothing, tag| {
comptime assert(nothing == {});
comptime assert(tag == .a);
try expect(@intFromEnum(tag) == @intFromEnum(@This().a));
},
.b, .d => |_, tag| {
try expect(tag == .b or tag == .d);
},
.e => |payload, tag| {
_ = &payload;
_ = &tag;
comptime unreachable;
},
else => |un, tag| {
try expect(tag == .c);
try expect(un == .c);
try expect(un.c == 0xFF);
},
}
switch (u) {
inline .a, .b, .c => |payload, tag| {
if (@TypeOf(payload) == void) comptime assert(tag == .a);
if (@TypeOf(payload) == i32) comptime assert(tag == .b);
if (@TypeOf(payload) == u8) comptime assert(tag == .c);
},
inline else => |payload, tag| {
if (@TypeOf(payload) == i32) comptime assert(tag == .d);
comptime assert(tag != .e);
},
}
}
};
try U.doTheTest();
try comptime U.doTheTest();
}
test "switch with complex item expressions" {
const S = struct {
fn doTheTest() !void {
try doTheSwitch(2000, 20);
try doTheSwitch(2000, 10);
try doTheSwitch(2000, 5);
try doTheOtherSwitch(@enumFromInt(123));
try doTheOtherSwitch(@enumFromInt(456));
}
fn doTheSwitch(x: u32, comptime factor: u32) !void {
const ok = switch (x) {
num(factor) => true,
typedNum(u32, factor) => true,
blk: {
var val = 400;
val *= factor;
break :blk val;
} => true,
else => false,
};
try expect(ok);
}
fn num(factor: u32) u32 {
return 100 * factor;
}
fn typedNum(comptime T: type, factor: T) T {
return 200 * factor;
}
const E = enum(u32) { _ };
fn doTheOtherSwitch(e: E) !void {
const ok = switch (e) {
@enumFromInt(123) => true,
@enumFromInt(456) => true,
else => false,
};
try expect(ok);
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "switch evaluation order" {
const eu: anyerror!u32 = 0;
_ = eu catch |err| switch (err) {
if (true) @compileError("unreachable") => unreachable,
else => unreachable,
};
}
test "switch resolves lazy values correctly" {
const S = extern struct {
a: u16,
b: i16,
};
switch (@sizeOf(S)) {
4 => {},
else => comptime unreachable,
}
}
test "single-item prong in switch on enum has comptime-known capture" {
const E = enum {
a,
b,
c,
fn doTheTest(e: @This()) !void {
switch (e) {
.a => |tag| comptime assert(tag == .a),
.b => return error.TestFailed,
.c => return error.TestFailed,
}
}
};
try E.doTheTest(.a);
try comptime E.doTheTest(.a);
}
test "single range switch prong capture" {
const S = struct {
fn doTheTest(x: u8) !void {
switch (x) {
1...5 => |val| {
try expect(val == 2);
},
else => return error.TestFailed,
}
switch (x) {
1...5, 6 => |val| {
try expect(val == 2);
},
else => return error.TestFailed,
}
}
};
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 },
});
}
test "switch on large types" {
const S = struct {
fn doTheTest(a: u128, b: i500) !void {
switch (a) {
0x0,
0x3...0xFFFF_FFFF_FFFF_FFFF_FFFF_ABCD,
0xFFFF_FFFF_FFFF_FFFF_FFFF_EF00,
=> return error.TestFailed,
0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_0000...0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFF0,
=> |val| {
try expect(val == 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234);
},
else => return error.TestFailed,
}
switch (b) {
0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_0000...0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234,
=> return error.TestFailed,
0xFFFF_1234,
0xFFFF_FFFF_FFFF_FFFF_FFFF_0123...0xFFFF_FFFF_FFFF_FFFF_FFFF_4567,
=> |val| {
try expect(val == 0xFFFF_1234);
},
else => return error.TestFailed,
}
}
};
try S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);
try comptime S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);
}